Skip to content

Commit

Permalink
Type hints
Browse files Browse the repository at this point in the history
Now that we require Python 3.5 our library can provide type hints for our IDE
users...

  https://docs.python.org/3/library/typing.html

Needless to say this is a large overhaul. Dynamic return types and our
custom enumerations are incompatible with formal typing, and will get
removed from our library in the future.
  • Loading branch information
atagar committed May 11, 2020
2 parents d1174a8 + 13e401d commit 99c3043
Show file tree
Hide file tree
Showing 62 changed files with 2,443 additions and 1,856 deletions.
15 changes: 9 additions & 6 deletions docs/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ To start hacking on Stem please do the following and don't hesitate to let me
know if you get stuck or would like to discuss anything!

#. Clone our `git <http://git-scm.com/>`_ repository: **git clone https://git.torproject.org/stem.git**
#. Get our test dependencies: **sudo pip install mock pycodestyle pyflakes**.
#. Get our test dependencies: **sudo pip install mock pycodestyle pyflakes mypy**.
#. Find a `bug or feature <https://github.com/torproject/stem/issues/>`_ that sounds interesting.
#. When you have something that you would like to contribute back do the following...

Expand Down Expand Up @@ -588,11 +588,14 @@ can exercise alternate Tor configurations with the ``--target`` argument (see
~/stem$ ./run_tests.py --integ --tor /path/to/tor
~/stem$ ./run_tests.py --integ --target RUN_COOKIE

**Static** tests use `pyflakes <https://launchpad.net/pyflakes>`_ to do static
error checking and `pycodestyle
<http://pycodestyle.readthedocs.org/en/latest/>`_ for style checking. If you
have them installed then they automatically take place as part of all test
runs.
**Static** tests use...

* `pyflakes <https://launchpad.net/pyflakes>`_ for error checks
* `pycodestyle <http://pycodestyle.readthedocs.org/en/latest/>`_ for style checks
* `mypy <http://mypy-lang.org/>`_ for type checks

If you have them installed then they automatically take place as part of all
test runs.

See ``run_tests.py --help`` for more usage information.

Expand Down
10 changes: 6 additions & 4 deletions run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,15 +194,15 @@ def main():
test_config.load(os.environ['STEM_TEST_CONFIG'])

try:
args = test.arguments.parse(sys.argv[1:])
args = test.arguments.Arguments.parse(sys.argv[1:])
test.task.TOR_VERSION.args = (args.tor_path,)
test.output.SUPPRESS_STDOUT = args.quiet
except ValueError as exc:
println(str(exc))
sys.exit(1)

if args.print_help:
println(test.arguments.get_help())
println(test.arguments.Arguments.get_help())
sys.exit()
elif not args.run_unit and not args.run_integ:
println('Nothing to run (for usage provide --help)\n')
Expand All @@ -217,12 +217,14 @@ def main():
test.task.CRYPTO_VERSION,
test.task.PYFLAKES_VERSION,
test.task.PYCODESTYLE_VERSION,
test.task.MYPY_VERSION,
test.task.CLEAN_PYC,
test.task.UNUSED_TESTS,
test.task.IMPORT_TESTS,
test.task.REMOVE_TOR_DATA_DIR if args.run_integ else None,
test.task.PYFLAKES_TASK if not args.specific_test else None,
test.task.PYCODESTYLE_TASK if not args.specific_test else None,
test.task.MYPY_TASK if not args.specific_test else None,
)

# Test logging. If '--log-file' is provided we log to that location,
Expand Down Expand Up @@ -334,7 +336,7 @@ def main():

static_check_issues = {}

for task in (test.task.PYFLAKES_TASK, test.task.PYCODESTYLE_TASK):
for task in (test.task.PYFLAKES_TASK, test.task.PYCODESTYLE_TASK, test.task.MYPY_TASK):
if not task.is_available and task.unavailable_msg:
println(task.unavailable_msg, ERROR)
else:
Expand Down Expand Up @@ -381,7 +383,7 @@ def _print_static_issues(static_check_issues):
if static_check_issues:
println('STATIC CHECKS', STATUS)

for file_path in static_check_issues:
for file_path in sorted(static_check_issues):
println('* %s' % file_path, STATUS)

# Make a dict of line numbers to its issues. This is so we can both sort
Expand Down
36 changes: 20 additions & 16 deletions stem/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,8 @@
import stem.util
import stem.util.enum

from typing import Any, Optional, Sequence

__version__ = '1.8.0-dev'
__author__ = 'Damian Johnson'
__contact__ = 'atagar@torproject.org'
Expand Down Expand Up @@ -565,7 +567,7 @@
]

# Constant that we use by default for our User-Agent when downloading descriptors
stem.USER_AGENT = 'Stem/%s' % __version__
USER_AGENT = 'Stem/%s' % __version__

