From 17ac12a2e659becaa6164610a7359216c58b9b1f Mon Sep 17 00:00:00 2001 From: Chris Lunsford Date: Thu, 30 Sep 2021 10:27:14 -0400 Subject: [PATCH 1/5] Initial feature implementation: Add JSON md5 hash attribute to AWSIPPrefixes --- awsipranges/__init__.py | 1 + awsipranges/data_loading.py | 24 +++++++-- awsipranges/exceptions.py | 77 +++++++++++++++++++++++++++++ awsipranges/models/awsipprefixes.py | 14 ++++++ tests/unit/test_data_loading.py | 12 +++-- 5 files changed, 120 insertions(+), 8 deletions(-) create mode 100644 awsipranges/exceptions.py diff --git a/awsipranges/__init__.py b/awsipranges/__init__.py index ba57a4e..ba149ff 100644 --- a/awsipranges/__init__.py +++ b/awsipranges/__init__.py @@ -15,6 +15,7 @@ # limitations under the License. from awsipranges.data_loading import get_ranges # noqa: F401 +from awsipranges.exceptions import AWSIPRangesException, HTTPError # noqa: F401 from awsipranges.models.awsipprefix import ( # noqa: F401 AWSIPPrefix, AWSIPv4Prefix, diff --git a/awsipranges/data_loading.py b/awsipranges/data_loading.py index f257873..1158f52 100644 --- a/awsipranges/data_loading.py +++ b/awsipranges/data_loading.py @@ -3,17 +3,19 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 +import hashlib import json import urllib.request from datetime import datetime from pathlib import Path -from typing import Any, Dict, Union +from typing import Any, Dict, Optional, Tuple, Union from awsipranges.config import ( AWS_IP_ADDRESS_RANGES_URL, CREATE_DATE_FORMAT, CREATE_DATE_TIMEZONE, ) +from awsipranges.exceptions import raise_for_status from awsipranges.models.awsipprefix import aws_ip_prefix from awsipranges.models.awsipprefixes import AWSIPPrefixes from awsipranges.utils import check_type @@ -21,7 +23,7 @@ def get_json_data( cafile: Union[str, Path, None] = None, capath: Union[str, Path, None] = None -) -> Dict[str, Any]: +) -> Tuple[Dict[str, Any], Optional[str]]: """Retrieve and parse the AWS IP address ranges JSON file.""" check_type("cafile", cafile, (str, Path), optional=True) cafile = Path(cafile) if isinstance(cafile, str) else cafile @@ -38,8 +40,19 @@ def get_json_data( with urllib.request.urlopen( AWS_IP_ADDRESS_RANGES_URL, cafile=cafile, capath=capath ) as response: - response_data = json.load(response) - return response_data + raise_for_status(response) + + response_bytes = response.read() + response_data = json.loads(response_bytes) + + if "md5" in hashlib.algorithms_available: + md5_hash = hashlib.md5() + md5_hash.update(response_bytes) + md5_hex_digest = md5_hash.hexdigest() + else: + md5_hex_digest = None + + return response_data, md5_hex_digest def get_ranges(cafile: Path = None, capath: Path = None) -> AWSIPPrefixes: @@ -78,7 +91,7 @@ def get_ranges(cafile: Path = None, capath: Path = None) -> AWSIPPrefixes: The AWS IP address ranges in a `AWSIPPrefixes` collection. """ - json_data = get_json_data(cafile=cafile, capath=capath) + json_data, json_md5 = get_json_data(cafile=cafile, capath=capath) assert "syncToken" in json_data assert "createDate" in json_data @@ -92,4 +105,5 @@ def get_ranges(cafile: Path = None, capath: Path = None) -> AWSIPPrefixes: ).replace(tzinfo=CREATE_DATE_TIMEZONE), ipv4_prefixes=(aws_ip_prefix(record) for record in json_data["prefixes"]), ipv6_prefixes=(aws_ip_prefix(record) for record in json_data["ipv6_prefixes"]), + md5=json_md5, ) diff --git a/awsipranges/exceptions.py b/awsipranges/exceptions.py new file mode 100644 index 0000000..fa8b23c --- /dev/null +++ b/awsipranges/exceptions.py @@ -0,0 +1,77 @@ +"""Custom exceptions.""" + +from typing import Optional, Tuple + + +class AWSIPRangesException(Exception): + """Base class for all awsipranges exceptions.""" + + +class HTTPError(AWSIPRangesException): + """An HTTP/HTTPS error.""" + + args: Tuple[object, ...] + status: Optional[int] + reason: Optional[str] + + def __init__(self, *args, status: Optional[int], reason: Optional[str]): + super(HTTPError, self).__init__(*args) + self.args = args + self.status = status + self.reason = reason + + def __repr__(self): + return ( + f"{self.__class__.__name__}(" + f"{', '.join([repr(arg) for arg in self.args])}, " + f"status={self.status!r}, " + f"reason={self.reason!r}" + f")" + ) + + def __str__(self): + msg = [] + if self.status: + msg.append(str(self.status)) + + if self.reason: + msg.append(self.reason) + + if self.args: + if msg: + msg.append("-") + msg += [str(arg) for arg in self.args] + + return " ".join(msg) + + +def raise_for_status(response): + """Raise an HTTPError on 4xx and 5xx status codes.""" + # Get the status code + if hasattr(response, "status"): + status = int(response.status) + elif hasattr(response, "code"): + status = int(response.code) + elif hasattr(response, "getstatus"): + status = int(response.getstatus()) + else: + raise ValueError( + f"Response object {response!r} does not contain a status code." + ) + + # Get the URL + if hasattr(response, "url"): + url = response.url + elif hasattr(response, "geturl"): + url = response.geturl() + else: + raise ValueError(f"Response object {response!r} does not contain a url.") + + # Get the reason, if available + reason = response.reason if hasattr(response, "reason") else None + + if 400 <= status < 500: + raise HTTPError(f"Client error for URL: {url}", status=status, reason=reason) + + if 500 <= status < 600: + raise HTTPError(f"Server error for URL: {url}", status=status, reason=reason) diff --git a/awsipranges/models/awsipprefixes.py b/awsipranges/models/awsipprefixes.py index 767998c..bd1c992 100644 --- a/awsipranges/models/awsipprefixes.py +++ b/awsipranges/models/awsipprefixes.py @@ -35,6 +35,7 @@ class AWSIPPrefixes(object): _create_date: Optional[datetime] _ipv4_prefixes: Tuple[AWSIPv4Prefix, ...] _ipv6_prefixes: Tuple[AWSIPv6Prefix, ...] + _md5: Optional[str] _regions: Optional[FrozenSet[str]] = None _network_border_groups: Optional[FrozenSet[str]] = None @@ -46,6 +47,7 @@ def __init__( create_date: Optional[datetime] = None, ipv4_prefixes: Iterable[AWSIPv4Prefix] = None, ipv6_prefixes: Iterable[AWSIPv6Prefix] = None, + md5: Optional[str] = None, ) -> None: super().__init__() @@ -53,11 +55,13 @@ def __init__( check_type("create_date", create_date, datetime, optional=True) check_type("ipv4_prefixes", ipv4_prefixes, Iterable) check_type("ipv6_prefixes", ipv6_prefixes, Iterable) + check_type("md5", md5, str, optional=True) self._sync_token = sync_token self._create_date = create_date self._ipv4_prefixes = self._process_prefixes(ipv4_prefixes) self._ipv6_prefixes = self._process_prefixes(ipv6_prefixes) + self._md5 = md5 @staticmethod def _process_prefixes( @@ -153,6 +157,15 @@ def ipv6_prefixes(self) -> Tuple[AWSIPv6Prefix, ...]: """The IPv6 prefixes in the collection.""" return self._ipv6_prefixes + @property + def md5(self) -> Optional[str]: + """The MD5 cryptographic hash value of the ip-ranges.json file. + + You can use this value to check whether the downloaded file is + corrupted. + """ + return self._md5 + def __repr__(self) -> str: return pprint.pformat( { @@ -160,6 +173,7 @@ def __repr__(self) -> str: "create_date": self.create_date, "ipv4_prefixes": self.ipv4_prefixes, "ipv6_prefixes": self.ipv6_prefixes, + "md5": self.md5, } ) diff --git a/tests/unit/test_data_loading.py b/tests/unit/test_data_loading.py index 3c9c34d..24ff3f3 100644 --- a/tests/unit/test_data_loading.py +++ b/tests/unit/test_data_loading.py @@ -121,15 +121,21 @@ def capath( # Happy path tests def test_get_json_data(): - json_data = get_json_data() + json_data, json_md5 = get_json_data() assert isinstance(json_data, dict) + assert isinstance(json_md5, str) + assert json_md5 def test_get_json_data_with_cafile(cafile: Path): - json_data = get_json_data(cafile=cafile) + json_data, json_md5 = get_json_data(cafile=cafile) assert isinstance(json_data, dict) + assert isinstance(json_md5, str) + assert json_md5 def test_get_json_data_with_capath(capath: Path): - json_data = get_json_data(capath=capath) + json_data, json_md5 = get_json_data(capath=capath) assert isinstance(json_data, dict) + assert isinstance(json_md5, str) + assert json_md5 From 3f077550dd5df54b52925aa50800f6b067cb4b45 Mon Sep 17 00:00:00 2001 From: Chris Lunsford Date: Fri, 1 Oct 2021 16:59:25 -0400 Subject: [PATCH 2/5] Add integration test for new AWSIPPrefixes.md5 attribute --- tests/integration/test_package_apis.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/integration/test_package_apis.py b/tests/integration/test_package_apis.py index 4c73d39..fb2d043 100644 --- a/tests/integration/test_package_apis.py +++ b/tests/integration/test_package_apis.py @@ -42,6 +42,12 @@ def test_sync_token_is_opaque_string(aws_ip_ranges: AWSIPPrefixes): assert len(aws_ip_ranges.sync_token) > 0 +def test_md5_is_hexadecimal_string(aws_ip_ranges: AWSIPPrefixes): + assert isinstance(aws_ip_ranges.md5, str) + assert len(aws_ip_ranges.md5) > 0 + assert int(aws_ip_ranges.md5, 16) > 0 + + def test_ipv4_prefixes_are_aws_ip4_prefixes(aws_ip_ranges: AWSIPPrefixes): for prefix in aws_ip_ranges.ipv4_prefixes: assert isinstance(prefix, AWSIPv4Prefix) From c4dfaabc4855a5b471be32c753c393f2818217d3 Mon Sep 17 00:00:00 2001 From: Chris Lunsford Date: Fri, 1 Oct 2021 16:59:47 -0400 Subject: [PATCH 3/5] Add unit tests for custom package exceptions --- tests/unit/test_exceptions.py | 146 ++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 tests/unit/test_exceptions.py diff --git a/tests/unit/test_exceptions.py b/tests/unit/test_exceptions.py new file mode 100644 index 0000000..b2c58e7 --- /dev/null +++ b/tests/unit/test_exceptions.py @@ -0,0 +1,146 @@ +"""Unit tests for the awsipranges custom exceptions.""" + +from dataclasses import dataclass + +import pytest + +from awsipranges.config import AWS_IP_ADDRESS_RANGES_URL +from awsipranges.exceptions import AWSIPRangesException, HTTPError, raise_for_status + + +# Helper classes +@dataclass +class Response: + url: str = AWS_IP_ADDRESS_RANGES_URL + status: int = 200 + reason: str = "OK" + + +@dataclass +class LegacyResponse: + _url: str = AWS_IP_ADDRESS_RANGES_URL + _status: int = 200 + + def geturl(self) -> str: + return self._url + + def getstatus(self) -> int: + return self._status + + +@dataclass +class LegacyResponseWithCode: + _url: str = AWS_IP_ADDRESS_RANGES_URL + code: int = 200 + + def geturl(self) -> str: + return self._url + + +@dataclass +class BadResponseNoStatus: + url: str = AWS_IP_ADDRESS_RANGES_URL + + +@dataclass +class BadResponseNoURL: + status: int = 200 + + +# Happy path tests +def test_raising_aws_ip_ranges_exception(): + with pytest.raises(AWSIPRangesException): + raise AWSIPRangesException("Custom error message.") + + +def test_raising_http_error(): + with pytest.raises(HTTPError): + raise HTTPError( + "Request failed because of something you did", + status=400, + reason="Bad Request", + ) + + +def test_convert_aws_ip_ranges_exception_to_str(): + exception = AWSIPRangesException("Custom error message.") + exception_str = str(exception) + print(exception_str) + + +def test_convert_aws_ip_ranges_exception_to_repr(): + exception = AWSIPRangesException("Custom error message.") + exception_repr = repr(exception) + print(exception_repr) + + +def test_convert_http_error_to_str(): + exception = HTTPError( + "Request failed because of something you did", + status=400, + reason="Bad Request", + ) + exception_str = str(exception) + print(exception_str) + + +def test_convert_http_error_to_repr(): + exception = HTTPError( + "Request failed because of something you did", + status=400, + reason="Bad Request", + ) + exception_repr = repr(exception) + print(exception_repr) + + +def test_raise_for_status_ok(): + response = Response() + raise_for_status(response) + + +def test_legacy_raise_for_status_ok(): + response = LegacyResponse() + raise_for_status(response) + + +def test_legacy_response_with_code_ok(): + response = LegacyResponseWithCode() + raise_for_status(response) + + +# Unhappy path tests +def test_raise_for_status_client_error(): + with pytest.raises(HTTPError): + response = Response(status=400, reason="Bad Request") + raise_for_status(response) + + +def test_raise_for_status_server_error(): + with pytest.raises(HTTPError): + response = Response(status=500, reason="Internal Server Error") + raise_for_status(response) + + +def test_legacy_raise_for_status_client_error(): + with pytest.raises(HTTPError): + response = LegacyResponse(_status=400) + raise_for_status(response) + + +def test_legacy_response_with_code_client_error(): + with pytest.raises(HTTPError): + response = LegacyResponseWithCode(code=400) + raise_for_status(response) + + +def test_bad_response_no_status(): + with pytest.raises(ValueError): + response = BadResponseNoStatus() + raise_for_status(response) + + +def test_bad_response_no_url(): + with pytest.raises(ValueError): + response = BadResponseNoURL() + raise_for_status(response) From 7810307f4bf8304d1d22f329e0eb3cc3876c35c4 Mon Sep 17 00:00:00 2001 From: Chris Lunsford Date: Fri, 1 Oct 2021 22:08:06 -0400 Subject: [PATCH 4/5] Update development dependencies --- poetry.lock | 177 ++++++++++++++++++++++--------------------- requirements-dev.txt | 6 +- 2 files changed, 93 insertions(+), 90 deletions(-) diff --git a/poetry.lock b/poetry.lock index eeb51a9..5d0b7a0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -108,7 +108,7 @@ toml = ["toml"] [[package]] name = "cryptography" -version = "3.4.8" +version = "35.0.0" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "dev" optional = false @@ -121,9 +121,9 @@ cffi = ">=1.12" docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] -sdist = ["setuptools-rust (>=0.11.4)"] +sdist = ["setuptools_rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] +test = ["pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] [[package]] name = "decorator" @@ -576,7 +576,7 @@ pyyaml = "*" [[package]] name = "regex" -version = "2021.9.24" +version = "2021.9.30" description = "Alternative regular expression module, to replace re." category = "dev" optional = false @@ -635,14 +635,14 @@ python-versions = "*" [[package]] name = "watchdog" -version = "2.1.5" +version = "2.1.6" description = "Filesystem events monitoring" category = "dev" optional = false python-versions = ">=3.6" [package.extras] -watchmedo = ["PyYAML (>=3.10)", "argh (>=0.24.1)"] +watchmedo = ["PyYAML (>=3.10)"] [[package]] name = "wcwidth" @@ -800,23 +800,26 @@ coverage = [ {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, ] cryptography = [ - {file = "cryptography-3.4.8-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a00cf305f07b26c351d8d4e1af84ad7501eca8a342dedf24a7acb0e7b7406e14"}, - {file = "cryptography-3.4.8-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:f44d141b8c4ea5eb4dbc9b3ad992d45580c1d22bf5e24363f2fbf50c2d7ae8a7"}, - {file = "cryptography-3.4.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0a7dcbcd3f1913f664aca35d47c1331fce738d44ec34b7be8b9d332151b0b01e"}, - {file = "cryptography-3.4.8-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34dae04a0dce5730d8eb7894eab617d8a70d0c97da76b905de9efb7128ad7085"}, - {file = "cryptography-3.4.8-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1eb7bb0df6f6f583dd8e054689def236255161ebbcf62b226454ab9ec663746b"}, - {file = "cryptography-3.4.8-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:9965c46c674ba8cc572bc09a03f4c649292ee73e1b683adb1ce81e82e9a6a0fb"}, - {file = "cryptography-3.4.8-cp36-abi3-win32.whl", hash = "sha256:21ca464b3a4b8d8e86ba0ee5045e103a1fcfac3b39319727bc0fc58c09c6aff7"}, - {file = "cryptography-3.4.8-cp36-abi3-win_amd64.whl", hash = "sha256:3520667fda779eb788ea00080124875be18f2d8f0848ec00733c0ec3bb8219fc"}, - {file = "cryptography-3.4.8-pp36-pypy36_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d2a6e5ef66503da51d2110edf6c403dc6b494cc0082f85db12f54e9c5d4c3ec5"}, - {file = "cryptography-3.4.8-pp36-pypy36_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a305600e7a6b7b855cd798e00278161b681ad6e9b7eca94c721d5f588ab212af"}, - {file = "cryptography-3.4.8-pp36-pypy36_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:3fa3a7ccf96e826affdf1a0a9432be74dc73423125c8f96a909e3835a5ef194a"}, - {file = "cryptography-3.4.8-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:d9ec0e67a14f9d1d48dd87a2531009a9b251c02ea42851c060b25c782516ff06"}, - {file = "cryptography-3.4.8-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b0fbfae7ff7febdb74b574055c7466da334a5371f253732d7e2e7525d570498"}, - {file = "cryptography-3.4.8-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94fff993ee9bc1b2440d3b7243d488c6a3d9724cc2b09cdb297f6a886d040ef7"}, - {file = "cryptography-3.4.8-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:8695456444f277af73a4877db9fc979849cd3ee74c198d04fc0776ebc3db52b9"}, - {file = "cryptography-3.4.8-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:cd65b60cfe004790c795cc35f272e41a3df4631e2fb6b35aa7ac6ef2859d554e"}, - {file = "cryptography-3.4.8.tar.gz", hash = "sha256:94cc5ed4ceaefcbe5bf38c8fba6a21fc1d365bb8fb826ea1688e3370b2e24a1c"}, + {file = "cryptography-35.0.0-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:d57e0cdc1b44b6cdf8af1d01807db06886f10177469312fbde8f44ccbb284bc9"}, + {file = "cryptography-35.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:ced40344e811d6abba00295ced98c01aecf0c2de39481792d87af4fa58b7b4d6"}, + {file = "cryptography-35.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:54b2605e5475944e2213258e0ab8696f4f357a31371e538ef21e8d61c843c28d"}, + {file = "cryptography-35.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:7b7ceeff114c31f285528ba8b390d3e9cfa2da17b56f11d366769a807f17cbaa"}, + {file = "cryptography-35.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d69645f535f4b2c722cfb07a8eab916265545b3475fdb34e0be2f4ee8b0b15e"}, + {file = "cryptography-35.0.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a2d0e0acc20ede0f06ef7aa58546eee96d2592c00f450c9acb89c5879b61992"}, + {file = "cryptography-35.0.0-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:07bb7fbfb5de0980590ddfc7f13081520def06dc9ed214000ad4372fb4e3c7f6"}, + {file = "cryptography-35.0.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7eba2cebca600a7806b893cb1d541a6e910afa87e97acf2021a22b32da1df52d"}, + {file = "cryptography-35.0.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:18d90f4711bf63e2fb21e8c8e51ed8189438e6b35a6d996201ebd98a26abbbe6"}, + {file = "cryptography-35.0.0-cp36-abi3-win32.whl", hash = "sha256:c10c797ac89c746e488d2ee92bd4abd593615694ee17b2500578b63cad6b93a8"}, + {file = "cryptography-35.0.0-cp36-abi3-win_amd64.whl", hash = "sha256:7075b304cd567694dc692ffc9747f3e9cb393cc4aa4fb7b9f3abd6f5c4e43588"}, + {file = "cryptography-35.0.0-pp36-pypy36_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a688ebcd08250eab5bb5bca318cc05a8c66de5e4171a65ca51db6bd753ff8953"}, + {file = "cryptography-35.0.0-pp36-pypy36_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d99915d6ab265c22873f1b4d6ea5ef462ef797b4140be4c9d8b179915e0985c6"}, + {file = "cryptography-35.0.0-pp36-pypy36_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:928185a6d1ccdb816e883f56ebe92e975a262d31cc536429041921f8cb5a62fd"}, + {file = "cryptography-35.0.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ebeddd119f526bcf323a89f853afb12e225902a24d29b55fe18dd6fcb2838a76"}, + {file = "cryptography-35.0.0-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:22a38e96118a4ce3b97509443feace1d1011d0571fae81fc3ad35f25ba3ea999"}, + {file = "cryptography-35.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb80e8a1f91e4b7ef8b33041591e6d89b2b8e122d787e87eeb2b08da71bb16ad"}, + {file = "cryptography-35.0.0-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:abb5a361d2585bb95012a19ed9b2c8f412c5d723a9836418fab7aaa0243e67d2"}, + {file = "cryptography-35.0.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:1ed82abf16df40a60942a8c211251ae72858b25b7421ce2497c2eb7a1cee817c"}, + {file = "cryptography-35.0.0.tar.gz", hash = "sha256:9933f28f70d0517686bd7de36166dda42094eac49415459d9bdf5e7df3e0086d"}, ] decorator = [ {file = "decorator-5.1.0-py3-none-any.whl", hash = "sha256:7b12e7c3c6ab203a29e157335e9122cb03de9ab7264b137594103fd4a683b374"}, @@ -1049,47 +1052,47 @@ pyyaml-env-tag = [ {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, ] regex = [ - {file = "regex-2021.9.24-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0628ed7d6334e8f896f882a5c1240de8c4d9b0dd7c7fb8e9f4692f5684b7d656"}, - {file = "regex-2021.9.24-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3baf3eaa41044d4ced2463fd5d23bf7bd4b03d68739c6c99a59ce1f95599a673"}, - {file = "regex-2021.9.24-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c000635fd78400a558bd7a3c2981bb2a430005ebaa909d31e6e300719739a949"}, - {file = "regex-2021.9.24-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:295bc8a13554a25ad31e44c4bedabd3c3e28bba027e4feeb9bb157647a2344a7"}, - {file = "regex-2021.9.24-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0e3f59d3c772f2c3baaef2db425e6fc4149d35a052d874bb95ccfca10a1b9f4"}, - {file = "regex-2021.9.24-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:aea4006b73b555fc5bdb650a8b92cf486d678afa168cf9b38402bb60bf0f9c18"}, - {file = "regex-2021.9.24-cp310-cp310-win32.whl", hash = "sha256:09eb62654030f39f3ba46bc6726bea464069c29d00a9709e28c9ee9623a8da4a"}, - {file = "regex-2021.9.24-cp310-cp310-win_amd64.whl", hash = "sha256:8d80087320632457aefc73f686f66139801959bf5b066b4419b92be85be3543c"}, - {file = "regex-2021.9.24-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:7e3536f305f42ad6d31fc86636c54c7dafce8d634e56fef790fbacb59d499dd5"}, - {file = "regex-2021.9.24-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c31f35a984caffb75f00a86852951a337540b44e4a22171354fb760cefa09346"}, - {file = "regex-2021.9.24-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c7cb25adba814d5f419733fe565f3289d6fa629ab9e0b78f6dff5fa94ab0456"}, - {file = "regex-2021.9.24-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:85c61bee5957e2d7be390392feac7e1d7abd3a49cbaed0c8cee1541b784c8561"}, - {file = "regex-2021.9.24-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c94722bf403b8da744b7d0bb87e1f2529383003ceec92e754f768ef9323f69ad"}, - {file = "regex-2021.9.24-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6adc1bd68f81968c9d249aab8c09cdc2cbe384bf2d2cb7f190f56875000cdc72"}, - {file = "regex-2021.9.24-cp36-cp36m-win32.whl", hash = "sha256:2054dea683f1bda3a804fcfdb0c1c74821acb968093d0be16233873190d459e3"}, - {file = "regex-2021.9.24-cp36-cp36m-win_amd64.whl", hash = "sha256:7783d89bd5413d183a38761fbc68279b984b9afcfbb39fa89d91f63763fbfb90"}, - {file = "regex-2021.9.24-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b15dc34273aefe522df25096d5d087abc626e388a28a28ac75a4404bb7668736"}, - {file = "regex-2021.9.24-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10a7a9cbe30bd90b7d9a1b4749ef20e13a3528e4215a2852be35784b6bd070f0"}, - {file = "regex-2021.9.24-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb9f5844db480e2ef9fce3a72e71122dd010ab7b2920f777966ba25f7eb63819"}, - {file = "regex-2021.9.24-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:17310b181902e0bb42b29c700e2c2346b8d81f26e900b1328f642e225c88bce1"}, - {file = "regex-2021.9.24-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0bba1f6df4eafe79db2ecf38835c2626dbd47911e0516f6962c806f83e7a99ae"}, - {file = "regex-2021.9.24-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:821e10b73e0898544807a0692a276e539e5bafe0a055506a6882814b6a02c3ec"}, - {file = "regex-2021.9.24-cp37-cp37m-win32.whl", hash = "sha256:9c371dd326289d85906c27ec2bc1dcdedd9d0be12b543d16e37bad35754bde48"}, - {file = "regex-2021.9.24-cp37-cp37m-win_amd64.whl", hash = "sha256:1e8d1898d4fb817120a5f684363b30108d7b0b46c7261264b100d14ec90a70e7"}, - {file = "regex-2021.9.24-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8a5c2250c0a74428fd5507ae8853706fdde0f23bfb62ee1ec9418eeacf216078"}, - {file = "regex-2021.9.24-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8aec4b4da165c4a64ea80443c16e49e3b15df0f56c124ac5f2f8708a65a0eddc"}, - {file = "regex-2021.9.24-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:650c4f1fc4273f4e783e1d8e8b51a3e2311c2488ba0fcae6425b1e2c248a189d"}, - {file = "regex-2021.9.24-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2cdb3789736f91d0b3333ac54d12a7e4f9efbc98f53cb905d3496259a893a8b3"}, - {file = "regex-2021.9.24-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e61100200fa6ab7c99b61476f9f9653962ae71b931391d0264acfb4d9527d9c"}, - {file = "regex-2021.9.24-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8c268e78d175798cd71d29114b0a1f1391c7d011995267d3b62319ec1a4ecaa1"}, - {file = "regex-2021.9.24-cp38-cp38-win32.whl", hash = "sha256:658e3477676009083422042c4bac2bdad77b696e932a3de001c42cc046f8eda2"}, - {file = "regex-2021.9.24-cp38-cp38-win_amd64.whl", hash = "sha256:a731552729ee8ae9c546fb1c651c97bf5f759018fdd40d0e9b4d129e1e3a44c8"}, - {file = "regex-2021.9.24-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:86f9931eb92e521809d4b64ec8514f18faa8e11e97d6c2d1afa1bcf6c20a8eab"}, - {file = "regex-2021.9.24-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcbbc9cfa147d55a577d285fd479b43103188855074552708df7acc31a476dd9"}, - {file = "regex-2021.9.24-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29385c4dbb3f8b3a55ce13de6a97a3d21bd00de66acd7cdfc0b49cb2f08c906c"}, - {file = "regex-2021.9.24-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c50a6379763c733562b1fee877372234d271e5c78cd13ade5f25978aa06744db"}, - {file = "regex-2021.9.24-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f74b6d8f59f3cfb8237e25c532b11f794b96f5c89a6f4a25857d85f84fbef11"}, - {file = "regex-2021.9.24-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6c4d83d21d23dd854ffbc8154cf293f4e43ba630aa9bd2539c899343d7f59da3"}, - {file = "regex-2021.9.24-cp39-cp39-win32.whl", hash = "sha256:95e89a8558c8c48626dcffdf9c8abac26b7c251d352688e7ab9baf351e1c7da6"}, - {file = "regex-2021.9.24-cp39-cp39-win_amd64.whl", hash = "sha256:835962f432bce92dc9bf22903d46c50003c8d11b1dc64084c8fae63bca98564a"}, - {file = "regex-2021.9.24.tar.gz", hash = "sha256:6266fde576e12357b25096351aac2b4b880b0066263e7bc7a9a1b4307991bb0e"}, + {file = "regex-2021.9.30-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:66696c8336a1b5d1182464f3af3427cc760118f26d0b09a2ddc16a976a4d2637"}, + {file = "regex-2021.9.30-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d87459ad3ab40cd8493774f8a454b2e490d8e729e7e402a0625867a983e4e02"}, + {file = "regex-2021.9.30-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78cf6a1e023caf5e9a982f5377414e1aeac55198831b852835732cfd0a0ca5ff"}, + {file = "regex-2021.9.30-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:255791523f80ea8e48e79af7120b4697ef3b74f6886995dcdb08c41f8e516be0"}, + {file = "regex-2021.9.30-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e502f8d4e5ef714bcc2c94d499684890c94239526d61fdf1096547db91ca6aa6"}, + {file = "regex-2021.9.30-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4907fb0f9b9309a5bded72343e675a252c2589a41871874feace9a05a540241e"}, + {file = "regex-2021.9.30-cp310-cp310-win32.whl", hash = "sha256:3be40f720af170a6b20ddd2ad7904c58b13d2b56f6734ee5d09bbdeed2fa4816"}, + {file = "regex-2021.9.30-cp310-cp310-win_amd64.whl", hash = "sha256:c2b180ed30856dfa70cfe927b0fd38e6b68198a03039abdbeb1f2029758d87e7"}, + {file = "regex-2021.9.30-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e6f2d2f93001801296fe3ca86515eb04915472b5380d4d8752f09f25f0b9b0ed"}, + {file = "regex-2021.9.30-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fa7ba9ab2eba7284e0d7d94f61df7af86015b0398e123331362270d71fab0b9"}, + {file = "regex-2021.9.30-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28040e89a04b60d579c69095c509a4f6a1a5379cd865258e3a186b7105de72c6"}, + {file = "regex-2021.9.30-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f588209d3e4797882cd238195c175290dbc501973b10a581086b5c6bcd095ffb"}, + {file = "regex-2021.9.30-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42952d325439ef223e4e9db7ee6d9087b5c68c5c15b1f9de68e990837682fc7b"}, + {file = "regex-2021.9.30-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cae4099031d80703954c39680323dabd87a69b21262303160776aa0e55970ca0"}, + {file = "regex-2021.9.30-cp36-cp36m-win32.whl", hash = "sha256:0de8ad66b08c3e673b61981b9e3626f8784d5564f8c3928e2ad408c0eb5ac38c"}, + {file = "regex-2021.9.30-cp36-cp36m-win_amd64.whl", hash = "sha256:b345ecde37c86dd7084c62954468a4a655fd2d24fd9b237949dd07a4d0dd6f4c"}, + {file = "regex-2021.9.30-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6f08187136f11e430638c2c66e1db091105d7c2e9902489f0dbc69b44c222b4"}, + {file = "regex-2021.9.30-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b55442650f541d195a535ccec33078c78a9521973fb960923da7515e9ed78fa6"}, + {file = "regex-2021.9.30-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87e9c489aa98f50f367fb26cc9c8908d668e9228d327644d7aa568d47e456f47"}, + {file = "regex-2021.9.30-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e2cb7d4909ed16ed35729d38af585673f1f0833e73dfdf0c18e5be0061107b99"}, + {file = "regex-2021.9.30-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0861e7f6325e821d5c40514c551fd538b292f8cc3960086e73491b9c5d8291d"}, + {file = "regex-2021.9.30-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:81fdc90f999b2147fc62e303440c424c47e5573a9b615ed5d43a5b832efcca9e"}, + {file = "regex-2021.9.30-cp37-cp37m-win32.whl", hash = "sha256:8c1ad61fa024195136a6b7b89538030bd00df15f90ac177ca278df9b2386c96f"}, + {file = "regex-2021.9.30-cp37-cp37m-win_amd64.whl", hash = "sha256:e3770781353a4886b68ef10cec31c1f61e8e3a0be5f213c2bb15a86efd999bc4"}, + {file = "regex-2021.9.30-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9c065d95a514a06b92a5026766d72ac91bfabf581adb5b29bc5c91d4b3ee9b83"}, + {file = "regex-2021.9.30-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9925985be05d54b3d25fd6c1ea8e50ff1f7c2744c75bdc4d3b45c790afa2bcb3"}, + {file = "regex-2021.9.30-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:470f2c882f2672d8eeda8ab27992aec277c067d280b52541357e1acd7e606dae"}, + {file = "regex-2021.9.30-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ad0517df22a97f1da20d8f1c8cb71a5d1997fa383326b81f9cf22c9dadfbdf34"}, + {file = "regex-2021.9.30-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e30838df7bfd20db6466fd309d9b580d32855f8e2c2e6d74cf9da27dcd9b63"}, + {file = "regex-2021.9.30-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b34d2335d6aedec7dcadd3f8283b9682fadad8b9b008da8788d2fce76125ebe"}, + {file = "regex-2021.9.30-cp38-cp38-win32.whl", hash = "sha256:e07049cece3462c626d650e8bf42ddbca3abf4aa08155002c28cb6d9a5a281e2"}, + {file = "regex-2021.9.30-cp38-cp38-win_amd64.whl", hash = "sha256:37868075eda024470bd0feab872c692ac4ee29db1e14baec103257bf6cc64346"}, + {file = "regex-2021.9.30-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d331f238a7accfbbe1c4cd1ba610d4c087b206353539331e32a8f05345c74aec"}, + {file = "regex-2021.9.30-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6348a7ab2a502cbdd0b7fd0496d614007489adb7361956b38044d1d588e66e04"}, + {file = "regex-2021.9.30-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce7b1cca6c23f19bee8dc40228d9c314d86d1e51996b86f924aca302fc8f8bf9"}, + {file = "regex-2021.9.30-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1f1125bc5172ab3a049bc6f4b9c0aae95a2a2001a77e6d6e4239fa3653e202b5"}, + {file = "regex-2021.9.30-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:638e98d069b14113e8afba6a54d1ca123f712c0d105e67c1f9211b2a825ef926"}, + {file = "regex-2021.9.30-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9a0b0db6b49da7fa37ca8eddf9f40a8dbc599bad43e64f452284f37b6c34d91c"}, + {file = "regex-2021.9.30-cp39-cp39-win32.whl", hash = "sha256:9910869c472e5a6728680ca357b5846546cbbd2ab3ad5bef986ef0bc438d0aa6"}, + {file = "regex-2021.9.30-cp39-cp39-win_amd64.whl", hash = "sha256:3b71213ec3bad9a5a02e049f2ec86b3d7c3e350129ae0f4e2f99c12b5da919ed"}, + {file = "regex-2021.9.30.tar.gz", hash = "sha256:81e125d9ba54c34579e4539a967e976a3c56150796674aec318b1b2f49251be7"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, @@ -1145,29 +1148,29 @@ typing-extensions = [ {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, ] watchdog = [ - {file = "watchdog-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5f57ce4f7e498278fb2a091f39359930144a0f2f90ea8cbf4523c4e25de34028"}, - {file = "watchdog-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8b74d0d92a69a7ab5f101f9fe74e44ba017be269efa824337366ccbb4effde85"}, - {file = "watchdog-2.1.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:59767f476cd1f48531bf378f0300565d879688c82da8369ca8c52f633299523c"}, - {file = "watchdog-2.1.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:814d396859c95598f7576d15bc257c3bd3ba61fa4bc1db7dfc18f09070ded7da"}, - {file = "watchdog-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:28777dbed3bbd95f9c70f461443990a36c07dbf49ae7cd69932cdd1b8fb2850c"}, - {file = "watchdog-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5cf78f794c9d7bc64a626ef4f71aff88f57a7ae288e0b359a9c6ea711a41395f"}, - {file = "watchdog-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:43bf728eb7830559f329864ab5da2302c15b2efbac24ad84ccc09949ba753c40"}, - {file = "watchdog-2.1.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a7053d4d22dc95c5e0c90aeeae1e4ed5269d2f04001798eec43a654a03008d22"}, - {file = "watchdog-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6f3ad1d973fe8fc8fe64ba38f6a934b74346342fa98ef08ad5da361a05d46044"}, - {file = "watchdog-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:41d44ef21a77a32b55ce9bf59b75777063751f688de51098859b7c7f6466589a"}, - {file = "watchdog-2.1.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ed4ca4351cd2bb0d863ee737a2011ca44d8d8be19b43509bd4507f8a449b376b"}, - {file = "watchdog-2.1.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8874d5ad6b7f43b18935d9b0183e29727a623a216693d6938d07dfd411ba462f"}, - {file = "watchdog-2.1.5-py3-none-manylinux2014_aarch64.whl", hash = "sha256:50a7f81f99d238f72185f481b493f9de80096e046935b60ea78e1276f3d76960"}, - {file = "watchdog-2.1.5-py3-none-manylinux2014_armv7l.whl", hash = "sha256:e40e33a4889382824846b4baa05634e1365b47c6fa40071dc2d06b4d7c715fc1"}, - {file = "watchdog-2.1.5-py3-none-manylinux2014_i686.whl", hash = "sha256:78b1514067ff4089f4dac930b043a142997a5b98553120919005e97fbaba6546"}, - {file = "watchdog-2.1.5-py3-none-manylinux2014_ppc64.whl", hash = "sha256:58ae842300cbfe5e62fb068c83901abe76e4f413234b7bec5446e4275eb1f9cb"}, - {file = "watchdog-2.1.5-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:b0cc7d8b7d60da6c313779d85903ce39a63d89d866014b085f720a083d5f3e9a"}, - {file = "watchdog-2.1.5-py3-none-manylinux2014_s390x.whl", hash = "sha256:e60d3bb7166b7cb830b86938d1eb0e6cfe23dfd634cce05c128f8f9967895193"}, - {file = "watchdog-2.1.5-py3-none-manylinux2014_x86_64.whl", hash = "sha256:51af09ae937ada0e9a10cc16988ec03c649754a91526170b6839b89fc56d6acb"}, - {file = "watchdog-2.1.5-py3-none-win32.whl", hash = "sha256:9391003635aa783957b9b11175d9802d3272ed67e69ef2e3394c0b6d9d24fa9a"}, - {file = "watchdog-2.1.5-py3-none-win_amd64.whl", hash = "sha256:eab14adfc417c2c983fbcb2c73ef3f28ba6990d1fff45d1180bf7e38bda0d98d"}, - {file = "watchdog-2.1.5-py3-none-win_ia64.whl", hash = "sha256:a2888a788893c4ef7e562861ec5433875b7915f930a5a7ed3d32c048158f1be5"}, - {file = "watchdog-2.1.5.tar.gz", hash = "sha256:5563b005907613430ef3d4aaac9c78600dd5704e84764cb6deda4b3d72807f09"}, + {file = "watchdog-2.1.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9693f35162dc6208d10b10ddf0458cc09ad70c30ba689d9206e02cd836ce28a3"}, + {file = "watchdog-2.1.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:aba5c812f8ee8a3ff3be51887ca2d55fb8e268439ed44110d3846e4229eb0e8b"}, + {file = "watchdog-2.1.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ae38bf8ba6f39d5b83f78661273216e7db5b00f08be7592062cb1fc8b8ba542"}, + {file = "watchdog-2.1.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ad6f1796e37db2223d2a3f302f586f74c72c630b48a9872c1e7ae8e92e0ab669"}, + {file = "watchdog-2.1.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:922a69fa533cb0c793b483becaaa0845f655151e7256ec73630a1b2e9ebcb660"}, + {file = "watchdog-2.1.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b2fcf9402fde2672545b139694284dc3b665fd1be660d73eca6805197ef776a3"}, + {file = "watchdog-2.1.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3386b367e950a11b0568062b70cc026c6f645428a698d33d39e013aaeda4cc04"}, + {file = "watchdog-2.1.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f1c00aa35f504197561060ca4c21d3cc079ba29cf6dd2fe61024c70160c990b"}, + {file = "watchdog-2.1.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b52b88021b9541a60531142b0a451baca08d28b74a723d0c99b13c8c8d48d604"}, + {file = "watchdog-2.1.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8047da932432aa32c515ec1447ea79ce578d0559362ca3605f8e9568f844e3c6"}, + {file = "watchdog-2.1.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e92c2d33858c8f560671b448205a268096e17870dcf60a9bb3ac7bfbafb7f5f9"}, + {file = "watchdog-2.1.6-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b7d336912853d7b77f9b2c24eeed6a5065d0a0cc0d3b6a5a45ad6d1d05fb8cd8"}, + {file = "watchdog-2.1.6-py3-none-manylinux2014_aarch64.whl", hash = "sha256:cca7741c0fcc765568350cb139e92b7f9f3c9a08c4f32591d18ab0a6ac9e71b6"}, + {file = "watchdog-2.1.6-py3-none-manylinux2014_armv7l.whl", hash = "sha256:25fb5240b195d17de949588628fdf93032ebf163524ef08933db0ea1f99bd685"}, + {file = "watchdog-2.1.6-py3-none-manylinux2014_i686.whl", hash = "sha256:be9be735f827820a06340dff2ddea1fb7234561fa5e6300a62fe7f54d40546a0"}, + {file = "watchdog-2.1.6-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d0d19fb2441947b58fbf91336638c2b9f4cc98e05e1045404d7a4cb7cddc7a65"}, + {file = "watchdog-2.1.6-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:3becdb380d8916c873ad512f1701f8a92ce79ec6978ffde92919fd18d41da7fb"}, + {file = "watchdog-2.1.6-py3-none-manylinux2014_s390x.whl", hash = "sha256:ae67501c95606072aafa865b6ed47343ac6484472a2f95490ba151f6347acfc2"}, + {file = "watchdog-2.1.6-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e0f30db709c939cabf64a6dc5babb276e6d823fd84464ab916f9b9ba5623ca15"}, + {file = "watchdog-2.1.6-py3-none-win32.whl", hash = "sha256:e02794ac791662a5eafc6ffeaf9bcc149035a0e48eb0a9d40a8feb4622605a3d"}, + {file = "watchdog-2.1.6-py3-none-win_amd64.whl", hash = "sha256:bd9ba4f332cf57b2c1f698be0728c020399ef3040577cde2939f2e045b39c1e5"}, + {file = "watchdog-2.1.6-py3-none-win_ia64.whl", hash = "sha256:a0f1c7edf116a12f7245be06120b1852275f9506a7d90227648b250755a03923"}, + {file = "watchdog-2.1.6.tar.gz", hash = "sha256:a36e75df6c767cbf46f61a91c70b3ba71811dfa0aca4a324d9407a06a8b7a2e7"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, diff --git a/requirements-dev.txt b/requirements-dev.txt index 575e825..1e9b368 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ cffi==1.14.6; python_version >= "3.6" and python_full_version < "3.0.0" or pytho click==8.0.1; python_version >= "3.6" and python_full_version >= "3.6.2" colorama==0.4.4; sys_platform == "win32" and python_version >= "3.7" and python_full_version >= "3.6.2" and platform_system == "Windows" and (python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6") or sys_platform == "win32" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6") and python_full_version >= "3.5.0") coverage==5.5; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0" and python_version < "4") -cryptography==3.4.8; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" +cryptography==35.0.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" decorator==5.1.0; python_version >= "3.7" flake8==3.9.2; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") ghp-import==2.0.2; python_version >= "3.6" @@ -46,13 +46,13 @@ pytest==6.2.5; python_version >= "3.6" python-dateutil==2.8.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" pyyaml-env-tag==0.1; python_version >= "3.6" pyyaml==5.4.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" -regex==2021.9.24; python_full_version >= "3.6.2" +regex==2021.9.30; python_full_version >= "3.6.2" six==1.16.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" toml==0.10.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" tomli==1.2.1; python_version >= "3.6" and python_full_version >= "3.6.2" traitlets==5.1.0; python_version >= "3.7" typed-ast==1.4.3; python_version < "3.8" and python_full_version >= "3.6.2" typing-extensions==3.10.0.2 -watchdog==2.1.5; python_version >= "3.6" +watchdog==2.1.6; python_version >= "3.6" wcwidth==0.2.5; python_full_version >= "3.6.2" and python_version >= "3.7" zipp==3.6.0; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.5.0" and python_version < "3.8" and python_version >= "3.6" From 6ce8419c39098d3debcec1a788832a82822ec49d Mon Sep 17 00:00:00 2001 From: Chris Lunsford Date: Fri, 1 Oct 2021 22:08:21 -0400 Subject: [PATCH 5/5] Update docs and docstrings --- awsipranges/models/awsipprefixes.py | 43 ++++++++++++++--------------- docs/quickstart.md | 34 ++++++++++++----------- 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/awsipranges/models/awsipprefixes.py b/awsipranges/models/awsipprefixes.py index bd1c992..b1631f2 100644 --- a/awsipranges/models/awsipprefixes.py +++ b/awsipranges/models/awsipprefixes.py @@ -161,8 +161,7 @@ def ipv6_prefixes(self) -> Tuple[AWSIPv6Prefix, ...]: def md5(self) -> Optional[str]: """The MD5 cryptographic hash value of the ip-ranges.json file. - You can use this value to check whether the downloaded file is - corrupted. + You can use this value to verify the integrity of the downloaded file. """ return self._md5 @@ -261,23 +260,23 @@ def get( ], default=None, ) -> Union[AWSIPv4Prefix, AWSIPv6Prefix]: - """Get the AWS IP address prefix that contains the IPv4 or IPv6 item. + """Get the AWS IP address prefix that contains the IPv4 or IPv6 key. - Returns the longest-match prefix that contains the provided item or the - value of the `default=` parameter if the item is not found in the + Returns the longest-match prefix that contains the provided key or the + value of the `default=` parameter if the key is not found in the collection. **Parameters:** - - **item** (str, IPv4Address, IPv6Address, IPv4Network, IPv6Network, - IPv4Interface, IPv6Interface, AWSIPv4Prefix, AWSIPv6Prefix) - the item - to retrieve from the collection - - **default** - the value to return if the item is not found in the + - **key** (str, IPv4Address, IPv6Address, IPv4Network, IPv6Network, + IPv4Interface, IPv6Interface, AWSIPv4Prefix, AWSIPv6Prefix) - the IP + address or network to retrieve from the collection + - **default** - the value to return if the key is not found in the collection **Returns:** - The `AWSIPv4Prefix` or `AWSIPv6Prefix` that contains the provided `item`. + The `AWSIPv4Prefix` or `AWSIPv6Prefix` that contains the provided key. """ try: return self[key] @@ -286,7 +285,7 @@ def get( def get_prefix_and_supernets( self, - item: Union[ + key: Union[ str, IPv4Address, IPv6Address, @@ -299,32 +298,32 @@ def get_prefix_and_supernets( ], default=None, ) -> Optional[Tuple[Union[AWSIPv4Prefix, AWSIPv6Prefix], ...]]: - """Get the prefix and supernets that contain the IPv4 or IPv6 item. + """Get the prefix and supernets that contain the IPv4 or IPv6 key. Returns a tuple that contains the longest-match prefix and supernets - that contains the provided or the value of the `default=` parameter if - the item is not found in the collection. + that contains the provided key or the value of the `default=` parameter + if the key is not found in the collection. The tuple is sorted by prefix length in ascending order (shorter prefixes come before longer prefixes). **Parameters:** - - **item** (str, IPv4Address, IPv6Address, IPv4Network, IPv6Network, - IPv4Interface, IPv6Interface, AWSIPv4Prefix, AWSIPv6Prefix) - the item - to retrieve from the collection - - **default** - the value to return if the item is not found in the + - **key** (str, IPv4Address, IPv6Address, IPv4Network, IPv6Network, + IPv4Interface, IPv6Interface, AWSIPv4Prefix, AWSIPv6Prefix) - the IP + address or network to retrieve from the collection + - **default** - the value to return if the key is not found in the collection **Returns:** A tuple of the `AWSIPv4Prefix`es or `AWSIPv6Prefix`es that contains the - provided `item`. + provided key. """ - if isinstance(item, (AWSIPv4Prefix, AWSIPv6Prefix)): - network = item.prefix + if isinstance(key, (AWSIPv4Prefix, AWSIPv6Prefix)): + network = key.prefix else: - network = ip_network(item, strict=False) + network = ip_network(key, strict=False) prefixes = list() for supernet in supernets(network): diff --git a/docs/quickstart.md b/docs/quickstart.md index 0b21720..5b0f2a0 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -7,7 +7,7 @@ To get started, make sure you have: - `awsipranges` [installed](./index.md#installation) - `awsipranges` [upgraded to the latest version](./index.md#installation) -Then dive-in with this QuickStart to start using the AWS IP address ranges as native Python objects! +Then dive-in with this Quickstart to begin working with the AWS IP address ranges as native Python objects! ## Verify server TLS certificates @@ -27,15 +27,15 @@ To verify the TLS certificate presented by the Amazon IP ranges server (ip-range - Stacking (concatenating) the files into a single certificate bundle (single file) - - Storing them in a directory using the OpenSSL hash filenames + - Storing them in a directory using OpenSSL hash filenames - You can do this with the [`c_rehash` script](https://www.openssl.org/docs/man1.1.0/man1/rehash.html) included in many OpenSSL distributions: + You can do this with the [`c_rehash` script](https://www.openssl.org/docs/man1.1.0/man1/rehash.html) included in many OpenSSL distributions: - ```shell - ❯ c_rehash amazon_root_certificates/ - ``` + ```shell + ❯ c_rehash amazon_root_certificates/ + ``` - > ***Tip***: See [`tests/unit/test_data_loading.py`](https://github.com/aws-samples/awsipranges/blob/main/tests/unit/test_data_loading.py) in the `awsipranges` repository for sample Python functions that download the Amazon Root CA certificates and prepare the certificates as both a stacked certificate bundle file and as a directory with OpenSSL hash filenames. + > ***Tip***: See [`tests/unit/test_data_loading.py`](https://github.com/aws-samples/awsipranges/blob/main/tests/unit/test_data_loading.py) in the `awsipranges` repository for sample Python functions that download the Amazon Root CA certificates and prepare the certificates as both a stacked certificate bundle file and as a directory with OpenSSL hash filenames. 3. Pass the path to the prepared certificates to the `awsipranges.get_ranges()` function using the `cafile` or `capath` parameters: @@ -64,17 +64,20 @@ One line of code (okay, two if you count the import statement) is all it takes t The [`awsipranges.get_ranges()`](./api.md#get_ranges) function returns an [`AWSIPPrefixes`](./api.md#awsipprefixes) object, which is a structured collection of AWS IP prefixes. -You can access the `create_date` and `sync_token` attributes of the `AWSIPPrefixes` collection to check the version of the downloaded JSON file: +You can access the `create_date` and `sync_token` attributes of the `AWSIPPrefixes` collection to check the version of the downloaded JSON file and verify the integrity of the file with the `md5` attribute: ```python >>> aws_ip_ranges.create_date -datetime.datetime(2021, 9, 16, 17, 43, 14, tzinfo=datetime.timezone.utc) +datetime.datetime(2021, 10, 1, 16, 33, 13, tzinfo=datetime.timezone.utc) >>> aws_ip_ranges.sync_token -'1631814194' +'1633105993' + +>>> aws_ip_ranges.md5 +'59e4cd7f4757a9f380c626d772a5eef2' ``` -And you can access the IPv4 and IPv6 address prefixes with the `ipv4_prefixes` and `ipv6_prefixes` attributes: +You can access the IPv4 and IPv6 address prefixes with the `ipv4_prefixes` and `ipv6_prefixes` attributes: ```python >>> aws_ip_ranges.ipv4_prefixes @@ -86,7 +89,6 @@ And you can access the IPv4 and IPv6 address prefixes with the `ipv4_prefixes` a ...) >>> aws_ip_ranges.ipv6_prefixes -Out[7]: (AWSIPv6Prefix('2400:6500:0:9::1/128', region='ap-southeast-3', network_border_group='ap-southeast-3', services=('AMAZON',)), AWSIPv6Prefix('2400:6500:0:9::2/128', region='ap-southeast-3', network_border_group='ap-southeast-3', services=('AMAZON',)), AWSIPv6Prefix('2400:6500:0:9::3/128', region='ap-southeast-3', network_border_group='ap-southeast-3', services=('AMAZON',)), @@ -149,7 +151,7 @@ The AWS IP address ranges contain supernet and subnet prefixes, so an IP address The `filter()` method allows you to select a subset of AWS IP prefixes from the collection. You can filter on `regions`, `network_border_groups`, IP `versions` (4, 6), and `services`. The `filter()` method returns a new `AWSIPPrefixes` object that contains the subset of IP prefixes that match your filter criteria. -You may pass a single value (`regions='eu-central-2'`) or a sequence of values (`regions=['eu-central-1', 'eu-central-2']`) to the filter parameters. The `filter()` method will return the prefixes that match all the provided parameters; selecting prefixes where the prefix's attributes are contained in the provided set of values. +You may pass a single value (`regions='eu-central-2'`) or a sequence of values (`regions=['eu-central-1', 'eu-central-2']`) to the filter parameters. The `filter()` method returns the prefixes that match all the provided parameters; selecting prefixes where the prefix's attributes intersect the provided set of values. For example, `filter(regions=['eu-central-1', 'eu-central-2'], services='EC2', versions=4)` will select all IP version `4` prefixes that have `EC2` in the prefix's list of services and are in the `eu-central-1` or `eu-central-2` Regions. @@ -195,9 +197,9 @@ frozenset({'AMAZON', *My router/firewall wants IP networks in a net-mask or host-mask format. Do the AWS IP prefix objects provide a way for me to get the prefix in the format I need?* -[`AWSIPv4Prefix`](./api.md#awsipv4prefix) and [`AWSIPv6Prefix`](./api.md#awsipv6prefix) objects are proxies around [`IPv4Network`](https://docs.python.org/3/library/ipaddress.html#ipaddress.IPv4Network) and [`IPv6Network`](https://docs.python.org/3/library/ipaddress.html#ipaddress.IPv6Network) objects from the Python standard library (see the [`ipaddress`](https://docs.python.org/3/library/ipaddress.html) module). They support all the attributes and methods available on the `IPv4Network` and `IPv6Network` objects. They also inherit additional attributes (like `region`, `network_border_group`, and `services`) and additional functionality from the [`AWSIPPrefix`](./api.md#awsipprefix) base class. +[`AWSIPv4Prefix`](./api.md#awsipv4prefix) and [`AWSIPv6Prefix`](./api.md#awsipv6prefix) objects are proxies around [`IPv4Network`](https://docs.python.org/3/library/ipaddress.html#ipaddress.IPv4Network) and [`IPv6Network`](https://docs.python.org/3/library/ipaddress.html#ipaddress.IPv6Network) objects from the Python standard library (see the [`ipaddress`](https://docs.python.org/3/library/ipaddress.html) module). They support all the attributes and methods available on the `IPv4Network` and `IPv6Network` objects. They also inherit additional attributes (like `region`, `network_border_group`, and `services`) and additional functionality from the [`AWSIPPrefix`](./api.md#awsipprefix) base class. -Combining the functionality provided by the standard library objects with the rich collection capabilities provided by the `awsipranges` library allows you to complete what could be a complex task easily: +Combining the functionality provided by the standard library objects with the rich collection capabilities provided by the `awsipranges` library allows you to complete complex tasks easily: Like adding routes to the `DYNAMODB` prefixes in the `eu-west-1` Region to a router: @@ -211,7 +213,7 @@ ip route 52.94.26.0 255.255.254.0 1.1.1.1 ip route 52.119.240.0 255.255.248.0 1.1.1.1 ``` -Or, configuring an access control list to allow traffic to `S3` prefixes in `eu-north-1`: +Or, configuring an access control list to allow traffic to the `S3` prefixes in `eu-north-1`: ```python >>> for prefix in aws_ip_ranges.filter(regions='eu-north-1', services='S3', versions=4):