Skip to content
This repository has been archived by the owner on Jan 13, 2023. It is now read-only.

Commit

Permalink
Merge pull request #321 from lzpap/type_annotations
Browse files Browse the repository at this point in the history
Introduce Type Annotations
  • Loading branch information
lzpap authored Mar 20, 2020
2 parents b1b068f + 0e609ad commit 07f9a86
Show file tree
Hide file tree
Showing 70 changed files with 1,369 additions and 1,370 deletions.
2 changes: 1 addition & 1 deletion examples/send_transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
Tag,
TryteString,
)
from address_generator import get_seed, output_seed
from .address_generator import get_seed, output_seed


def main(address, depth, message, tag, uri, value):
Expand Down
7 changes: 4 additions & 3 deletions iota/__init__.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
from typing import Dict

# Define a few magic constants.
DEFAULT_PORT = 14265
DEFAULT_PORT: int = 14265
"""
Default port to use when configuring an adapter, if the port is not
specified.
"""

TRITS_PER_TRYTE = 3
TRITS_PER_TRYTE: int = 3
"""
Number of trits in a tryte.
Changing this will probably break everything, but there's a chance it
could create a sexy new altcoin instead.
In that way, it's kind of like toxic waste in a superhero story.
"""

STANDARD_UNITS = {
STANDARD_UNITS: Dict[str, int] = {
# Valid IOTA unit suffixes. Example value '-273.15 Ki'
'i': 1,
'Ki': 1000,
Expand Down
110 changes: 60 additions & 50 deletions iota/adapter/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@

import json
from abc import ABCMeta, abstractmethod as abstract_method
from asyncio import Future
from collections import deque
from inspect import isabstract as is_abstract
from logging import DEBUG, Logger
from socket import getdefaulttimeout as get_default_timeout
from typing import Container, Dict, List, Optional, Text, Tuple, Union
from httpx import AsyncClient, Response, codes, auth
from typing import Container, List, Optional, Tuple, Union, Any, Dict
from httpx import AsyncClient, Response, codes, BasicAuth
import asyncio

from iota.exceptions import with_context
Expand All @@ -31,7 +32,7 @@
"""

# Custom types for type hints and docstrings.
AdapterSpec = Union[Text, 'BaseAdapter']
AdapterSpec = Union[str, 'BaseAdapter']
"""
Placeholder that means “URI or adapter instance”.
Expand All @@ -42,7 +43,8 @@
# Load SplitResult for IDE type hinting and autocompletion.
from urllib.parse import SplitResult, urlsplit

def async_return(result):

def async_return(result: Any) -> Future:
"""
Turns 'result' into a `Future` object with 'result' value.
Expand All @@ -52,6 +54,7 @@ def async_return(result):
f.set_result(result)
return f


class BadApiResponse(ValueError):
"""
Indicates that a non-success response was received from the node.
Expand All @@ -66,21 +69,20 @@ class InvalidUri(ValueError):
pass


adapter_registry = {} # type: Dict[Text, AdapterMeta]
adapter_registry: Dict[str, 'AdapterMeta'] = {}
"""
Keeps track of available adapters and their supported protocols.
"""


def resolve_adapter(uri):
# type: (AdapterSpec) -> BaseAdapter
def resolve_adapter(uri: AdapterSpec) -> 'BaseAdapter':
"""
Given a URI, returns a properly-configured adapter instance.
"""
if isinstance(uri, BaseAdapter):
return uri

parsed = urlsplit(uri) # type: SplitResult
parsed: SplitResult = urlsplit(uri)

if not parsed.scheme:
raise with_context(
Expand Down Expand Up @@ -116,7 +118,7 @@ class AdapterMeta(ABCMeta):
Automatically registers new adapter classes in ``adapter_registry``.
"""

def __init__(cls, what, bases=None, dict=None):
def __init__(cls, what, bases=None, dict=None) -> None:
super(AdapterMeta, cls).__init__(what, bases, dict)

if not is_abstract(cls):
Expand All @@ -125,8 +127,7 @@ def __init__(cls, what, bases=None, dict=None):
# adapters.
adapter_registry.setdefault(protocol, cls)

def configure(cls, parsed):
# type: (Union[Text, SplitResult]) -> HttpAdapter
def configure(cls, parsed: Union[str, SplitResult]) -> 'HttpAdapter':
"""
Creates a new instance using the specified URI.
Expand All @@ -143,21 +144,20 @@ class BaseAdapter(object, metaclass=AdapterMeta):
Adapters make it easy to customize the way an API instance
communicates with a node.
"""
supported_protocols = () # type: Tuple[Text]
supported_protocols: Tuple[str] = ()
"""
Protocols that ``resolve_adapter`` can use to identify this adapter
type.
"""

def __init__(self):
def __init__(self) -> None:
super(BaseAdapter, self).__init__()

self._logger = None # type: Logger
self.local_pow = False # type: boolean
self._logger: Optional[Logger] = None
self.local_pow: bool = False

@abstract_method
def get_uri(self):
# type: () -> Text
def get_uri(self) -> str:
"""
Returns the URI that this adapter will use.
"""
Expand All @@ -166,8 +166,7 @@ def get_uri(self):
)

@abstract_method
def send_request(self, payload, **kwargs):
# type: (dict, dict) -> dict
def send_request(self, payload: dict, **kwargs: Any) -> dict:
"""
Sends an API request to the node.
Expand All @@ -188,8 +187,7 @@ def send_request(self, payload, **kwargs):
'Not implemented in {cls}.'.format(cls=type(self).__name__),
)

def set_logger(self, logger):
# type: (Logger) -> BaseAdapter
def set_logger(self, logger: Logger) -> 'BaseAdapter':
"""
Attaches a logger instance to the adapter.
The adapter will send information about API requests/responses
Expand All @@ -198,16 +196,19 @@ def set_logger(self, logger):
self._logger = logger
return self

def _log(self, level, message, context=None):
# type: (int, Text, Optional[dict]) -> None
def _log(
self,
level: int,
message: str,
context: Optional[dict] = None
) -> None:
"""
Sends a message to the instance's logger, if configured.
"""
if self._logger:
self._logger.log(level, message, extra={'context': context or {}})

def set_local_pow(self, local_pow):
# type: (bool) -> None
def set_local_pow(self, local_pow: bool) -> None:
"""
Sets the local_pow attribute of the adapter. If it is true,
attach_to_tangle command calls external interface to perform
Expand All @@ -216,6 +217,7 @@ def set_local_pow(self, local_pow):
"""
self.local_pow = local_pow


class HttpAdapter(BaseAdapter):
"""
Sends standard HTTP(S) requests to the node.
Expand All @@ -229,7 +231,7 @@ class HttpAdapter(BaseAdapter):
:param Optional[int] timeout:
Connection timeout in seconds.
:param Optional[Tuple(Text,Text)] authentication:
:param Optional[Tuple(str,str)] authentication:
Credetentials for basic authentication with the node.
:return:
Expand Down Expand Up @@ -257,16 +259,20 @@ class HttpAdapter(BaseAdapter):
in the ``headers`` kwarg.
"""

def __init__(self, uri, timeout=None, authentication=None):
# type: (Union[Text, SplitResult], Optional[int]) -> None
def __init__(
self,
uri: Union[str, SplitResult],
timeout: Optional[int] = None,
authentication: Optional[Tuple[str, str]] = None
) -> None:
super(HttpAdapter, self).__init__()

self.client = AsyncClient()
self.timeout = timeout
self.authentication = authentication

if isinstance(uri, str):
uri = urlsplit(uri) # type: SplitResult
uri: SplitResult = urlsplit(uri)

if uri.scheme not in self.supported_protocols:
raise with_context(
Expand Down Expand Up @@ -310,19 +316,16 @@ def __init__(self, uri, timeout=None, authentication=None):
self.uri = uri

@property
def node_url(self):
# type: () -> Text
def node_url(self) -> str:
"""
Returns the node URL.
"""
return self.uri.geturl()

def get_uri(self):
# type: () -> Text
def get_uri(self) -> str:
return self.uri.geturl()

async def send_request(self, payload, **kwargs):
# type: (dict, dict) -> dict
async def send_request(self, payload: dict, **kwargs: Any) -> dict:
kwargs.setdefault('headers', {})
for key, value in self.DEFAULT_HEADERS.items():
kwargs['headers'].setdefault(key, value)
Expand All @@ -338,8 +341,13 @@ async def send_request(self, payload, **kwargs):

return self._interpret_response(response, payload, {codes['OK']})

async def _send_http_request(self, url, payload, method='post', **kwargs):
# type: (Text, Optional[Text], Text, dict) -> Response
async def _send_http_request(
self,
url: str,
payload: Optional[str],
method: str = 'post',
**kwargs: Any
) -> Response:
"""
Sends the actual HTTP request.
Expand All @@ -352,7 +360,7 @@ async def _send_http_request(self, url, payload, method='post', **kwargs):
)

if self.authentication:
kwargs.setdefault('auth', auth.HTTPBasicAuth(*self.authentication))
kwargs.setdefault('auth', BasicAuth(*self.authentication))

self._log(
level=DEBUG,
Expand Down Expand Up @@ -394,8 +402,12 @@ async def _send_http_request(self, url, payload, method='post', **kwargs):

return response

def _interpret_response(self, response, payload, expected_status):
# type: (Response, dict, Container[int]) -> dict
def _interpret_response(
self,
response: Response,
payload: dict,
expected_status: Container[int]
) -> dict:
"""
Interprets the HTTP response from the node.
Expand Down Expand Up @@ -425,7 +437,7 @@ def _interpret_response(self, response, payload, expected_status):
)

try:
decoded = json.loads(raw_content) # type: dict
decoded: dict = json.loads(raw_content)
# :bc: py2k doesn't have JSONDecodeError
except ValueError:
raise with_context(
Expand Down Expand Up @@ -523,17 +535,16 @@ class MockAdapter(BaseAdapter):
def configure(cls, uri):
return cls()

def __init__(self):
def __init__(self) -> None:
super(MockAdapter, self).__init__()

self.responses = {} # type: Dict[Text, deque]
self.requests = [] # type: List[dict]
self.responses: Dict[str, deque] = {}
self.requests: List[dict] = []

def get_uri(self):
def get_uri(self) -> str:
return 'mock://'

def seed_response(self, command, response):
# type: (Text, dict) -> MockAdapter
def seed_response(self, command: str, response: dict) -> 'MockAdapter':
"""
Sets the response that the adapter will return for the specified
command.
Expand All @@ -547,7 +558,7 @@ def seed_response(self, command, response):
have a seeded response for a particular command, it will raise a
``BadApiResponse`` exception (simulates a 404 response).
:param Text command:
:param str command:
The name of the command. Note that this is the camelCase version
of the command name (e.g., ``getNodeInfo``, not ``get_node_info``).
Expand All @@ -573,11 +584,10 @@ def seed_response(self, command, response):
self.responses[command].append(response)
return self

async def send_request(self, payload, **kwargs):
async def send_request(self, payload: Dict, **kwargs: Any) -> dict:
"""
Mimic asynchronous behavior of `HttpAdapter.send_request`.
"""
# type: (dict, dict) -> dict
# Store a snapshot so that we can inspect the request later.
self.requests.append(dict(payload))

Expand Down
Loading

0 comments on commit 07f9a86

Please sign in to comment.