From 6f4f8aa45822254873d5a9c2b00b315cad1e13cb Mon Sep 17 00:00:00 2001 From: Gustavo Correa Date: Sun, 3 Jul 2022 07:47:45 -0300 Subject: [PATCH 1/9] feat(aiohttpretty): reformat, refactor, and new functionality reformatted the code with black and isort, refactored some things to work with mypy updated all dependencies, dropped unittest in favor of pytest and created plugin and marker --- Makefile | 8 + aiohttpretty.py | 219 ----------- aiohttpretty/__init__.py | 5 + aiohttpretty/exc.py | 21 + aiohttpretty/helpers.py | 49 +++ aiohttpretty/main.py | 224 +++++++++++ aiohttpretty/plugin.py | 25 ++ aiohttpretty/types.py | 54 +++ dev-requirements.txt | 5 - poetry.lock | 803 +++++++++++++++++++++++++++++++++++++++ pyproject.toml | 40 ++ pytest.ini | 7 + requirements.txt | 4 - setup.cfg | 7 +- tests/__init__.py | 0 tests/conftest.py | 1 + tests/test_general.py | 430 +++++++++++---------- 17 files changed, 1451 insertions(+), 451 deletions(-) create mode 100644 Makefile delete mode 100644 aiohttpretty.py create mode 100644 aiohttpretty/__init__.py create mode 100644 aiohttpretty/exc.py create mode 100644 aiohttpretty/helpers.py create mode 100644 aiohttpretty/main.py create mode 100644 aiohttpretty/plugin.py create mode 100644 aiohttpretty/types.py delete mode 100644 dev-requirements.txt create mode 100644 poetry.lock create mode 100644 pyproject.toml create mode 100644 pytest.ini delete mode 100644 requirements.txt create mode 100644 tests/__init__.py create mode 100644 tests/conftest.py diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ac32f70 --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +.PHONY: format +format: + black --line-length 79 aiohttpretty tests + isort --profile black --line-length 79 aiohttpretty tests + +.PHONY: test +test: + pytest tests diff --git a/aiohttpretty.py b/aiohttpretty.py deleted file mode 100644 index a8f3f86..0000000 --- a/aiohttpretty.py +++ /dev/null @@ -1,219 +0,0 @@ -import sys -import copy -import json -import asyncio -import collections -from unittest.mock import Mock - -from yarl import URL -from furl import furl -from multidict import CIMultiDict -from aiohttp import ClientSession -from aiohttp.helpers import TimerNoop -from aiohttp.streams import StreamReader -from aiohttp.client import ClientResponse -from aiohttp.base_protocol import BaseProtocol - - -# TODO: Add static type checker with `mypy` -# TODO: Update docstr for most methods - -class ImmutableFurl: - - def __init__(self, url, params=None): - self._url = url - self._furl = furl(url) - self._params = furl(url).args - self._furl.set(args={}) - - params = params or {} - for (k, v) in params.items(): - self._params.add(k, v) - - def with_out_params(self): - return ImmutableFurl(self.url) - - @property - def url(self): - return self._furl.url - - @property - def params(self): - return self._params - - def __eq__(self, other): - return hash(self) == hash(other) - - def __hash__(self): - return hash(self.url + ''.join([ - self.params[x] or '' - for x in sorted(self.params) - ])) - - -class _MockStream(StreamReader): - - def __init__(self, data): - - protocol = BaseProtocol(Mock()) - super().__init__(protocol) - - self.size = len(data) - self.feed_data(data) - self.feed_eof() - - -def _wrap_content_stream(content): - - if isinstance(content, str): - content = content.encode('utf-8') - - if isinstance(content, bytes): - return _MockStream(content) - - if hasattr(content, 'read') and asyncio.iscoroutinefunction(content.read): - return content - - raise TypeError('Content must be of type bytes or str, or implement the stream interface.') - - -def build_raw_headers(headers): - """Convert a dict of headers to a tuple of tuples. Mimics the format of ClientResponse. - """ - raw_headers = [] - for k, v in headers.items(): - raw_headers.append((k.encode('utf8'), v.encode('utf8'))) - return tuple(raw_headers) - - -class _AioHttPretty: - - def __init__(self): - - self.calls = [] - self.registry = {} - self.request = None - - def make_call(self, **kwargs): - return kwargs - - async def process_request(self, **kwargs): - """Process request options as if the request was actually executed. - """ - data = kwargs.get('data') - if isinstance(data, asyncio.StreamReader): - await data.read() - - async def fake_request(self, method, uri, **kwargs): - - params = kwargs.get('params', None) - url = ImmutableFurl(uri, params=params) - - try: - response = self.registry[(method, url)] - except KeyError: - raise Exception( - 'No URLs matching {method} {uri} with params {url.params}. ' - 'Not making request. Go fix your test.'.format(**locals()) - ) - - if isinstance(response, collections.Sequence): - try: - response = response.pop(0) - except IndexError: - raise Exception('No responses left.') - - await self.process_request(**kwargs) - self.calls.append(self.make_call( - method=method, - uri=ImmutableFurl(uri, params=kwargs.pop('params', None)), - **kwargs - )) - - # For how to mock `ClientResponse` for `aiohttp>=3.1.0`, refer to the following link: - # https://github.com/pnuckowski/aioresponses/blob/master/aioresponses/core.py#L129-L156 - # Here is the original commit that added support for `aiohttp>=3.1.0`: - # https://github.com/pnuckowski/aioresponses/commit/87cf1041179139ad78a2554713b615684b8987db - loop = Mock() - loop.get_debug = Mock() - loop.get_debug.return_value = True - resp_kwargs = { - 'request_info': Mock(), - 'writer': Mock(), - 'continue100': None, - 'timer': TimerNoop(), - 'traces': [], - 'loop': loop, - 'session': None, - } - - # When init `ClientResponse`, the second parameter must be of type `yarl.URL` - # TODO: Integrate a property of this type to `ImmutableFurl` - y_url = URL(uri) - mock_response = ClientResponse(method, y_url, **resp_kwargs) - - # TODO: can we simplify this `_wrap_content_stream()` - mock_response.content = _wrap_content_stream(response.get('body', 'aiohttpretty')) - - # Build response headers manually - headers = CIMultiDict(response.get('headers', {})) - if response.get('auto_length'): - # Calculate and overwrite the "Content-Length" header on-the-fly if Waterbutler tests - # call `aiohttpretty.register_uri()` with `auto_length=True` - headers.update({'Content-Length': str(mock_response.content.size)}) - raw_headers = build_raw_headers(headers) - # Use `._headers` and `._raw_headers` for `aiohttp>=3.3.0`, compared to using `.headers` and - # `.raw_headers` for `3.3.0>aiohttp>=3.1.0` - mock_response._headers = headers - mock_response._raw_headers = raw_headers - - # Set response status and reason - mock_response.status = response.get('status', 200) - mock_response.reason = response.get('reason', '') - - return mock_response - - def register_uri(self, method, uri, **options): - if any(x.get('params') for x in options.get('responses', [])): - raise ValueError('Cannot specify params in responses, call register multiple times.') - params = options.pop('params', {}) - url = ImmutableFurl(uri, params=params) - self.registry[(method, url)] = options.get('responses', options) - - def register_json_uri(self, method, uri, **options): - body = json.dumps(options.pop('body', None)).encode('utf-8') - headers = {'Content-Type': 'application/json'} - headers.update(options.pop('headers', {})) - self.register_uri(method, uri, body=body, headers=headers, **options) - - def activate(self): - ClientSession._request, self.request = self.fake_request, ClientSession._request - - def deactivate(self): - ClientSession._request, self.request = self.request, None - - def clear(self): - self.calls = [] - self.registry = {} - - def compare_call(self, first, second): - for key, value in first.items(): - if second.get(key) != value: - return False - return True - - def has_call(self, uri, check_params=True, **kwargs): - """Check to see if the given uri was called. By default will verify that the query params - match up. Setting ``check_params`` to `False` will strip params from the *called* uri, not - the passed-in uri.""" - kwargs['uri'] = ImmutableFurl(uri, params=kwargs.pop('params', None)) - for call in self.calls: - if not check_params: - call = copy.deepcopy(call) - call['uri'] = call['uri'].with_out_params() - if self.compare_call(kwargs, call): - return True - return False - - -sys.modules[__name__] = _AioHttPretty() diff --git a/aiohttpretty/__init__.py b/aiohttpretty/__init__.py new file mode 100644 index 0000000..576f8ff --- /dev/null +++ b/aiohttpretty/__init__.py @@ -0,0 +1,5 @@ +import sys + +from .main import aiohttpretty + +__all__ = ['aiohttpretty'] diff --git a/aiohttpretty/exc.py b/aiohttpretty/exc.py new file mode 100644 index 0000000..0bec7df --- /dev/null +++ b/aiohttpretty/exc.py @@ -0,0 +1,21 @@ +class AioHttPrettyError(Exception): + """Base class for all AioHttPrettyErrors""" + + +class NoUrlMatching(AioHttPrettyError, KeyError): + """No url matches received url with given params and method""" + + def __str__(self) -> str: + return Exception.__str__(self) + + +class ExhaustedAllResponses(AioHttPrettyError, IndexError): + """No response left for given url""" + + +class InvalidBody(AioHttPrettyError, TypeError): + """Received invalid body type""" + + +class InvalidResponses(AioHttPrettyError, ValueError): + """Cannot specify params in responses""" diff --git a/aiohttpretty/helpers.py b/aiohttpretty/helpers.py new file mode 100644 index 0000000..e178e64 --- /dev/null +++ b/aiohttpretty/helpers.py @@ -0,0 +1,49 @@ +import asyncio +import typing + +from aiohttpretty import types + +DEFAULT_ENCODING = 'utf-8' + + +def encode_string(string: str, encoding: str = DEFAULT_ENCODING): + return string.encode(encoding) + + +def is_stream_like(value: typing.Any): + return hasattr(value, 'read') and asyncio.iscoroutinefunction(value.read) + + +class StreamLike(typing.Protocol): + async def read(self, n: int) -> bytes: + ... + + @property + def size(self) -> int: + ... + + +def wrap_content_stream(content: typing.Union[str, bytes, StreamLike]): + if isinstance(content, str): + content = encode_string(content) + + if isinstance(content, bytes): + return types.MockStream(content) + + return content + + +def build_raw_headers(headers: typing.Mapping[str, str]): + """Convert a dict of headers to a tuple of tuples. Mimics the format of ClientResponse. + """ + return tuple( + (encode_string(key), encode_string(value)) + for key, value in headers.items() + ) + + +def compare_mapping( + first: typing.Mapping[str, typing.Any], + second: typing.Mapping[str, typing.Any], +): + return all(second.get(key) == value for key, value in first.items()) diff --git a/aiohttpretty/main.py b/aiohttpretty/main.py new file mode 100644 index 0000000..3cd6103 --- /dev/null +++ b/aiohttpretty/main.py @@ -0,0 +1,224 @@ +import asyncio +import copy +import json +import typing +from contextlib import contextmanager +from functools import wraps +from http import HTTPStatus +from unittest.mock import Mock + +from aiohttp import ClientSession +from aiohttp.client import ClientResponse +from aiohttp.helpers import TimerNoop +from multidict import CIMultiDict, CIMultiDictProxy +from yarl import URL + +from aiohttpretty import exc, helpers, types + +METHODS = typing.Literal[ + 'GET', 'POST', 'PUT', 'PATCH', 'HEAD', 'OPTIONS', 'DELETE' +] +AsyncCallableT = typing.TypeVar( + 'AsyncCallableT', bound=typing.Callable[..., typing.Coroutine] +) +CallableT = typing.TypeVar('CallableT', bound=typing.Callable) + + +class AioHttPretty: + def __init__(self): + self.calls: typing.List[typing.MutableMapping[str, typing.Any]] = [] + self.registry = {} + self.request = None + + async def process_request(self, **kwargs): + """Process request options as if the request was actually executed. + """ + data = kwargs.get('data') + if isinstance(data, asyncio.StreamReader): + await data.read() + + def make_call(self, *, method: METHODS, uri: str, **kwargs): + self.calls.append( + { + 'method': method, + 'uri': types.ImmutableFurl( + uri, params=kwargs.pop('params', None) + ), + **kwargs, + } + ) + + async def fake_request( + self, method: METHODS, uri: str, **kwargs: typing.Any + ): + + response = self._find_request(method, uri, kwargs) + + await self.process_request(**kwargs) + self.make_call( + method=method, uri=uri, **kwargs, + ) + + return self._build_response(method, uri, response) + + def _build_response( + self, + method: METHODS, + uri: str, + response: typing.Mapping[str, typing.Any], + ): + loop = Mock() + loop.get_debug = Mock() + loop.get_debug.return_value = True + + y_url = URL(uri) + mock_response = ClientResponse( + method, + y_url, + request_info=Mock(), + writer=Mock(), + continue100=None, + timer=TimerNoop(), + traces=[], + loop=loop, + session=None, # type: ignore + ) + + content = helpers.wrap_content_stream( + response.get('body', 'aiohttpretty') + ) + mock_response.content = content # type: ignore + + # Build response headers manually + headers = CIMultiDict(response.get('headers', {})) + if response.get('auto_length'): + headers.update({'Content-Length': str(content)}) + raw_headers = helpers.build_raw_headers(headers) + + mock_response._headers = CIMultiDictProxy(headers) + mock_response._raw_headers = raw_headers + + # Set response status and reason + mock_response.status = response.get('status', HTTPStatus.OK) + mock_response.reason = response.get('reason', '') + return mock_response + + def _find_request( + self, + method: METHODS, + uri: str, + kwargs: typing.Mapping[str, typing.Any], + ): + params = kwargs.get('params') + url = types.ImmutableFurl(uri, params=params) + + try: + response = self.registry[(method, url)] + except KeyError as error: + raise exc.NoUrlMatching( + 'No URLs matching {method} {uri} with params {url.params}. ' + 'Not making request. Go fix your test.'.format( + method=method, uri=uri, url=url + ) + ) from error + + if isinstance(response, typing.MutableSequence): + try: + response = response.pop(0) + except IndexError as error: + raise exc.ExhaustedAllResponses( + 'No responses left.' + ) from error + + return response + + def validate_body(self, options: typing.Mapping[str, typing.Any]): + if body := options.get('body'): + if not isinstance( + body, (str, bytes) + ) and not helpers.is_stream_like(body): + raise exc.InvalidBody(body) + if responses := options.get('responses'): + for response in responses: + self.validate_body(response) + + def register_uri(self, method: METHODS, uri: str, **options: typing.Any): + if any(x.get('params') for x in options.get('responses', [])): + raise exc.InvalidResponses( + 'Cannot specify params in responses, call register multiple times.' + ) + self.validate_body(options) + params = options.pop('params', {}) + url = types.ImmutableFurl(uri, params=params) + self.registry[(method, url)] = options.get('responses', options) + + def register_json_uri( + self, + method: METHODS, + uri: str, + body: typing.Optional[typing.Any] = None, + headers: typing.Optional[typing.Mapping[str, str]] = None, + params: typing.Optional[typing.Mapping[str, str]] = None, + **options: typing.Any, + ): + body = helpers.encode_string(json.dumps(body)) + headers = { + 'Content-Type': 'application/json', + **(headers or {}), + } + self.register_uri( + method, + uri, + body=body, + headers=headers, + params=params or {}, + **options, + ) + + def activate(self): + ClientSession._request, self.request = ( # type: ignore + self.fake_request, + ClientSession._request, + ) + + def deactivate(self): + ClientSession._request, self.request = self.request, None # type: ignore + + def clear(self): + self.calls = [] + self.registry = {} + + def has_call(self, uri: str, check_params: bool = True, **kwargs): + """Check to see if the given uri was called. By default will verify that the query params + match up. Setting ``check_params`` to `False` will strip params from the *called* uri, not + the passed-in uri.""" + kwargs['uri'] = types.ImmutableFurl( + uri, params=kwargs.pop('params', None) + ) + for call in self.calls: + if not check_params: + call = copy.deepcopy(call) + call['uri'] = call['uri'].with_out_params() + if helpers.compare_mapping(kwargs, call): + return True + return False + + @contextmanager + def open(self): + self.activate() + yield + self.deactivate() + + def async_call(self, func: AsyncCallableT) -> AsyncCallableT: + @wraps(func) + async def inner(*args, **kwargs): + with self.open(): + return await func(*args, **kwargs) + + return inner # type: ignore + + def call(self, func: CallableT) -> CallableT: + return self.open()(func) # type: ignore + + +aiohttpretty = AioHttPretty() diff --git a/aiohttpretty/plugin.py b/aiohttpretty/plugin.py new file mode 100644 index 0000000..ceaa06f --- /dev/null +++ b/aiohttpretty/plugin.py @@ -0,0 +1,25 @@ +import pytest + +from .main import aiohttpretty + + +@pytest.hookimpl +def pytest_runtest_setup(item: pytest.Item): + if all(mark.name != 'aiohttpretty' for mark in item.iter_markers()): + return + aiohttpretty.clear() + aiohttpretty.activate() + + +@pytest.hookimpl +def pytest_runtest_teardown(item: pytest.Item): + if all(mark.name != 'aiohttpretty' for mark in item.iter_markers()): + return + aiohttpretty.deactivate() + aiohttpretty.clear() + + +def pytest_configure(config): + config.addinivalue_line( + 'markers', 'aiohttpretty: mark tests to activate aiohttpretty' + ) diff --git a/aiohttpretty/types.py b/aiohttpretty/types.py new file mode 100644 index 0000000..2f15e56 --- /dev/null +++ b/aiohttpretty/types.py @@ -0,0 +1,54 @@ +import typing +from unittest.mock import Mock + +from aiohttp.base_protocol import BaseProtocol +from aiohttp.streams import StreamReader +from furl import furl # type: ignore + + +class ImmutableFurl: + def __init__( + self, + url: str, + params: typing.Optional[typing.Mapping[str, str]] = None, + ): + self._url = url + self._furl = furl(url) + self._params = furl(url).args + self._furl.set(args={}) + + params = params or {} + for (k, v) in params.items(): + self._params.add(k, v) + + def with_out_params(self): + return ImmutableFurl(self.url) + + @property + def url(self): + return self._furl.url + + @property + def params(self): + return self._params + + def __eq__(self, other): + return hash(self) == hash(other) + + def __hash__(self): + return hash( + self.url + + ''.join(self.params[x] or '' for x in sorted(self.params)) + ) + + +DEFAULT_LIMIT = 2 ** 16 + + +class MockStream(StreamReader): + def __init__(self, data: typing.Any, limit: int = DEFAULT_LIMIT): + protocol = BaseProtocol(Mock()) + super().__init__(protocol, limit) + self.size = len(data) + self.feed_data(data) + self.feed_eof() diff --git a/dev-requirements.txt b/dev-requirements.txt deleted file mode 100644 index f4fbd6a..0000000 --- a/dev-requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ --r requirements.txt - -flake8==3.7.6 -pytest==4.3.1 -pytest-cov==2.6.1 diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..9377d54 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,803 @@ +[[package]] +name = "aiohttp" +version = "3.8.1" +description = "Async http client/server framework (asyncio)" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +aiosignal = ">=1.1.2" +async-timeout = ">=4.0.0a3,<5.0" +attrs = ">=17.3.0" +charset-normalizer = ">=2.0,<3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["aiodns", "brotli", "cchardet"] + +[[package]] +name = "aiosignal" +version = "1.2.0" +description = "aiosignal: a list of registered asynchronous callbacks" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +frozenlist = ">=1.1.0" + +[[package]] +name = "async-timeout" +version = "4.0.2" +description = "Timeout context manager for asyncio programs" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "21.4.0" +description = "Classes Without Boilerplate" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] + +[[package]] +name = "charset-normalizer" +version = "2.1.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.6.0" + +[package.extras] +unicode_backport = ["unicodedata2"] + +[[package]] +name = "colorama" +version = "0.4.5" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "coverage" +version = "6.4.1" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "flake8" +version = "4.0.1" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.8.0,<2.9.0" +pyflakes = ">=2.4.0,<2.5.0" + +[[package]] +name = "frozenlist" +version = "1.3.0" +description = "A list-like structure which implements collections.abc.MutableSequence" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "furl" +version = "2.1.3" +description = "URL manipulation made simple." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +orderedmultidict = ">=1.0.1" +six = ">=1.8.0" + +[[package]] +name = "idna" +version = "3.3" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "isort" +version = "5.10.1" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.6.1,<4.0" + +[package.extras] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] +requirements_deprecated_finder = ["pipreqs", "pip-api"] +colors = ["colorama (>=0.4.3,<0.5.0)"] +plugins = ["setuptools"] + +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "multidict" +version = "6.0.2" +description = "multidict implementation" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "mypy" +version = "0.961" +description = "Optional static typing for Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +mypy-extensions = ">=0.4.3" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=3.10" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +python2 = ["typed-ast (>=1.4.0,<2)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "orderedmultidict" +version = "1.0.1" +description = "Ordered Multivalue Dictionary" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +six = ">=1.8.0" + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pycodestyle" +version = "2.8.0" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pyflakes" +version = "2.4.0" +description = "passive checker of Python programs" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pyparsing" +version = "3.0.9" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +category = "dev" +optional = false +python-versions = ">=3.6.8" + +[package.extras] +diagrams = ["railroad-diagrams", "jinja2"] + +[[package]] +name = "pytest" +version = "7.1.2" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +py = ">=1.8.2" +tomli = ">=1.0.0" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "0.18.3" +description = "Pytest support for asyncio" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +pytest = ">=6.1.0" + +[package.extras] +testing = ["coverage (==6.2)", "hypothesis (>=5.7.1)", "flaky (>=3.5.0)", "mypy (==0.931)", "pytest-trio (>=0.7.0)"] + +[[package]] +name = "pytest-cov" +version = "3.0.0" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "typing-extensions" +version = "4.3.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "yarl" +version = "1.7.2" +description = "Yet another URL library" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[metadata] +lock-version = "1.1" +python-versions = "^3.8" +content-hash = "9dd5ed4f12a85b98639cf45ddf2b43b4a23565bad9540ab08e5c7a4544fa082a" + +[metadata.files] +aiohttp = [ + {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1ed0b6477896559f17b9eaeb6d38e07f7f9ffe40b9f0f9627ae8b9926ae260a8"}, + {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7dadf3c307b31e0e61689cbf9e06be7a867c563d5a63ce9dca578f956609abf8"}, + {file = "aiohttp-3.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a79004bb58748f31ae1cbe9fa891054baaa46fb106c2dc7af9f8e3304dc30316"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12de6add4038df8f72fac606dff775791a60f113a725c960f2bab01d8b8e6b15"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f0d5f33feb5f69ddd57a4a4bd3d56c719a141080b445cbf18f238973c5c9923"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eaba923151d9deea315be1f3e2b31cc39a6d1d2f682f942905951f4e40200922"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:099ebd2c37ac74cce10a3527d2b49af80243e2a4fa39e7bce41617fbc35fa3c1"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2e5d962cf7e1d426aa0e528a7e198658cdc8aa4fe87f781d039ad75dcd52c516"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fa0ffcace9b3aa34d205d8130f7873fcfefcb6a4dd3dd705b0dab69af6712642"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:61bfc23df345d8c9716d03717c2ed5e27374e0fe6f659ea64edcd27b4b044cf7"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:31560d268ff62143e92423ef183680b9829b1b482c011713ae941997921eebc8"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:01d7bdb774a9acc838e6b8f1d114f45303841b89b95984cbb7d80ea41172a9e3"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97ef77eb6b044134c0b3a96e16abcb05ecce892965a2124c566af0fd60f717e2"}, + {file = "aiohttp-3.8.1-cp310-cp310-win32.whl", hash = "sha256:c2aef4703f1f2ddc6df17519885dbfa3514929149d3ff900b73f45998f2532fa"}, + {file = "aiohttp-3.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:713ac174a629d39b7c6a3aa757b337599798da4c1157114a314e4e391cd28e32"}, + {file = "aiohttp-3.8.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:473d93d4450880fe278696549f2e7aed8cd23708c3c1997981464475f32137db"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b5eeae8e019e7aad8af8bb314fb908dd2e028b3cdaad87ec05095394cce632"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af642b43ce56c24d063325dd2cf20ee012d2b9ba4c3c008755a301aaea720ad"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3630c3ef435c0a7c549ba170a0633a56e92629aeed0e707fec832dee313fb7a"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4a4a4e30bf1edcad13fb0804300557aedd07a92cabc74382fdd0ba6ca2661091"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f8b01295e26c68b3a1b90efb7a89029110d3a4139270b24fda961893216c440"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a25fa703a527158aaf10dafd956f7d42ac6d30ec80e9a70846253dd13e2f067b"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5bfde62d1d2641a1f5173b8c8c2d96ceb4854f54a44c23102e2ccc7e02f003ec"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:51467000f3647d519272392f484126aa716f747859794ac9924a7aafa86cd411"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:03a6d5349c9ee8f79ab3ff3694d6ce1cfc3ced1c9d36200cb8f08ba06bd3b782"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:102e487eeb82afac440581e5d7f8f44560b36cf0bdd11abc51a46c1cd88914d4"}, + {file = "aiohttp-3.8.1-cp36-cp36m-win32.whl", hash = "sha256:4aed991a28ea3ce320dc8ce655875e1e00a11bdd29fe9444dd4f88c30d558602"}, + {file = "aiohttp-3.8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b0e20cddbd676ab8a64c774fefa0ad787cc506afd844de95da56060348021e96"}, + {file = "aiohttp-3.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:37951ad2f4a6df6506750a23f7cbabad24c73c65f23f72e95897bb2cecbae676"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c23b1ad869653bc818e972b7a3a79852d0e494e9ab7e1a701a3decc49c20d51"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15b09b06dae900777833fe7fc4b4aa426556ce95847a3e8d7548e2d19e34edb8"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:477c3ea0ba410b2b56b7efb072c36fa91b1e6fc331761798fa3f28bb224830dd"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2f2f69dca064926e79997f45b2f34e202b320fd3782f17a91941f7eb85502ee2"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ef9612483cb35171d51d9173647eed5d0069eaa2ee812793a75373447d487aa4"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6d69f36d445c45cda7b3b26afef2fc34ef5ac0cdc75584a87ef307ee3c8c6d00"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:55c3d1072704d27401c92339144d199d9de7b52627f724a949fc7d5fc56d8b93"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b9d00268fcb9f66fbcc7cd9fe423741d90c75ee029a1d15c09b22d23253c0a44"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:07b05cd3305e8a73112103c834e91cd27ce5b4bd07850c4b4dbd1877d3f45be7"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c34dc4958b232ef6188c4318cb7b2c2d80521c9a56c52449f8f93ab7bc2a8a1c"}, + {file = "aiohttp-3.8.1-cp37-cp37m-win32.whl", hash = "sha256:d2f9b69293c33aaa53d923032fe227feac867f81682f002ce33ffae978f0a9a9"}, + {file = "aiohttp-3.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6ae828d3a003f03ae31915c31fa684b9890ea44c9c989056fea96e3d12a9fa17"}, + {file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0c7ebbbde809ff4e970824b2b6cb7e4222be6b95a296e46c03cf050878fc1785"}, + {file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b7ef7cbd4fec9a1e811a5de813311ed4f7ac7d93e0fda233c9b3e1428f7dd7b"}, + {file = "aiohttp-3.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c3d6a4d0619e09dcd61021debf7059955c2004fa29f48788a3dfaf9c9901a7cd"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:718626a174e7e467f0558954f94af117b7d4695d48eb980146016afa4b580b2e"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:589c72667a5febd36f1315aa6e5f56dd4aa4862df295cb51c769d16142ddd7cd"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ed076098b171573161eb146afcb9129b5ff63308960aeca4b676d9d3c35e700"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:086f92daf51a032d062ec5f58af5ca6a44d082c35299c96376a41cbb33034675"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:11691cf4dc5b94236ccc609b70fec991234e7ef8d4c02dd0c9668d1e486f5abf"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:31d1e1c0dbf19ebccbfd62eff461518dcb1e307b195e93bba60c965a4dcf1ba0"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:11a67c0d562e07067c4e86bffc1553f2cf5b664d6111c894671b2b8712f3aba5"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:bb01ba6b0d3f6c68b89fce7305080145d4877ad3acaed424bae4d4ee75faa950"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:44db35a9e15d6fe5c40d74952e803b1d96e964f683b5a78c3cc64eb177878155"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:844a9b460871ee0a0b0b68a64890dae9c415e513db0f4a7e3cab41a0f2fedf33"}, + {file = "aiohttp-3.8.1-cp38-cp38-win32.whl", hash = "sha256:7d08744e9bae2ca9c382581f7dce1273fe3c9bae94ff572c3626e8da5b193c6a"}, + {file = "aiohttp-3.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:04d48b8ce6ab3cf2097b1855e1505181bdd05586ca275f2505514a6e274e8e75"}, + {file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f5315a2eb0239185af1bddb1abf472d877fede3cc8d143c6cddad37678293237"}, + {file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a996d01ca39b8dfe77440f3cd600825d05841088fd6bc0144cc6c2ec14cc5f74"}, + {file = "aiohttp-3.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:13487abd2f761d4be7c8ff9080de2671e53fff69711d46de703c310c4c9317ca"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea302f34477fda3f85560a06d9ebdc7fa41e82420e892fc50b577e35fc6a50b2"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2f635ce61a89c5732537a7896b6319a8fcfa23ba09bec36e1b1ac0ab31270d2"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e999f2d0e12eea01caeecb17b653f3713d758f6dcc770417cf29ef08d3931421"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0770e2806a30e744b4e21c9d73b7bee18a1cfa3c47991ee2e5a65b887c49d5cf"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d15367ce87c8e9e09b0f989bfd72dc641bcd04ba091c68cd305312d00962addd"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6c7cefb4b0640703eb1069835c02486669312bf2f12b48a748e0a7756d0de33d"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:71927042ed6365a09a98a6377501af5c9f0a4d38083652bcd2281a06a5976724"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:28d490af82bc6b7ce53ff31337a18a10498303fe66f701ab65ef27e143c3b0ef"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:b6613280ccedf24354406caf785db748bebbddcf31408b20c0b48cb86af76866"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81e3d8c34c623ca4e36c46524a3530e99c0bc95ed068fd6e9b55cb721d408fb2"}, + {file = "aiohttp-3.8.1-cp39-cp39-win32.whl", hash = "sha256:7187a76598bdb895af0adbd2fb7474d7f6025d170bc0a1130242da817ce9e7d1"}, + {file = "aiohttp-3.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:1c182cb873bc91b411e184dab7a2b664d4fea2743df0e4d57402f7f3fa644bac"}, + {file = "aiohttp-3.8.1.tar.gz", hash = "sha256:fc5471e1a54de15ef71c1bc6ebe80d4dc681ea600e68bfd1cbce40427f0b7578"}, +] +aiosignal = [ + {file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"}, + {file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"}, +] +async-timeout = [ + {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, + {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, +] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] +attrs = [ + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, +] +charset-normalizer = [ + {file = "charset-normalizer-2.1.0.tar.gz", hash = "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"}, + {file = "charset_normalizer-2.1.0-py3-none-any.whl", hash = "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5"}, +] +colorama = [ + {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, + {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, +] +coverage = [ + {file = "coverage-6.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f1d5aa2703e1dab4ae6cf416eb0095304f49d004c39e9db1d86f57924f43006b"}, + {file = "coverage-6.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ce1b258493cbf8aec43e9b50d89982346b98e9ffdfaae8ae5793bc112fb0068"}, + {file = "coverage-6.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83c4e737f60c6936460c5be330d296dd5b48b3963f48634c53b3f7deb0f34ec4"}, + {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84e65ef149028516c6d64461b95a8dbcfce95cfd5b9eb634320596173332ea84"}, + {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f69718750eaae75efe506406c490d6fc5a6161d047206cc63ce25527e8a3adad"}, + {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e57816f8ffe46b1df8f12e1b348f06d164fd5219beba7d9433ba79608ef011cc"}, + {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:01c5615d13f3dd3aa8543afc069e5319cfa0c7d712f6e04b920431e5c564a749"}, + {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75ab269400706fab15981fd4bd5080c56bd5cc07c3bccb86aab5e1d5a88dc8f4"}, + {file = "coverage-6.4.1-cp310-cp310-win32.whl", hash = "sha256:a7f3049243783df2e6cc6deafc49ea123522b59f464831476d3d1448e30d72df"}, + {file = "coverage-6.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:ee2ddcac99b2d2aec413e36d7a429ae9ebcadf912946b13ffa88e7d4c9b712d6"}, + {file = "coverage-6.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb73e0011b8793c053bfa85e53129ba5f0250fdc0392c1591fd35d915ec75c46"}, + {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106c16dfe494de3193ec55cac9640dd039b66e196e4641fa8ac396181578b982"}, + {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87f4f3df85aa39da00fd3ec4b5abeb7407e82b68c7c5ad181308b0e2526da5d4"}, + {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:961e2fb0680b4f5ad63234e0bf55dfb90d302740ae9c7ed0120677a94a1590cb"}, + {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cec3a0f75c8f1031825e19cd86ee787e87cf03e4fd2865c79c057092e69e3a3b"}, + {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:129cd05ba6f0d08a766d942a9ed4b29283aff7b2cccf5b7ce279d50796860bb3"}, + {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bf5601c33213d3cb19d17a796f8a14a9eaa5e87629a53979a5981e3e3ae166f6"}, + {file = "coverage-6.4.1-cp37-cp37m-win32.whl", hash = "sha256:269eaa2c20a13a5bf17558d4dc91a8d078c4fa1872f25303dddcbba3a813085e"}, + {file = "coverage-6.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f02cbbf8119db68455b9d763f2f8737bb7db7e43720afa07d8eb1604e5c5ae28"}, + {file = "coverage-6.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ffa9297c3a453fba4717d06df579af42ab9a28022444cae7fa605af4df612d54"}, + {file = "coverage-6.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:145f296d00441ca703a659e8f3eb48ae39fb083baba2d7ce4482fb2723e050d9"}, + {file = "coverage-6.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d44996140af8b84284e5e7d398e589574b376fb4de8ccd28d82ad8e3bea13"}, + {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2bd9a6fc18aab8d2e18f89b7ff91c0f34ff4d5e0ba0b33e989b3cd4194c81fd9"}, + {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3384f2a3652cef289e38100f2d037956194a837221edd520a7ee5b42d00cc605"}, + {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9b3e07152b4563722be523e8cd0b209e0d1a373022cfbde395ebb6575bf6790d"}, + {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1480ff858b4113db2718848d7b2d1b75bc79895a9c22e76a221b9d8d62496428"}, + {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:865d69ae811a392f4d06bde506d531f6a28a00af36f5c8649684a9e5e4a85c83"}, + {file = "coverage-6.4.1-cp38-cp38-win32.whl", hash = "sha256:664a47ce62fe4bef9e2d2c430306e1428ecea207ffd68649e3b942fa8ea83b0b"}, + {file = "coverage-6.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:26dff09fb0d82693ba9e6231248641d60ba606150d02ed45110f9ec26404ed1c"}, + {file = "coverage-6.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9c80df769f5ec05ad21ea34be7458d1dc51ff1fb4b2219e77fe24edf462d6df"}, + {file = "coverage-6.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:39ee53946bf009788108b4dd2894bf1349b4e0ca18c2016ffa7d26ce46b8f10d"}, + {file = "coverage-6.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5b66caa62922531059bc5ac04f836860412f7f88d38a476eda0a6f11d4724f4"}, + {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd180ed867e289964404051a958f7cccabdeed423f91a899829264bb7974d3d3"}, + {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84631e81dd053e8a0d4967cedab6db94345f1c36107c71698f746cb2636c63e3"}, + {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8c08da0bd238f2970230c2a0d28ff0e99961598cb2e810245d7fc5afcf1254e8"}, + {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d42c549a8f41dc103a8004b9f0c433e2086add8a719da00e246e17cbe4056f72"}, + {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:309ce4a522ed5fca432af4ebe0f32b21d6d7ccbb0f5fcc99290e71feba67c264"}, + {file = "coverage-6.4.1-cp39-cp39-win32.whl", hash = "sha256:fdb6f7bd51c2d1714cea40718f6149ad9be6a2ee7d93b19e9f00934c0f2a74d9"}, + {file = "coverage-6.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:342d4aefd1c3e7f620a13f4fe563154d808b69cccef415415aece4c786665397"}, + {file = "coverage-6.4.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:4803e7ccf93230accb928f3a68f00ffa80a88213af98ed338a57ad021ef06815"}, + {file = "coverage-6.4.1.tar.gz", hash = "sha256:4321f075095a096e70aff1d002030ee612b65a205a0a0f5b815280d5dc58100c"}, +] +flake8 = [ + {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, + {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, +] +frozenlist = [ + {file = "frozenlist-1.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2257aaba9660f78c7b1d8fea963b68f3feffb1a9d5d05a18401ca9eb3e8d0a3"}, + {file = "frozenlist-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4a44ebbf601d7bac77976d429e9bdb5a4614f9f4027777f9e54fd765196e9d3b"}, + {file = "frozenlist-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:45334234ec30fc4ea677f43171b18a27505bfb2dba9aca4398a62692c0ea8868"}, + {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47be22dc27ed933d55ee55845d34a3e4e9f6fee93039e7f8ebadb0c2f60d403f"}, + {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03a7dd1bfce30216a3f51a84e6dd0e4a573d23ca50f0346634916ff105ba6e6b"}, + {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:691ddf6dc50480ce49f68441f1d16a4c3325887453837036e0fb94736eae1e58"}, + {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bde99812f237f79eaf3f04ebffd74f6718bbd216101b35ac7955c2d47c17da02"}, + {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a202458d1298ced3768f5a7d44301e7c86defac162ace0ab7434c2e961166e8"}, + {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b9e3e9e365991f8cc5f5edc1fd65b58b41d0514a6a7ad95ef5c7f34eb49b3d3e"}, + {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:04cb491c4b1c051734d41ea2552fde292f5f3a9c911363f74f39c23659c4af78"}, + {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:436496321dad302b8b27ca955364a439ed1f0999311c393dccb243e451ff66aa"}, + {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:754728d65f1acc61e0f4df784456106e35afb7bf39cfe37227ab00436fb38676"}, + {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6eb275c6385dd72594758cbe96c07cdb9bd6becf84235f4a594bdf21e3596c9d"}, + {file = "frozenlist-1.3.0-cp310-cp310-win32.whl", hash = "sha256:e30b2f9683812eb30cf3f0a8e9f79f8d590a7999f731cf39f9105a7c4a39489d"}, + {file = "frozenlist-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:f7353ba3367473d1d616ee727945f439e027f0bb16ac1a750219a8344d1d5d3c"}, + {file = "frozenlist-1.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88aafd445a233dbbf8a65a62bc3249a0acd0d81ab18f6feb461cc5a938610d24"}, + {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4406cfabef8f07b3b3af0f50f70938ec06d9f0fc26cbdeaab431cbc3ca3caeaa"}, + {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8cf829bd2e2956066dd4de43fd8ec881d87842a06708c035b37ef632930505a2"}, + {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:603b9091bd70fae7be28bdb8aa5c9990f4241aa33abb673390a7f7329296695f"}, + {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25af28b560e0c76fa41f550eacb389905633e7ac02d6eb3c09017fa1c8cdfde1"}, + {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c7a8a9fc9383b52c410a2ec952521906d355d18fccc927fca52ab575ee8b93"}, + {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:65bc6e2fece04e2145ab6e3c47428d1bbc05aede61ae365b2c1bddd94906e478"}, + {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3f7c935c7b58b0d78c0beea0c7358e165f95f1fd8a7e98baa40d22a05b4a8141"}, + {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd89acd1b8bb4f31b47072615d72e7f53a948d302b7c1d1455e42622de180eae"}, + {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:6983a31698490825171be44ffbafeaa930ddf590d3f051e397143a5045513b01"}, + {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:adac9700675cf99e3615eb6a0eb5e9f5a4143c7d42c05cea2e7f71c27a3d0846"}, + {file = "frozenlist-1.3.0-cp37-cp37m-win32.whl", hash = "sha256:0c36e78b9509e97042ef869c0e1e6ef6429e55817c12d78245eb915e1cca7468"}, + {file = "frozenlist-1.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:57f4d3f03a18facacb2a6bcd21bccd011e3b75d463dc49f838fd699d074fabd1"}, + {file = "frozenlist-1.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8c905a5186d77111f02144fab5b849ab524f1e876a1e75205cd1386a9be4b00a"}, + {file = "frozenlist-1.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b5009062d78a8c6890d50b4e53b0ddda31841b3935c1937e2ed8c1bda1c7fb9d"}, + {file = "frozenlist-1.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2fdc3cd845e5a1f71a0c3518528bfdbfe2efaf9886d6f49eacc5ee4fd9a10953"}, + {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92e650bd09b5dda929523b9f8e7f99b24deac61240ecc1a32aeba487afcd970f"}, + {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:40dff8962b8eba91fd3848d857203f0bd704b5f1fa2b3fc9af64901a190bba08"}, + {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:768efd082074bb203c934e83a61654ed4931ef02412c2fbdecea0cff7ecd0274"}, + {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:006d3595e7d4108a12025ddf415ae0f6c9e736e726a5db0183326fd191b14c5e"}, + {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:871d42623ae15eb0b0e9df65baeee6976b2e161d0ba93155411d58ff27483ad8"}, + {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aff388be97ef2677ae185e72dc500d19ecaf31b698986800d3fc4f399a5e30a5"}, + {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9f892d6a94ec5c7b785e548e42722e6f3a52f5f32a8461e82ac3e67a3bd073f1"}, + {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:e982878792c971cbd60ee510c4ee5bf089a8246226dea1f2138aa0bb67aff148"}, + {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:c6c321dd013e8fc20735b92cb4892c115f5cdb82c817b1e5b07f6b95d952b2f0"}, + {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:30530930410855c451bea83f7b272fb1c495ed9d5cc72895ac29e91279401db3"}, + {file = "frozenlist-1.3.0-cp38-cp38-win32.whl", hash = "sha256:40ec383bc194accba825fbb7d0ef3dda5736ceab2375462f1d8672d9f6b68d07"}, + {file = "frozenlist-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:f20baa05eaa2bcd5404c445ec51aed1c268d62600362dc6cfe04fae34a424bd9"}, + {file = "frozenlist-1.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0437fe763fb5d4adad1756050cbf855bbb2bf0d9385c7bb13d7a10b0dd550486"}, + {file = "frozenlist-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b684c68077b84522b5c7eafc1dc735bfa5b341fb011d5552ebe0968e22ed641c"}, + {file = "frozenlist-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93641a51f89473837333b2f8100f3f89795295b858cd4c7d4a1f18e299dc0a4f"}, + {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6d32ff213aef0fd0bcf803bffe15cfa2d4fde237d1d4838e62aec242a8362fa"}, + {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31977f84828b5bb856ca1eb07bf7e3a34f33a5cddce981d880240ba06639b94d"}, + {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c62964192a1c0c30b49f403495911298810bada64e4f03249ca35a33ca0417a"}, + {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4eda49bea3602812518765810af732229b4291d2695ed24a0a20e098c45a707b"}, + {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acb267b09a509c1df5a4ca04140da96016f40d2ed183cdc356d237286c971b51"}, + {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e1e26ac0a253a2907d654a37e390904426d5ae5483150ce3adedb35c8c06614a"}, + {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f96293d6f982c58ebebb428c50163d010c2f05de0cde99fd681bfdc18d4b2dc2"}, + {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e84cb61b0ac40a0c3e0e8b79c575161c5300d1d89e13c0e02f76193982f066ed"}, + {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:ff9310f05b9d9c5c4dd472983dc956901ee6cb2c3ec1ab116ecdde25f3ce4951"}, + {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d26b650b71fdc88065b7a21f8ace70175bcf3b5bdba5ea22df4bfd893e795a3b"}, + {file = "frozenlist-1.3.0-cp39-cp39-win32.whl", hash = "sha256:01a73627448b1f2145bddb6e6c2259988bb8aee0fb361776ff8604b99616cd08"}, + {file = "frozenlist-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:772965f773757a6026dea111a15e6e2678fbd6216180f82a48a40b27de1ee2ab"}, + {file = "frozenlist-1.3.0.tar.gz", hash = "sha256:ce6f2ba0edb7b0c1d8976565298ad2deba6f8064d2bebb6ffce2ca896eb35b0b"}, +] +furl = [ + {file = "furl-2.1.3-py2.py3-none-any.whl", hash = "sha256:9ab425062c4217f9802508e45feb4a83e54324273ac4b202f1850363309666c0"}, + {file = "furl-2.1.3.tar.gz", hash = "sha256:5a6188fe2666c484a12159c18be97a1977a71d632ef5bb867ef15f54af39cc4e"}, +] +idna = [ + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, +] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +isort = [ + {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, + {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, +] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] +multidict = [ + {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2"}, + {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3"}, + {file = "multidict-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389"}, + {file = "multidict-6.0.2-cp310-cp310-win32.whl", hash = "sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293"}, + {file = "multidict-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658"}, + {file = "multidict-6.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15"}, + {file = "multidict-6.0.2-cp37-cp37m-win32.whl", hash = "sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc"}, + {file = "multidict-6.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d"}, + {file = "multidict-6.0.2-cp38-cp38-win32.whl", hash = "sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57"}, + {file = "multidict-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937"}, + {file = "multidict-6.0.2-cp39-cp39-win32.whl", hash = "sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a"}, + {file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"}, + {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"}, +] +mypy = [ + {file = "mypy-0.961-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:697540876638ce349b01b6786bc6094ccdaba88af446a9abb967293ce6eaa2b0"}, + {file = "mypy-0.961-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b117650592e1782819829605a193360a08aa99f1fc23d1d71e1a75a142dc7e15"}, + {file = "mypy-0.961-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bdd5ca340beffb8c44cb9dc26697628d1b88c6bddf5c2f6eb308c46f269bb6f3"}, + {file = "mypy-0.961-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3e09f1f983a71d0672bbc97ae33ee3709d10c779beb613febc36805a6e28bb4e"}, + {file = "mypy-0.961-cp310-cp310-win_amd64.whl", hash = "sha256:e999229b9f3198c0c880d5e269f9f8129c8862451ce53a011326cad38b9ccd24"}, + {file = "mypy-0.961-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b24be97351084b11582fef18d79004b3e4db572219deee0212078f7cf6352723"}, + {file = "mypy-0.961-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f4a21d01fc0ba4e31d82f0fff195682e29f9401a8bdb7173891070eb260aeb3b"}, + {file = "mypy-0.961-cp36-cp36m-win_amd64.whl", hash = "sha256:439c726a3b3da7ca84a0199a8ab444cd8896d95012c4a6c4a0d808e3147abf5d"}, + {file = "mypy-0.961-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5a0b53747f713f490affdceef835d8f0cb7285187a6a44c33821b6d1f46ed813"}, + {file = "mypy-0.961-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0e9f70df36405c25cc530a86eeda1e0867863d9471fe76d1273c783df3d35c2e"}, + {file = "mypy-0.961-cp37-cp37m-win_amd64.whl", hash = "sha256:b88f784e9e35dcaa075519096dc947a388319cb86811b6af621e3523980f1c8a"}, + {file = "mypy-0.961-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d5aaf1edaa7692490f72bdb9fbd941fbf2e201713523bdb3f4038be0af8846c6"}, + {file = "mypy-0.961-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9f5f5a74085d9a81a1f9c78081d60a0040c3efb3f28e5c9912b900adf59a16e6"}, + {file = "mypy-0.961-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f4b794db44168a4fc886e3450201365c9526a522c46ba089b55e1f11c163750d"}, + {file = "mypy-0.961-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:64759a273d590040a592e0f4186539858c948302c653c2eac840c7a3cd29e51b"}, + {file = "mypy-0.961-cp38-cp38-win_amd64.whl", hash = "sha256:63e85a03770ebf403291ec50097954cc5caf2a9205c888ce3a61bd3f82e17569"}, + {file = "mypy-0.961-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f1332964963d4832a94bebc10f13d3279be3ce8f6c64da563d6ee6e2eeda932"}, + {file = "mypy-0.961-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:006be38474216b833eca29ff6b73e143386f352e10e9c2fbe76aa8549e5554f5"}, + {file = "mypy-0.961-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9940e6916ed9371809b35b2154baf1f684acba935cd09928952310fbddaba648"}, + {file = "mypy-0.961-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a5ea0875a049de1b63b972456542f04643daf320d27dc592d7c3d9cd5d9bf950"}, + {file = "mypy-0.961-cp39-cp39-win_amd64.whl", hash = "sha256:1ece702f29270ec6af25db8cf6185c04c02311c6bb21a69f423d40e527b75c56"}, + {file = "mypy-0.961-py3-none-any.whl", hash = "sha256:03c6cc893e7563e7b2949b969e63f02c000b32502a1b4d1314cabe391aa87d66"}, + {file = "mypy-0.961.tar.gz", hash = "sha256:f730d56cb924d371c26b8eaddeea3cc07d78ff51c521c6d04899ac6904b75492"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +orderedmultidict = [ + {file = "orderedmultidict-1.0.1-py2.py3-none-any.whl", hash = "sha256:43c839a17ee3cdd62234c47deca1a8508a3f2ca1d0678a3bf791c87cf84adbf3"}, + {file = "orderedmultidict-1.0.1.tar.gz", hash = "sha256:04070bbb5e87291cc9bfa51df413677faf2141c73c61d2a5f7b26bea3cd882ad"}, +] +packaging = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] +pluggy = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] +py = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] +pycodestyle = [ + {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, + {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, +] +pyflakes = [ + {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, + {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, +] +pyparsing = [ + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, +] +pytest = [ + {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, + {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, +] +pytest-asyncio = [ + {file = "pytest-asyncio-0.18.3.tar.gz", hash = "sha256:7659bdb0a9eb9c6e3ef992eef11a2b3e69697800ad02fb06374a210d85b29f91"}, + {file = "pytest_asyncio-0.18.3-1-py3-none-any.whl", hash = "sha256:16cf40bdf2b4fb7fc8e4b82bd05ce3fbcd454cbf7b92afc445fe299dabb88213"}, + {file = "pytest_asyncio-0.18.3-py3-none-any.whl", hash = "sha256:8fafa6c52161addfd41ee7ab35f11836c5a16ec208f93ee388f752bea3493a84"}, +] +pytest-cov = [ + {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, + {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +tomli = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] +typing-extensions = [ + {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, + {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, +] +yarl = [ + {file = "yarl-1.7.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2a8508f7350512434e41065684076f640ecce176d262a7d54f0da41d99c5a95"}, + {file = "yarl-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da6df107b9ccfe52d3a48165e48d72db0eca3e3029b5b8cb4fe6ee3cb870ba8b"}, + {file = "yarl-1.7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1d0894f238763717bdcfea74558c94e3bc34aeacd3351d769460c1a586a8b05"}, + {file = "yarl-1.7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe4b95b7e00c6635a72e2d00b478e8a28bfb122dc76349a06e20792eb53a523"}, + {file = "yarl-1.7.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c145ab54702334c42237a6c6c4cc08703b6aa9b94e2f227ceb3d477d20c36c63"}, + {file = "yarl-1.7.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ca56f002eaf7998b5fcf73b2421790da9d2586331805f38acd9997743114e98"}, + {file = "yarl-1.7.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1d3d5ad8ea96bd6d643d80c7b8d5977b4e2fb1bab6c9da7322616fd26203d125"}, + {file = "yarl-1.7.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:167ab7f64e409e9bdd99333fe8c67b5574a1f0495dcfd905bc7454e766729b9e"}, + {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:95a1873b6c0dd1c437fb3bb4a4aaa699a48c218ac7ca1e74b0bee0ab16c7d60d"}, + {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6152224d0a1eb254f97df3997d79dadd8bb2c1a02ef283dbb34b97d4f8492d23"}, + {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:5bb7d54b8f61ba6eee541fba4b83d22b8a046b4ef4d8eb7f15a7e35db2e1e245"}, + {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:9c1f083e7e71b2dd01f7cd7434a5f88c15213194df38bc29b388ccdf1492b739"}, + {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f44477ae29025d8ea87ec308539f95963ffdc31a82f42ca9deecf2d505242e72"}, + {file = "yarl-1.7.2-cp310-cp310-win32.whl", hash = "sha256:cff3ba513db55cc6a35076f32c4cdc27032bd075c9faef31fec749e64b45d26c"}, + {file = "yarl-1.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:c9c6d927e098c2d360695f2e9d38870b2e92e0919be07dbe339aefa32a090265"}, + {file = "yarl-1.7.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9b4c77d92d56a4c5027572752aa35082e40c561eec776048330d2907aead891d"}, + {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c01a89a44bb672c38f42b49cdb0ad667b116d731b3f4c896f72302ff77d71656"}, + {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c19324a1c5399b602f3b6e7db9478e5b1adf5cf58901996fc973fe4fccd73eed"}, + {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3abddf0b8e41445426d29f955b24aeecc83fa1072be1be4e0d194134a7d9baee"}, + {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6a1a9fe17621af43e9b9fcea8bd088ba682c8192d744b386ee3c47b56eaabb2c"}, + {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8b0915ee85150963a9504c10de4e4729ae700af11df0dc5550e6587ed7891e92"}, + {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:29e0656d5497733dcddc21797da5a2ab990c0cb9719f1f969e58a4abac66234d"}, + {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:bf19725fec28452474d9887a128e98dd67eee7b7d52e932e6949c532d820dc3b"}, + {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:d6f3d62e16c10e88d2168ba2d065aa374e3c538998ed04996cd373ff2036d64c"}, + {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ac10bbac36cd89eac19f4e51c032ba6b412b3892b685076f4acd2de18ca990aa"}, + {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:aa32aaa97d8b2ed4e54dc65d241a0da1c627454950f7d7b1f95b13985afd6c5d"}, + {file = "yarl-1.7.2-cp36-cp36m-win32.whl", hash = "sha256:87f6e082bce21464857ba58b569370e7b547d239ca22248be68ea5d6b51464a1"}, + {file = "yarl-1.7.2-cp36-cp36m-win_amd64.whl", hash = "sha256:ac35ccde589ab6a1870a484ed136d49a26bcd06b6a1c6397b1967ca13ceb3913"}, + {file = "yarl-1.7.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a467a431a0817a292121c13cbe637348b546e6ef47ca14a790aa2fa8cc93df63"}, + {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ab0c3274d0a846840bf6c27d2c60ba771a12e4d7586bf550eefc2df0b56b3b4"}, + {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d260d4dc495c05d6600264a197d9d6f7fc9347f21d2594926202fd08cf89a8ba"}, + {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fc4dd8b01a8112809e6b636b00f487846956402834a7fd59d46d4f4267181c41"}, + {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c1164a2eac148d85bbdd23e07dfcc930f2e633220f3eb3c3e2a25f6148c2819e"}, + {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:67e94028817defe5e705079b10a8438b8cb56e7115fa01640e9c0bb3edf67332"}, + {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:89ccbf58e6a0ab89d487c92a490cb5660d06c3a47ca08872859672f9c511fc52"}, + {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8cce6f9fa3df25f55521fbb5c7e4a736683148bcc0c75b21863789e5185f9185"}, + {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:211fcd65c58bf250fb994b53bc45a442ddc9f441f6fec53e65de8cba48ded986"}, + {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c10ea1e80a697cf7d80d1ed414b5cb8f1eec07d618f54637067ae3c0334133c4"}, + {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:52690eb521d690ab041c3919666bea13ab9fbff80d615ec16fa81a297131276b"}, + {file = "yarl-1.7.2-cp37-cp37m-win32.whl", hash = "sha256:695ba021a9e04418507fa930d5f0704edbce47076bdcfeeaba1c83683e5649d1"}, + {file = "yarl-1.7.2-cp37-cp37m-win_amd64.whl", hash = "sha256:c17965ff3706beedafd458c452bf15bac693ecd146a60a06a214614dc097a271"}, + {file = "yarl-1.7.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fce78593346c014d0d986b7ebc80d782b7f5e19843ca798ed62f8e3ba8728576"}, + {file = "yarl-1.7.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c2a1ac41a6aa980db03d098a5531f13985edcb451bcd9d00670b03129922cd0d"}, + {file = "yarl-1.7.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:39d5493c5ecd75c8093fa7700a2fb5c94fe28c839c8e40144b7ab7ccba6938c8"}, + {file = "yarl-1.7.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1eb6480ef366d75b54c68164094a6a560c247370a68c02dddb11f20c4c6d3c9d"}, + {file = "yarl-1.7.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ba63585a89c9885f18331a55d25fe81dc2d82b71311ff8bd378fc8004202ff6"}, + {file = "yarl-1.7.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e39378894ee6ae9f555ae2de332d513a5763276a9265f8e7cbaeb1b1ee74623a"}, + {file = "yarl-1.7.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c0910c6b6c31359d2f6184828888c983d54d09d581a4a23547a35f1d0b9484b1"}, + {file = "yarl-1.7.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6feca8b6bfb9eef6ee057628e71e1734caf520a907b6ec0d62839e8293e945c0"}, + {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8300401dc88cad23f5b4e4c1226f44a5aa696436a4026e456fe0e5d2f7f486e6"}, + {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:788713c2896f426a4e166b11f4ec538b5736294ebf7d5f654ae445fd44270832"}, + {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:fd547ec596d90c8676e369dd8a581a21227fe9b4ad37d0dc7feb4ccf544c2d59"}, + {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:737e401cd0c493f7e3dd4db72aca11cfe069531c9761b8ea474926936b3c57c8"}, + {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baf81561f2972fb895e7844882898bda1eef4b07b5b385bcd308d2098f1a767b"}, + {file = "yarl-1.7.2-cp38-cp38-win32.whl", hash = "sha256:ede3b46cdb719c794427dcce9d8beb4abe8b9aa1e97526cc20de9bd6583ad1ef"}, + {file = "yarl-1.7.2-cp38-cp38-win_amd64.whl", hash = "sha256:cc8b7a7254c0fc3187d43d6cb54b5032d2365efd1df0cd1749c0c4df5f0ad45f"}, + {file = "yarl-1.7.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:580c1f15500e137a8c37053e4cbf6058944d4c114701fa59944607505c2fe3a0"}, + {file = "yarl-1.7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ec1d9a0d7780416e657f1e405ba35ec1ba453a4f1511eb8b9fbab81cb8b3ce1"}, + {file = "yarl-1.7.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3bf8cfe8856708ede6a73907bf0501f2dc4e104085e070a41f5d88e7faf237f3"}, + {file = "yarl-1.7.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1be4bbb3d27a4e9aa5f3df2ab61e3701ce8fcbd3e9846dbce7c033a7e8136746"}, + {file = "yarl-1.7.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:534b047277a9a19d858cde163aba93f3e1677d5acd92f7d10ace419d478540de"}, + {file = "yarl-1.7.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6ddcd80d79c96eb19c354d9dca95291589c5954099836b7c8d29278a7ec0bda"}, + {file = "yarl-1.7.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9bfcd43c65fbb339dc7086b5315750efa42a34eefad0256ba114cd8ad3896f4b"}, + {file = "yarl-1.7.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f64394bd7ceef1237cc604b5a89bf748c95982a84bcd3c4bbeb40f685c810794"}, + {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044daf3012e43d4b3538562da94a88fb12a6490652dbc29fb19adfa02cf72eac"}, + {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:368bcf400247318382cc150aaa632582d0780b28ee6053cd80268c7e72796dec"}, + {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:bab827163113177aee910adb1f48ff7af31ee0289f434f7e22d10baf624a6dfe"}, + {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0cba38120db72123db7c58322fa69e3c0efa933040ffb586c3a87c063ec7cae8"}, + {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:59218fef177296451b23214c91ea3aba7858b4ae3306dde120224cfe0f7a6ee8"}, + {file = "yarl-1.7.2-cp39-cp39-win32.whl", hash = "sha256:1edc172dcca3f11b38a9d5c7505c83c1913c0addc99cd28e993efeaafdfaa18d"}, + {file = "yarl-1.7.2-cp39-cp39-win_amd64.whl", hash = "sha256:797c2c412b04403d2da075fb93c123df35239cd7b4cc4e0cd9e5839b73f52c58"}, + {file = "yarl-1.7.2.tar.gz", hash = "sha256:45399b46d60c253327a460e99856752009fcee5f5d3c80b2f7c0cae1c38d56dd"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..88006d8 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,40 @@ +[tool.poetry] +name = "aiohttpretty" +version = "0.1.0" +description = "AioHTTPretty" +authors = [ + "Gustavo Correa ", + "Center for Open Science ", +] +classifiers = [ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'Natural Language :: English', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3', +] + + +[tool.poetry.dependencies] +python = "^3.8" +aiohttp = "^3.8.1" +furl = "^2.1.3" +multidict = "^6.0.2" +yarl = "^1.7.2" + +[tool.poetry.dev-dependencies] +mypy = "^0.961" +isort = "^5.10.1" +pytest = "^7.1.2" +flake8 = "^4.0.1" +pytest-cov = "^3.0.0" +pytest-asyncio = "^0.18.3" + +[tool.poetry.plugins] +pytest11 = { aiohttpretty = 'aiohttpretty.plugin' } + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..cc053b4 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,7 @@ +[pytest] +asyncio_mode=auto +addopts = + --cov-report term-missing + --cov aiohttpretty + --maxfail 1 + -sv diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 2b26e92..0000000 --- a/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -aiohttp>=3.5.4 -furl==2.0.0 -multidict==4.5.2 -yarl==1.3.0 diff --git a/setup.cfg b/setup.cfg index 88e8f50..ffb87ee 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,13 +8,10 @@ # W292: no newline at end of file # E999: invalid syntax (async def) [flake8] -ignore = E501,E127,E128,E265,E301,E302,F403,W292,E999 -max-line-length = 100 +ignore = E,F403,W292,W503 +max-line-length = 79 exclude = .ropeproject,tests/* -[tool:pytest] -addopts = --cov-report term-missing --cov aiohttpretty - # turn on branch coverage testing [coverage:run] branch = True diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..6d06870 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1 @@ +pytest_plugins = ['aiohttpretty.plugin'] diff --git a/tests/test_general.py b/tests/test_general.py index 4c7c43f..6bb2ed6 100644 --- a/tests/test_general.py +++ b/tests/test_general.py @@ -1,24 +1,14 @@ -import json import asyncio -import unittest +import json import pytest from aiohttp import ClientSession -import aiohttpretty - - -def async_test(f): - def wrapper(*args, **kwargs): - coro = asyncio.coroutine(f) - future = coro(*args, **kwargs) - loop = asyncio.get_event_loop() - loop.run_until_complete(future) - return wrapper +from aiohttpretty import aiohttpretty +from aiohttpretty.exc import NoUrlMatching class DummyAsyncStream(asyncio.StreamReader): - def __init__(self, data): super().__init__() self.size = len(data) @@ -26,134 +16,164 @@ def __init__(self, data): self.feed_eof() -class TestGeneral(unittest.TestCase): - - def tearDown(self): - aiohttpretty.clear() - - @async_test - async def test_fake_request(self): - desired_response = b'example data' - url = 'http://example.com/' - - aiohttpretty.register_uri('GET', url, body=desired_response) - - response = await aiohttpretty.fake_request('GET', url) - data = await response.read() - assert data == desired_response - - def test_register_uri(self): - url = 'http://example.com/' - desired_response = b'example data' - - aiohttpretty.register_uri('GET', url, body=desired_response) - options = aiohttpretty.registry[('GET', 'http://example.com/')] - assert options == {'body': b'example data'} - - def test_register_json_uri(self): - url = 'http://example.com/' - desired_response = {'test_key': 'test_value'} - - aiohttpretty.register_json_uri('GET', url, body=desired_response) - options = aiohttpretty.registry[('GET', 'http://example.com/')] - assert json.loads(options['body'].decode('utf-8')) == desired_response - - @async_test - async def test_param_handling(self): - url = 'http://example-params.com/?test=test' - desired_error_msg = ( - "No URLs matching GET http://example-params.com/?test=test with params {'test': 'test'}. " - "Not making request. Go fix your test." - ) - try: - await aiohttpretty.fake_request('GET', url) - except Exception as exception: - assert str(exception) == desired_error_msg - - @async_test - async def test_params(self): - desired_response = b'example data' - url = 'http://example.com/' - params = {'meow': 'quack', 'woof': 'beans'}; - - aiohttpretty.register_uri('GET', url, params=params, body=desired_response) - - response = await aiohttpretty.fake_request('GET', - 'http://example.com/?meow=quack&woof=beans') - data = await response.read() - assert data == desired_response - - @async_test - async def test_str_response_encoding(self): - aiohttpretty.register_uri('GET', - 'http://example.com/', - body='example résumé data') - response = await aiohttpretty.fake_request('GET', - 'http://example.com/') - data = await response.read() - assert data == 'example résumé data'.encode('utf-8') - - @async_test - async def test_has_call(self): - aiohttpretty.register_uri('GET', - 'http://example.com/', - params={'alpha': '1', 'beta': None}, - body='foo') - response = await aiohttpretty.fake_request('GET', - 'http://example.com/?alpha=1&beta=') - assert await response.read() == b'foo' - - params_equivalent = [ - 'http://example.com/?alpha=1&beta=', - 'http://example.com/?beta=&alpha=1', - ] - for uri in params_equivalent: - assert aiohttpretty.has_call(method='GET', uri=uri) - - params_different = [ - 'http://example.com/', - 'http://example.com/?alpha=2&beta=', - # 'http://example.com/?alpha=1', # buggy atm - 'http://example.com/?beta=', - 'http://example.com/?alpha=1&beta=1', - 'http://example.com/?alpha=&beta=', - ] - for uri in params_different: - assert not aiohttpretty.has_call(method='GET', uri=uri) - - assert aiohttpretty.has_call(method='GET', uri='http://example.com/', - params={'alpha': '1', 'beta': None}) - assert aiohttpretty.has_call(method='GET', uri='http://example.com/', check_params=False) - assert not aiohttpretty.has_call(method='POST', uri='http://example.com/?alpha=1&beta=') - assert not aiohttpretty.has_call(method='GET', uri='http://otherexample.com/') - - def test_activate(self): - orig_real_id = id(ClientSession._request) - orig_fake_id = id(aiohttpretty.fake_request) - - assert aiohttpretty.request is None - assert ClientSession._request != aiohttpretty.fake_request - assert id(ClientSession._request) == orig_real_id - assert id(ClientSession._request) != orig_fake_id - - aiohttpretty.activate() - - assert aiohttpretty.request is not None - assert id(aiohttpretty.request) == orig_real_id - - assert ClientSession._request == aiohttpretty.fake_request - assert id(ClientSession._request) != orig_real_id - assert id(ClientSession._request) == orig_fake_id - - aiohttpretty.deactivate() - - assert aiohttpretty.request is None - assert ClientSession._request != aiohttpretty.fake_request - assert id(ClientSession._request) == orig_real_id - assert id(ClientSession._request) != orig_fake_id - - @async_test - async def test_multiple_responses(self): +async def test_fake_request(): + desired_response = b'example data' + url = 'http://example.com/' + + aiohttpretty.register_uri('GET', url, body=desired_response) + + response = await aiohttpretty.fake_request('GET', url) + data = await response.read() + assert data == desired_response + + +def test_register_uri(): + url = 'http://example.com/' + desired_response = b'example data' + + aiohttpretty.register_uri('GET', url, body=desired_response) + options = aiohttpretty.registry[('GET', 'http://example.com/')] + assert options == {'body': b'example data'} + + +def test_register_json_uri(): + url = 'http://example.com/' + desired_response = {'test_key': 'test_value'} + + aiohttpretty.register_json_uri('GET', url, body=desired_response) + options = aiohttpretty.registry[('GET', 'http://example.com/')] + assert json.loads(options['body'].decode('utf-8')) == desired_response + + +async def test_param_handling(): + url = 'http://example-params.com/?test=test' + desired_error_msg = ( + "No URLs matching GET http://example-params.com/?test=test with params {'test': 'test'}. " + 'Not making request. Go fix your test.' + ) + with pytest.raises(NoUrlMatching) as exc_info: + await aiohttpretty.fake_request('GET', url) + assert str(exc_info.value) == desired_error_msg + + +async def test_params(): + desired_response = b'example data' + url = 'http://example.com/' + params = {'meow': 'quack', 'woof': 'beans'} + + aiohttpretty.register_uri('GET', url, params=params, body=desired_response) + + response = await aiohttpretty.fake_request( + 'GET', 'http://example.com/?meow=quack&woof=beans' + ) + data = await response.read() + assert data == desired_response + + +async def test_str_response_encoding(): + aiohttpretty.register_uri( + 'GET', 'http://example.com/', body='example résumé data' + ) + response = await aiohttpretty.fake_request('GET', 'http://example.com/') + data = await response.read() + assert data == 'example résumé data'.encode('utf-8') + + +@pytest.mark.aiohttpretty +async def test_has_call(): + aiohttpretty.register_uri( + 'GET', + 'http://example.com/', + params={'alpha': '1', 'beta': None}, + body='foo', + ) + response = await aiohttpretty.fake_request( + 'GET', 'http://example.com/?alpha=1&beta=' + ) + assert await response.read() == b'foo' + + params_equivalent = [ + 'http://example.com/?alpha=1&beta=', + 'http://example.com/?beta=&alpha=1', + ] + for uri in params_equivalent: + assert aiohttpretty.has_call(method='GET', uri=uri) + + params_different = [ + 'http://example.com/', + 'http://example.com/?alpha=2&beta=', + # 'http://example.com/?alpha=1', # buggy atm + 'http://example.com/?beta=', + 'http://example.com/?alpha=1&beta=1', + 'http://example.com/?alpha=&beta=', + ] + for uri in params_different: + assert not aiohttpretty.has_call(method='GET', uri=uri) + + assert aiohttpretty.has_call( + method='GET', + uri='http://example.com/', + params={'alpha': '1', 'beta': None}, + ) + assert aiohttpretty.has_call( + method='GET', uri='http://example.com/', check_params=False + ) + assert not aiohttpretty.has_call( + method='POST', uri='http://example.com/?alpha=1&beta=' + ) + assert not aiohttpretty.has_call( + method='GET', uri='http://otherexample.com/' + ) + + +def test_activate(): + orig_real_id = id(ClientSession._request) + orig_fake_id = id(aiohttpretty.fake_request) + + assert aiohttpretty.request is None + assert ClientSession._request != aiohttpretty.fake_request + assert id(ClientSession._request) == orig_real_id + assert id(ClientSession._request) != orig_fake_id + + aiohttpretty.activate() + + assert aiohttpretty.request is not None + assert id(aiohttpretty.request) == orig_real_id + + assert ClientSession._request == aiohttpretty.fake_request + assert id(ClientSession._request) != orig_real_id + assert id(ClientSession._request) == orig_fake_id + + aiohttpretty.deactivate() + + assert aiohttpretty.request is None + assert ClientSession._request != aiohttpretty.fake_request + assert id(ClientSession._request) == orig_real_id + assert id(ClientSession._request) != orig_fake_id + + +async def test_multiple_responses(): + aiohttpretty.register_uri( + 'GET', + 'http://example.com/', + responses=[ + {'status': 200, 'body': 'moo',}, + {'status': 200, 'body': 'quack',}, + ], + ) + + first_resp = await aiohttpretty.fake_request('GET', 'http://example.com/') + assert await first_resp.read() == b'moo' + + second_resp = await aiohttpretty.fake_request('GET', 'http://example.com/') + assert await second_resp.read() == b'quack' + + with pytest.raises(Exception): + await aiohttpretty.fake_request('GET', 'http://example.com/') + + +def test_no_params_in_responses(): + with pytest.raises(ValueError): aiohttpretty.register_uri( 'GET', 'http://example.com/', @@ -161,95 +181,69 @@ async def test_multiple_responses(self): { 'status': 200, 'body': 'moo', + 'params': {'alpha': '1', 'beta': None}, }, + ], + ) + + with pytest.raises(ValueError): + aiohttpretty.register_uri( + 'GET', + 'http://example.com/', + responses=[ + {'status': 200, 'body': 'woof',}, { 'status': 200, - 'body': 'quack', + 'body': 'moo', + 'params': {'alpha': '1', 'beta': None}, }, ], ) - first_resp = await aiohttpretty.fake_request('GET', 'http://example.com/') - assert await first_resp.read() == b'moo' - - second_resp = await aiohttpretty.fake_request('GET', 'http://example.com/') - assert await second_resp.read() == b'quack' - - with pytest.raises(Exception) as exc: - await aiohttpretty.fake_request('GET', 'http://example.com/') - - def test_no_params_in_responses(self): - with pytest.raises(ValueError): - aiohttpretty.register_uri( - 'GET', - 'http://example.com/', - responses=[ - { - 'status': 200, - 'body': 'moo', - 'params': {'alpha': '1', 'beta': None} - }, - ], - ) - - with pytest.raises(ValueError): - aiohttpretty.register_uri( - 'GET', - 'http://example.com/', - responses=[ - { - 'status': 200, - 'body': 'woof', - }, - { - 'status': 200, - 'body': 'moo', - 'params': {'alpha': '1', 'beta': None} - }, - ], - ) - - @async_test - async def test_headers_in_response(self): - aiohttpretty.register_uri('GET', 'http://example.com/', - headers={'X-Magic-Header': '1'}) - - first_resp = await aiohttpretty.fake_request('GET', 'http://example.com/') - assert 'X-Magic-Header' in first_resp.headers - - @async_test - async def test_async_streaming_body(self): - stream = DummyAsyncStream(b'meow') - aiohttpretty.register_uri('GET', 'http://example.com/', body=stream) - - resp = await aiohttpretty.fake_request('GET', 'http://example.com/') - assert await resp.read() == b'meow' - - @async_test - async def test_invalid_body(self): + +async def test_headers_in_response(): + aiohttpretty.register_uri( + 'GET', 'http://example.com/', headers={'X-Magic-Header': '1'} + ) + + first_resp = await aiohttpretty.fake_request('GET', 'http://example.com/') + assert 'X-Magic-Header' in first_resp.headers + + +async def test_async_streaming_body(): + stream = DummyAsyncStream(b'meow') + aiohttpretty.register_uri('GET', 'http://example.com/', body=stream) + + resp = await aiohttpretty.fake_request('GET', 'http://example.com/') + assert await resp.read() == b'meow' + + +async def test_invalid_body(): + with pytest.raises(TypeError): aiohttpretty.register_uri('GET', 'http://example.com/', body=1234) - with pytest.raises(TypeError): - await aiohttpretty.fake_request('GET', 'http://example.com/') - @async_test - async def test_passed_data_is_read(self): - aiohttpretty.register_uri('GET', 'http://example.com/', body='woof') +async def test_passed_data_is_read(): + aiohttpretty.register_uri('GET', 'http://example.com/', body='woof') + + stream = DummyAsyncStream(b'meow') + assert not stream.at_eof() - stream = DummyAsyncStream(b'meow') - assert not stream.at_eof() + resp = await aiohttpretty.fake_request( + 'GET', 'http://example.com/', data=stream + ) - resp = await aiohttpretty.fake_request('GET', 'http://example.com/', data=stream) + assert stream.at_eof() + assert await resp.read() == b'woof' - assert stream.at_eof() - assert await resp.read() == b'woof' - @async_test - async def test_aiohttp_request(self): - aiohttpretty.register_uri('GET', 'http://example.com/', body=b'example data') +async def test_aiohttp_request(): + aiohttpretty.register_uri( + 'GET', 'http://example.com/', body=b'example data' + ) - aiohttpretty.activate() - async with ClientSession() as session: - async with session.get('http://example.com/') as response: - assert await response.read() == b'example data' - aiohttpretty.deactivate() + aiohttpretty.activate() + async with ClientSession() as session: + async with session.get('http://example.com/') as response: + assert await response.read() == b'example data' + aiohttpretty.deactivate() From ebf4eb8e38d72c79c756e135547008967b41a759 Mon Sep 17 00:00:00 2001 From: Gustavo Correa Date: Sun, 3 Jul 2022 08:05:06 -0300 Subject: [PATCH 2/9] build(requirements): exported poetry dependencies to requirements.txt --- dev-requirements.txt | 30 ++++++++++++++++++++++++++++++ requirements.txt | 12 ++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 dev-requirements.txt create mode 100644 requirements.txt diff --git a/dev-requirements.txt b/dev-requirements.txt new file mode 100644 index 0000000..cb49a64 --- /dev/null +++ b/dev-requirements.txt @@ -0,0 +1,30 @@ +attrs==21.4.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7" +six==1.16.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" +coverage==6.4.1; python_version >= "3.7" +iniconfig==1.1.1; python_version >= "3.7" +async-timeout==4.0.2; python_version >= "3.6" +idna==3.3; python_version >= "3.6" +flake8==4.0.1; python_version >= "3.6" +packaging==21.3; python_version >= "3.7" +colorama==0.4.5; python_version >= "3.7" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.7" and python_full_version >= "3.5.0" +py==1.11.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7" +pyparsing==3.0.9; python_full_version >= "3.6.8" and python_version >= "3.7" +typing-extensions==4.3.0; python_version >= "3.7" +pycodestyle==2.8.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" +aiosignal==1.2.0; python_version >= "3.6" +mypy==0.961; python_version >= "3.6" +pytest-cov==3.0.0; python_version >= "3.6" +mypy-extensions==0.4.3; python_version >= "3.6" +charset-normalizer==2.1.0; python_full_version >= "3.6.0" and python_version >= "3.6" +mccabe==0.6.1; python_version >= "3.6" +pyflakes==2.4.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" +atomicwrites==1.4.0; python_version >= "3.7" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.7" and python_full_version >= "3.4.0" +aiohttp==3.8.1; python_version >= "3.6" +yarl==1.7.2; python_version >= "3.6" +frozenlist==1.3.0; python_version >= "3.7" +isort==5.10.1; python_full_version >= "3.6.1" and python_version < "4.0" +pluggy==1.0.0; python_version >= "3.7" +multidict==6.0.2; python_version >= "3.7" +pytest-asyncio==0.18.3; python_version >= "3.7" +tomli==2.0.1; python_version < "3.11" and python_version >= "3.7" and python_full_version <= "3.11.0a6" +pytest==7.1.2; python_version >= "3.7" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..26544dd --- /dev/null +++ b/requirements.txt @@ -0,0 +1,12 @@ +aiohttp==3.8.1; python_version >= "3.6" +aiosignal==1.2.0; python_version >= "3.6" +async-timeout==4.0.2; python_version >= "3.6" +attrs==21.4.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" +charset-normalizer==2.1.0; python_full_version >= "3.6.0" and python_version >= "3.6" +frozenlist==1.3.0; python_version >= "3.7" +furl==2.1.3 +idna==3.3; python_version >= "3.6" +multidict==6.0.2; python_version >= "3.7" +orderedmultidict==1.0.1 +six==1.16.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" +yarl==1.7.2; python_version >= "3.6" From e596208f35b9b8f25ae39ed5aa802b3164d2cbe5 Mon Sep 17 00:00:00 2001 From: Gustavo Correa Date: Mon, 4 Jul 2022 03:19:47 -0300 Subject: [PATCH 3/9] feat(typing): improved typing with main.pyi and updated setuppy --- .cz.toml | 9 +++++ README.md | 41 ++++++++++---------- aiohttpretty/__init__.py | 3 +- aiohttpretty/main.py | 84 ++++++++++++++++++++-------------------- aiohttpretty/main.pyi | 73 ++++++++++++++++++++++++++++++++++ aiohttpretty/py.typed | 0 pytest.ini | 2 +- setup.py | 78 +++++++++++++++++++++++-------------- 8 files changed, 195 insertions(+), 95 deletions(-) create mode 100644 .cz.toml create mode 100644 aiohttpretty/main.pyi create mode 100644 aiohttpretty/py.typed diff --git a/.cz.toml b/.cz.toml new file mode 100644 index 0000000..eb52c44 --- /dev/null +++ b/.cz.toml @@ -0,0 +1,9 @@ +[tool] +[tool.commitizen] +name = "cz_conventional_commits" +version = "0.1.0" +tag_format = "$version" +version_files = [ + 'pyproject.toml:version', + 'aiohttpretty/__init__.py:__version__', +] \ No newline at end of file diff --git a/README.md b/README.md index a5d1630..1a0e786 100644 --- a/README.md +++ b/README.md @@ -94,32 +94,31 @@ Purge the registry and the list of intercepted calls. Checks to see if the given uri was called during the test. By default, will verify that the query params match up. Setting `check_params` to `False` will strip params from the *registered* uri, not the passed-in uri. +### `.open(clear: bool = True)` +Runs `.clear()` if `clear`, `.activate()` on enter and `.deactivate()` on exit. + +### `.async_decorate(func)` +Decorator to run `with aiohttpretty.open()` before running coroutine function + +### `.decorate(func)` +Same as `.async_decorate()` but for normal functions ## Other ### pytest marker -To simplify usage of `aiohttpretty` in tests, you can make a pytest marker that will automatically activate/deactivate `aiohttpretty` for the scope of a test. To do so, add the following to your `conftest.py`: +To simplify usage of `aiohttpretty` in tests, it already ships with a pytest plugin. To use, add `@pytest.mark.aiohttpretty` to your test: -``` -import aiohttpretty +```python +import pytest -def pytest_configure(config): - config.addinivalue_line( - 'markers', - 'aiohttpretty: mark tests to activate aiohttpretty' - ) - -def pytest_runtest_setup(item): - marker = item.get_marker('aiohttpretty') - if marker is not None: - aiohttpretty.clear() - aiohttpretty.activate() - -def pytest_runtest_teardown(item, nextitem): - marker = item.get_marker('aiohttpretty') - if marker is not None: - aiohttpretty.deactivate() -``` +@pytest.mark.aiohttpretty +async def my_test(): + # inside test function aiohttpretty is clean and active + assert thing() == other -Then add `@pytest.mark.aiohttpretty` before `async def test_foo`. +@pytes.mark.aiohttpretty +def my_other_test(test_client): + # aiohttpretty also supports sync functions in case of testing web applications + assert test_client.get('/my-route') == other +``` diff --git a/aiohttpretty/__init__.py b/aiohttpretty/__init__.py index 576f8ff..a2dd849 100644 --- a/aiohttpretty/__init__.py +++ b/aiohttpretty/__init__.py @@ -1,5 +1,4 @@ -import sys - from .main import aiohttpretty __all__ = ['aiohttpretty'] +__version__ = '0.1.0' diff --git a/aiohttpretty/main.py b/aiohttpretty/main.py index 3cd6103..ca0572d 100644 --- a/aiohttpretty/main.py +++ b/aiohttpretty/main.py @@ -30,6 +30,19 @@ def __init__(self): self.registry = {} self.request = None + async def fake_request( + self, method: METHODS, uri: str, **kwargs: typing.Any + ): + + response = self._find_request(method, uri, kwargs) + + await self.process_request(**kwargs) + self.make_call( + method=method, uri=uri, **kwargs, + ) + + return self._build_response(method, uri, response) + async def process_request(self, **kwargs): """Process request options as if the request was actually executed. """ @@ -48,18 +61,34 @@ def make_call(self, *, method: METHODS, uri: str, **kwargs): } ) - async def fake_request( - self, method: METHODS, uri: str, **kwargs: typing.Any + def _find_request( + self, + method: METHODS, + uri: str, + kwargs: typing.Mapping[str, typing.Any], ): + params = kwargs.get('params') + url = types.ImmutableFurl(uri, params=params) - response = self._find_request(method, uri, kwargs) + try: + response = self.registry[(method, url)] + except KeyError as error: + raise exc.NoUrlMatching( + 'No URLs matching {method} {uri} with params {url.params}. ' + 'Not making request. Go fix your test.'.format( + method=method, uri=uri, url=url + ) + ) from error - await self.process_request(**kwargs) - self.make_call( - method=method, uri=uri, **kwargs, - ) + if isinstance(response, typing.MutableSequence): + try: + response = response.pop(0) + except IndexError as error: + raise exc.ExhaustedAllResponses( + 'No responses left.' + ) from error - return self._build_response(method, uri, response) + return response def _build_response( self, @@ -81,7 +110,7 @@ def _build_response( timer=TimerNoop(), traces=[], loop=loop, - session=None, # type: ignore + session=Mock(), ) content = helpers.wrap_content_stream( @@ -103,35 +132,6 @@ def _build_response( mock_response.reason = response.get('reason', '') return mock_response - def _find_request( - self, - method: METHODS, - uri: str, - kwargs: typing.Mapping[str, typing.Any], - ): - params = kwargs.get('params') - url = types.ImmutableFurl(uri, params=params) - - try: - response = self.registry[(method, url)] - except KeyError as error: - raise exc.NoUrlMatching( - 'No URLs matching {method} {uri} with params {url.params}. ' - 'Not making request. Go fix your test.'.format( - method=method, uri=uri, url=url - ) - ) from error - - if isinstance(response, typing.MutableSequence): - try: - response = response.pop(0) - except IndexError as error: - raise exc.ExhaustedAllResponses( - 'No responses left.' - ) from error - - return response - def validate_body(self, options: typing.Mapping[str, typing.Any]): if body := options.get('body'): if not isinstance( @@ -204,12 +204,14 @@ def has_call(self, uri: str, check_params: bool = True, **kwargs): return False @contextmanager - def open(self): + def open(self, clear: bool = True): + if clear: + self.clear() self.activate() yield self.deactivate() - def async_call(self, func: AsyncCallableT) -> AsyncCallableT: + def async_decorate(self, func: AsyncCallableT) -> AsyncCallableT: @wraps(func) async def inner(*args, **kwargs): with self.open(): @@ -217,7 +219,7 @@ async def inner(*args, **kwargs): return inner # type: ignore - def call(self, func: CallableT) -> CallableT: + def decorate(self, func: CallableT) -> CallableT: return self.open()(func) # type: ignore diff --git a/aiohttpretty/main.pyi b/aiohttpretty/main.pyi new file mode 100644 index 0000000..f826195 --- /dev/null +++ b/aiohttpretty/main.pyi @@ -0,0 +1,73 @@ +import typing +from http import HTTPStatus + +import aiohttp +import typing_extensions + +from aiohttpretty.helpers import StreamLike + +METHODS = typing.Literal[ + 'GET', 'POST', 'PUT', 'PATCH', 'HEAD', 'OPTIONS', 'DELETE' +] +OptionalMapping = typing.Optional[typing.Mapping[str, str]] +BodyType = typing.Union[str, bytes, StreamLike, None] +P = typing_extensions.ParamSpec('P') +T = typing.TypeVar('T') + +class ResponseType(typing.TypedDict, total=False): + status: int + reason: str + auto_length: bool + headers: typing.Mapping[str, str] + body: typing.Mapping[str, str] + +class AioHttPretty: + def register_uri( + self, + method: METHODS, + uri: str, + body: BodyType = None, + headers: OptionalMapping = None, + params: OptionalMapping = None, + responses: typing.Optional[typing.List[ResponseType]] = None, + status: int = HTTPStatus.OK, + reason: str = '', + auto_length: bool = False, + ) -> None: ... + def register_json_uri( + self, + method: METHODS, + uri: str, + body: typing.Optional[typing.Any] = None, + headers: OptionalMapping = None, + params: OptionalMapping = None, + responses: typing.Optional[typing.List[ResponseType]] = None, + status: int = HTTPStatus.OK, + reason: str = '', + auto_length: bool = False, + ) -> None: ... + def fake_request( + self, + method: METHODS, + uri: str, + params: OptionalMapping = None, + data: BodyType = None, + **kwargs: typing.Any, + ) -> aiohttp.ClientResponse: ... + def activate(self) -> None: ... + def deactivate(self) -> None: ... + def clear(self) -> None: ... + def has_call( + self, + uri: str, + check_params: bool = True, + params: OptionalMapping = None, + ) -> bool: ... + def open(self, clear: bool = True) -> typing.ContextManager[None]: ... + def async_decorate( + self, + func: typing.Callable[P, typing.Coroutine[typing.Any, typing.Any, T]], + ) -> typing.Callable[P, typing.Coroutine[typing.Any, typing.Any, T]]: ... + def decorate( + self, func: typing.Callable[P, T] + ) -> typing.Callable[P, T]: ... diff --git a/aiohttpretty/py.typed b/aiohttpretty/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/pytest.ini b/pytest.ini index cc053b4..2aab0f5 100644 --- a/pytest.ini +++ b/pytest.ini @@ -2,6 +2,6 @@ asyncio_mode=auto addopts = --cov-report term-missing - --cov aiohttpretty + --cov aiohttpretty --maxfail 1 -sv diff --git a/setup.py b/setup.py index 7d311b1..6504652 100644 --- a/setup.py +++ b/setup.py @@ -1,30 +1,48 @@ -from setuptools import setup, find_packages - - -def parse_requirements(requirements): - with open(requirements) as f: - return [l.strip('\n') for l in f if l.strip('\n') and not l.startswith('#')] - - -requirements = parse_requirements('requirements.txt') - -setup( - name='aiohttpretty', - version='19.0.0', - description='AioHTTPretty', - author='Center for Open Science', - author_email='contact@cos.io', - url='https://github.com/CenterForOpenScience/aiohttpretty', - packages=find_packages(exclude=('tests*', )), - py_modules=['aiohttpretty'], - include_package_data=True, - install_requires=requirements, - zip_safe=False, - classifiers=[ - 'Development Status :: 4 - Beta', - 'Intended Audience :: Developers', - 'Natural Language :: English', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - ], -) +# -*- coding: utf-8 -*- +from setuptools import setup + +packages = ['aiohttpretty'] + +package_data = {'': ['*']} + +install_requires = [ + 'aiohttp>=3.8.1,<4.0.0', + 'furl>=2.1.3,<3.0.0', + 'multidict>=6.0.2,<7.0.0', + 'yarl>=1.7.2,<2.0.0', +] + +entry_points = {'pytest11': ['aiohttpretty = aiohttpretty.plugin']} + +classifiers = [ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'Natural Language :: English', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3', +] + +setup_kwargs = { + 'name': 'aiohttpretty', + 'version': '0.1.0', + 'description': 'AioHTTPretty', + 'long_description': None, + 'author': 'Center for Open Science', + 'author_email': 'contact@cos.i', + 'maintainer': None, + 'maintainer_email': None, + 'url': None, + 'packages': packages, + 'package_data': package_data, + 'install_requires': install_requires, + 'entry_points': entry_points, + 'python_requires': '>=3.8,<4.0', + 'classifiers': classifiers, +} + + +setup(**setup_kwargs) + +# This setup.py was autogenerated using poetry. From 0e454a1395a9679b50f5e6f105ffc50045413088 Mon Sep 17 00:00:00 2001 From: Gustavo Correa Date: Mon, 4 Jul 2022 03:20:03 -0300 Subject: [PATCH 4/9] =?UTF-8?q?bump:=20version=200.1.0=20=E2=86=92=201.0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cz.toml | 2 +- CHANGELOG.md | 10 ++++++++++ aiohttpretty/__init__.py | 8 ++++---- aiohttpretty/main.pyi | 3 +++ pyproject.toml | 2 +- 5 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 CHANGELOG.md diff --git a/.cz.toml b/.cz.toml index eb52c44..26c003e 100644 --- a/.cz.toml +++ b/.cz.toml @@ -1,7 +1,7 @@ [tool] [tool.commitizen] name = "cz_conventional_commits" -version = "0.1.0" +version = "1.0.0" tag_format = "$version" version_files = [ 'pyproject.toml:version', diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f05b873 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +## 1.0.0 (2022-07-04) + +### Feat + +- **typing**: improved typing with main.pyi and updated setuppy +- **aiohttpretty**: reformat, refactor, and new functionality + +## 0.1.0 (2017-02-10) + +## 0.0.2 (2016-05-03) diff --git a/aiohttpretty/__init__.py b/aiohttpretty/__init__.py index a2dd849..db20339 100644 --- a/aiohttpretty/__init__.py +++ b/aiohttpretty/__init__.py @@ -1,4 +1,4 @@ -from .main import aiohttpretty - -__all__ = ['aiohttpretty'] -__version__ = '0.1.0' +from .main import aiohttpretty + +__all__ = ['aiohttpretty'] +__version__ = '1.0.0' diff --git a/aiohttpretty/main.pyi b/aiohttpretty/main.pyi index f826195..a5b5d99 100644 --- a/aiohttpretty/main.pyi +++ b/aiohttpretty/main.pyi @@ -71,3 +71,6 @@ class AioHttPretty: def decorate( self, func: typing.Callable[P, T] ) -> typing.Callable[P, T]: ... + + +aiohttpretty: AioHttPretty \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 88006d8..7db2ebf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "aiohttpretty" -version = "0.1.0" +version = "1.0.0" description = "AioHTTPretty" authors = [ "Gustavo Correa ", From 17b73a358597f7a2045b2fd877c9a9bd9a08c206 Mon Sep 17 00:00:00 2001 From: Gustavo Correa Date: Thu, 7 Jul 2022 06:03:52 -0300 Subject: [PATCH 5/9] style(files): changed all files from crlf to lf --- CHANGELOG.md | 10 ----- Makefile | 16 ++++---- aiohttpretty/__init__.py | 8 ++-- dev-requirements.txt | 60 +++++++++++++++--------------- poetry.lock | 2 +- pyproject.toml | 80 ++++++++++++++++++++-------------------- requirements.txt | 24 ++++++------ 7 files changed, 95 insertions(+), 105 deletions(-) delete mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index f05b873..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,10 +0,0 @@ -## 1.0.0 (2022-07-04) - -### Feat - -- **typing**: improved typing with main.pyi and updated setuppy -- **aiohttpretty**: reformat, refactor, and new functionality - -## 0.1.0 (2017-02-10) - -## 0.0.2 (2016-05-03) diff --git a/Makefile b/Makefile index ac32f70..5e28747 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ -.PHONY: format -format: - black --line-length 79 aiohttpretty tests - isort --profile black --line-length 79 aiohttpretty tests - -.PHONY: test -test: - pytest tests +.PHONY: format +format: + black --line-length 79 aiohttpretty tests + isort --profile black --line-length 79 aiohttpretty tests + +.PHONY: test +test: + pytest tests diff --git a/aiohttpretty/__init__.py b/aiohttpretty/__init__.py index db20339..c7fc3b1 100644 --- a/aiohttpretty/__init__.py +++ b/aiohttpretty/__init__.py @@ -1,4 +1,4 @@ -from .main import aiohttpretty - -__all__ = ['aiohttpretty'] -__version__ = '1.0.0' +from .main import aiohttpretty + +__all__ = ['aiohttpretty'] +__version__ = '1.0.0' diff --git a/dev-requirements.txt b/dev-requirements.txt index cb49a64..fd6c4e7 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,30 +1,30 @@ -attrs==21.4.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7" -six==1.16.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" -coverage==6.4.1; python_version >= "3.7" -iniconfig==1.1.1; python_version >= "3.7" -async-timeout==4.0.2; python_version >= "3.6" -idna==3.3; python_version >= "3.6" -flake8==4.0.1; python_version >= "3.6" -packaging==21.3; python_version >= "3.7" -colorama==0.4.5; python_version >= "3.7" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.7" and python_full_version >= "3.5.0" -py==1.11.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7" -pyparsing==3.0.9; python_full_version >= "3.6.8" and python_version >= "3.7" -typing-extensions==4.3.0; python_version >= "3.7" -pycodestyle==2.8.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -aiosignal==1.2.0; python_version >= "3.6" -mypy==0.961; python_version >= "3.6" -pytest-cov==3.0.0; python_version >= "3.6" -mypy-extensions==0.4.3; python_version >= "3.6" -charset-normalizer==2.1.0; python_full_version >= "3.6.0" and python_version >= "3.6" -mccabe==0.6.1; python_version >= "3.6" -pyflakes==2.4.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" -atomicwrites==1.4.0; python_version >= "3.7" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.7" and python_full_version >= "3.4.0" -aiohttp==3.8.1; python_version >= "3.6" -yarl==1.7.2; python_version >= "3.6" -frozenlist==1.3.0; python_version >= "3.7" -isort==5.10.1; python_full_version >= "3.6.1" and python_version < "4.0" -pluggy==1.0.0; python_version >= "3.7" -multidict==6.0.2; python_version >= "3.7" -pytest-asyncio==0.18.3; python_version >= "3.7" -tomli==2.0.1; python_version < "3.11" and python_version >= "3.7" and python_full_version <= "3.11.0a6" -pytest==7.1.2; python_version >= "3.7" +attrs==21.4.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7" +six==1.16.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" +coverage==6.4.1; python_version >= "3.7" +iniconfig==1.1.1; python_version >= "3.7" +async-timeout==4.0.2; python_version >= "3.6" +idna==3.3; python_version >= "3.6" +flake8==4.0.1; python_version >= "3.6" +packaging==21.3; python_version >= "3.7" +colorama==0.4.5; python_version >= "3.7" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.7" and python_full_version >= "3.5.0" +py==1.11.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7" +pyparsing==3.0.9; python_full_version >= "3.6.8" and python_version >= "3.7" +typing-extensions==4.3.0; python_version >= "3.7" +pycodestyle==2.8.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" +aiosignal==1.2.0; python_version >= "3.6" +mypy==0.961; python_version >= "3.6" +pytest-cov==3.0.0; python_version >= "3.6" +mypy-extensions==0.4.3; python_version >= "3.6" +charset-normalizer==2.1.0; python_full_version >= "3.6.0" and python_version >= "3.6" +mccabe==0.6.1; python_version >= "3.6" +pyflakes==2.4.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" +atomicwrites==1.4.0; python_version >= "3.7" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.7" and python_full_version >= "3.4.0" +aiohttp==3.8.1; python_version >= "3.6" +yarl==1.7.2; python_version >= "3.6" +frozenlist==1.3.0; python_version >= "3.7" +isort==5.10.1; python_full_version >= "3.6.1" and python_version < "4.0" +pluggy==1.0.0; python_version >= "3.7" +multidict==6.0.2; python_version >= "3.7" +pytest-asyncio==0.18.3; python_version >= "3.7" +tomli==2.0.1; python_version < "3.11" and python_version >= "3.7" and python_full_version <= "3.11.0a6" +pytest==7.1.2; python_version >= "3.7" diff --git a/poetry.lock b/poetry.lock index 9377d54..fb7d15b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -354,7 +354,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" -python-versions = "^3.8" +python-versions = "^3.8" content-hash = "9dd5ed4f12a85b98639cf45ddf2b43b4a23565bad9540ab08e5c7a4544fa082a" [metadata.files] diff --git a/pyproject.toml b/pyproject.toml index 7db2ebf..23854dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,40 +1,40 @@ -[tool.poetry] -name = "aiohttpretty" -version = "1.0.0" -description = "AioHTTPretty" -authors = [ - "Gustavo Correa ", - "Center for Open Science ", -] -classifiers = [ - 'Development Status :: 4 - Beta', - 'Intended Audience :: Developers', - 'Natural Language :: English', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3', -] - - -[tool.poetry.dependencies] -python = "^3.8" -aiohttp = "^3.8.1" -furl = "^2.1.3" -multidict = "^6.0.2" -yarl = "^1.7.2" - -[tool.poetry.dev-dependencies] -mypy = "^0.961" -isort = "^5.10.1" -pytest = "^7.1.2" -flake8 = "^4.0.1" -pytest-cov = "^3.0.0" -pytest-asyncio = "^0.18.3" - -[tool.poetry.plugins] -pytest11 = { aiohttpretty = 'aiohttpretty.plugin' } - -[build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" +[tool.poetry] +name = "aiohttpretty" +version = "1.0.0" +description = "AioHTTPretty" +authors = [ + "Gustavo Correa ", + "Center for Open Science ", +] +classifiers = [ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'Natural Language :: English', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3', +] + + +[tool.poetry.dependencies] +python = "^3.8" +aiohttp = "^3.8.1" +furl = "^2.1.3" +multidict = "^6.0.2" +yarl = "^1.7.2" + +[tool.poetry.dev-dependencies] +mypy = "^0.961" +isort = "^5.10.1" +pytest = "^7.1.2" +flake8 = "^4.0.1" +pytest-cov = "^3.0.0" +pytest-asyncio = "^0.18.3" + +[tool.poetry.plugins] +pytest11 = { aiohttpretty = 'aiohttpretty.plugin' } + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt index 26544dd..76eb410 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,12 @@ -aiohttp==3.8.1; python_version >= "3.6" -aiosignal==1.2.0; python_version >= "3.6" -async-timeout==4.0.2; python_version >= "3.6" -attrs==21.4.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -charset-normalizer==2.1.0; python_full_version >= "3.6.0" and python_version >= "3.6" -frozenlist==1.3.0; python_version >= "3.7" -furl==2.1.3 -idna==3.3; python_version >= "3.6" -multidict==6.0.2; python_version >= "3.7" -orderedmultidict==1.0.1 -six==1.16.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" -yarl==1.7.2; python_version >= "3.6" +aiohttp==3.8.1; python_version >= "3.6" +aiosignal==1.2.0; python_version >= "3.6" +async-timeout==4.0.2; python_version >= "3.6" +attrs==21.4.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" +charset-normalizer==2.1.0; python_full_version >= "3.6.0" and python_version >= "3.6" +frozenlist==1.3.0; python_version >= "3.7" +furl==2.1.3 +idna==3.3; python_version >= "3.6" +multidict==6.0.2; python_version >= "3.7" +orderedmultidict==1.0.1 +six==1.16.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" +yarl==1.7.2; python_version >= "3.6" From f859bf95246bfbd84221f92ace2eaba4cd6ffa95 Mon Sep 17 00:00:00 2001 From: Gustavo Correa Date: Thu, 7 Jul 2022 06:07:42 -0300 Subject: [PATCH 6/9] style(gitattributes): created gitattributes to enforce lf --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..5296e27 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto From 19ec7ac6258e489cc7d5ff953d59713c77c517ab Mon Sep 17 00:00:00 2001 From: Gustavo Correa Date: Thu, 7 Jul 2022 06:11:41 -0300 Subject: [PATCH 7/9] bump: version 19.0.0 -> 22.0.0 --- .cz.toml | 2 +- aiohttpretty/__init__.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.cz.toml b/.cz.toml index 26c003e..75d8598 100644 --- a/.cz.toml +++ b/.cz.toml @@ -1,7 +1,7 @@ [tool] [tool.commitizen] name = "cz_conventional_commits" -version = "1.0.0" +version = "22.0.0" tag_format = "$version" version_files = [ 'pyproject.toml:version', diff --git a/aiohttpretty/__init__.py b/aiohttpretty/__init__.py index c7fc3b1..12eedb0 100644 --- a/aiohttpretty/__init__.py +++ b/aiohttpretty/__init__.py @@ -1,4 +1,4 @@ from .main import aiohttpretty __all__ = ['aiohttpretty'] -__version__ = '1.0.0' +__version__ = '22.0.0' diff --git a/pyproject.toml b/pyproject.toml index 23854dd..5758be6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "aiohttpretty" -version = "1.0.0" +version = "22.0.0" description = "AioHTTPretty" authors = [ "Gustavo Correa ", From 32fee4115992c3bcf0f6f9a7abbabc0ff388f2ec Mon Sep 17 00:00:00 2001 From: Gustavo Correa Date: Thu, 7 Jul 2022 06:14:52 -0300 Subject: [PATCH 8/9] style(aiohttpretty): executed isort and black on all python files --- aiohttpretty/main.pyi | 3 +-- pyproject.toml | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/aiohttpretty/main.pyi b/aiohttpretty/main.pyi index a5b5d99..6204c39 100644 --- a/aiohttpretty/main.pyi +++ b/aiohttpretty/main.pyi @@ -72,5 +72,4 @@ class AioHttPretty: self, func: typing.Callable[P, T] ) -> typing.Callable[P, T]: ... - -aiohttpretty: AioHttPretty \ No newline at end of file +aiohttpretty: AioHttPretty diff --git a/pyproject.toml b/pyproject.toml index 5758be6..217b8db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,8 +3,8 @@ name = "aiohttpretty" version = "22.0.0" description = "AioHTTPretty" authors = [ + "Center for Open Science ", "Gustavo Correa ", - "Center for Open Science ", ] classifiers = [ 'Development Status :: 4 - Beta', diff --git a/setup.py b/setup.py index 6504652..3892103 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ 'description': 'AioHTTPretty', 'long_description': None, 'author': 'Center for Open Science', - 'author_email': 'contact@cos.i', + 'author_email': 'contact@cos.io', 'maintainer': None, 'maintainer_email': None, 'url': None, From dcf606e7ebb147a7c229569c622b23004585ef8d Mon Sep 17 00:00:00 2001 From: Gustavo Correa Date: Thu, 7 Jul 2022 06:22:18 -0300 Subject: [PATCH 9/9] docs(aiohttpretty): returned comments removed during refactor --- .gitattributes | 1 + aiohttpretty/main.py | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index 5296e27..b7a9235 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ + * text=auto diff --git a/aiohttpretty/main.py b/aiohttpretty/main.py index ca0572d..422ed79 100644 --- a/aiohttpretty/main.py +++ b/aiohttpretty/main.py @@ -99,7 +99,8 @@ def _build_response( loop = Mock() loop.get_debug = Mock() loop.get_debug.return_value = True - + # When init `ClientResponse`, the second parameter must be of type `yarl.URL` + # TODO: Integrate a property of this type to `ImmutableFurl` y_url = URL(uri) mock_response = ClientResponse( method, @@ -121,6 +122,8 @@ def _build_response( # Build response headers manually headers = CIMultiDict(response.get('headers', {})) if response.get('auto_length'): + # Calculate and overwrite the "Content-Length" header on-the-fly if Waterbutler tests + # call `aiohttpretty.register_uri()` with `auto_length=True` headers.update({'Content-Length': str(content)}) raw_headers = helpers.build_raw_headers(headers) @@ -133,6 +136,7 @@ def _build_response( return mock_response def validate_body(self, options: typing.Mapping[str, typing.Any]): + """Validate body type to prevent unexpected behavior""" if body := options.get('body'): if not isinstance( body, (str, bytes) @@ -147,6 +151,7 @@ def register_uri(self, method: METHODS, uri: str, **options: typing.Any): raise exc.InvalidResponses( 'Cannot specify params in responses, call register multiple times.' ) + # Validates body before registry instead of during call following the fail-fast principle self.validate_body(options) params = options.pop('params', {}) url = types.ImmutableFurl(uri, params=params)