# Constant to indicate an undefined argument default. Usually we'd use None for
# this, but users will commonly provide None as the argument so need something
Expand All @@ -584,7 +586,7 @@ class Endpoint(object):
:var int port: port of the endpoint
"""

def __init__(self, address, port):
def __init__(self, address: str, port: int) -> None:
if not stem.util.connection.is_valid_ipv4_address(address) and not stem.util.connection.is_valid_ipv6_address(address):
raise ValueError("'%s' isn't a valid IPv4 or IPv6 address" % address)
elif not stem.util.connection.is_valid_port(port):
Expand All @@ -593,13 +595,13 @@ def __init__(self, address, port):
self.address = address
self.port = int(port)

def __hash__(self):
def __hash__(self) -> int:
return stem.util._hash_attr(self, 'address', 'port', cache = True)

def __eq__(self, other):
def __eq__(self, other: Any) -> bool:
return hash(self) == hash(other) if isinstance(other, Endpoint) else False

def __ne__(self, other):
def __ne__(self, other: Any) -> bool:
return not self == other


Expand All @@ -610,11 +612,11 @@ class ORPort(Endpoint):
:var list link_protocols: link protocol version we're willing to establish
"""

def __init__(self, address, port, link_protocols = None):
def __init__(self, address: str, port: int, link_protocols: Optional[Sequence['stem.client.datatype.LinkProtocol']] = None) -> None: # type: ignore
super(ORPort, self).__init__(address, port)
self.link_protocols = link_protocols

def __hash__(self):
def __hash__(self) -> int:
return stem.util._hash_attr(self, 'link_protocols', parent = Endpoint, cache = True)


Expand Down Expand Up @@ -642,7 +644,9 @@ class OperationFailed(ControllerError):
message
"""

def __init__(self, code = None, message = None):
# TODO: should the code be an int instead?

def __init__(self, code: Optional[str] = None, message: Optional[str] = None) -> None:
super(ControllerError, self).__init__(message)
self.code = code
self.message = message
Expand All @@ -658,10 +662,10 @@ class CircuitExtensionFailed(UnsatisfiableRequest):
"""
An attempt to create or extend a circuit failed.
:var stem.response.CircuitEvent circ: response notifying us of the failure
:var stem.response.events.CircuitEvent circ: response notifying us of the failure
"""

def __init__(self, message, circ = None):
def __init__(self, message: str, circ: Optional['stem.response.events.CircuitEvent'] = None) -> None: # type: ignore
super(CircuitExtensionFailed, self).__init__(message = message)
self.circ = circ

Expand All @@ -674,7 +678,7 @@ class DescriptorUnavailable(UnsatisfiableRequest):
Subclassed under UnsatisfiableRequest rather than OperationFailed.
"""

def __init__(self, message):
def __init__(self, message: str) -> None:
super(DescriptorUnavailable, self).__init__(message = message)


Expand All @@ -685,7 +689,7 @@ class Timeout(UnsatisfiableRequest):
.. versionadded:: 1.7.0
"""

def __init__(self, message):
def __init__(self, message: str) -> None:
super(Timeout, self).__init__(message = message)


Expand All @@ -705,7 +709,7 @@ class InvalidArguments(InvalidRequest):
:var list arguments: a list of arguments which were invalid
"""

def __init__(self, code = None, message = None, arguments = None):
def __init__(self, code: Optional[str] = None, message: Optional[str] = None, arguments: Optional[Sequence[str]] = None):
super(InvalidArguments, self).__init__(code, message)
self.arguments = arguments

Expand Down Expand Up @@ -736,7 +740,7 @@ class DownloadFailed(IOError):
:var str stacktrace_str: string representation of the stacktrace
"""

def __init__(self, url, error, stacktrace, message = None):
def __init__(self, url: str, error: BaseException, stacktrace: Any, message: Optional[str] = None) -> None:
if message is None:
# The string representation of exceptions can reside in several places.
# urllib.URLError use a 'reason' attribute that in turn may referrence
Expand Down Expand Up @@ -773,7 +777,7 @@ class DownloadTimeout(DownloadFailed):
.. versionadded:: 1.8.0
"""

def __init__(self, url, error, stacktrace, timeout):
def __init__(self, url: str, error: BaseException, stacktrace: Any, timeout: float):
message = 'Failed to download from %s: %0.1f second timeout reached' % (url, timeout)
super(DownloadTimeout, self).__init__(url, error, stacktrace, message)

Expand Down Expand Up @@ -917,7 +921,7 @@ def __init__(self, url, error, stacktrace, timeout):
)

# StreamClosureReason is a superset of RelayEndReason
StreamClosureReason = stem.util.enum.UppercaseEnum(*(RelayEndReason.keys() + [
StreamClosureReason = stem.util.enum.UppercaseEnum(*(RelayEndReason.keys() + [ # type: ignore
'END',
'PRIVATE_ADDR',
]))
Expand Down
Loading

0 comments on commit 99c3043

Please sign in to comment.