From 437757b8af471e9e40b77ebd59ab3e4a7df0c913 Mon Sep 17 00:00:00 2001 From: Nate Williams Date: Mon, 28 Jun 2021 11:48:04 -0500 Subject: [PATCH 01/49] Update the ids and trackingURL variables to use lowercase camelcase, and point the tests to the new SimEngine API URL [skip ci] --- shipengine_sdk/jsonrpc/process_request.py | 6 ++++-- shipengine_sdk/models/carriers/__init__.py | 2 +- shipengine_sdk/models/enums/__init__.py | 2 +- shipengine_sdk/models/package/__init__.py | 10 +++++----- shipengine_sdk/services/track_package.py | 2 +- tests/models/carriers/test_carrier_account.py | 2 +- tests/models/track_package/test_package.py | 4 ++-- tests/models/track_package/test_shipment.py | 12 ++++++------ .../track_package/test_track_package_result.py | 8 ++++---- tests/util/test_helpers.py | 4 +++- 10 files changed, 28 insertions(+), 24 deletions(-) diff --git a/shipengine_sdk/jsonrpc/process_request.py b/shipengine_sdk/jsonrpc/process_request.py index a7d59df..3b3ccaf 100644 --- a/shipengine_sdk/jsonrpc/process_request.py +++ b/shipengine_sdk/jsonrpc/process_request.py @@ -23,11 +23,13 @@ def wrap_request(method: str, params: Optional[Dict[str, Any]]) -> Dict[str, Any is optional and can either be a dictionary or None. :type params: Optional[Dict[str, Any]] """ + # A base58 variant of f"req_{str(uuid4()).replace('-', '')}" here to replace + # "req_42" if params is None: - return dict(id=f"req_{str(uuid4()).replace('-', '')}", jsonrpc="2.0", method=method) + return dict(id="req_42", jsonrpc="2.0", method=method) else: return dict( - id=f"req_{str(uuid4()).replace('-', '')}", jsonrpc="2.0", method=method, params=params + id=f"req_42", jsonrpc="2.0", method=method, params=params ) diff --git a/shipengine_sdk/models/carriers/__init__.py b/shipengine_sdk/models/carriers/__init__.py index 90f958e..a7c04fd 100644 --- a/shipengine_sdk/models/carriers/__init__.py +++ b/shipengine_sdk/models/carriers/__init__.py @@ -31,7 +31,7 @@ class CarrierAccount: def __init__(self, account_information: Dict[str, Any]) -> None: """This class represents a given account with a Carrier provider e.g. `FedEx`, `UPS`, `USPS`.""" self._set_carrier(account_information["carrierCode"]) - self.account_id = account_information["accountID"] + self.account_id = account_information["accountId"] self.account_number = account_information["accountNumber"] def _set_carrier(self, carrier: str) -> None: diff --git a/shipengine_sdk/models/enums/__init__.py b/shipengine_sdk/models/enums/__init__.py index 9badc5a..a3f5d05 100644 --- a/shipengine_sdk/models/enums/__init__.py +++ b/shipengine_sdk/models/enums/__init__.py @@ -12,7 +12,7 @@ class Endpoints(Enum): """API Endpoint URI's used throughout the ShipEngine SDK.""" - TEST_RPC_URL = "https://simengine.herokuapp.com/jsonrpc" + TEST_RPC_URL = "https://shipengine-web-api.herokuapp.com/jsonrpc" SHIPENGINE_RPC_URL = "https://api.shipengine.com/jsonrpc" diff --git a/shipengine_sdk/models/package/__init__.py b/shipengine_sdk/models/package/__init__.py index 432526d..88d7830 100644 --- a/shipengine_sdk/models/package/__init__.py +++ b/shipengine_sdk/models/package/__init__.py @@ -26,8 +26,8 @@ def __init__( ) -> None: """This object represents a given Shipment.""" self.config = config - self.shipment_id = shipment["shipmentID"] if "shipmentID" in shipment else None - self.account_id = shipment["carrierAccountID"] if "carrierAccountID" in shipment else None + self.shipment_id = shipment["shipmentId"] if "shipmentId" in shipment else None + self.account_id = shipment["carrierAccountId"] if "carrierAccountId" in shipment else None if self.account_id is not None: self.carrier_account = self._get_carrier_account( @@ -59,7 +59,7 @@ def _get_carrier_account(self, carrier: str, account_id: str) -> CarrierAccount: return target_carrier[0] raise ShipEngineError( - message=f"accountID [{account_id}] doesn't match any of the accounts connected to your ShipEngine Account." # noqa + message=f"accountId [{account_id}] doesn't match any of the accounts connected to your ShipEngine Account." # noqa ) def to_dict(self) -> Dict[str, Any]: @@ -90,11 +90,11 @@ class Package: tracking_url: Optional[str] def __init__(self, package: Dict[str, Any]) -> None: - self.package_id = package["packageID"] if "packageID" in package else None + self.package_id = package["packageId"] if "packageId" in package else None self.weight = package["weight"] if "weight" in package else None self.dimensions = package["dimensions"] if "dimensions" in package else None self.tracking_number = package["trackingNumber"] if "trackingNumber" in package else None - self.tracking_url = package["trackingURL"] if "trackingURL" in package else None + self.tracking_url = package["trackingUrl"] if "trackingUrl" in package else None def to_dict(self) -> Dict[str, Any]: return (lambda o: o.__dict__)(self) diff --git a/shipengine_sdk/services/track_package.py b/shipengine_sdk/services/track_package.py index c862efd..764d6c2 100644 --- a/shipengine_sdk/services/track_package.py +++ b/shipengine_sdk/services/track_package.py @@ -14,7 +14,7 @@ def track(tracking_data: Union[str, TrackingQuery], config: ShipEngineConfig) -> api_response = rpc_request( method=RPCMethods.TRACK_PACKAGE.value, config=config, - params={"packageID": tracking_data}, + params={"packageId": tracking_data}, ) return TrackPackageResult(api_response, config) diff --git a/tests/models/carriers/test_carrier_account.py b/tests/models/carriers/test_carrier_account.py index d796aff..4fd8ab0 100644 --- a/tests/models/carriers/test_carrier_account.py +++ b/tests/models/carriers/test_carrier_account.py @@ -11,7 +11,7 @@ def stub_carrier_account_object() -> Dict[str, Any]: from the returned ShipEngine API response. """ return { - "accountID": "car_1knseddGBrseWTiw", + "accountId": "car_1knseddGBrseWTiw", "accountNumber": "1169350", "carrierCode": "royal_mail", "name": "United Parcel Service", diff --git a/tests/models/track_package/test_package.py b/tests/models/track_package/test_package.py index abe4813..b21d5db 100644 --- a/tests/models/track_package/test_package.py +++ b/tests/models/track_package/test_package.py @@ -6,9 +6,9 @@ def stub_package_data() -> Dict[str, Any]: return { - "packageID": "pkg_1FedExAccepted", + "packageId": "pkg_1FedExAccepted", "trackingNumber": "5fSkgyuh3GkfUjTZSEAQ8gHeTU29tZ", - "trackingURL": "https://www.fedex.com/track/5fSkgyuh3GkfUjTZSEAQ8gHeTU29tZ", + "trackingUrl": "https://www.fedex.com/track/5fSkgyuh3GkfUjTZSEAQ8gHeTU29tZ", "weight": {"value": 76, "unit": "kilogram"}, "dimensions": {"length": 36, "width": 36, "height": 23, "unit": "inch"}, } diff --git a/tests/models/track_package/test_shipment.py b/tests/models/track_package/test_shipment.py index 1c692b7..995a700 100644 --- a/tests/models/track_package/test_shipment.py +++ b/tests/models/track_package/test_shipment.py @@ -17,8 +17,8 @@ def stub_valid_shipment_data() -> Dict[str, Any]: """ return { "carrierCode": "fedex", - "carrierAccountID": "car_kfUjTZSEAQ8gHeT", - "shipmentID": "shp_yuh3GkfUjTZSEAQ", + "carrierAccountId": "car_kfUjTZSEAQ8gHeT", + "shipmentId": "shp_yuh3GkfUjTZSEAQ", "estimatedDelivery": "2021-06-15T21:00:00.000Z", } @@ -26,18 +26,18 @@ def stub_valid_shipment_data() -> Dict[str, Any]: def stub_invalid_shipment_data() -> Dict[str, Any]: """ Return a dictionary that mimics the Shipment data that would - be returned by ShipEngine API, where the `carrierAccountID` is invalid. + be returned by ShipEngine API, where the `carrierAccountId` is invalid. """ return { "carrierCode": "fedex", - "carrierAccountID": "car_kfUoSHIPENGINEQ8gHeT", - "shipmentID": "shp_yuh3GkfUjTZSEAQ", + "carrierAccountId": "car_kfUoSHIPENGINEQ8gHeT", + "shipmentId": "shp_yuh3GkfUjTZSEAQ", "estimatedDelivery": "2021-06-15T21:00:00.000Z", } def stub_invalid_account_id_shipment_instantiation() -> Shipment: - """Return a test Shipment object that has an invalid `carrierAccountID`..""" + """Return a test Shipment object that has an invalid `carrierAccountId`..""" return Shipment( shipment=stub_invalid_shipment_data(), actual_delivery_date=IsoString("2021-06-10T21:00:00.000"), diff --git a/tests/models/track_package/test_track_package_result.py b/tests/models/track_package/test_track_package_result.py index c0612e9..2e27234 100644 --- a/tests/models/track_package/test_track_package_result.py +++ b/tests/models/track_package/test_track_package_result.py @@ -17,14 +17,14 @@ def stub_track_package_data() -> Dict[str, Any]: "result": { "shipment": { "carrierCode": "fedex", - "carrierAccountID": "car_kfUjTZSEAQ8gHeT", - "shipmentID": "shp_tJUaQJz3Twz57iL", + "carrierAccountId": "car_kfUjTZSEAQ8gHeT", + "shipmentId": "shp_tJUaQJz3Twz57iL", "estimatedDelivery": "2021-06-15T21:00:00.000Z", }, "package": { - "packageID": "pkg_1FedexDeLiveredException", + "packageId": "pkg_1FedexDeLiveredException", "trackingNumber": "2A4g3tJUaQJz3Twz57iLWBciD7wZWH", - "trackingURL": "https://www.fedex.com/track/2A4g3tJUaQJz3Twz57iLWBciD7wZWH", + "trackingUrl": "https://www.fedex.com/track/2A4g3tJUaQJz3Twz57iLWBciD7wZWH", "weight": {"value": 76, "unit": "kilogram"}, "dimensions": {"length": 36, "width": 36, "height": 23, "unit": "inch"}, }, diff --git a/tests/util/test_helpers.py b/tests/util/test_helpers.py index 3eb7868..1e6f51e 100644 --- a/tests/util/test_helpers.py +++ b/tests/util/test_helpers.py @@ -18,7 +18,9 @@ def stub_config( when instantiating the ShipEngine object. """ return dict( - api_key="baz", + # The simengine trigger is currently set with a "_sim" suffix on the + # API-Key, but this will soon change to expect a prefix of "TEST_" + api_key="baz_sim", base_uri=Endpoints.TEST_RPC_URL.value, page_size=50, retries=retries, From 970d9df0f3c3a06fc529ca0171c218f9e1fba998 Mon Sep 17 00:00:00 2001 From: Nate Williams Date: Mon, 28 Jun 2021 12:12:59 -0500 Subject: [PATCH 02/49] Revert request id [skip ci] --- shipengine_sdk/jsonrpc/process_request.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/shipengine_sdk/jsonrpc/process_request.py b/shipengine_sdk/jsonrpc/process_request.py index 3b3ccaf..a7d59df 100644 --- a/shipengine_sdk/jsonrpc/process_request.py +++ b/shipengine_sdk/jsonrpc/process_request.py @@ -23,13 +23,11 @@ def wrap_request(method: str, params: Optional[Dict[str, Any]]) -> Dict[str, Any is optional and can either be a dictionary or None. :type params: Optional[Dict[str, Any]] """ - # A base58 variant of f"req_{str(uuid4()).replace('-', '')}" here to replace - # "req_42" if params is None: - return dict(id="req_42", jsonrpc="2.0", method=method) + return dict(id=f"req_{str(uuid4()).replace('-', '')}", jsonrpc="2.0", method=method) else: return dict( - id=f"req_42", jsonrpc="2.0", method=method, params=params + id=f"req_{str(uuid4()).replace('-', '')}", jsonrpc="2.0", method=method, params=params ) From 6505215d2adda417a0d6dc39e99914cd89cb7d65 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Mon, 28 Jun 2021 17:07:06 -0500 Subject: [PATCH 03/49] made requestId a base58 string and added a dependency fuuid --- poetry.lock | 31 +++++++++++- pyproject.toml | 1 + requirements.txt | 62 +++++++++++++++++++++-- shipengine_sdk/jsonrpc/process_request.py | 9 ++-- 4 files changed, 94 insertions(+), 9 deletions(-) diff --git a/poetry.lock b/poetry.lock index c1ff5ad..3a72cfc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -74,6 +74,17 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] pytz = ">=2015.7" +[[package]] +name = "base58" +version = "2.1.0" +description = "Base58 and Base58Check implementation." +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +tests = ["pytest (>=4.6)", "pytest-flake8", "pytest-cov", "PyHamcrest (>=2.0.2)", "coveralls", "pytest-benchmark"] + [[package]] name = "black" version = "20.8b1" @@ -233,6 +244,17 @@ mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.7.0,<2.8.0" pyflakes = ">=2.3.0,<2.4.0" +[[package]] +name = "fuuid" +version = "0.1.0" +description = "Functional UUIDs for Python." +category = "main" +optional = false +python-versions = ">=3.7,<4.0" + +[package.dependencies] +base58 = ">=2.1.0,<3.0.0" + [[package]] name = "identify" version = "2.2.6" @@ -826,7 +848,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "d97d275553d9fb816a0b9191f3edf0a090c8d271d96a62e2cdbd60302e9a76f5" +content-hash = "083c6658ca9a8b94febf3980d41ca7586e55d968ffe371e90469dc81ae199f2a" [metadata.files] aiohttp = [ @@ -892,6 +914,10 @@ babel = [ {file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"}, {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, ] +base58 = [ + {file = "base58-2.1.0-py3-none-any.whl", hash = "sha256:8225891d501b68c843ffe30b86371f844a21c6ba00da76f52f9b998ba771fb48"}, + {file = "base58-2.1.0.tar.gz", hash = "sha256:171a547b4a3c61e1ae3807224a6f7aec75e364c4395e7562649d7335768001a2"}, +] black = [ {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, ] @@ -996,6 +1022,9 @@ flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, ] +fuuid = [ + {file = "fuuid-0.1.0.tar.gz", hash = "sha256:ce8aec9ae81078941fa730dca0bf5ff7ca56675bea9327e938f1a04c8fad18b2"}, +] identify = [ {file = "identify-2.2.6-py2.py3-none-any.whl", hash = "sha256:1560bb645b93d5c05c3535c72a4f4884133006423d02c692ac6862a45eb0d521"}, {file = "identify-2.2.6.tar.gz", hash = "sha256:01ebbc7af37043806216c7550539210cde4f82451983eb8735a02b3b9d013e40"}, diff --git a/pyproject.toml b/pyproject.toml index fc3d5df..6131ba7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ aiohttp = "^3.7.4" requests = "^2.25.1" python-dotenv = "^0.15.0" dataclasses-json = "^0.5.3" +fuuid = "^0.1.0" [tool.poetry.dev-dependencies] pytest = "^4.6" diff --git a/requirements.txt b/requirements.txt index cc82309..ff688c1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,18 +1,74 @@ aiohttp==3.7.4.post0; python_version >= "3.6" +alabaster==0.7.12; python_version >= "3.5" +appdirs==1.4.4; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" async-timeout==3.0.1; python_full_version >= "3.5.3" and python_version >= "3.6" +atomicwrites==1.4.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" attrs==21.2.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -certifi==2020.12.5; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" +babel==2.9.1; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5" +base58==2.1.0; python_version >= "3.7" and python_version < "4.0" +black==20.8b1; python_version >= "3.6" +certifi==2020.12.5; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5" +cfgv==3.3.0; python_full_version >= "3.6.1" chardet==4.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" +click==8.0.1; python_version >= "3.6" +colorama==0.4.4; python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0") and platform_system == "Windows" and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0") or sys_platform == "win32" and python_version >= "3.6" and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0") and python_full_version >= "3.5.0" and platform_system == "Windows" and (python_version >= "2.7" and python_full_version < "3.0.0" or 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") +coveralls==3.1.0; python_version >= "3.5" dataclasses-json==0.5.3; python_version >= "3.6" +distlib==0.3.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" +docopt==0.6.2; python_version >= "3.5" +docutils==0.16; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5" +filelock==3.0.12; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" +flake8==3.9.2; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") +fuuid==0.1.0; python_version >= "3.7" and python_version < "4.0" +identify==2.2.6; python_full_version >= "3.6.1" idna==2.10; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" +imagesize==1.2.0; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5" +importlib-metadata==4.2.0; python_full_version >= "3.6.1" and python_version < "3.8" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.4.0" and python_version < "3.8" and python_version >= "3.6") and (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") +isort==5.8.0; python_version >= "3.6" and python_version < "4.0" +jinja2==3.0.1; python_version >= "3.6" +markupsafe==2.0.1; python_version >= "3.6" marshmallow==3.12.1; python_version >= "3.6" marshmallow-enum==1.5.1; python_version >= "3.6" +mccabe==0.6.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" +more-itertools==8.8.0; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5" multidict==5.1.0; python_version >= "3.6" mypy-extensions==0.4.3; python_version >= "3.6" +nodeenv==1.6.0; python_full_version >= "3.6.1" +packaging==20.9; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5" +pathspec==0.8.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" +pluggy==0.13.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" +pre-commit==2.13.0; python_full_version >= "3.6.1" +py==1.10.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" +pycodestyle==2.7.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" +pyflakes==2.3.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" +pygments==2.9.0; python_version >= "3.5" +pyparsing==2.4.7; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" +pytest==4.6.11; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") +pytest-cov==2.12.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") python-dotenv==0.15.0 +pytz==2021.1; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5" +pyyaml==5.4.1; python_full_version >= "3.6.1" +regex==2021.4.4; python_version >= "3.6" requests==2.25.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") +responses==0.13.3; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") +six==1.16.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" +snowballstemmer==2.1.0; python_version >= "3.5" +sphinx==3.5.4; python_version >= "3.5" +sphinxcontrib-applehelp==1.0.2; python_version >= "3.5" +sphinxcontrib-devhelp==1.0.2; python_version >= "3.5" +sphinxcontrib-htmlhelp==2.0.0; python_version >= "3.6" +sphinxcontrib-jsmath==1.0.1; python_version >= "3.5" +sphinxcontrib-qthelp==1.0.3; python_version >= "3.5" +sphinxcontrib-serializinghtml==1.1.5; python_version >= "3.5" stringcase==1.2.0; python_version >= "3.6" -typing-extensions==3.10.0.0; python_version < "3.8" and python_version >= "3.6" +toml==0.10.2; python_full_version >= "3.6.1" and python_version >= "3.6" and python_version < "4" and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version < "4") +tox==3.23.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") +typed-ast==1.4.3; python_version >= "3.6" +typing-extensions==3.10.0.0; python_version < "3.8" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.4.0" and python_version < "3.8" and python_version >= "3.6") typing-inspect==0.6.0; python_version >= "3.6" -urllib3==1.26.5; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version < "4" +urllib3==1.26.5; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version < "4" and python_version >= "3.5" +virtualenv==20.4.7; python_full_version >= "3.6.1" +wcwidth==0.2.5; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" yarl==1.6.3; python_version >= "3.6" +zipp==3.4.1; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.4.0" and python_version < "3.8" and python_version >= "3.6" diff --git a/shipengine_sdk/jsonrpc/process_request.py b/shipengine_sdk/jsonrpc/process_request.py index a7d59df..05fa692 100644 --- a/shipengine_sdk/jsonrpc/process_request.py +++ b/shipengine_sdk/jsonrpc/process_request.py @@ -1,6 +1,7 @@ """Functions that help with process requests and handle responses.""" from typing import Any, Dict, Optional -from uuid import uuid4 + +from fuuid import b58_fuuid from ..errors import ( AccountStatusError, @@ -24,11 +25,9 @@ def wrap_request(method: str, params: Optional[Dict[str, Any]]) -> Dict[str, Any :type params: Optional[Dict[str, Any]] """ if params is None: - return dict(id=f"req_{str(uuid4()).replace('-', '')}", jsonrpc="2.0", method=method) + return dict(id=f"req_{b58_fuuid()}", jsonrpc="2.0", method=method) else: - return dict( - id=f"req_{str(uuid4()).replace('-', '')}", jsonrpc="2.0", method=method, params=params - ) + return dict(id=f"req_{b58_fuuid()}", jsonrpc="2.0", method=method, params=params) def handle_response(response_body: Dict[str, Any]) -> Dict[str, Any]: From 785b4ea486c93c2a739f322fb5bf2b390ebd60c5 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Mon, 28 Jun 2021 17:18:45 -0500 Subject: [PATCH 04/49] updated lock file --- poetry.lock | 70 ++++++++++++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/poetry.lock b/poetry.lock index 3a72cfc..bd368cb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -109,7 +109,7 @@ d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] [[package]] name = "certifi" -version = "2020.12.5" +version = "2021.5.30" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false @@ -159,9 +159,6 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -[package.dependencies] -toml = {version = "*", optional = true, markers = "extra == \"toml\""} - [package.extras] toml = ["toml"] @@ -183,7 +180,7 @@ yaml = ["PyYAML (>=3.10)"] [[package]] name = "dataclasses-json" -version = "0.5.3" +version = "0.5.4" description = "Easily serialize dataclasses to and from JSON" category = "main" optional = false @@ -200,7 +197,7 @@ dev = ["pytest (>=6.2.3)", "ipython", "mypy (>=0.710)", "hypothesis", "portray", [[package]] name = "distlib" -version = "0.3.1" +version = "0.3.2" description = "Distribution utilities" category = "dev" optional = false @@ -257,7 +254,7 @@ base58 = ">=2.1.0,<3.0.0" [[package]] name = "identify" -version = "2.2.6" +version = "2.2.10" description = "File identification library for Python" category = "dev" optional = false @@ -284,7 +281,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "importlib-metadata" -version = "4.2.0" +version = "4.6.0" description = "Read metadata from Python packages" category = "dev" optional = false @@ -296,20 +293,22 @@ zipp = ">=0.5" [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +perf = ["ipython"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] [[package]] name = "isort" -version = "5.8.0" +version = "5.9.1" description = "A Python utility / library to sort Python imports." category = "dev" optional = false -python-versions = ">=3.6,<4.0" +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 = "jinja2" @@ -513,18 +512,19 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "nose", "requests", "mock"] [[package]] name = "pytest-cov" -version = "2.12.0" +version = "2.12.1" description = "Pytest plugin for measuring coverage." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} +coverage = ">=5.2.1" pytest = ">=4.6" +toml = "*" [package.extras] -testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", "virtualenv"] +testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] [[package]] name = "python-dotenv" @@ -770,7 +770,7 @@ python-versions = "*" [[package]] name = "typing-inspect" -version = "0.6.0" +version = "0.7.1" description = "Runtime inspection utilities for typing module." category = "main" optional = false @@ -782,7 +782,7 @@ typing-extensions = ">=3.7.4" [[package]] name = "urllib3" -version = "1.26.5" +version = "1.26.6" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false @@ -922,8 +922,8 @@ black = [ {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, ] certifi = [ - {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, - {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, + {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"}, + {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"}, ] cfgv = [ {file = "cfgv-3.3.0-py2.py3-none-any.whl", hash = "sha256:b449c9c6118fe8cca7fa5e00b9ec60ba08145d281d52164230a69211c5d597a1"}, @@ -1000,12 +1000,12 @@ coveralls = [ {file = "coveralls-3.1.0.tar.gz", hash = "sha256:9b3236e086627340bf2c95f89f757d093cbed43d17179d3f4fb568c347e7d29a"}, ] dataclasses-json = [ - {file = "dataclasses-json-0.5.3.tar.gz", hash = "sha256:fe17da934cfc4ec792ebe7e9a303434ecf4f5f8d8a7705acfbbe7ccbd34bf1ae"}, - {file = "dataclasses_json-0.5.3-py3-none-any.whl", hash = "sha256:740e7b564d72ddaa0f66406b4ecb799447afda2799c1c425a4a76151bfcfda50"}, + {file = "dataclasses-json-0.5.4.tar.gz", hash = "sha256:6c3976816fd3cdd8db3be2b516b64fc083acd46ac22c680d3dc24cb1d6ae3367"}, + {file = "dataclasses_json-0.5.4-py3-none-any.whl", hash = "sha256:0b25143f621d0122a2de123c156a5f6909c28d0fdd8c2e1ca2a6e4042130ad32"}, ] distlib = [ - {file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"}, - {file = "distlib-0.3.1.zip", hash = "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"}, + {file = "distlib-0.3.2-py2.py3-none-any.whl", hash = "sha256:23e223426b28491b1ced97dc3bbe183027419dfc7982b4fa2f05d5f3ff10711c"}, + {file = "distlib-0.3.2.zip", hash = "sha256:106fef6dc37dd8c0e2c0a60d3fca3e77460a48907f335fa28420463a6f799736"}, ] docopt = [ {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, @@ -1026,8 +1026,8 @@ fuuid = [ {file = "fuuid-0.1.0.tar.gz", hash = "sha256:ce8aec9ae81078941fa730dca0bf5ff7ca56675bea9327e938f1a04c8fad18b2"}, ] identify = [ - {file = "identify-2.2.6-py2.py3-none-any.whl", hash = "sha256:1560bb645b93d5c05c3535c72a4f4884133006423d02c692ac6862a45eb0d521"}, - {file = "identify-2.2.6.tar.gz", hash = "sha256:01ebbc7af37043806216c7550539210cde4f82451983eb8735a02b3b9d013e40"}, + {file = "identify-2.2.10-py2.py3-none-any.whl", hash = "sha256:18d0c531ee3dbc112fa6181f34faa179de3f57ea57ae2899754f16a7e0ff6421"}, + {file = "identify-2.2.10.tar.gz", hash = "sha256:5b41f71471bc738e7b586308c3fca172f78940195cb3bf6734c1e66fdac49306"}, ] idna = [ {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, @@ -1038,12 +1038,12 @@ imagesize = [ {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.2.0-py3-none-any.whl", hash = "sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b"}, - {file = "importlib_metadata-4.2.0.tar.gz", hash = "sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31"}, + {file = "importlib_metadata-4.6.0-py3-none-any.whl", hash = "sha256:c6513572926a96458f8c8f725bf0e00108fba0c9583ade9bd15b869c9d726e33"}, + {file = "importlib_metadata-4.6.0.tar.gz", hash = "sha256:4a5611fea3768d3d967c447ab4e93f567d95db92225b43b7b238dbfb855d70bb"}, ] isort = [ - {file = "isort-5.8.0-py3-none-any.whl", hash = "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"}, - {file = "isort-5.8.0.tar.gz", hash = "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6"}, + {file = "isort-5.9.1-py3-none-any.whl", hash = "sha256:8e2c107091cfec7286bc0f68a547d0ba4c094d460b732075b6fba674f1035c0c"}, + {file = "isort-5.9.1.tar.gz", hash = "sha256:83510593e07e433b77bd5bff0f6f607dbafa06d1a89022616f02d8b699cfcd56"}, ] jinja2 = [ {file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"}, @@ -1189,8 +1189,8 @@ pytest = [ {file = "pytest-4.6.11.tar.gz", hash = "sha256:50fa82392f2120cc3ec2ca0a75ee615be4c479e66669789771f1758332be4353"}, ] pytest-cov = [ - {file = "pytest-cov-2.12.0.tar.gz", hash = "sha256:8535764137fecce504a49c2b742288e3d34bc09eed298ad65963616cc98fd45e"}, - {file = "pytest_cov-2.12.0-py2.py3-none-any.whl", hash = "sha256:95d4933dcbbacfa377bb60b29801daa30d90c33981ab2a79e9ab4452c165066e"}, + {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, + {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, ] python-dotenv = [ {file = "python-dotenv-0.15.0.tar.gz", hash = "sha256:587825ed60b1711daea4832cf37524dfd404325b7db5e25ebe88c495c9f807a0"}, @@ -1367,13 +1367,13 @@ typing-extensions = [ {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, ] typing-inspect = [ - {file = "typing_inspect-0.6.0-py2-none-any.whl", hash = "sha256:de08f50a22955ddec353876df7b2545994d6df08a2f45d54ac8c05e530372ca0"}, - {file = "typing_inspect-0.6.0-py3-none-any.whl", hash = "sha256:3b98390df4d999a28cf5b35d8b333425af5da2ece8a4ea9e98f71e7591347b4f"}, - {file = "typing_inspect-0.6.0.tar.gz", hash = "sha256:8f1b1dd25908dbfd81d3bebc218011531e7ab614ba6e5bf7826d887c834afab7"}, + {file = "typing_inspect-0.7.1-py2-none-any.whl", hash = "sha256:b1f56c0783ef0f25fb064a01be6e5407e54cf4a4bf4f3ba3fe51e0bd6dcea9e5"}, + {file = "typing_inspect-0.7.1-py3-none-any.whl", hash = "sha256:3cd7d4563e997719a710a3bfe7ffb544c6b72069b6812a02e9b414a8fa3aaa6b"}, + {file = "typing_inspect-0.7.1.tar.gz", hash = "sha256:047d4097d9b17f46531bf6f014356111a1b6fb821a24fe7ac909853ca2a782aa"}, ] urllib3 = [ - {file = "urllib3-1.26.5-py2.py3-none-any.whl", hash = "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c"}, - {file = "urllib3-1.26.5.tar.gz", hash = "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098"}, + {file = "urllib3-1.26.6-py2.py3-none-any.whl", hash = "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4"}, + {file = "urllib3-1.26.6.tar.gz", hash = "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"}, ] virtualenv = [ {file = "virtualenv-20.4.7-py2.py3-none-any.whl", hash = "sha256:2b0126166ea7c9c3661f5b8e06773d28f83322de7a3ff7d06f0aed18c9de6a76"}, From 8f810460a39bf56a4249f3d068fad54172fd74ed Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Mon, 28 Jun 2021 17:19:42 -0500 Subject: [PATCH 05/49] updated requirements.txt --- requirements.txt | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/requirements.txt b/requirements.txt index ff688c1..fdfe2a1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,25 +7,25 @@ attrs==21.2.0; python_version >= "3.6" and python_full_version < "3.0.0" or pyth babel==2.9.1; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5" base58==2.1.0; python_version >= "3.7" and python_version < "4.0" black==20.8b1; python_version >= "3.6" -certifi==2020.12.5; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5" +certifi==2021.5.30; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5" cfgv==3.3.0; python_full_version >= "3.6.1" chardet==4.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" click==8.0.1; python_version >= "3.6" colorama==0.4.4; python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0") and platform_system == "Windows" and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0") or sys_platform == "win32" and python_version >= "3.6" and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0") and python_full_version >= "3.5.0" and platform_system == "Windows" and (python_version >= "2.7" and python_full_version < "3.0.0" or 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") coveralls==3.1.0; python_version >= "3.5" -dataclasses-json==0.5.3; python_version >= "3.6" -distlib==0.3.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" +dataclasses-json==0.5.4; python_version >= "3.6" +distlib==0.3.2; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" docopt==0.6.2; python_version >= "3.5" docutils==0.16; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5" filelock==3.0.12; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" flake8==3.9.2; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") fuuid==0.1.0; python_version >= "3.7" and python_version < "4.0" -identify==2.2.6; python_full_version >= "3.6.1" +identify==2.2.10; python_full_version >= "3.6.1" idna==2.10; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" imagesize==1.2.0; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5" -importlib-metadata==4.2.0; python_full_version >= "3.6.1" and python_version < "3.8" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.4.0" and python_version < "3.8" and python_version >= "3.6") and (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") -isort==5.8.0; python_version >= "3.6" and python_version < "4.0" +importlib-metadata==4.6.0; python_full_version >= "3.6.1" and python_version < "3.8" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.4.0" and python_version < "3.8" and python_version >= "3.6") and (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") +isort==5.9.1; python_full_version >= "3.6.1" and python_version < "4.0" jinja2==3.0.1; python_version >= "3.6" markupsafe==2.0.1; python_version >= "3.6" marshmallow==3.12.1; python_version >= "3.6" @@ -45,7 +45,7 @@ pyflakes==2.3.1; python_version >= "2.7" and python_full_version < "3.0.0" or py pygments==2.9.0; python_version >= "3.5" pyparsing==2.4.7; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" pytest==4.6.11; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") -pytest-cov==2.12.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") +pytest-cov==2.12.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") python-dotenv==0.15.0 pytz==2021.1; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5" pyyaml==5.4.1; python_full_version >= "3.6.1" @@ -62,12 +62,12 @@ sphinxcontrib-jsmath==1.0.1; python_version >= "3.5" sphinxcontrib-qthelp==1.0.3; python_version >= "3.5" sphinxcontrib-serializinghtml==1.1.5; python_version >= "3.5" stringcase==1.2.0; python_version >= "3.6" -toml==0.10.2; python_full_version >= "3.6.1" and python_version >= "3.6" and python_version < "4" and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version < "4") +toml==0.10.2; python_full_version >= "3.6.1" and python_version >= "3.6" tox==3.23.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") typed-ast==1.4.3; python_version >= "3.6" typing-extensions==3.10.0.0; python_version < "3.8" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.4.0" and python_version < "3.8" and python_version >= "3.6") -typing-inspect==0.6.0; python_version >= "3.6" -urllib3==1.26.5; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version < "4" and python_version >= "3.5" +typing-inspect==0.7.1; python_version >= "3.6" +urllib3==1.26.6; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version < "4" and python_version >= "3.5" virtualenv==20.4.7; python_full_version >= "3.6.1" wcwidth==0.2.5; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" yarl==1.6.3; python_version >= "3.6" From 0973b6fe5163bf4e48ff6ab28801799894f7f4b9 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Tue, 29 Jun 2021 15:49:56 -0500 Subject: [PATCH 06/49] work in progress - obeserver pattern --- shipengine_sdk/events/__init__.py | 13 +++++++++++++ shipengine_sdk/events/request_sent_event.py | 1 + .../events/response_received_event.py | 1 + shipengine_sdk/events/shipengine_event.py | 19 +++++++++++++++++++ shipengine_sdk/models/enums/__init__.py | 9 ++++++++- 5 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 shipengine_sdk/events/__init__.py create mode 100644 shipengine_sdk/events/request_sent_event.py create mode 100644 shipengine_sdk/events/response_received_event.py create mode 100644 shipengine_sdk/events/shipengine_event.py diff --git a/shipengine_sdk/events/__init__.py b/shipengine_sdk/events/__init__.py new file mode 100644 index 0000000..c1ae941 --- /dev/null +++ b/shipengine_sdk/events/__init__.py @@ -0,0 +1,13 @@ +""" +ShipEngine event emission via Observer Pattern. The ShipEngine SDK emits when an +HTTP request is sent and when an HTTP response is received for said request. +""" +from .shipengine_event import ShipEngineEvent + + +class Publisher(ShipEngineEvent): + pass + + +class Subscriber(ShipEngineEvent): + pass diff --git a/shipengine_sdk/events/request_sent_event.py b/shipengine_sdk/events/request_sent_event.py new file mode 100644 index 0000000..b5bcf10 --- /dev/null +++ b/shipengine_sdk/events/request_sent_event.py @@ -0,0 +1 @@ +"""Initial Docstring""" diff --git a/shipengine_sdk/events/response_received_event.py b/shipengine_sdk/events/response_received_event.py new file mode 100644 index 0000000..b5bcf10 --- /dev/null +++ b/shipengine_sdk/events/response_received_event.py @@ -0,0 +1 @@ +"""Initial Docstring""" diff --git a/shipengine_sdk/events/shipengine_event.py b/shipengine_sdk/events/shipengine_event.py new file mode 100644 index 0000000..19c9187 --- /dev/null +++ b/shipengine_sdk/events/shipengine_event.py @@ -0,0 +1,19 @@ +""" +ShipEngine event emission via Observer Pattern. The ShipEngine SDK emits when an +HTTP request is sent and when an HTTP response is received for said request. +""" +# from datetime import datetime + + +class ShipEngineEvent: + # timestamp: str + # + # def __init__(self, event_type: str, message: str) -> None: + # self.timestamp = datetime.now().isoformat() + # self.type = event_type + # self.message = message + # + # @staticmethod + # def emit_event(): + # publisher = Publisher() + pass diff --git a/shipengine_sdk/models/enums/__init__.py b/shipengine_sdk/models/enums/__init__.py index 9badc5a..e0c228e 100644 --- a/shipengine_sdk/models/enums/__init__.py +++ b/shipengine_sdk/models/enums/__init__.py @@ -12,10 +12,17 @@ class Endpoints(Enum): """API Endpoint URI's used throughout the ShipEngine SDK.""" - TEST_RPC_URL = "https://simengine.herokuapp.com/jsonrpc" + TEST_RPC_URL = "https://shipengine-web-api.herokuapp.com/jsonrpc" SHIPENGINE_RPC_URL = "https://api.shipengine.com/jsonrpc" +class Events(Enum): + """ShipEngine Events emitted by the SDK when a request is sent or when a response is received.""" + + ON_REQUEST_SENT = "on_request_sent" + ON_RESPONSE_RECEIVED = "on_response_received" + + class RPCMethods(Enum): """A collection of RPC Methods used throughout the ShipEngine SDK.""" From c4d659d8026ae73b78c26c181ab076c26e7aa0ff Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Wed, 30 Jun 2021 15:23:35 -0500 Subject: [PATCH 07/49] work in progress - Event/Observer pattern implementation --- shipengine_sdk/events/__init__.py | 43 ++++++++++- shipengine_sdk/events/request_sent_event.py | 27 ++++++- .../events/response_received_event.py | 27 +++++++ shipengine_sdk/events/shipengine_event.py | 75 ++++++++++++++++--- .../events/shipengine_event_listener.py | 15 ++++ shipengine_sdk/shipengine_config.py | 14 +++- 6 files changed, 182 insertions(+), 19 deletions(-) create mode 100644 shipengine_sdk/events/shipengine_event_listener.py diff --git a/shipengine_sdk/events/__init__.py b/shipengine_sdk/events/__init__.py index c1ae941..49636e9 100644 --- a/shipengine_sdk/events/__init__.py +++ b/shipengine_sdk/events/__init__.py @@ -2,12 +2,47 @@ ShipEngine event emission via Observer Pattern. The ShipEngine SDK emits when an HTTP request is sent and when an HTTP response is received for said request. """ +import json +from typing import Callable, List, Optional, Union + +from .request_sent_event import RequestSentEvent +from .response_received_event import ResponseReceivedEvent from .shipengine_event import ShipEngineEvent -class Publisher(ShipEngineEvent): - pass +class Publisher: + def __init__(self, events: List[str]) -> None: + self.events = {event: dict() for event in events} + + def get_subscribers(self, event: Optional[str] = None): + return self.events[event] + + def register(self, event, subscriber, callback: Optional[Callable] = None): + if callback is None: + callback = getattr(subscriber, "update") + self.get_subscribers(event)[subscriber] = callback + + def unregister(self, event, subscriber): + del self.get_subscribers(event)[subscriber] + + def dispatch(self, event, event_name: str = None): + for subscriber, callback in self.get_subscribers(event_name).items(): + callback(event) + + +class Subscriber: + def __init__(self, name=None) -> None: + if name is not None: + self.name = name + else: + self.name = "Event Subscriber" + + @staticmethod + def update(event: Union[RequestSentEvent, ResponseReceivedEvent]): + return event + def to_dict(self): + return (lambda o: o.__dict__)(self) -class Subscriber(ShipEngineEvent): - pass + def to_json(self): + return json.dumps(self, default=lambda o: o.__dict__, indent=2) diff --git a/shipengine_sdk/events/request_sent_event.py b/shipengine_sdk/events/request_sent_event.py index b5bcf10..546fb86 100644 --- a/shipengine_sdk/events/request_sent_event.py +++ b/shipengine_sdk/events/request_sent_event.py @@ -1 +1,26 @@ -"""Initial Docstring""" +"""This event gets emitted everytime the **ShipEngineClient** sends a request to ShipEngine API.""" +from typing import Any, Dict, List + +from . import ShipEngineEvent + + +class RequestSentEvent(ShipEngineEvent): + REQUEST_SENT = "request_sent" + + def __init__( + self, + request_id: str, + message: str, + url: str, + headers: List[str], + body: Dict[str, Any], + retry: int, + timeout: int, + ) -> None: + super().__init__(event_type=self.REQUEST_SENT, message=message) + self.request_id = request_id + self.url = url + self.headers = headers + self.body = body + self.retry = retry + self.timeout = timeout diff --git a/shipengine_sdk/events/response_received_event.py b/shipengine_sdk/events/response_received_event.py index b5bcf10..bacef0a 100644 --- a/shipengine_sdk/events/response_received_event.py +++ b/shipengine_sdk/events/response_received_event.py @@ -1 +1,28 @@ """Initial Docstring""" +from typing import Any, Dict, List + +from . import ShipEngineEvent + + +class ResponseReceivedEvent(ShipEngineEvent): + RESPONSE_RECEIVED = "response_received" + + def __init__( + self, + message: str, + request_id: str, + url: str, + status_code: int, + headers: List[str], + body: Dict[str, Any], + retry: int, + elapsed: str, + ) -> None: + super().__init__(event_type=self.RESPONSE_RECEIVED, message=message) + self.request_id = request_id + self.url = url + self.status_code = status_code + self.headers = headers + self.body = body + self.retry = retry + self.elapsed = elapsed diff --git a/shipengine_sdk/events/shipengine_event.py b/shipengine_sdk/events/shipengine_event.py index 19c9187..8c36b30 100644 --- a/shipengine_sdk/events/shipengine_event.py +++ b/shipengine_sdk/events/shipengine_event.py @@ -2,18 +2,69 @@ ShipEngine event emission via Observer Pattern. The ShipEngine SDK emits when an HTTP request is sent and when an HTTP response is received for said request. """ -# from datetime import datetime +import json +from datetime import datetime +from typing import Any, Dict + +from .. import ShipEngineConfig +from ..errors import ShipEngineError +from ..events import Publisher, RequestSentEvent +from ..events.response_received_event import ResponseReceivedEvent +from ..models.enums import Events class ShipEngineEvent: - # timestamp: str - # - # def __init__(self, event_type: str, message: str) -> None: - # self.timestamp = datetime.now().isoformat() - # self.type = event_type - # self.message = message - # - # @staticmethod - # def emit_event(): - # publisher = Publisher() - pass + timestamp: str + + def __init__(self, event_type: str, message: str) -> None: + self.timestamp = datetime.now().isoformat() + self.type = event_type + self.message = message + + @staticmethod + def emit_event(emitted_event_type: str, event_data: Dict[str, Any], config: ShipEngineConfig): + publisher = Publisher([Events.ON_REQUEST_SENT.value, Events.ON_RESPONSE_RECEIVED.value]) + publisher.register(event=Events.ON_REQUEST_SENT.value, subscriber=config.event_listener) + publisher.register( + event=Events.ON_RESPONSE_RECEIVED.value, subscriber=config.event_listener + ) + + if emitted_event_type == RequestSentEvent.REQUEST_SENT: + request_sent_event = RequestSentEvent( + message=event_data["message"], + request_id=event_data["request_id"], + url=event_data["base_uri"], + headers=event_data["request_headers"], + body=event_data["body"], + retry=event_data["retry"], + timeout=event_data["timeout"], + ) + + publisher.dispatch(event=request_sent_event, event_name=Events.ON_REQUEST_SENT.value) + return request_sent_event + elif emitted_event_type == ResponseReceivedEvent.RESPONSE_RECEIVED: + response_received_event = ResponseReceivedEvent( + message=event_data["message"], + request_id=event_data["request_id"], + url=event_data["base_uri"], + status_code=event_data["status_code"], + headers=event_data["request_headers"], + body=event_data["body"], + retry=event_data["retry"], + elapsed=event_data["elapsed"], + ) + + publisher.dispatch( + event=response_received_event, event_name=Events.ON_RESPONSE_RECEIVED.value + ) + return response_received_event + else: + raise ShipEngineError( + f"Event type [{emitted_event_type}] is not a valid type of event." + ) + + def to_dict(self): + return (lambda o: o.__dict__)(self) + + def to_json(self): + return json.dumps(self, default=lambda o: o.__dict__, indent=2) diff --git a/shipengine_sdk/events/shipengine_event_listener.py b/shipengine_sdk/events/shipengine_event_listener.py new file mode 100644 index 0000000..44ba48c --- /dev/null +++ b/shipengine_sdk/events/shipengine_event_listener.py @@ -0,0 +1,15 @@ +"""A default event listener to consume the events emitted by the SDK.""" +from typing import Union + +from ..events import RequestSentEvent, Subscriber +from .response_received_event import ResponseReceivedEvent + + +class ShipEngineEventListener(Subscriber): + def __init__(self, name=None) -> None: + super().__init__(name=name) + + # You can add your own event consumption logic by adding/overriding the parent `update()` method below. + @staticmethod + def update(event: Union[RequestSentEvent, ResponseReceivedEvent]): + print(event.to_dict()) diff --git a/shipengine_sdk/shipengine_config.py b/shipengine_sdk/shipengine_config.py index 5649b1b..5b51764 100644 --- a/shipengine_sdk/shipengine_config.py +++ b/shipengine_sdk/shipengine_config.py @@ -2,6 +2,7 @@ import json from typing import Any, Dict, Optional +from .events.shipengine_event_listener import ShipEngineEventListener from .models import Endpoints from .util import is_api_key_valid, is_retries_valid, is_timeout_valid @@ -48,7 +49,11 @@ def __init__(self, config: Dict[str, Any]) -> None: self.retries: int = config["retries"] else: self.retries: int = self.DEFAULT_RETRIES - # TODO: add event listener to config object once it"s implemented. + + if "event_listener" in config: + self.event_listener = config["event_listener"] + else: + self.event_listener = ShipEngineEventListener() def merge(self, new_config: Optional[Dict[str, Any]] = None): """ @@ -79,7 +84,12 @@ def merge(self, new_config: Optional[Dict[str, Any]] = None): config.update( {"timeout": new_config["timeout"]} ) if "timeout" in new_config else config.update({"timeout": self.timeout}) - # TODO: added merge rule for event_listener once it is implemented. + + config.update( + {"event_listener": new_config["event_listener"]} + ) if "event_listener" in new_config else config.update( + {"event_listener": self.event_listener} + ) return ShipEngineConfig(config) From 8874d417601e3326c402db46f497106d90c4e81c Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Wed, 30 Jun 2021 15:27:02 -0500 Subject: [PATCH 08/49] import fixes --- shipengine_sdk/events/__init__.py | 1 + shipengine_sdk/events/shipengine_event.py | 3 +-- shipengine_sdk/events/shipengine_event_listener.py | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/shipengine_sdk/events/__init__.py b/shipengine_sdk/events/__init__.py index 49636e9..7fc6dc1 100644 --- a/shipengine_sdk/events/__init__.py +++ b/shipengine_sdk/events/__init__.py @@ -8,6 +8,7 @@ from .request_sent_event import RequestSentEvent from .response_received_event import ResponseReceivedEvent from .shipengine_event import ShipEngineEvent +from .shipengine_event_listener import ShipEngineEventListener class Publisher: diff --git a/shipengine_sdk/events/shipengine_event.py b/shipengine_sdk/events/shipengine_event.py index 8c36b30..60f2d75 100644 --- a/shipengine_sdk/events/shipengine_event.py +++ b/shipengine_sdk/events/shipengine_event.py @@ -8,9 +8,8 @@ from .. import ShipEngineConfig from ..errors import ShipEngineError -from ..events import Publisher, RequestSentEvent -from ..events.response_received_event import ResponseReceivedEvent from ..models.enums import Events +from . import Publisher, RequestSentEvent, ResponseReceivedEvent class ShipEngineEvent: diff --git a/shipengine_sdk/events/shipengine_event_listener.py b/shipengine_sdk/events/shipengine_event_listener.py index 44ba48c..9a460db 100644 --- a/shipengine_sdk/events/shipengine_event_listener.py +++ b/shipengine_sdk/events/shipengine_event_listener.py @@ -1,8 +1,7 @@ """A default event listener to consume the events emitted by the SDK.""" from typing import Union -from ..events import RequestSentEvent, Subscriber -from .response_received_event import ResponseReceivedEvent +from . import RequestSentEvent, ResponseReceivedEvent, Subscriber class ShipEngineEventListener(Subscriber): From 00c9a69b3cf90e8bcd94e89d72177a262adec012 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Wed, 30 Jun 2021 16:58:24 -0500 Subject: [PATCH 09/49] Observer pattern implementation to emit events from the SDK :dart: --- shipengine_sdk/events/__init__.py | 127 +++++++++++++++++- shipengine_sdk/events/request_sent_event.py | 26 ---- .../events/response_received_event.py | 28 ---- shipengine_sdk/events/shipengine_event.py | 69 ---------- .../events/shipengine_event_listener.py | 14 -- shipengine_sdk/models/__init__.py | 1 + shipengine_sdk/models/package/__init__.py | 27 +++- shipengine_sdk/shipengine_config.py | 2 +- 8 files changed, 145 insertions(+), 149 deletions(-) delete mode 100644 shipengine_sdk/events/request_sent_event.py delete mode 100644 shipengine_sdk/events/response_received_event.py delete mode 100644 shipengine_sdk/events/shipengine_event.py delete mode 100644 shipengine_sdk/events/shipengine_event_listener.py diff --git a/shipengine_sdk/events/__init__.py b/shipengine_sdk/events/__init__.py index 7fc6dc1..e827d22 100644 --- a/shipengine_sdk/events/__init__.py +++ b/shipengine_sdk/events/__init__.py @@ -3,15 +3,85 @@ HTTP request is sent and when an HTTP response is received for said request. """ import json -from typing import Callable, List, Optional, Union +from datetime import datetime +from typing import Any, Callable, Dict, List, Optional, Union -from .request_sent_event import RequestSentEvent -from .response_received_event import ResponseReceivedEvent -from .shipengine_event import ShipEngineEvent -from .shipengine_event_listener import ShipEngineEventListener +from ..errors import ShipEngineError +from ..models.enums import Events -class Publisher: +class ShipEngineEvent: + timestamp: str + + def __init__(self, event_type: str, message: str) -> None: + self.timestamp = datetime.now().isoformat() + self.type = event_type + self.message = message + + def to_dict(self): + return (lambda o: o.__dict__)(self) + + def to_json(self): + return json.dumps(self, default=lambda o: o.__dict__, indent=2) + + @staticmethod + def new_event_message(method: str, base_uri: str, message_type: str) -> str: + """A method to dynamically create an event message based on the $messageType being passed in.""" + if message_type == "base_message": + return f"Calling the ShipEngine {method} API at {base_uri}" + elif message_type == "retry_message": + return f"Retrying the ShipEngine {method} API at {base_uri}" + else: + raise ShipEngineError(f"Message type [{message_type}] is not a valid type of message.") + + +class RequestSentEvent(ShipEngineEvent): + REQUEST_SENT = "request_sent" + + def __init__( + self, + request_id: str, + message: str, + url: str, + headers: List[str], + body: Dict[str, Any], + retry: int, + timeout: int, + ) -> None: + super().__init__(event_type=self.REQUEST_SENT, message=message) + self.request_id = request_id + self.url = url + self.headers = headers + self.body = body + self.retry = retry + self.timeout = timeout + + +class ResponseReceivedEvent(ShipEngineEvent): + RESPONSE_RECEIVED = "response_received" + + def __init__( + self, + message: str, + request_id: str, + url: str, + status_code: int, + headers: List[str], + body: Dict[str, Any], + retry: int, + elapsed: str, + ) -> None: + super().__init__(event_type=self.RESPONSE_RECEIVED, message=message) + self.request_id = request_id + self.url = url + self.status_code = status_code + self.headers = headers + self.body = body + self.retry = retry + self.elapsed = elapsed + + +class Dispatcher: def __init__(self, events: List[str]) -> None: self.events = {event: dict() for event in events} @@ -47,3 +117,48 @@ def to_dict(self): def to_json(self): return json.dumps(self, default=lambda o: o.__dict__, indent=2) + + +class ShipEngineEventListener(Subscriber): + def __init__(self, name=None) -> None: + super().__init__(name=name) + + # You can add your own event consumption logic by adding/overriding the parent `update()` method below. + @staticmethod + def update(event: Union[RequestSentEvent, ResponseReceivedEvent]): + print(event.to_dict()) + + +def emit_event(emitted_event_type: str, event_data, config): + dispatcher = Dispatcher([Events.ON_REQUEST_SENT.value, Events.ON_RESPONSE_RECEIVED.value]) + dispatcher.register(event=Events.ON_REQUEST_SENT.value, subscriber=config.event_listener) + dispatcher.register(event=Events.ON_RESPONSE_RECEIVED.value, subscriber=config.event_listener) + if emitted_event_type == RequestSentEvent.REQUEST_SENT: + request_sent_event = RequestSentEvent( + message=event_data.message, + request_id=event_data.request_id, + url=event_data.base_uri, + headers=event_data.request_headers, + body=event_data.body, + retry=event_data.retry, + timeout=event_data.timeout, + ) + dispatcher.dispatch(event=request_sent_event, event_name=Events.ON_REQUEST_SENT.value) + return request_sent_event + elif emitted_event_type == ResponseReceivedEvent.RESPONSE_RECEIVED: + response_received_event = ResponseReceivedEvent( + message=event_data.message, + request_id=event_data.request_id, + url=event_data.base_uri, + status_code=event_data.status_code, + headers=event_data.request_headers, + body=event_data.body, + retry=event_data.retry, + elapsed=event_data.elapsed, + ) + dispatcher.dispatch( + event=response_received_event, event_name=Events.ON_RESPONSE_RECEIVED.value + ) + return response_received_event + else: + raise ShipEngineError(f"Event type [{emitted_event_type}] is not a valid type of event.") diff --git a/shipengine_sdk/events/request_sent_event.py b/shipengine_sdk/events/request_sent_event.py deleted file mode 100644 index 546fb86..0000000 --- a/shipengine_sdk/events/request_sent_event.py +++ /dev/null @@ -1,26 +0,0 @@ -"""This event gets emitted everytime the **ShipEngineClient** sends a request to ShipEngine API.""" -from typing import Any, Dict, List - -from . import ShipEngineEvent - - -class RequestSentEvent(ShipEngineEvent): - REQUEST_SENT = "request_sent" - - def __init__( - self, - request_id: str, - message: str, - url: str, - headers: List[str], - body: Dict[str, Any], - retry: int, - timeout: int, - ) -> None: - super().__init__(event_type=self.REQUEST_SENT, message=message) - self.request_id = request_id - self.url = url - self.headers = headers - self.body = body - self.retry = retry - self.timeout = timeout diff --git a/shipengine_sdk/events/response_received_event.py b/shipengine_sdk/events/response_received_event.py deleted file mode 100644 index bacef0a..0000000 --- a/shipengine_sdk/events/response_received_event.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Initial Docstring""" -from typing import Any, Dict, List - -from . import ShipEngineEvent - - -class ResponseReceivedEvent(ShipEngineEvent): - RESPONSE_RECEIVED = "response_received" - - def __init__( - self, - message: str, - request_id: str, - url: str, - status_code: int, - headers: List[str], - body: Dict[str, Any], - retry: int, - elapsed: str, - ) -> None: - super().__init__(event_type=self.RESPONSE_RECEIVED, message=message) - self.request_id = request_id - self.url = url - self.status_code = status_code - self.headers = headers - self.body = body - self.retry = retry - self.elapsed = elapsed diff --git a/shipengine_sdk/events/shipengine_event.py b/shipengine_sdk/events/shipengine_event.py deleted file mode 100644 index 60f2d75..0000000 --- a/shipengine_sdk/events/shipengine_event.py +++ /dev/null @@ -1,69 +0,0 @@ -""" -ShipEngine event emission via Observer Pattern. The ShipEngine SDK emits when an -HTTP request is sent and when an HTTP response is received for said request. -""" -import json -from datetime import datetime -from typing import Any, Dict - -from .. import ShipEngineConfig -from ..errors import ShipEngineError -from ..models.enums import Events -from . import Publisher, RequestSentEvent, ResponseReceivedEvent - - -class ShipEngineEvent: - timestamp: str - - def __init__(self, event_type: str, message: str) -> None: - self.timestamp = datetime.now().isoformat() - self.type = event_type - self.message = message - - @staticmethod - def emit_event(emitted_event_type: str, event_data: Dict[str, Any], config: ShipEngineConfig): - publisher = Publisher([Events.ON_REQUEST_SENT.value, Events.ON_RESPONSE_RECEIVED.value]) - publisher.register(event=Events.ON_REQUEST_SENT.value, subscriber=config.event_listener) - publisher.register( - event=Events.ON_RESPONSE_RECEIVED.value, subscriber=config.event_listener - ) - - if emitted_event_type == RequestSentEvent.REQUEST_SENT: - request_sent_event = RequestSentEvent( - message=event_data["message"], - request_id=event_data["request_id"], - url=event_data["base_uri"], - headers=event_data["request_headers"], - body=event_data["body"], - retry=event_data["retry"], - timeout=event_data["timeout"], - ) - - publisher.dispatch(event=request_sent_event, event_name=Events.ON_REQUEST_SENT.value) - return request_sent_event - elif emitted_event_type == ResponseReceivedEvent.RESPONSE_RECEIVED: - response_received_event = ResponseReceivedEvent( - message=event_data["message"], - request_id=event_data["request_id"], - url=event_data["base_uri"], - status_code=event_data["status_code"], - headers=event_data["request_headers"], - body=event_data["body"], - retry=event_data["retry"], - elapsed=event_data["elapsed"], - ) - - publisher.dispatch( - event=response_received_event, event_name=Events.ON_RESPONSE_RECEIVED.value - ) - return response_received_event - else: - raise ShipEngineError( - f"Event type [{emitted_event_type}] is not a valid type of event." - ) - - def to_dict(self): - return (lambda o: o.__dict__)(self) - - def to_json(self): - return json.dumps(self, default=lambda o: o.__dict__, indent=2) diff --git a/shipengine_sdk/events/shipengine_event_listener.py b/shipengine_sdk/events/shipengine_event_listener.py deleted file mode 100644 index 9a460db..0000000 --- a/shipengine_sdk/events/shipengine_event_listener.py +++ /dev/null @@ -1,14 +0,0 @@ -"""A default event listener to consume the events emitted by the SDK.""" -from typing import Union - -from . import RequestSentEvent, ResponseReceivedEvent, Subscriber - - -class ShipEngineEventListener(Subscriber): - def __init__(self, name=None) -> None: - super().__init__(name=name) - - # You can add your own event consumption logic by adding/overriding the parent `update()` method below. - @staticmethod - def update(event: Union[RequestSentEvent, ResponseReceivedEvent]): - print(event.to_dict()) diff --git a/shipengine_sdk/models/__init__.py b/shipengine_sdk/models/__init__.py index a1ebfd0..45ef805 100644 --- a/shipengine_sdk/models/__init__.py +++ b/shipengine_sdk/models/__init__.py @@ -15,6 +15,7 @@ get_carrier_name_value, ) from .package import ( + EventOptions, Location, Package, Shipment, diff --git a/shipengine_sdk/models/package/__init__.py b/shipengine_sdk/models/package/__init__.py index 432526d..fe0f2bf 100644 --- a/shipengine_sdk/models/package/__init__.py +++ b/shipengine_sdk/models/package/__init__.py @@ -26,8 +26,8 @@ def __init__( ) -> None: """This object represents a given Shipment.""" self.config = config - self.shipment_id = shipment["shipmentID"] if "shipmentID" in shipment else None - self.account_id = shipment["carrierAccountID"] if "carrierAccountID" in shipment else None + self.shipment_id = shipment["shipmentId"] if "shipmentId" in shipment else None + self.account_id = shipment["carrierAccountId"] if "carrierAccountId" in shipment else None if self.account_id is not None: self.carrier_account = self._get_carrier_account( @@ -59,7 +59,7 @@ def _get_carrier_account(self, carrier: str, account_id: str) -> CarrierAccount: return target_carrier[0] raise ShipEngineError( - message=f"accountID [{account_id}] doesn't match any of the accounts connected to your ShipEngine Account." # noqa + message=f"accountId [{account_id}] doesn't match any of the accounts connected to your ShipEngine Account." # noqa ) def to_dict(self) -> Dict[str, Any]: @@ -90,11 +90,11 @@ class Package: tracking_url: Optional[str] def __init__(self, package: Dict[str, Any]) -> None: - self.package_id = package["packageID"] if "packageID" in package else None + self.package_id = package["packageId"] if "packageId" in package else None self.weight = package["weight"] if "weight" in package else None self.dimensions = package["dimensions"] if "dimensions" in package else None self.tracking_number = package["trackingNumber"] if "trackingNumber" in package else None - self.tracking_url = package["trackingURL"] if "trackingURL" in package else None + self.tracking_url = package["trackingUrl"] if "trackingUrl" in package else None def to_dict(self) -> Dict[str, Any]: return (lambda o: o.__dict__)(self) @@ -239,3 +239,20 @@ def to_json(self): def __repr__(self): return f"TrackPackageResult({self.shipment}, {self.package}, {self.events})" + + +@dataclass_json(letter_case=LetterCase.CAMEL) +@dataclass +class EventOptions: + """To be used as the main argument in the **emitEvent()** function.""" + + message: Optional[str] + id: Optional[str] + base_uri: Optional[str] + body: Optional[Dict[str, Any]] + status_code: Optional[int] + retry: Optional[int] + request_headers: Optional[List[str]] = None + response_headers: Optional[List[str]] = None + timeout: Optional[int] = None + elapsed: Optional[str] = None diff --git a/shipengine_sdk/shipengine_config.py b/shipengine_sdk/shipengine_config.py index 5b51764..596bbc1 100644 --- a/shipengine_sdk/shipengine_config.py +++ b/shipengine_sdk/shipengine_config.py @@ -2,7 +2,7 @@ import json from typing import Any, Dict, Optional -from .events.shipengine_event_listener import ShipEngineEventListener +from .events import ShipEngineEventListener from .models import Endpoints from .util import is_api_key_valid, is_retries_valid, is_timeout_valid From a283b1b27d6dfb10bcdb757ea5851fcdc7c1a0bf Mon Sep 17 00:00:00 2001 From: Nate Williams Date: Wed, 30 Jun 2021 17:54:21 -0500 Subject: [PATCH 10/49] Handle country code non-US casing ON -> On and postal code formatting 'M6 K 3 C3' -> 'M6K 3C3' --- shipengine_sdk/models/enums/__init__.py | 3 ++- tests/util/test_helpers.py | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/shipengine_sdk/models/enums/__init__.py b/shipengine_sdk/models/enums/__init__.py index a3f5d05..0846953 100644 --- a/shipengine_sdk/models/enums/__init__.py +++ b/shipengine_sdk/models/enums/__init__.py @@ -12,7 +12,8 @@ class Endpoints(Enum): """API Endpoint URI's used throughout the ShipEngine SDK.""" - TEST_RPC_URL = "https://shipengine-web-api.herokuapp.com/jsonrpc" + # TEST_RPC_URL = "https://shipengine-web-api.herokuapp.com/jsonrpc" + TEST_RPC_URL = "http://localhost:4000/jsonrpc" SHIPENGINE_RPC_URL = "https://api.shipengine.com/jsonrpc" diff --git a/tests/util/test_helpers.py b/tests/util/test_helpers.py index 1e6f51e..b7c3735 100644 --- a/tests/util/test_helpers.py +++ b/tests/util/test_helpers.py @@ -90,7 +90,7 @@ def address_with_warnings() -> Address: return Address( street=["170 Warning Blvd", "Apartment 32-B"], city_locality="Toronto", - state_province="ON", + state_province="On", postal_code="M6K 3C3", country_code="CA", ) @@ -123,7 +123,7 @@ def valid_canadian_address() -> Address: return Address( street=["170 Princes Blvd", "Ste 200"], city_locality="Toronto", - state_province="ON", + state_province="On", postal_code="M6K 3C3", country_code="CA", ) @@ -181,7 +181,7 @@ def unknown_address() -> Address: return Address( street=["4 Unknown St"], city_locality="Toronto", - state_province="ON", + state_province="On", postal_code="M6K 3C3", country_code="CA", ) @@ -357,7 +357,7 @@ def canada_valid_avs_assertions( assert address is not None assert address.city_locality == original_address.city_locality assert address.state_province == original_address.state_province.title() - assert address.postal_code == "M6 K 3 C3" + assert address.postal_code == "M6K 3C3" assert address.country_code == original_address.country_code.upper() assert address.is_residential is expected_residential_indicator @@ -391,6 +391,6 @@ def canada_valid_normalize_assertions( assert type(normalized_address) is Address assert normalized_address.city_locality == original_address.city_locality assert normalized_address.state_province == original_address.state_province.title() - assert normalized_address.postal_code == "M6 K 3 C3" + assert normalized_address.postal_code == "M6K 3C3" assert normalized_address.country_code == original_address.country_code.upper() assert normalized_address.is_residential is expected_residential_indicator From 5b4ac7c9c47ee46db84b69474ca57bfd48f84683 Mon Sep 17 00:00:00 2001 From: Nate Williams Date: Wed, 30 Jun 2021 18:13:25 -0500 Subject: [PATCH 11/49] Update server error message for tests --- shipengine_sdk/services/address_validation.py | 2 +- tests/services/test_track_package.py | 2 +- tests/util/test_helpers.py | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/shipengine_sdk/services/address_validation.py b/shipengine_sdk/services/address_validation.py index f38318d..5febb0a 100644 --- a/shipengine_sdk/services/address_validation.py +++ b/shipengine_sdk/services/address_validation.py @@ -27,7 +27,7 @@ def validate(address: Address, config: ShipEngineConfig) -> AddressValidateResul is_valid=result["isValid"], request_id=api_response["id"], normalized_address=Address.from_dict(result["normalizedAddress"]) - if "normalizedAddress" in result + if "normalizedAddress" in result and result["normalizedAddress"] != None else None, messages=result["messages"], ) diff --git a/tests/services/test_track_package.py b/tests/services/test_track_package.py index d4e3a59..ea5938d 100644 --- a/tests/services/test_track_package.py +++ b/tests/services/test_track_package.py @@ -321,4 +321,4 @@ def test_server_side_error(self) -> None: assert err.source == ErrorSource.SHIPENGINE.value assert err.error_type == ErrorType.SYSTEM.value assert err.error_code == ErrorCode.UNSPECIFIED.value - assert err.message == "Unable to connect to the database" + assert err.message == "Unable to process this request. A downstream API error occurred." diff --git a/tests/util/test_helpers.py b/tests/util/test_helpers.py index b7c3735..9dbf8e2 100644 --- a/tests/util/test_helpers.py +++ b/tests/util/test_helpers.py @@ -18,8 +18,6 @@ def stub_config( when instantiating the ShipEngine object. """ return dict( - # The simengine trigger is currently set with a "_sim" suffix on the - # API-Key, but this will soon change to expect a prefix of "TEST_" api_key="baz_sim", base_uri=Endpoints.TEST_RPC_URL.value, page_size=50, From 8439c4805afbdac74dd1667df468d8d0d8b0a941 Mon Sep 17 00:00:00 2001 From: Nate Williams Date: Wed, 30 Jun 2021 18:37:24 -0500 Subject: [PATCH 12/49] Handle NoneType track package data fields --- shipengine_sdk/models/package/__init__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/shipengine_sdk/models/package/__init__.py b/shipengine_sdk/models/package/__init__.py index 88d7830..f1c3149 100644 --- a/shipengine_sdk/models/package/__init__.py +++ b/shipengine_sdk/models/package/__init__.py @@ -125,15 +125,15 @@ class Location: def __init__(self, location_data: Dict[str, Any]) -> None: self.city_locality = ( - location_data["cityLocality"] if "cityLocality" in location_data else None + location_data["cityLocality"] if "cityLocality" in location_data and location_data != None else None ) self.state_province = ( - location_data["stateProvince"] if "stateProvince" in location_data else None + location_data["stateProvince"] if "stateProvince" in location_data and location_data != None else None ) - self.postal_code = location_data["postalCode"] if "postalCode" in location_data else None - self.country_code = location_data["countryCode"] if "countryCode" in location_data else None + self.postal_code = location_data["postalCode"] if "postalCode" in location_data and location_data != None else None + self.country_code = location_data["countryCode"] if "countryCode" in location_data and location_data != None else None - if "coordinates" in location_data: + if "coordinates" in location_data and location_data != None and location_data["coordinates"] != None: self.latitude = location_data["coordinates"]["latitude"] self.longitude = location_data["coordinates"]["longitude"] @@ -169,7 +169,7 @@ def __init__(self, event: Dict[str, Any]) -> None: event["carrierStatusCode"] if "carrierStatusCode" in event else None ) self.signer = event["signer"] if "signer" in event else None - self.location = Location(event["location"]) if "location" in event else None + self.location = Location(event["location"]) if "location" in event and event["location"] != None else None def to_dict(self): return (lambda o: o.__dict__)(self) From a83214bc6440467073c818bdda1c55da1a127ebd Mon Sep 17 00:00:00 2001 From: Nate Williams Date: Wed, 30 Jun 2021 19:11:22 -0500 Subject: [PATCH 13/49] Reset to the correct test url --- shipengine_sdk/models/enums/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shipengine_sdk/models/enums/__init__.py b/shipengine_sdk/models/enums/__init__.py index 0846953..a3f5d05 100644 --- a/shipengine_sdk/models/enums/__init__.py +++ b/shipengine_sdk/models/enums/__init__.py @@ -12,8 +12,7 @@ class Endpoints(Enum): """API Endpoint URI's used throughout the ShipEngine SDK.""" - # TEST_RPC_URL = "https://shipengine-web-api.herokuapp.com/jsonrpc" - TEST_RPC_URL = "http://localhost:4000/jsonrpc" + TEST_RPC_URL = "https://shipengine-web-api.herokuapp.com/jsonrpc" SHIPENGINE_RPC_URL = "https://api.shipengine.com/jsonrpc" From bcc739ff9e95aa2a4f7978b9b04acc0e0516f309 Mon Sep 17 00:00:00 2001 From: Nate Williams Date: Thu, 1 Jul 2021 11:35:03 -0500 Subject: [PATCH 14/49] Add missing microseconds for date strings that do not have it on the response --- shipengine_sdk/util/iso_string.py | 15 +++++++++++++-- tests/services/test_track_package.py | 1 + 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/shipengine_sdk/util/iso_string.py b/shipengine_sdk/util/iso_string.py index 3d660a4..882ce78 100644 --- a/shipengine_sdk/util/iso_string.py +++ b/shipengine_sdk/util/iso_string.py @@ -25,10 +25,11 @@ def to_string(self) -> str: return self.iso_string def to_datetime_object(self) -> datetime: + iso_string = self.maybe_add_microseconds(self.iso_string) if self.has_timezone(): - return datetime.strptime(self.iso_string, "%Y-%m-%dT%H:%M:%S.%fZ") + return datetime.strptime(iso_string, "%Y-%m-%dT%H:%M:%S.%fZ") elif self.is_valid_iso_string_no_tz(self.iso_string): - return datetime.fromisoformat(self.iso_string) + return datetime.fromisoformat(iso_string) def has_timezone(self) -> bool: if self.is_valid_iso_string(self.iso_string): @@ -49,3 +50,13 @@ def is_valid_iso_string(iso_str: str): return True else: return False + + @staticmethod + def maybe_add_microseconds(iso_str: str): + if '.' not in iso_str: + if 'Z' not in iso_str: + return iso_str + '.0' + else: + return iso_str[:-1] + '.0Z' + else: + return iso_str diff --git a/tests/services/test_track_package.py b/tests/services/test_track_package.py index ea5938d..6abb777 100644 --- a/tests/services/test_track_package.py +++ b/tests/services/test_track_package.py @@ -83,6 +83,7 @@ def test_track_by_tracking_number_and_carrier_code(self) -> None: tracking_data = TrackingQuery(carrier_code="fedex", tracking_number="abcFedExDelivered") tracking_result = shipengine.track_package(tracking_data=tracking_data) + # Update this/model def for carrier.code -> carrier_code? assert tracking_data.carrier_code == tracking_result.shipment.carrier.code assert tracking_data.tracking_number == tracking_result.package.tracking_number assert tracking_result.package.tracking_url is not None From 43a11ca55abbeb467dc7a66fcbf8ff8e75760995 Mon Sep 17 00:00:00 2001 From: Nate Williams Date: Thu, 1 Jul 2021 12:43:43 -0500 Subject: [PATCH 15/49] Format multiline None checks --- shipengine_sdk/models/package/__init__.py | 24 +++++++++++++++-------- shipengine_sdk/util/iso_string.py | 16 +++++++-------- tests/services/test_track_package.py | 5 ++--- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/shipengine_sdk/models/package/__init__.py b/shipengine_sdk/models/package/__init__.py index f1c3149..dbdeee0 100644 --- a/shipengine_sdk/models/package/__init__.py +++ b/shipengine_sdk/models/package/__init__.py @@ -26,7 +26,8 @@ def __init__( ) -> None: """This object represents a given Shipment.""" self.config = config - self.shipment_id = shipment["shipmentId"] if "shipmentId" in shipment else None + self.shipment_id = shipment["shipmentId"] if "shipmentId" in shipment \ + else None self.account_id = shipment["carrierAccountId"] if "carrierAccountId" in shipment else None if self.account_id is not None: @@ -125,15 +126,20 @@ class Location: def __init__(self, location_data: Dict[str, Any]) -> None: self.city_locality = ( - location_data["cityLocality"] if "cityLocality" in location_data and location_data != None else None + location_data["cityLocality"] if "cityLocality" in location_data \ + and location_data != None else None ) self.state_province = ( - location_data["stateProvince"] if "stateProvince" in location_data and location_data != None else None + location_data["stateProvince"] if "stateProvince" in location_data \ + and location_data != None else None ) - self.postal_code = location_data["postalCode"] if "postalCode" in location_data and location_data != None else None - self.country_code = location_data["countryCode"] if "countryCode" in location_data and location_data != None else None + self.postal_code = location_data["postalCode"] if "postalCode" in \ + location_data and location_data != None else None + self.country_code = location_data["countryCode"] if "countryCode" in \ + location_data and location_data != None else None - if "coordinates" in location_data and location_data != None and location_data["coordinates"] != None: + if "coordinates" in location_data and location_data is not None and \ + location_data["coordinates"] != None: self.latitude = location_data["coordinates"]["latitude"] self.longitude = location_data["coordinates"]["longitude"] @@ -164,7 +170,8 @@ def __init__(self, event: Dict[str, Any]) -> None: self.carrier_date_time = IsoString(iso_string=event["carrierTimestamp"]) self.status = event["status"] - self.description = event["description"] if "description" in event else None + self.description = event["description"] if "description" in event else \ + None self.carrier_status_code = ( event["carrierStatusCode"] if "carrierStatusCode" in event else None ) @@ -202,7 +209,8 @@ def __init__(self, api_response: Dict[str, Any], config: ShipEngineConfig) -> No if "shipment" in result else None ) - self.package = Package(result["package"]) if "package" in result else None + self.package = Package(result["package"]) if "package" in result else \ + None def get_errors(self) -> List[TrackingEvent]: """Returns **only** the exception events.""" diff --git a/shipengine_sdk/util/iso_string.py b/shipengine_sdk/util/iso_string.py index 882ce78..fc8833c 100644 --- a/shipengine_sdk/util/iso_string.py +++ b/shipengine_sdk/util/iso_string.py @@ -25,34 +25,34 @@ def to_string(self) -> str: return self.iso_string def to_datetime_object(self) -> datetime: - iso_string = self.maybe_add_microseconds(self.iso_string) + iso_string = self._maybe_add_microseconds(self.iso_string) if self.has_timezone(): return datetime.strptime(iso_string, "%Y-%m-%dT%H:%M:%S.%fZ") - elif self.is_valid_iso_string_no_tz(self.iso_string): + elif self._is_valid_iso_string_no_tz(self.iso_string): return datetime.fromisoformat(iso_string) def has_timezone(self) -> bool: if self.is_valid_iso_string(self.iso_string): - return False if self.is_valid_iso_string_no_tz(self.iso_string) else True + return False if self._is_valid_iso_string_no_tz(self.iso_string) else True @staticmethod - def is_valid_iso_string_no_tz(iso_str: str): - pattern = re.compile(RegexPatterns.VALID_ISO_STRING_NO_TZ.value) + def is_valid_iso_string(iso_str: str): + pattern = re.compile(RegexPatterns.VALID_ISO_STRING.value) if pattern.match(iso_str): return True else: return False @staticmethod - def is_valid_iso_string(iso_str: str): - pattern = re.compile(RegexPatterns.VALID_ISO_STRING.value) + def _is_valid_iso_string_no_tz(iso_str: str): + pattern = re.compile(RegexPatterns.VALID_ISO_STRING_NO_TZ.value) if pattern.match(iso_str): return True else: return False @staticmethod - def maybe_add_microseconds(iso_str: str): + def _maybe_add_microseconds(iso_str: str): if '.' not in iso_str: if 'Z' not in iso_str: return iso_str + '.0' diff --git a/tests/services/test_track_package.py b/tests/services/test_track_package.py index 6abb777..58014d8 100644 --- a/tests/services/test_track_package.py +++ b/tests/services/test_track_package.py @@ -83,8 +83,7 @@ def test_track_by_tracking_number_and_carrier_code(self) -> None: tracking_data = TrackingQuery(carrier_code="fedex", tracking_number="abcFedExDelivered") tracking_result = shipengine.track_package(tracking_data=tracking_data) - # Update this/model def for carrier.code -> carrier_code? - assert tracking_data.carrier_code == tracking_result.shipment.carrier.code + assert tracking_data.carrier_code == tracking_result.shipment.carrier["code"] assert tracking_data.tracking_number == tracking_result.package.tracking_number assert tracking_result.package.tracking_url is not None assert type(tracking_result.package.tracking_url) is str @@ -129,7 +128,7 @@ def test_multiple_delivery_attempts(self) -> None: tracking_result = shipengine.track_package(tracking_data=package_id) track_package_assertions(tracking_result=tracking_result) - assert len(tracking_result.events) == 9 + assert len(tracking_result.events) == 5 assert_events_in_order(tracking_result.events) assert tracking_result.events[0].status == "accepted" assert tracking_result.events[1].status == "in_transit" From 5bc5af08bb24e8bb648fc3b53f014d0b22a441dd Mon Sep 17 00:00:00 2001 From: Nate Williams Date: Thu, 1 Jul 2021 13:15:30 -0500 Subject: [PATCH 16/49] Update track package result assertions for elixir SimEngine API endpoint --- tests/services/test_track_package.py | 35 +++++++++++++--------------- tests/util/test_iso_string.py | 3 --- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/tests/services/test_track_package.py b/tests/services/test_track_package.py index 58014d8..c783271 100644 --- a/tests/services/test_track_package.py +++ b/tests/services/test_track_package.py @@ -23,11 +23,11 @@ def assertions_on_delivered_after_exception_or_multiple_attempts( assert len(tracking_result.events) == 8 assert tracking_result.events[0].status == "accepted" assert tracking_result.events[1].status == "in_transit" - assert tracking_result.events[2].status == "unknown" - assert tracking_result.events[3].status == "in_transit" + assert tracking_result.events[2].status == "in_transit" + assert tracking_result.events[3].status == "exception" assert tracking_result.events[4].status == "exception" - assert tracking_result.events[5].status == "exception" - assert tracking_result.events[6].status == "in_transit" + assert tracking_result.events[5].status == "attempted_delivery" + assert tracking_result.events[6].status == "attempted_delivery" assert tracking_result.events[7].status == "delivered" assert tracking_result.events[-1].status == "delivered" @@ -85,8 +85,8 @@ def test_track_by_tracking_number_and_carrier_code(self) -> None: assert tracking_data.carrier_code == tracking_result.shipment.carrier["code"] assert tracking_data.tracking_number == tracking_result.package.tracking_number - assert tracking_result.package.tracking_url is not None - assert type(tracking_result.package.tracking_url) is str + # assert tracking_result.package.tracking_url is not None + # assert type(tracking_result.package.tracking_url) is str def test_track_by_package_id(self) -> None: """DX-1086 - Test track by package ID.""" @@ -96,7 +96,7 @@ def test_track_by_package_id(self) -> None: assert tracking_result.package.package_id == package_id assert tracking_result.package.tracking_number is not None - assert tracking_result.package.tracking_url is not None + # assert tracking_result.package.tracking_url is not None assert tracking_result.shipment.shipment_id is not None assert tracking_result.shipment.account_id is not None @@ -132,13 +132,9 @@ def test_multiple_delivery_attempts(self) -> None: assert_events_in_order(tracking_result.events) assert tracking_result.events[0].status == "accepted" assert tracking_result.events[1].status == "in_transit" - assert tracking_result.events[2].status == "unknown" - assert tracking_result.events[3].status == "in_transit" - assert tracking_result.events[4].status == "attempted_delivery" - assert tracking_result.events[5].status == "in_transit" - assert tracking_result.events[6].status == "attempted_delivery" - assert tracking_result.events[7].status == "in_transit" - assert tracking_result.events[8].status == "delivered" + assert tracking_result.events[2].status == "attempted_delivery" + assert tracking_result.events[3].status == "attempted_delivery" + assert tracking_result.events[4].status == "delivered" def test_delivered_on_first_try(self) -> None: """DX-1091 - Test delivered on first try tracking event.""" @@ -171,7 +167,6 @@ def test_delivered_with_signature(self) -> None: assert len(tracking_result.events) == 5 assert tracking_result.events[0].status == "accepted" assert tracking_result.events[1].status == "in_transit" - assert tracking_result.events[2].status == "unknown" assert tracking_result.events[3].status == "in_transit" assert tracking_result.events[4].status == "delivered" assert tracking_result.events[-1].status == "delivered" @@ -216,7 +211,7 @@ def test_track_with_multiple_exceptions(self) -> None: assert len(tracking_result.events) == 8 assert tracking_result.events[0].status == "accepted" assert tracking_result.events[4].status == "exception" - assert tracking_result.events[5].status == "exception" + assert tracking_result.events[5].status == "attempted_delivery" assert tracking_result.events[7].status == "delivered" assert tracking_result.events[-1].status == "delivered" @@ -229,11 +224,13 @@ def test_multiple_locations_in_tracking_event(self) -> None: track_package_assertions(tracking_result=tracking_result) assert_events_in_order(tracking_result.events) assert tracking_result.events[0].location is None - assert tracking_result.events[1].location is None + print(tracking_result.events[1].location) + assert tracking_result.events[1].location.latitude is None + assert tracking_result.events[1].location.longitude is None assert type(tracking_result.events[2].location.latitude) is float assert type(tracking_result.events[2].location.longitude) is float - assert tracking_result.events[4].location.latitude is None - assert tracking_result.events[4].location.longitude is None + assert type(tracking_result.events[4].location.latitude) is float + assert type(tracking_result.events[4].location.longitude) is float def test_carrier_date_time_without_timezone(self) -> None: """DX-1098 - Test track package where carrierDateTime has no timezone.""" diff --git a/tests/util/test_iso_string.py b/tests/util/test_iso_string.py index 63a723f..1ec43e7 100644 --- a/tests/util/test_iso_string.py +++ b/tests/util/test_iso_string.py @@ -17,9 +17,6 @@ def test_to_datetime_object(self) -> None: assert type(iso_str) is datetime.datetime - def test_static_no_tz_check(self) -> None: - assert IsoString.is_valid_iso_string_no_tz(self._test_iso_string_no_tz) is True - def test_static_valid_iso_check(self) -> None: assert IsoString.is_valid_iso_string(self._test_iso_string_no_tz) is True From 708995959ac97e84108f8ce0c3b68d6dd7413a12 Mon Sep 17 00:00:00 2001 From: Nate Williams Date: Thu, 1 Jul 2021 13:27:33 -0500 Subject: [PATCH 17/49] Revert to main branch lock/requirements files --- poetry.lock | 101 +++++++++++++++++------------------------------ requirements.txt | 66 +++---------------------------- 2 files changed, 41 insertions(+), 126 deletions(-) diff --git a/poetry.lock b/poetry.lock index bd368cb..c1ff5ad 100644 --- a/poetry.lock +++ b/poetry.lock @@ -74,17 +74,6 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] pytz = ">=2015.7" -[[package]] -name = "base58" -version = "2.1.0" -description = "Base58 and Base58Check implementation." -category = "main" -optional = false -python-versions = ">=3.5" - -[package.extras] -tests = ["pytest (>=4.6)", "pytest-flake8", "pytest-cov", "PyHamcrest (>=2.0.2)", "coveralls", "pytest-benchmark"] - [[package]] name = "black" version = "20.8b1" @@ -109,7 +98,7 @@ d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] [[package]] name = "certifi" -version = "2021.5.30" +version = "2020.12.5" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false @@ -159,6 +148,9 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +[package.dependencies] +toml = {version = "*", optional = true, markers = "extra == \"toml\""} + [package.extras] toml = ["toml"] @@ -180,7 +172,7 @@ yaml = ["PyYAML (>=3.10)"] [[package]] name = "dataclasses-json" -version = "0.5.4" +version = "0.5.3" description = "Easily serialize dataclasses to and from JSON" category = "main" optional = false @@ -197,7 +189,7 @@ dev = ["pytest (>=6.2.3)", "ipython", "mypy (>=0.710)", "hypothesis", "portray", [[package]] name = "distlib" -version = "0.3.2" +version = "0.3.1" description = "Distribution utilities" category = "dev" optional = false @@ -241,20 +233,9 @@ mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.7.0,<2.8.0" pyflakes = ">=2.3.0,<2.4.0" -[[package]] -name = "fuuid" -version = "0.1.0" -description = "Functional UUIDs for Python." -category = "main" -optional = false -python-versions = ">=3.7,<4.0" - -[package.dependencies] -base58 = ">=2.1.0,<3.0.0" - [[package]] name = "identify" -version = "2.2.10" +version = "2.2.6" description = "File identification library for Python" category = "dev" optional = false @@ -281,7 +262,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "importlib-metadata" -version = "4.6.0" +version = "4.2.0" description = "Read metadata from Python packages" category = "dev" optional = false @@ -293,22 +274,20 @@ zipp = ">=0.5" [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -perf = ["ipython"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] [[package]] name = "isort" -version = "5.9.1" +version = "5.8.0" description = "A Python utility / library to sort Python imports." category = "dev" optional = false -python-versions = ">=3.6.1,<4.0" +python-versions = ">=3.6,<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 = "jinja2" @@ -512,19 +491,18 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "nose", "requests", "mock"] [[package]] name = "pytest-cov" -version = "2.12.1" +version = "2.12.0" description = "Pytest plugin for measuring coverage." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] -coverage = ">=5.2.1" +coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" -toml = "*" [package.extras] -testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] +testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", "virtualenv"] [[package]] name = "python-dotenv" @@ -770,7 +748,7 @@ python-versions = "*" [[package]] name = "typing-inspect" -version = "0.7.1" +version = "0.6.0" description = "Runtime inspection utilities for typing module." category = "main" optional = false @@ -782,7 +760,7 @@ typing-extensions = ">=3.7.4" [[package]] name = "urllib3" -version = "1.26.6" +version = "1.26.5" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false @@ -848,7 +826,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "083c6658ca9a8b94febf3980d41ca7586e55d968ffe371e90469dc81ae199f2a" +content-hash = "d97d275553d9fb816a0b9191f3edf0a090c8d271d96a62e2cdbd60302e9a76f5" [metadata.files] aiohttp = [ @@ -914,16 +892,12 @@ babel = [ {file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"}, {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, ] -base58 = [ - {file = "base58-2.1.0-py3-none-any.whl", hash = "sha256:8225891d501b68c843ffe30b86371f844a21c6ba00da76f52f9b998ba771fb48"}, - {file = "base58-2.1.0.tar.gz", hash = "sha256:171a547b4a3c61e1ae3807224a6f7aec75e364c4395e7562649d7335768001a2"}, -] black = [ {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, ] certifi = [ - {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"}, - {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"}, + {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, + {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, ] cfgv = [ {file = "cfgv-3.3.0-py2.py3-none-any.whl", hash = "sha256:b449c9c6118fe8cca7fa5e00b9ec60ba08145d281d52164230a69211c5d597a1"}, @@ -1000,12 +974,12 @@ coveralls = [ {file = "coveralls-3.1.0.tar.gz", hash = "sha256:9b3236e086627340bf2c95f89f757d093cbed43d17179d3f4fb568c347e7d29a"}, ] dataclasses-json = [ - {file = "dataclasses-json-0.5.4.tar.gz", hash = "sha256:6c3976816fd3cdd8db3be2b516b64fc083acd46ac22c680d3dc24cb1d6ae3367"}, - {file = "dataclasses_json-0.5.4-py3-none-any.whl", hash = "sha256:0b25143f621d0122a2de123c156a5f6909c28d0fdd8c2e1ca2a6e4042130ad32"}, + {file = "dataclasses-json-0.5.3.tar.gz", hash = "sha256:fe17da934cfc4ec792ebe7e9a303434ecf4f5f8d8a7705acfbbe7ccbd34bf1ae"}, + {file = "dataclasses_json-0.5.3-py3-none-any.whl", hash = "sha256:740e7b564d72ddaa0f66406b4ecb799447afda2799c1c425a4a76151bfcfda50"}, ] distlib = [ - {file = "distlib-0.3.2-py2.py3-none-any.whl", hash = "sha256:23e223426b28491b1ced97dc3bbe183027419dfc7982b4fa2f05d5f3ff10711c"}, - {file = "distlib-0.3.2.zip", hash = "sha256:106fef6dc37dd8c0e2c0a60d3fca3e77460a48907f335fa28420463a6f799736"}, + {file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"}, + {file = "distlib-0.3.1.zip", hash = "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"}, ] docopt = [ {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, @@ -1022,12 +996,9 @@ flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, ] -fuuid = [ - {file = "fuuid-0.1.0.tar.gz", hash = "sha256:ce8aec9ae81078941fa730dca0bf5ff7ca56675bea9327e938f1a04c8fad18b2"}, -] identify = [ - {file = "identify-2.2.10-py2.py3-none-any.whl", hash = "sha256:18d0c531ee3dbc112fa6181f34faa179de3f57ea57ae2899754f16a7e0ff6421"}, - {file = "identify-2.2.10.tar.gz", hash = "sha256:5b41f71471bc738e7b586308c3fca172f78940195cb3bf6734c1e66fdac49306"}, + {file = "identify-2.2.6-py2.py3-none-any.whl", hash = "sha256:1560bb645b93d5c05c3535c72a4f4884133006423d02c692ac6862a45eb0d521"}, + {file = "identify-2.2.6.tar.gz", hash = "sha256:01ebbc7af37043806216c7550539210cde4f82451983eb8735a02b3b9d013e40"}, ] idna = [ {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, @@ -1038,12 +1009,12 @@ imagesize = [ {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.6.0-py3-none-any.whl", hash = "sha256:c6513572926a96458f8c8f725bf0e00108fba0c9583ade9bd15b869c9d726e33"}, - {file = "importlib_metadata-4.6.0.tar.gz", hash = "sha256:4a5611fea3768d3d967c447ab4e93f567d95db92225b43b7b238dbfb855d70bb"}, + {file = "importlib_metadata-4.2.0-py3-none-any.whl", hash = "sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b"}, + {file = "importlib_metadata-4.2.0.tar.gz", hash = "sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31"}, ] isort = [ - {file = "isort-5.9.1-py3-none-any.whl", hash = "sha256:8e2c107091cfec7286bc0f68a547d0ba4c094d460b732075b6fba674f1035c0c"}, - {file = "isort-5.9.1.tar.gz", hash = "sha256:83510593e07e433b77bd5bff0f6f607dbafa06d1a89022616f02d8b699cfcd56"}, + {file = "isort-5.8.0-py3-none-any.whl", hash = "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"}, + {file = "isort-5.8.0.tar.gz", hash = "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6"}, ] jinja2 = [ {file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"}, @@ -1189,8 +1160,8 @@ pytest = [ {file = "pytest-4.6.11.tar.gz", hash = "sha256:50fa82392f2120cc3ec2ca0a75ee615be4c479e66669789771f1758332be4353"}, ] pytest-cov = [ - {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, - {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, + {file = "pytest-cov-2.12.0.tar.gz", hash = "sha256:8535764137fecce504a49c2b742288e3d34bc09eed298ad65963616cc98fd45e"}, + {file = "pytest_cov-2.12.0-py2.py3-none-any.whl", hash = "sha256:95d4933dcbbacfa377bb60b29801daa30d90c33981ab2a79e9ab4452c165066e"}, ] python-dotenv = [ {file = "python-dotenv-0.15.0.tar.gz", hash = "sha256:587825ed60b1711daea4832cf37524dfd404325b7db5e25ebe88c495c9f807a0"}, @@ -1367,13 +1338,13 @@ typing-extensions = [ {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, ] typing-inspect = [ - {file = "typing_inspect-0.7.1-py2-none-any.whl", hash = "sha256:b1f56c0783ef0f25fb064a01be6e5407e54cf4a4bf4f3ba3fe51e0bd6dcea9e5"}, - {file = "typing_inspect-0.7.1-py3-none-any.whl", hash = "sha256:3cd7d4563e997719a710a3bfe7ffb544c6b72069b6812a02e9b414a8fa3aaa6b"}, - {file = "typing_inspect-0.7.1.tar.gz", hash = "sha256:047d4097d9b17f46531bf6f014356111a1b6fb821a24fe7ac909853ca2a782aa"}, + {file = "typing_inspect-0.6.0-py2-none-any.whl", hash = "sha256:de08f50a22955ddec353876df7b2545994d6df08a2f45d54ac8c05e530372ca0"}, + {file = "typing_inspect-0.6.0-py3-none-any.whl", hash = "sha256:3b98390df4d999a28cf5b35d8b333425af5da2ece8a4ea9e98f71e7591347b4f"}, + {file = "typing_inspect-0.6.0.tar.gz", hash = "sha256:8f1b1dd25908dbfd81d3bebc218011531e7ab614ba6e5bf7826d887c834afab7"}, ] urllib3 = [ - {file = "urllib3-1.26.6-py2.py3-none-any.whl", hash = "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4"}, - {file = "urllib3-1.26.6.tar.gz", hash = "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"}, + {file = "urllib3-1.26.5-py2.py3-none-any.whl", hash = "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c"}, + {file = "urllib3-1.26.5.tar.gz", hash = "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098"}, ] virtualenv = [ {file = "virtualenv-20.4.7-py2.py3-none-any.whl", hash = "sha256:2b0126166ea7c9c3661f5b8e06773d28f83322de7a3ff7d06f0aed18c9de6a76"}, diff --git a/requirements.txt b/requirements.txt index fdfe2a1..cc82309 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,74 +1,18 @@ aiohttp==3.7.4.post0; python_version >= "3.6" -alabaster==0.7.12; python_version >= "3.5" -appdirs==1.4.4; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" async-timeout==3.0.1; python_full_version >= "3.5.3" and python_version >= "3.6" -atomicwrites==1.4.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" attrs==21.2.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -babel==2.9.1; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5" -base58==2.1.0; python_version >= "3.7" and python_version < "4.0" -black==20.8b1; python_version >= "3.6" -certifi==2021.5.30; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5" -cfgv==3.3.0; python_full_version >= "3.6.1" +certifi==2020.12.5; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" chardet==4.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" -click==8.0.1; python_version >= "3.6" -colorama==0.4.4; python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0") and platform_system == "Windows" and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0") or sys_platform == "win32" and python_version >= "3.6" and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0") and python_full_version >= "3.5.0" and platform_system == "Windows" and (python_version >= "2.7" and python_full_version < "3.0.0" or 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") -coveralls==3.1.0; python_version >= "3.5" -dataclasses-json==0.5.4; python_version >= "3.6" -distlib==0.3.2; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" -docopt==0.6.2; python_version >= "3.5" -docutils==0.16; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5" -filelock==3.0.12; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" -flake8==3.9.2; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") -fuuid==0.1.0; python_version >= "3.7" and python_version < "4.0" -identify==2.2.10; python_full_version >= "3.6.1" +dataclasses-json==0.5.3; python_version >= "3.6" idna==2.10; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -imagesize==1.2.0; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5" -importlib-metadata==4.6.0; python_full_version >= "3.6.1" and python_version < "3.8" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.4.0" and python_version < "3.8" and python_version >= "3.6") and (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") -isort==5.9.1; python_full_version >= "3.6.1" and python_version < "4.0" -jinja2==3.0.1; python_version >= "3.6" -markupsafe==2.0.1; python_version >= "3.6" marshmallow==3.12.1; python_version >= "3.6" marshmallow-enum==1.5.1; python_version >= "3.6" -mccabe==0.6.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" -more-itertools==8.8.0; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5" multidict==5.1.0; python_version >= "3.6" mypy-extensions==0.4.3; python_version >= "3.6" -nodeenv==1.6.0; python_full_version >= "3.6.1" -packaging==20.9; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5" -pathspec==0.8.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -pluggy==0.13.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" -pre-commit==2.13.0; python_full_version >= "3.6.1" -py==1.10.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" -pycodestyle==2.7.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" -pyflakes==2.3.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" -pygments==2.9.0; python_version >= "3.5" -pyparsing==2.4.7; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" -pytest==4.6.11; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") -pytest-cov==2.12.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") python-dotenv==0.15.0 -pytz==2021.1; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5" -pyyaml==5.4.1; python_full_version >= "3.6.1" -regex==2021.4.4; python_version >= "3.6" requests==2.25.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") -responses==0.13.3; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") -six==1.16.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" -snowballstemmer==2.1.0; python_version >= "3.5" -sphinx==3.5.4; python_version >= "3.5" -sphinxcontrib-applehelp==1.0.2; python_version >= "3.5" -sphinxcontrib-devhelp==1.0.2; python_version >= "3.5" -sphinxcontrib-htmlhelp==2.0.0; python_version >= "3.6" -sphinxcontrib-jsmath==1.0.1; python_version >= "3.5" -sphinxcontrib-qthelp==1.0.3; python_version >= "3.5" -sphinxcontrib-serializinghtml==1.1.5; python_version >= "3.5" stringcase==1.2.0; python_version >= "3.6" -toml==0.10.2; python_full_version >= "3.6.1" and python_version >= "3.6" -tox==3.23.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") -typed-ast==1.4.3; python_version >= "3.6" -typing-extensions==3.10.0.0; python_version < "3.8" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.4.0" and python_version < "3.8" and python_version >= "3.6") -typing-inspect==0.7.1; python_version >= "3.6" -urllib3==1.26.6; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version < "4" and python_version >= "3.5" -virtualenv==20.4.7; python_full_version >= "3.6.1" -wcwidth==0.2.5; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" +typing-extensions==3.10.0.0; python_version < "3.8" and python_version >= "3.6" +typing-inspect==0.6.0; python_version >= "3.6" +urllib3==1.26.5; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version < "4" yarl==1.6.3; python_version >= "3.6" -zipp==3.4.1; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.4.0" and python_version < "3.8" and python_version >= "3.6" From 208f2aca3564c1312ec4bb420a25f7f59f828bf8 Mon Sep 17 00:00:00 2001 From: Nate Williams Date: Thu, 1 Jul 2021 13:30:15 -0500 Subject: [PATCH 18/49] Revert to main branch toml file --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6131ba7..fc3d5df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,6 @@ aiohttp = "^3.7.4" requests = "^2.25.1" python-dotenv = "^0.15.0" dataclasses-json = "^0.5.3" -fuuid = "^0.1.0" [tool.poetry.dev-dependencies] pytest = "^4.6" From 8e0b11d6c586bc9fac183617c8174a07b8cf6171 Mon Sep 17 00:00:00 2001 From: Nate Williams Date: Thu, 1 Jul 2021 13:39:29 -0500 Subject: [PATCH 19/49] Sensibly re-add fuuid from Nate's botched depedency flow --- poetry.lock | 31 ++++++++++++++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index c1ff5ad..3a72cfc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -74,6 +74,17 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] pytz = ">=2015.7" +[[package]] +name = "base58" +version = "2.1.0" +description = "Base58 and Base58Check implementation." +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +tests = ["pytest (>=4.6)", "pytest-flake8", "pytest-cov", "PyHamcrest (>=2.0.2)", "coveralls", "pytest-benchmark"] + [[package]] name = "black" version = "20.8b1" @@ -233,6 +244,17 @@ mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.7.0,<2.8.0" pyflakes = ">=2.3.0,<2.4.0" +[[package]] +name = "fuuid" +version = "0.1.0" +description = "Functional UUIDs for Python." +category = "main" +optional = false +python-versions = ">=3.7,<4.0" + +[package.dependencies] +base58 = ">=2.1.0,<3.0.0" + [[package]] name = "identify" version = "2.2.6" @@ -826,7 +848,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "d97d275553d9fb816a0b9191f3edf0a090c8d271d96a62e2cdbd60302e9a76f5" +content-hash = "083c6658ca9a8b94febf3980d41ca7586e55d968ffe371e90469dc81ae199f2a" [metadata.files] aiohttp = [ @@ -892,6 +914,10 @@ babel = [ {file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"}, {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, ] +base58 = [ + {file = "base58-2.1.0-py3-none-any.whl", hash = "sha256:8225891d501b68c843ffe30b86371f844a21c6ba00da76f52f9b998ba771fb48"}, + {file = "base58-2.1.0.tar.gz", hash = "sha256:171a547b4a3c61e1ae3807224a6f7aec75e364c4395e7562649d7335768001a2"}, +] black = [ {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, ] @@ -996,6 +1022,9 @@ flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, ] +fuuid = [ + {file = "fuuid-0.1.0.tar.gz", hash = "sha256:ce8aec9ae81078941fa730dca0bf5ff7ca56675bea9327e938f1a04c8fad18b2"}, +] identify = [ {file = "identify-2.2.6-py2.py3-none-any.whl", hash = "sha256:1560bb645b93d5c05c3535c72a4f4884133006423d02c692ac6862a45eb0d521"}, {file = "identify-2.2.6.tar.gz", hash = "sha256:01ebbc7af37043806216c7550539210cde4f82451983eb8735a02b3b9d013e40"}, diff --git a/pyproject.toml b/pyproject.toml index fc3d5df..6131ba7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ aiohttp = "^3.7.4" requests = "^2.25.1" python-dotenv = "^0.15.0" dataclasses-json = "^0.5.3" +fuuid = "^0.1.0" [tool.poetry.dev-dependencies] pytest = "^4.6" From f7c101c706d899b941069b48a61cc381bfed3db5 Mon Sep 17 00:00:00 2001 From: Nate Williams Date: Thu, 1 Jul 2021 14:10:36 -0500 Subject: [PATCH 20/49] Remove unused output statement --- tests/services/test_track_package.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/services/test_track_package.py b/tests/services/test_track_package.py index c783271..7fe1dbd 100644 --- a/tests/services/test_track_package.py +++ b/tests/services/test_track_package.py @@ -224,7 +224,6 @@ def test_multiple_locations_in_tracking_event(self) -> None: track_package_assertions(tracking_result=tracking_result) assert_events_in_order(tracking_result.events) assert tracking_result.events[0].location is None - print(tracking_result.events[1].location) assert tracking_result.events[1].location.latitude is None assert tracking_result.events[1].location.longitude is None assert type(tracking_result.events[2].location.latitude) is float From 98ad92c3614d983db3a6a8a5629255d8aeae4541 Mon Sep 17 00:00:00 2001 From: Nate Williams Date: Thu, 1 Jul 2021 14:38:53 -0500 Subject: [PATCH 21/49] Add fuuid dependency needed for test to the tox.ini --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 114a92e..84fe137 100644 --- a/tox.ini +++ b/tox.ini @@ -34,6 +34,7 @@ deps = coverage coveralls responses + fuuid commands = pytest {posargs:} ; coveralls --submit={toxworkdir}/.coverage.{envname} From 8583d379714aa02071a334ca7150fef38bd5d764 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Thu, 1 Jul 2021 14:52:14 -0500 Subject: [PATCH 22/49] Added calls to emit_event() in the client to emit Req and Resp events --- shipengine_sdk/events/__init__.py | 42 ++++++--- shipengine_sdk/http_client/client.py | 103 ++++++++++++++++++---- shipengine_sdk/models/__init__.py | 1 - shipengine_sdk/models/package/__init__.py | 17 ---- 4 files changed, 119 insertions(+), 44 deletions(-) diff --git a/shipengine_sdk/events/__init__.py b/shipengine_sdk/events/__init__.py index e827d22..2447e47 100644 --- a/shipengine_sdk/events/__init__.py +++ b/shipengine_sdk/events/__init__.py @@ -3,18 +3,21 @@ HTTP request is sent and when an HTTP response is received for said request. """ import json +from dataclasses import dataclass from datetime import datetime from typing import Any, Callable, Dict, List, Optional, Union +from dataclasses_json import dataclass_json + from ..errors import ShipEngineError from ..models.enums import Events class ShipEngineEvent: - timestamp: str + timestamp: datetime def __init__(self, event_type: str, message: str) -> None: - self.timestamp = datetime.now().isoformat() + self.timestamp = datetime.now() self.type = event_type self.message = message @@ -42,7 +45,7 @@ def __init__( self, request_id: str, message: str, - url: str, + base_uri: str, headers: List[str], body: Dict[str, Any], retry: int, @@ -50,7 +53,7 @@ def __init__( ) -> None: super().__init__(event_type=self.REQUEST_SENT, message=message) self.request_id = request_id - self.url = url + self.base_uri = base_uri self.headers = headers self.body = body self.retry = retry @@ -64,16 +67,16 @@ def __init__( self, message: str, request_id: str, - url: str, + base_uri: str, status_code: int, headers: List[str], body: Dict[str, Any], retry: int, - elapsed: str, + elapsed: float, ) -> None: super().__init__(event_type=self.RESPONSE_RECEIVED, message=message) self.request_id = request_id - self.url = url + self.base_uri = base_uri self.status_code = status_code self.headers = headers self.body = body @@ -136,8 +139,8 @@ def emit_event(emitted_event_type: str, event_data, config): if emitted_event_type == RequestSentEvent.REQUEST_SENT: request_sent_event = RequestSentEvent( message=event_data.message, - request_id=event_data.request_id, - url=event_data.base_uri, + request_id=event_data.id, + base_uri=event_data.base_uri, headers=event_data.request_headers, body=event_data.body, retry=event_data.retry, @@ -148,8 +151,8 @@ def emit_event(emitted_event_type: str, event_data, config): elif emitted_event_type == ResponseReceivedEvent.RESPONSE_RECEIVED: response_received_event = ResponseReceivedEvent( message=event_data.message, - request_id=event_data.request_id, - url=event_data.base_uri, + request_id=event_data.id, + base_uri=event_data.base_uri, status_code=event_data.status_code, headers=event_data.request_headers, body=event_data.body, @@ -162,3 +165,20 @@ def emit_event(emitted_event_type: str, event_data, config): return response_received_event else: raise ShipEngineError(f"Event type [{emitted_event_type}] is not a valid type of event.") + + +@dataclass_json +@dataclass +class EventOptions: + """To be used as the main argument in the **emitEvent()** function.""" + + message: Optional[str] + id: Optional[str] + base_uri: Optional[str] + body: Optional[Dict[str, Any]] + retry: Optional[int] + status_code: Optional[int] = None + request_headers: Optional[Dict[str, Any]] = None + response_headers: Any = None + timeout: Optional[int] = None + elapsed: Optional[float] = None diff --git a/shipengine_sdk/http_client/client.py b/shipengine_sdk/http_client/client.py index 2a13aa0..e02d053 100644 --- a/shipengine_sdk/http_client/client.py +++ b/shipengine_sdk/http_client/client.py @@ -2,6 +2,7 @@ import json import os import platform +from datetime import datetime from typing import Any, Dict, Optional import requests @@ -13,12 +14,48 @@ from shipengine_sdk import __version__ from ..errors import ShipEngineError +from ..events import ( + EventOptions, + RequestSentEvent, + ResponseReceivedEvent, + ShipEngineEvent, + emit_event, +) from ..jsonrpc.process_request import handle_response, wrap_request from ..models import ErrorCode, ErrorSource, ErrorType from ..shipengine_config import ShipEngineConfig from ..util.sdk_assertions import is_response_404, is_response_429, is_response_500 +def base_url(config) -> str: + return config.base_uri if os.getenv("CLIENT_BASE_URI") is None else os.getenv("CLIENT_BASE_URI") + + +def check_for_errors(status_code: int, response_body: Dict[str, Any], config) -> None: + is_response_404(status_code=status_code, response_body=response_body, config=config) + is_response_429(status_code=status_code, response_body=response_body, config=config) + is_response_500(status_code=status_code, response_body=response_body) + + +def generate_event_message(retry: int, method: str, base_uri: str) -> str: + if retry == 0: + return ShipEngineEvent.new_event_message( + method=method, base_uri=base_uri, message_type="base_message" + ) + else: + return ShipEngineEvent.new_event_message( + method=method, base_uri=base_uri, message_type="retry_message" + ) + + +def request_headers(user_agent: str) -> Dict[str, Any]: + return { + "User-Agent": user_agent, + "Content-Type": "application/json", + "Accept": "application/json", + } + + class ShipEngineAuth(AuthBase): def __init__(self, api_key: str) -> None: """Auth Base appends `Api-Key` header to all requests.""" @@ -44,29 +81,50 @@ def send_rpc_request( * is successful, the result is returned. Otherwise, an error is thrown. """ client: Session = self._request_retry_session(retries=config.retries) - base_uri: Optional[str] = ( - config.base_uri - if os.getenv("CLIENT_BASE_URI") is None - else os.getenv("CLIENT_BASE_URI") - ) + base_uri = base_url(config=config) - request_headers: Dict[str, Any] = { - "User-Agent": self._derive_user_agent(), - "Content-Type": "application/json", - "Accept": "application/json", - } + # base_uri: Optional[str] = ( + # config.base_uri + # if os.getenv("CLIENT_BASE_URI") is None + # else os.getenv("CLIENT_BASE_URI") + # ) - request_body: Dict[str, Any] = wrap_request(method=method, params=params) + # request_headers: Dict[str, Any] = { + # "User-Agent": self._derive_user_agent(), + # "Content-Type": "application/json", + # "Accept": "application/json", + # } + request_body: Dict[str, Any] = wrap_request(method=method, params=params) + req_headers = request_headers(self._derive_user_agent()) req: Request = Request( method="POST", url=base_uri, data=json.dumps(request_body), - headers=request_headers, + headers=req_headers, auth=ShipEngineAuth(config.api_key), ) prepared_req: PreparedRequest = req.prepare() + request_event_message = generate_event_message( + retry=retry, method=method, base_uri=base_uri + ) + + request_event_data = EventOptions( + message=request_event_message, + id=request_body["id"], + base_uri=base_uri, + request_headers=req_headers, + body=request_body, + retry=retry, + timeout=config.timeout, + ) + request_sent_event = emit_event( + emitted_event_type=RequestSentEvent.REQUEST_SENT, + event_data=request_event_data, + config=config, + ) + try: resp: Response = client.send(request=prepared_req, timeout=config.timeout) except RequestException as err: @@ -80,10 +138,25 @@ def send_rpc_request( resp_body: Dict[str, Any] = resp.json() status_code: int = resp.status_code - is_response_404(status_code=status_code, response_body=resp_body, config=config) - is_response_429(status_code=status_code, response_body=resp_body, config=config) - is_response_500(status_code=status_code, response_body=resp_body) + response_event_data = EventOptions( + message=request_event_message, + id=request_body["id"], + base_uri=base_uri, + status_code=status_code, + response_headers=resp.headers, + body=request_body, + retry=retry, + elapsed=(request_sent_event.timestamp - datetime.now()).total_seconds(), + ) + + # Emit `ResponseReceivedEvent` to registered Subscribers. + emit_event( + emitted_event_type=ResponseReceivedEvent.RESPONSE_RECEIVED, + event_data=response_event_data, + config=config, + ) + check_for_errors(status_code=status_code, response_body=resp_body, config=config) return handle_response(resp.json()) def _request_retry_session( diff --git a/shipengine_sdk/models/__init__.py b/shipengine_sdk/models/__init__.py index 45ef805..a1ebfd0 100644 --- a/shipengine_sdk/models/__init__.py +++ b/shipengine_sdk/models/__init__.py @@ -15,7 +15,6 @@ get_carrier_name_value, ) from .package import ( - EventOptions, Location, Package, Shipment, diff --git a/shipengine_sdk/models/package/__init__.py b/shipengine_sdk/models/package/__init__.py index fe0f2bf..88d7830 100644 --- a/shipengine_sdk/models/package/__init__.py +++ b/shipengine_sdk/models/package/__init__.py @@ -239,20 +239,3 @@ def to_json(self): def __repr__(self): return f"TrackPackageResult({self.shipment}, {self.package}, {self.events})" - - -@dataclass_json(letter_case=LetterCase.CAMEL) -@dataclass -class EventOptions: - """To be used as the main argument in the **emitEvent()** function.""" - - message: Optional[str] - id: Optional[str] - base_uri: Optional[str] - body: Optional[Dict[str, Any]] - status_code: Optional[int] - retry: Optional[int] - request_headers: Optional[List[str]] = None - response_headers: Optional[List[str]] = None - timeout: Optional[int] = None - elapsed: Optional[str] = None From f3316543fa90a7215901cc456ea3e2ee3d26d153 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Fri, 2 Jul 2021 11:00:47 -0500 Subject: [PATCH 23/49] Optimized response checks for 404, 429, and 500 error cases. --- shipengine_sdk/http_client/client.py | 22 ++-------------------- shipengine_sdk/util/sdk_assertions.py | 14 ++++++-------- 2 files changed, 8 insertions(+), 28 deletions(-) diff --git a/shipengine_sdk/http_client/client.py b/shipengine_sdk/http_client/client.py index e02d053..e5a76e3 100644 --- a/shipengine_sdk/http_client/client.py +++ b/shipengine_sdk/http_client/client.py @@ -24,19 +24,13 @@ from ..jsonrpc.process_request import handle_response, wrap_request from ..models import ErrorCode, ErrorSource, ErrorType from ..shipengine_config import ShipEngineConfig -from ..util.sdk_assertions import is_response_404, is_response_429, is_response_500 +from ..util.sdk_assertions import check_response_for_errors def base_url(config) -> str: return config.base_uri if os.getenv("CLIENT_BASE_URI") is None else os.getenv("CLIENT_BASE_URI") -def check_for_errors(status_code: int, response_body: Dict[str, Any], config) -> None: - is_response_404(status_code=status_code, response_body=response_body, config=config) - is_response_429(status_code=status_code, response_body=response_body, config=config) - is_response_500(status_code=status_code, response_body=response_body) - - def generate_event_message(retry: int, method: str, base_uri: str) -> str: if retry == 0: return ShipEngineEvent.new_event_message( @@ -83,18 +77,6 @@ def send_rpc_request( client: Session = self._request_retry_session(retries=config.retries) base_uri = base_url(config=config) - # base_uri: Optional[str] = ( - # config.base_uri - # if os.getenv("CLIENT_BASE_URI") is None - # else os.getenv("CLIENT_BASE_URI") - # ) - - # request_headers: Dict[str, Any] = { - # "User-Agent": self._derive_user_agent(), - # "Content-Type": "application/json", - # "Accept": "application/json", - # } - request_body: Dict[str, Any] = wrap_request(method=method, params=params) req_headers = request_headers(self._derive_user_agent()) req: Request = Request( @@ -156,7 +138,7 @@ def send_rpc_request( config=config, ) - check_for_errors(status_code=status_code, response_body=resp_body, config=config) + check_response_for_errors(status_code=status_code, response_body=resp_body, config=config) return handle_response(resp.json()) def _request_retry_session( diff --git a/shipengine_sdk/util/sdk_assertions.py b/shipengine_sdk/util/sdk_assertions.py index ebe46c6..740fb6b 100644 --- a/shipengine_sdk/util/sdk_assertions.py +++ b/shipengine_sdk/util/sdk_assertions.py @@ -174,8 +174,10 @@ def timeout_validation_error_assertions(error) -> None: assert error.source is ErrorSource.SHIPENGINE.value -def is_response_404(status_code: int, response_body: Dict[str, Any], config) -> None: - """Check if status_code is 404 and raises an error if so.""" +def check_response_for_errors(status_code: int, response_body: Dict[str, Any], config) -> None: + """Checks response and status_code for 404, 429, and 500 error cases and raises an approved exception.""" + + # Check if status_code is 404 and raises an error if so. if "error" in response_body and status_code == 404: error = response_body["error"] error_data = error["data"] @@ -194,9 +196,7 @@ def is_response_404(status_code: int, response_body: Dict[str, Any], config) -> error_code=ErrorCode.NOT_FOUND.value, ) - -def is_response_429(status_code: int, response_body: Dict[str, Any], config) -> None: - """Check if status_code is 429 and raises an error if so.""" + # Check if status_code is 429 and raises an error if so. if "error" in response_body and status_code == 429: error = response_body["error"] retry_after = error["data"]["retryAfter"] @@ -213,9 +213,7 @@ def is_response_429(status_code: int, response_body: Dict[str, Any], config) -> request_id=response_body["id"], ) - -def is_response_500(status_code: int, response_body: Dict[str, Any]) -> None: - """Check if the status code is 500 and raises an error if so.""" + # Check if the status code is 500 and raises an error if so. if status_code == 500: error = response_body["error"] error_data = error["data"] From 8431b82b854c854d01cc2ea933edd650802e3b45 Mon Sep 17 00:00:00 2001 From: Nate Williams Date: Fri, 2 Jul 2021 11:06:45 -0500 Subject: [PATCH 24/49] Appease linters --- shipengine_sdk/models/package/__init__.py | 46 ++++++++++++------- shipengine_sdk/services/address_validation.py | 2 +- shipengine_sdk/util/iso_string.py | 8 ++-- 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/shipengine_sdk/models/package/__init__.py b/shipengine_sdk/models/package/__init__.py index dbdeee0..6332234 100644 --- a/shipengine_sdk/models/package/__init__.py +++ b/shipengine_sdk/models/package/__init__.py @@ -26,8 +26,7 @@ def __init__( ) -> None: """This object represents a given Shipment.""" self.config = config - self.shipment_id = shipment["shipmentId"] if "shipmentId" in shipment \ - else None + self.shipment_id = shipment["shipmentId"] if "shipmentId" in shipment else None self.account_id = shipment["carrierAccountId"] if "carrierAccountId" in shipment else None if self.account_id is not None: @@ -126,20 +125,31 @@ class Location: def __init__(self, location_data: Dict[str, Any]) -> None: self.city_locality = ( - location_data["cityLocality"] if "cityLocality" in location_data \ - and location_data != None else None + location_data["cityLocality"] + if "cityLocality" in location_data and location_data is not None + else None ) self.state_province = ( - location_data["stateProvince"] if "stateProvince" in location_data \ - and location_data != None else None + location_data["stateProvince"] + if "stateProvince" in location_data and location_data is not None + else None + ) + self.postal_code = ( + location_data["postalCode"] + if "postalCode" in location_data and location_data is not None + else None + ) + self.country_code = ( + location_data["countryCode"] + if "countryCode" in location_data and location_data is not None + else None ) - self.postal_code = location_data["postalCode"] if "postalCode" in \ - location_data and location_data != None else None - self.country_code = location_data["countryCode"] if "countryCode" in \ - location_data and location_data != None else None - if "coordinates" in location_data and location_data is not None and \ - location_data["coordinates"] != None: + if ( + "coordinates" in location_data + and location_data is not None + and location_data["coordinates"] is not None + ): self.latitude = location_data["coordinates"]["latitude"] self.longitude = location_data["coordinates"]["longitude"] @@ -170,13 +180,16 @@ def __init__(self, event: Dict[str, Any]) -> None: self.carrier_date_time = IsoString(iso_string=event["carrierTimestamp"]) self.status = event["status"] - self.description = event["description"] if "description" in event else \ - None + self.description = event["description"] if "description" in event else None self.carrier_status_code = ( event["carrierStatusCode"] if "carrierStatusCode" in event else None ) self.signer = event["signer"] if "signer" in event else None - self.location = Location(event["location"]) if "location" in event and event["location"] != None else None + self.location = ( + Location(event["location"]) + if "location" in event and event["location"] is not None + else None + ) def to_dict(self): return (lambda o: o.__dict__)(self) @@ -209,8 +222,7 @@ def __init__(self, api_response: Dict[str, Any], config: ShipEngineConfig) -> No if "shipment" in result else None ) - self.package = Package(result["package"]) if "package" in result else \ - None + self.package = Package(result["package"]) if "package" in result else None def get_errors(self) -> List[TrackingEvent]: """Returns **only** the exception events.""" diff --git a/shipengine_sdk/services/address_validation.py b/shipengine_sdk/services/address_validation.py index 5febb0a..c971f5b 100644 --- a/shipengine_sdk/services/address_validation.py +++ b/shipengine_sdk/services/address_validation.py @@ -27,7 +27,7 @@ def validate(address: Address, config: ShipEngineConfig) -> AddressValidateResul is_valid=result["isValid"], request_id=api_response["id"], normalized_address=Address.from_dict(result["normalizedAddress"]) - if "normalizedAddress" in result and result["normalizedAddress"] != None + if "normalizedAddress" in result and result["normalizedAddress"] is not None else None, messages=result["messages"], ) diff --git a/shipengine_sdk/util/iso_string.py b/shipengine_sdk/util/iso_string.py index fc8833c..4fc39f1 100644 --- a/shipengine_sdk/util/iso_string.py +++ b/shipengine_sdk/util/iso_string.py @@ -53,10 +53,10 @@ def _is_valid_iso_string_no_tz(iso_str: str): @staticmethod def _maybe_add_microseconds(iso_str: str): - if '.' not in iso_str: - if 'Z' not in iso_str: - return iso_str + '.0' + if "." not in iso_str: + if "Z" not in iso_str: + return iso_str + ".0" else: - return iso_str[:-1] + '.0Z' + return iso_str[:-1] + ".0Z" else: return iso_str From 0e28ad6c76d2c0791525275d18766ce660f28718 Mon Sep 17 00:00:00 2001 From: Nate Williams Date: Fri, 2 Jul 2021 11:31:16 -0500 Subject: [PATCH 25/49] Generate updated requirements file for updated packages --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index 5763db5..fdfe2a1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,7 @@ async-timeout==3.0.1; python_full_version >= "3.5.3" and python_version >= "3.6" atomicwrites==1.4.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" attrs==21.2.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" babel==2.9.1; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5" +base58==2.1.0; python_version >= "3.7" and python_version < "4.0" black==20.8b1; python_version >= "3.6" certifi==2021.5.30; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5" cfgv==3.3.0; python_full_version >= "3.6.1" @@ -19,6 +20,7 @@ docopt==0.6.2; python_version >= "3.5" docutils==0.16; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5" filelock==3.0.12; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" flake8==3.9.2; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") +fuuid==0.1.0; python_version >= "3.7" and python_version < "4.0" identify==2.2.10; python_full_version >= "3.6.1" idna==2.10; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" imagesize==1.2.0; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5" From 19bb8ad4f16be47848d55cde301a3c192e0cdb22 Mon Sep 17 00:00:00 2001 From: Nate Williams Date: Tue, 6 Jul 2021 10:23:43 -0500 Subject: [PATCH 26/49] Enable tracking URL asertions for track package tests for elixir SimEngine updates --- tests/services/test_track_package.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/services/test_track_package.py b/tests/services/test_track_package.py index 7fe1dbd..ec16eda 100644 --- a/tests/services/test_track_package.py +++ b/tests/services/test_track_package.py @@ -85,8 +85,8 @@ def test_track_by_tracking_number_and_carrier_code(self) -> None: assert tracking_data.carrier_code == tracking_result.shipment.carrier["code"] assert tracking_data.tracking_number == tracking_result.package.tracking_number - # assert tracking_result.package.tracking_url is not None - # assert type(tracking_result.package.tracking_url) is str + assert tracking_result.package.tracking_url is not None + assert type(tracking_result.package.tracking_url) is str def test_track_by_package_id(self) -> None: """DX-1086 - Test track by package ID.""" @@ -96,7 +96,7 @@ def test_track_by_package_id(self) -> None: assert tracking_result.package.package_id == package_id assert tracking_result.package.tracking_number is not None - # assert tracking_result.package.tracking_url is not None + assert tracking_result.package.tracking_url is not None assert tracking_result.shipment.shipment_id is not None assert tracking_result.shipment.account_id is not None From fa8ca6830c951617bf0ef7656b99fbf1e3cfb500 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Tue, 6 Jul 2021 11:04:54 -0500 Subject: [PATCH 27/49] Update models to handle null values for string fields, and ensure no warnings come up on the tests --- shipengine_sdk/models/address/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shipengine_sdk/models/address/__init__.py b/shipengine_sdk/models/address/__init__.py index 7061a10..b55a71a 100644 --- a/shipengine_sdk/models/address/__init__.py +++ b/shipengine_sdk/models/address/__init__.py @@ -23,9 +23,9 @@ class Address: postal_code: str country_code: str is_residential: Optional[bool] = None - name: str = "" - phone: str = "" - company: str = "" + name: Optional[str] = "" + phone: Optional[str] = "" + company: Optional[str] = "" def __post_init__(self) -> None: is_street_valid(self.street) From d9713e988e557d4d9be096179058d6c12af73d3f Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Tue, 6 Jul 2021 11:37:14 -0500 Subject: [PATCH 28/49] Added pytest-mock --- README.md | 10 ++- poetry.lock | 207 +++++++++++++++++++++++++------------------------ pyproject.toml | 3 +- 3 files changed, 117 insertions(+), 103 deletions(-) diff --git a/README.md b/README.md index 699ecd4..bd2e98d 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,15 @@ poetry install poetry shell ``` -### Pre-Commit Hooks +## Adding dependencies to the project +If your changes require you to install a python package/module using `poetry add ` or +`poetry add -D` for a dev dependency. You will also need to run the following command to +regenerate a `requirements.txt` file that includes the newly added dependencies: +```bash +poetry export -f requirements.txt --output requirements.txt --without-hashes --dev +``` + +## Pre-Commit Hooks We are using [Pre-Commit](https://pre-commit.com/) to enforce formatting, lint rules, and code analysis so that this repo is always in good health. - If you choose not to globally install `pre-commit`, then you can skip installing via `pip` or `homebrew` directly. diff --git a/poetry.lock b/poetry.lock index d7d0994..db5540b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -45,7 +45,7 @@ python-versions = ">=3.5.3" name = "atomicwrites" version = "1.4.0" description = "Atomic file writes." -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" @@ -147,7 +147,7 @@ importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} name = "colorama" version = "0.4.4" description = "Cross-platform colored terminal text." -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" @@ -281,9 +281,9 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "importlib-metadata" -version = "4.6.0" +version = "4.6.1" description = "Read metadata from Python packages" -category = "dev" +category = "main" optional = false python-versions = ">=3.6" @@ -296,6 +296,14 @@ docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] perf = ["ipython"] testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "isort" version = "5.9.1" @@ -365,14 +373,6 @@ category = "dev" optional = false python-versions = "*" -[[package]] -name = "more-itertools" -version = "8.8.0" -description = "More routines for operating on iterables, beyond itertools" -category = "dev" -optional = false -python-versions = ">=3.5" - [[package]] name = "multidict" version = "5.1.0" @@ -399,11 +399,11 @@ python-versions = "*" [[package]] name = "packaging" -version = "20.9" +version = "21.0" description = "Core utilities for Python packages" -category = "dev" +category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.dependencies] pyparsing = ">=2.0.2" @@ -420,7 +420,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" name = "pluggy" version = "0.13.1" description = "plugin and hook calling mechanisms for python" -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" @@ -451,7 +451,7 @@ virtualenv = ">=20.0.8" name = "py" version = "1.10.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" @@ -483,32 +483,31 @@ python-versions = ">=3.5" name = "pyparsing" version = "2.4.7" description = "Python parsing module" -category = "dev" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "pytest" -version = "4.6.11" +version = "6.2.4" description = "pytest: simple powerful testing with Python" -category = "dev" +category = "main" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +python-versions = ">=3.6" [package.dependencies] -atomicwrites = ">=1.0" -attrs = ">=17.4.0" -colorama = {version = "*", markers = "sys_platform == \"win32\" and python_version != \"3.4\""} +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} -more-itertools = {version = ">=4.0.0", markers = "python_version > \"2.7\""} +iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<1.0" -py = ">=1.5.0" -six = ">=1.10.0" -wcwidth = "*" +pluggy = ">=0.12,<1.0.0a1" +py = ">=1.8.2" +toml = "*" [package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "nose", "requests", "mock"] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] [[package]] name = "pytest-cov" @@ -526,6 +525,20 @@ toml = "*" [package.extras] testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] +[[package]] +name = "pytest-mock" +version = "3.6.1" +description = "Thin-wrapper around the mock package for easier use with pytest" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pytest = ">=5.0" + +[package.extras] +dev = ["pre-commit", "tox", "pytest-asyncio"] + [[package]] name = "python-dotenv" version = "0.15.0" @@ -555,7 +568,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [[package]] name = "regex" -version = "2021.4.4" +version = "2021.7.6" description = "Alternative regular expression module, to replace re." category = "dev" optional = false @@ -725,7 +738,7 @@ python-versions = "*" name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" @@ -812,14 +825,6 @@ six = ">=1.9.0,<2" docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"] testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)", "xonsh (>=0.9.16)"] -[[package]] -name = "wcwidth" -version = "0.2.5" -description = "Measures the displayed width of unicode strings in a terminal" -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "yarl" version = "1.6.3" @@ -835,20 +840,20 @@ typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} [[package]] name = "zipp" -version = "3.4.1" +version = "3.5.0" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "dev" +category = "main" optional = false python-versions = ">=3.6" [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "b7316a306fa935df2cfd84976fd149bf601b9595dcc9302a39fe0041380b50b7" +content-hash = "c6a2aadd1b9fca49b995b94d87457c14b6b2878318692f754affd2872b2b4216" [metadata.files] aiohttp = [ @@ -1038,8 +1043,12 @@ imagesize = [ {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.6.0-py3-none-any.whl", hash = "sha256:c6513572926a96458f8c8f725bf0e00108fba0c9583ade9bd15b869c9d726e33"}, - {file = "importlib_metadata-4.6.0.tar.gz", hash = "sha256:4a5611fea3768d3d967c447ab4e93f567d95db92225b43b7b238dbfb855d70bb"}, + {file = "importlib_metadata-4.6.1-py3-none-any.whl", hash = "sha256:9f55f560e116f8643ecf2922d9cd3e1c7e8d52e683178fecd9d08f6aa357e11e"}, + {file = "importlib_metadata-4.6.1.tar.gz", hash = "sha256:079ada16b7fc30dfbb5d13399a5113110dab1aa7c2bc62f66af75f0b717c8cac"}, +] +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.9.1-py3-none-any.whl", hash = "sha256:8e2c107091cfec7286bc0f68a547d0ba4c094d460b732075b6fba674f1035c0c"}, @@ -1097,10 +1106,6 @@ mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] -more-itertools = [ - {file = "more-itertools-8.8.0.tar.gz", hash = "sha256:83f0308e05477c68f56ea3a888172c78ed5d5b3c282addb67508e7ba6c8f813a"}, - {file = "more_itertools-8.8.0-py3-none-any.whl", hash = "sha256:2cf89ec599962f2ddc4d568a05defc40e0a587fbc10d5989713638864c36be4d"}, -] multidict = [ {file = "multidict-5.1.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f"}, {file = "multidict-5.1.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf"}, @@ -1149,8 +1154,8 @@ nodeenv = [ {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, ] packaging = [ - {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, - {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, + {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, + {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, ] pathspec = [ {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, @@ -1185,13 +1190,17 @@ pyparsing = [ {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] pytest = [ - {file = "pytest-4.6.11-py2.py3-none-any.whl", hash = "sha256:a00a7d79cbbdfa9d21e7d0298392a8dd4123316bfac545075e6f8f24c94d8c97"}, - {file = "pytest-4.6.11.tar.gz", hash = "sha256:50fa82392f2120cc3ec2ca0a75ee615be4c479e66669789771f1758332be4353"}, + {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, + {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, ] pytest-cov = [ {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, ] +pytest-mock = [ + {file = "pytest-mock-3.6.1.tar.gz", hash = "sha256:40217a058c52a63f1042f0784f62009e976ba824c418cced42e88d5f40ab0e62"}, + {file = "pytest_mock-3.6.1-py3-none-any.whl", hash = "sha256:30c2f2cc9759e76eee674b81ea28c9f0b94f8f0445a1b87762cadf774f0df7e3"}, +] python-dotenv = [ {file = "python-dotenv-0.15.0.tar.gz", hash = "sha256:587825ed60b1711daea4832cf37524dfd404325b7db5e25ebe88c495c9f807a0"}, {file = "python_dotenv-0.15.0-py2.py3-none-any.whl", hash = "sha256:0c8d1b80d1a1e91717ea7d526178e3882732420b03f08afea0406db6402e220e"}, @@ -1232,47 +1241,47 @@ pyyaml = [ {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, ] regex = [ - {file = "regex-2021.4.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7"}, - {file = "regex-2021.4.4-cp36-cp36m-win32.whl", hash = "sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29"}, - {file = "regex-2021.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79"}, - {file = "regex-2021.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439"}, - {file = "regex-2021.4.4-cp37-cp37m-win32.whl", hash = "sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d"}, - {file = "regex-2021.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3"}, - {file = "regex-2021.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87"}, - {file = "regex-2021.4.4-cp38-cp38-win32.whl", hash = "sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac"}, - {file = "regex-2021.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2"}, - {file = "regex-2021.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042"}, - {file = "regex-2021.4.4-cp39-cp39-win32.whl", hash = "sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6"}, - {file = "regex-2021.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07"}, - {file = "regex-2021.4.4.tar.gz", hash = "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb"}, + {file = "regex-2021.7.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e6a1e5ca97d411a461041d057348e578dc344ecd2add3555aedba3b408c9f874"}, + {file = "regex-2021.7.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:6afe6a627888c9a6cfbb603d1d017ce204cebd589d66e0703309b8048c3b0854"}, + {file = "regex-2021.7.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ccb3d2190476d00414aab36cca453e4596e8f70a206e2aa8db3d495a109153d2"}, + {file = "regex-2021.7.6-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:ed693137a9187052fc46eedfafdcb74e09917166362af4cc4fddc3b31560e93d"}, + {file = "regex-2021.7.6-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:99d8ab206a5270c1002bfcf25c51bf329ca951e5a169f3b43214fdda1f0b5f0d"}, + {file = "regex-2021.7.6-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:b85ac458354165405c8a84725de7bbd07b00d9f72c31a60ffbf96bb38d3e25fa"}, + {file = "regex-2021.7.6-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:3f5716923d3d0bfb27048242a6e0f14eecdb2e2a7fac47eda1d055288595f222"}, + {file = "regex-2021.7.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5983c19d0beb6af88cb4d47afb92d96751fb3fa1784d8785b1cdf14c6519407"}, + {file = "regex-2021.7.6-cp36-cp36m-win32.whl", hash = "sha256:c92831dac113a6e0ab28bc98f33781383fe294df1a2c3dfd1e850114da35fd5b"}, + {file = "regex-2021.7.6-cp36-cp36m-win_amd64.whl", hash = "sha256:791aa1b300e5b6e5d597c37c346fb4d66422178566bbb426dd87eaae475053fb"}, + {file = "regex-2021.7.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:59506c6e8bd9306cd8a41511e32d16d5d1194110b8cfe5a11d102d8b63cf945d"}, + {file = "regex-2021.7.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:564a4c8a29435d1f2256ba247a0315325ea63335508ad8ed938a4f14c4116a5d"}, + {file = "regex-2021.7.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:59c00bb8dd8775473cbfb967925ad2c3ecc8886b3b2d0c90a8e2707e06c743f0"}, + {file = "regex-2021.7.6-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:9a854b916806c7e3b40e6616ac9e85d3cdb7649d9e6590653deb5b341a736cec"}, + {file = "regex-2021.7.6-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:db2b7df831c3187a37f3bb80ec095f249fa276dbe09abd3d35297fc250385694"}, + {file = "regex-2021.7.6-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:173bc44ff95bc1e96398c38f3629d86fa72e539c79900283afa895694229fe6a"}, + {file = "regex-2021.7.6-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:15dddb19823f5147e7517bb12635b3c82e6f2a3a6b696cc3e321522e8b9308ad"}, + {file = "regex-2021.7.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ddeabc7652024803666ea09f32dd1ed40a0579b6fbb2a213eba590683025895"}, + {file = "regex-2021.7.6-cp37-cp37m-win32.whl", hash = "sha256:f080248b3e029d052bf74a897b9d74cfb7643537fbde97fe8225a6467fb559b5"}, + {file = "regex-2021.7.6-cp37-cp37m-win_amd64.whl", hash = "sha256:d8bbce0c96462dbceaa7ac4a7dfbbee92745b801b24bce10a98d2f2b1ea9432f"}, + {file = "regex-2021.7.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:edd1a68f79b89b0c57339bce297ad5d5ffcc6ae7e1afdb10f1947706ed066c9c"}, + {file = "regex-2021.7.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:422dec1e7cbb2efbbe50e3f1de36b82906def93ed48da12d1714cabcd993d7f0"}, + {file = "regex-2021.7.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cbe23b323988a04c3e5b0c387fe3f8f363bf06c0680daf775875d979e376bd26"}, + {file = "regex-2021.7.6-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:0eb2c6e0fcec5e0f1d3bcc1133556563222a2ffd2211945d7b1480c1b1a42a6f"}, + {file = "regex-2021.7.6-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:1c78780bf46d620ff4fff40728f98b8afd8b8e35c3efd638c7df67be2d5cddbf"}, + {file = "regex-2021.7.6-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:bc84fb254a875a9f66616ed4538542fb7965db6356f3df571d783f7c8d256edd"}, + {file = "regex-2021.7.6-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:598c0a79b4b851b922f504f9f39a863d83ebdfff787261a5ed061c21e67dd761"}, + {file = "regex-2021.7.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:875c355360d0f8d3d827e462b29ea7682bf52327d500a4f837e934e9e4656068"}, + {file = "regex-2021.7.6-cp38-cp38-win32.whl", hash = "sha256:e586f448df2bbc37dfadccdb7ccd125c62b4348cb90c10840d695592aa1b29e0"}, + {file = "regex-2021.7.6-cp38-cp38-win_amd64.whl", hash = "sha256:2fe5e71e11a54e3355fa272137d521a40aace5d937d08b494bed4529964c19c4"}, + {file = "regex-2021.7.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6110bab7eab6566492618540c70edd4d2a18f40ca1d51d704f1d81c52d245026"}, + {file = "regex-2021.7.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4f64fc59fd5b10557f6cd0937e1597af022ad9b27d454e182485f1db3008f417"}, + {file = "regex-2021.7.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:89e5528803566af4df368df2d6f503c84fbfb8249e6631c7b025fe23e6bd0cde"}, + {file = "regex-2021.7.6-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2366fe0479ca0e9afa534174faa2beae87847d208d457d200183f28c74eaea59"}, + {file = "regex-2021.7.6-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f9392a4555f3e4cb45310a65b403d86b589adc773898c25a39184b1ba4db8985"}, + {file = "regex-2021.7.6-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:2bceeb491b38225b1fee4517107b8491ba54fba77cf22a12e996d96a3c55613d"}, + {file = "regex-2021.7.6-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:f98dc35ab9a749276f1a4a38ab3e0e2ba1662ce710f6530f5b0a6656f1c32b58"}, + {file = "regex-2021.7.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:319eb2a8d0888fa6f1d9177705f341bc9455a2c8aca130016e52c7fe8d6c37a3"}, + {file = "regex-2021.7.6-cp39-cp39-win32.whl", hash = "sha256:eaf58b9e30e0e546cdc3ac06cf9165a1ca5b3de8221e9df679416ca667972035"}, + {file = "regex-2021.7.6-cp39-cp39-win_amd64.whl", hash = "sha256:4c9c3155fe74269f61e27617529b7f09552fbb12e44b1189cebbdb24294e6e1c"}, + {file = "regex-2021.7.6.tar.gz", hash = "sha256:8394e266005f2d8c6f0bc6780001f7afa3ef81a7a2111fa35058ded6fce79e4d"}, ] requests = [ {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, @@ -1379,10 +1388,6 @@ virtualenv = [ {file = "virtualenv-20.4.7-py2.py3-none-any.whl", hash = "sha256:2b0126166ea7c9c3661f5b8e06773d28f83322de7a3ff7d06f0aed18c9de6a76"}, {file = "virtualenv-20.4.7.tar.gz", hash = "sha256:14fdf849f80dbb29a4eb6caa9875d476ee2a5cf76a5f5415fa2f1606010ab467"}, ] -wcwidth = [ - {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, - {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, -] yarl = [ {file = "yarl-1.6.3-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434"}, {file = "yarl-1.6.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478"}, @@ -1423,6 +1428,6 @@ yarl = [ {file = "yarl-1.6.3.tar.gz", hash = "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10"}, ] zipp = [ - {file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"}, - {file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"}, + {file = "zipp-3.5.0-py3-none-any.whl", hash = "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3"}, + {file = "zipp-3.5.0.tar.gz", hash = "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"}, ] diff --git a/pyproject.toml b/pyproject.toml index d35803b..3044172 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,9 +14,10 @@ requests = "^2.25.1" python-dotenv = "^0.15.0" dataclasses-json = "^0.5.3" fuuid = "^0.1.0" +pytest-mock = "^3.6.1" [tool.poetry.dev-dependencies] -pytest = "^4.6" +pytest = ">=5.0" black = "^20.8b1" flake8 = "^3.8.4" Sphinx = "^3.5.2" From b8fc8f6a937ec132666e5137bca8c8f21384c181 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Tue, 6 Jul 2021 13:09:45 -0500 Subject: [PATCH 29/49] work in progress - test Event/Observer pattern --- poetry.lock | 26 ++++++++--------- pyproject.toml | 4 +-- requirements.txt | 28 +++++++++---------- shipengine_sdk/events/__init__.py | 20 +++++++++---- shipengine_sdk/http_client/client.py | 16 ++++++++--- shipengine_sdk/jsonrpc/__init__.py | 4 +-- shipengine_sdk/services/address_validation.py | 2 +- tests/events/__init__.py | 1 + tests/events/test_request_sent_event.py | 13 +++++++++ 9 files changed, 72 insertions(+), 42 deletions(-) create mode 100644 tests/events/__init__.py create mode 100644 tests/events/test_request_sent_event.py diff --git a/poetry.lock b/poetry.lock index db5540b..fdfb143 100644 --- a/poetry.lock +++ b/poetry.lock @@ -45,7 +45,7 @@ python-versions = ">=3.5.3" name = "atomicwrites" version = "1.4.0" description = "Atomic file writes." -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" @@ -147,7 +147,7 @@ importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} name = "colorama" version = "0.4.4" description = "Cross-platform colored terminal text." -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" @@ -283,7 +283,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" name = "importlib-metadata" version = "4.6.1" description = "Read metadata from Python packages" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -300,7 +300,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes name = "iniconfig" version = "1.1.1" description = "iniconfig: brain-dead simple config-ini parsing" -category = "main" +category = "dev" optional = false python-versions = "*" @@ -401,7 +401,7 @@ python-versions = "*" name = "packaging" version = "21.0" description = "Core utilities for Python packages" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -420,7 +420,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" name = "pluggy" version = "0.13.1" description = "plugin and hook calling mechanisms for python" -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" @@ -451,7 +451,7 @@ virtualenv = ">=20.0.8" name = "py" version = "1.10.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" @@ -483,7 +483,7 @@ python-versions = ">=3.5" name = "pyparsing" version = "2.4.7" description = "Python parsing module" -category = "main" +category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" @@ -491,7 +491,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" name = "pytest" version = "6.2.4" description = "pytest: simple powerful testing with Python" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -529,7 +529,7 @@ testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtuale name = "pytest-mock" version = "3.6.1" description = "Thin-wrapper around the mock package for easier use with pytest" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -738,7 +738,7 @@ python-versions = "*" name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" -category = "main" +category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" @@ -842,7 +842,7 @@ typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} name = "zipp" version = "3.5.0" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -853,7 +853,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "c6a2aadd1b9fca49b995b94d87457c14b6b2878318692f754affd2872b2b4216" +content-hash = "283e5c399488f650f34c803603e03da438fed630ae384b275e070f96c37e2fa4" [metadata.files] aiohttp = [ diff --git a/pyproject.toml b/pyproject.toml index 3044172..7d73e52 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,16 +14,16 @@ requests = "^2.25.1" python-dotenv = "^0.15.0" dataclasses-json = "^0.5.3" fuuid = "^0.1.0" -pytest-mock = "^3.6.1" [tool.poetry.dev-dependencies] pytest = ">=5.0" +pytest-cov = "^2.11.1" +pytest-mock = "^3.6.1" black = "^20.8b1" flake8 = "^3.8.4" Sphinx = "^3.5.2" tox = "^3.23.0" coverage = "^5.5" -pytest-cov = "^2.11.1" isort = "^5.8.0" responses = "^0.13.3" coveralls = "^3.1.0" diff --git a/requirements.txt b/requirements.txt index fdfe2a1..6c995d9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ aiohttp==3.7.4.post0; python_version >= "3.6" alabaster==0.7.12; python_version >= "3.5" appdirs==1.4.4; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" async-timeout==3.0.1; python_full_version >= "3.5.3" and python_version >= "3.6" -atomicwrites==1.4.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" +atomicwrites==1.4.0; 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_full_version >= "3.4.0" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6") attrs==21.2.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" babel==2.9.1; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5" base58==2.1.0; python_version >= "3.7" and python_version < "4.0" @@ -11,7 +11,7 @@ certifi==2021.5.30; python_version >= "3.5" and python_full_version < "3.0.0" or cfgv==3.3.0; python_full_version >= "3.6.1" chardet==4.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" click==8.0.1; python_version >= "3.6" -colorama==0.4.4; python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0") and platform_system == "Windows" and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0") or sys_platform == "win32" and python_version >= "3.6" and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0") and python_full_version >= "3.5.0" and platform_system == "Windows" and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0") +colorama==0.4.4; python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" and platform_system == "Windows" 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_full_version >= "3.5.0" and platform_system == "Windows" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6") 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") coveralls==3.1.0; python_version >= "3.5" dataclasses-json==0.5.4; python_version >= "3.6" @@ -24,32 +24,33 @@ fuuid==0.1.0; python_version >= "3.7" and python_version < "4.0" identify==2.2.10; python_full_version >= "3.6.1" idna==2.10; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" imagesize==1.2.0; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5" -importlib-metadata==4.6.0; python_full_version >= "3.6.1" and python_version < "3.8" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.4.0" and python_version < "3.8" and python_version >= "3.6") and (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") +importlib-metadata==4.6.1; python_version < "3.8" and python_version >= "3.6" and (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") and python_full_version >= "3.6.1" and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.4.0" and python_version >= "3.6" and python_version < "3.8") and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6") +iniconfig==1.1.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" isort==5.9.1; python_full_version >= "3.6.1" and python_version < "4.0" jinja2==3.0.1; python_version >= "3.6" markupsafe==2.0.1; python_version >= "3.6" marshmallow==3.12.1; python_version >= "3.6" marshmallow-enum==1.5.1; python_version >= "3.6" mccabe==0.6.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" -more-itertools==8.8.0; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5" multidict==5.1.0; python_version >= "3.6" mypy-extensions==0.4.3; python_version >= "3.6" nodeenv==1.6.0; python_full_version >= "3.6.1" -packaging==20.9; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5" +packaging==21.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" pathspec==0.8.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -pluggy==0.13.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" +pluggy==0.13.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" pre-commit==2.13.0; python_full_version >= "3.6.1" -py==1.10.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" +py==1.10.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" pycodestyle==2.7.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" pyflakes==2.3.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" pygments==2.9.0; python_version >= "3.5" -pyparsing==2.4.7; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" -pytest==4.6.11; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") +pyparsing==2.4.7; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" +pytest==6.2.4; python_version >= "3.6" pytest-cov==2.12.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") +pytest-mock==3.6.1; python_version >= "3.6" python-dotenv==0.15.0 pytz==2021.1; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5" pyyaml==5.4.1; python_full_version >= "3.6.1" -regex==2021.4.4; python_version >= "3.6" +regex==2021.7.6; python_version >= "3.6" requests==2.25.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") responses==0.13.3; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") six==1.16.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" @@ -62,13 +63,12 @@ sphinxcontrib-jsmath==1.0.1; python_version >= "3.5" sphinxcontrib-qthelp==1.0.3; python_version >= "3.5" sphinxcontrib-serializinghtml==1.1.5; python_version >= "3.5" stringcase==1.2.0; python_version >= "3.6" -toml==0.10.2; python_full_version >= "3.6.1" and python_version >= "3.6" +toml==0.10.2; python_full_version >= "3.6.1" 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") tox==3.23.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") typed-ast==1.4.3; python_version >= "3.6" -typing-extensions==3.10.0.0; python_version < "3.8" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.4.0" and python_version < "3.8" and python_version >= "3.6") +typing-extensions==3.10.0.0; python_version < "3.8" and python_version >= "3.6" typing-inspect==0.7.1; python_version >= "3.6" urllib3==1.26.6; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version < "4" and python_version >= "3.5" virtualenv==20.4.7; python_full_version >= "3.6.1" -wcwidth==0.2.5; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" yarl==1.6.3; python_version >= "3.6" -zipp==3.4.1; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.4.0" and python_version < "3.8" and python_version >= "3.6" +zipp==3.5.0; python_version < "3.8" and python_version >= "3.6" diff --git a/shipengine_sdk/events/__init__.py b/shipengine_sdk/events/__init__.py index 2447e47..3cdfa58 100644 --- a/shipengine_sdk/events/__init__.py +++ b/shipengine_sdk/events/__init__.py @@ -85,8 +85,13 @@ def __init__( class Dispatcher: - def __init__(self, events: List[str]) -> None: - self.events = {event: dict() for event in events} + def __init__(self, events: Optional[List[str]] = None) -> None: + events_list = [Events.ON_REQUEST_SENT.value, Events.ON_RESPONSE_RECEIVED.value] + if events: + for i in events: + events_list.append(i) + + self.events = {event: dict() for event in events_list} def get_subscribers(self, event: Optional[str] = None): return self.events[event] @@ -103,6 +108,12 @@ def dispatch(self, event, event_name: str = None): for subscriber, callback in self.get_subscribers(event_name).items(): callback(event) + def to_dict(self): + return (lambda o: o.__dict__)(self) + + def to_json(self): + return json.dumps(self, default=lambda o: o.__dict__, indent=2) + class Subscriber: def __init__(self, name=None) -> None: @@ -132,10 +143,7 @@ def update(event: Union[RequestSentEvent, ResponseReceivedEvent]): print(event.to_dict()) -def emit_event(emitted_event_type: str, event_data, config): - dispatcher = Dispatcher([Events.ON_REQUEST_SENT.value, Events.ON_RESPONSE_RECEIVED.value]) - dispatcher.register(event=Events.ON_REQUEST_SENT.value, subscriber=config.event_listener) - dispatcher.register(event=Events.ON_RESPONSE_RECEIVED.value, subscriber=config.event_listener) +def emit_event(emitted_event_type: str, event_data, dispatcher: Dispatcher): if emitted_event_type == RequestSentEvent.REQUEST_SENT: request_sent_event = RequestSentEvent( message=event_data.message, diff --git a/shipengine_sdk/http_client/client.py b/shipengine_sdk/http_client/client.py index e5a76e3..2e1a0da 100644 --- a/shipengine_sdk/http_client/client.py +++ b/shipengine_sdk/http_client/client.py @@ -15,6 +15,7 @@ from ..errors import ShipEngineError from ..events import ( + Dispatcher, EventOptions, RequestSentEvent, ResponseReceivedEvent, @@ -23,6 +24,7 @@ ) from ..jsonrpc.process_request import handle_response, wrap_request from ..models import ErrorCode, ErrorSource, ErrorType +from ..models.enums import Events from ..shipengine_config import ShipEngineConfig from ..util.sdk_assertions import check_response_for_errors @@ -61,10 +63,16 @@ def __call__(self, request: Request, *args, **kwargs) -> Request: class ShipEngineClient: - _BASE_URI: str = "" + _DISPATCHER: Dispatcher = Dispatcher() - def __init__(self) -> None: + def __init__(self, config: ShipEngineConfig) -> None: """A `JSON-RPC 2.0` HTTP client used to send all HTTP requests from the SDK.""" + self._DISPATCHER.register( + event=Events.ON_REQUEST_SENT.value, subscriber=config.event_listener + ) + self._DISPATCHER.register( + event=Events.ON_RESPONSE_RECEIVED.value, subscriber=config.event_listener + ) self.session = requests.session() def send_rpc_request( @@ -104,7 +112,7 @@ def send_rpc_request( request_sent_event = emit_event( emitted_event_type=RequestSentEvent.REQUEST_SENT, event_data=request_event_data, - config=config, + dispatcher=self._DISPATCHER, ) try: @@ -135,7 +143,7 @@ def send_rpc_request( emit_event( emitted_event_type=ResponseReceivedEvent.RESPONSE_RECEIVED, event_data=response_event_data, - config=config, + dispatcher=self._DISPATCHER, ) check_response_for_errors(status_code=status_code, response_body=resp_body, config=config) diff --git a/shipengine_sdk/jsonrpc/__init__.py b/shipengine_sdk/jsonrpc/__init__.py index d25f3b4..e6a3e3c 100644 --- a/shipengine_sdk/jsonrpc/__init__.py +++ b/shipengine_sdk/jsonrpc/__init__.py @@ -21,8 +21,7 @@ def rpc_request( def rpc_request_loop( method: str, params: Optional[Dict[str, Any]], config: ShipEngineConfig ) -> Dict[str, Any]: - client: ShipEngineClient = ShipEngineClient() - api_response: Optional[Dict[str, Any]] = None + client: ShipEngineClient = ShipEngineClient(config=config) retry: int = 0 while retry <= config.retries: try: @@ -36,6 +35,7 @@ def rpc_request_loop( and err.retry_after < config.timeout ): time.sleep(err.retry_after) + continue else: raise err retry += 1 diff --git a/shipengine_sdk/services/address_validation.py b/shipengine_sdk/services/address_validation.py index c971f5b..31b4552 100644 --- a/shipengine_sdk/services/address_validation.py +++ b/shipengine_sdk/services/address_validation.py @@ -20,7 +20,7 @@ def validate(address: Address, config: ShipEngineConfig) -> AddressValidateResul api_response: Dict[str, Any] = rpc_request( method=RPCMethods.ADDRESS_VALIDATE.value, config=config, - params={"address": address.to_dict()}, # type: ignore + params={"address": address.to_dict()}, ) result: Dict[str, Any] = api_response["result"] return AddressValidateResult( diff --git a/tests/events/__init__.py b/tests/events/__init__.py new file mode 100644 index 0000000..39fe0c3 --- /dev/null +++ b/tests/events/__init__.py @@ -0,0 +1 @@ +"""Tests around events emitted from ShipEngine SDK.""" diff --git a/tests/events/test_request_sent_event.py b/tests/events/test_request_sent_event.py new file mode 100644 index 0000000..b0fa376 --- /dev/null +++ b/tests/events/test_request_sent_event.py @@ -0,0 +1,13 @@ +"""Test that `RequestSentEvents` are emitted from the SDK properly.""" +from pytest_mock import MockerFixture + +# from shipengine_sdk.events import ShipEngineEventListener + +# from ..util import stub_shipengine_config + + +class TestRequestSentEvent: + def test_config_with_retries_disabled(self, mocker: MockerFixture) -> None: + """Tests that the SDK does not automatically retry if retries in config is set to 0.""" + # spy = mocker.spy(ShipEngineEventListener, "update") + # shipengine = stub_shipengine_config() From 3000e05461b177cd696a9e1fe2d8f3a542bd0451 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Fri, 9 Jul 2021 14:43:43 -0500 Subject: [PATCH 30/49] quick fixes --- shipengine_sdk/events/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shipengine_sdk/events/__init__.py b/shipengine_sdk/events/__init__.py index 3cdfa58..d54be51 100644 --- a/shipengine_sdk/events/__init__.py +++ b/shipengine_sdk/events/__init__.py @@ -140,7 +140,8 @@ def __init__(self, name=None) -> None: # You can add your own event consumption logic by adding/overriding the parent `update()` method below. @staticmethod def update(event: Union[RequestSentEvent, ResponseReceivedEvent]): - print(event.to_dict()) + # print(event.to_dict()) + return event def emit_event(emitted_event_type: str, event_data, dispatcher: Dispatcher): From 5494f2178861b260160e42e98910b22afd410064 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Wed, 14 Jul 2021 17:40:47 -0500 Subject: [PATCH 31/49] :construction: Work in progress. --- .github/workflows/main.yml | 6 +---- poetry.lock | 32 ++++++++++++++++++++++- pyproject.toml | 1 + requirements.txt | 2 ++ shipengine_sdk/models/package/__init__.py | 3 +++ shipengine_sdk/util/iso_string.py | 12 ++++----- tests/services/test_track_package.py | 16 +++++------- tests/util/test_iso_string.py | 6 ++--- 8 files changed, 52 insertions(+), 26 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ed89b7b..05c9a41 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -52,7 +52,7 @@ jobs: tox -e lint - tox-coveralls: + coveralls: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -77,10 +77,6 @@ jobs: python -m pip install tox pytest pytest-cov coverage responses if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Tox testenv - run: | - tox - - name: Pytest run: | pytest diff --git a/poetry.lock b/poetry.lock index fdfb143..e647c18 100644 --- a/poetry.lock +++ b/poetry.lock @@ -219,6 +219,17 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "execnet" +version = "1.9.0" +description = "execnet: rapid multi-Python deployment" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +testing = ["pre-commit"] + [[package]] name = "filelock" version = "3.0.12" @@ -509,6 +520,18 @@ toml = "*" [package.extras] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] +[[package]] +name = "pytest-cache" +version = "1.0" +description = "pytest plugin with mechanisms for caching across test runs" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +execnet = ">=1.1.dev1" +pytest = ">=2.2" + [[package]] name = "pytest-cov" version = "2.12.1" @@ -853,7 +876,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "283e5c399488f650f34c803603e03da438fed630ae384b275e070f96c37e2fa4" +content-hash = "f0261212a97f8b5644e21852153f644bf5ac5b4d65dd34b06df45d67a4ec984c" [metadata.files] aiohttp = [ @@ -1019,6 +1042,10 @@ docutils = [ {file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"}, {file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"}, ] +execnet = [ + {file = "execnet-1.9.0-py2.py3-none-any.whl", hash = "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142"}, + {file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"}, +] filelock = [ {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, @@ -1193,6 +1220,9 @@ pytest = [ {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, ] +pytest-cache = [ + {file = "pytest-cache-1.0.tar.gz", hash = "sha256:be7468edd4d3d83f1e844959fd6e3fd28e77a481440a7118d430130ea31b07a9"}, +] pytest-cov = [ {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, diff --git a/pyproject.toml b/pyproject.toml index 7d73e52..156fadb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,7 @@ isort = "^5.8.0" responses = "^0.13.3" coveralls = "^3.1.0" pre-commit = "2.13.0" +pytest-cache = "^1.0" [tool.black] line-length = 100 diff --git a/requirements.txt b/requirements.txt index 6c995d9..ece45c6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,6 +18,7 @@ dataclasses-json==0.5.4; python_version >= "3.6" distlib==0.3.2; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" docopt==0.6.2; python_version >= "3.5" docutils==0.16; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5" +execnet==1.9.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" filelock==3.0.12; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" flake8==3.9.2; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") fuuid==0.1.0; python_version >= "3.7" and python_version < "4.0" @@ -45,6 +46,7 @@ pyflakes==2.3.1; python_version >= "2.7" and python_full_version < "3.0.0" or py pygments==2.9.0; python_version >= "3.5" pyparsing==2.4.7; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" pytest==6.2.4; python_version >= "3.6" +pytest-cache==1.0 pytest-cov==2.12.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") pytest-mock==3.6.1; python_version >= "3.6" python-dotenv==0.15.0 diff --git a/shipengine_sdk/models/package/__init__.py b/shipengine_sdk/models/package/__init__.py index 6332234..b204fa9 100644 --- a/shipengine_sdk/models/package/__init__.py +++ b/shipengine_sdk/models/package/__init__.py @@ -184,6 +184,9 @@ def __init__(self, event: Dict[str, Any]) -> None: self.carrier_status_code = ( event["carrierStatusCode"] if "carrierStatusCode" in event else None ) + self.carrier_detail_code = ( + event["carrierDetailCode"] if "carrierDetailCode" in event else None + ) self.signer = event["signer"] if "signer" in event else None self.location = ( Location(event["location"]) diff --git a/shipengine_sdk/util/iso_string.py b/shipengine_sdk/util/iso_string.py index 4fc39f1..cdd02e3 100644 --- a/shipengine_sdk/util/iso_string.py +++ b/shipengine_sdk/util/iso_string.py @@ -11,9 +11,7 @@ def __init__(self, iso_string: str) -> None: A string representing a Date, DateTime, or DateTime with Timezone. The object also has a method to return a `datetime.datetime` object, which is the native datetime object in python as of 3.7. - This class object takes in an **ISO-8601** string. Learn more here: https://en.wikipedia.org/wiki/ISO_8601 - :param str iso_string: An `ISO-8601` string. Learn more here: https://en.wikipedia.org/wiki/ISO_8601 """ self.iso_string = iso_string @@ -28,15 +26,15 @@ def to_datetime_object(self) -> datetime: iso_string = self._maybe_add_microseconds(self.iso_string) if self.has_timezone(): return datetime.strptime(iso_string, "%Y-%m-%dT%H:%M:%S.%fZ") - elif self._is_valid_iso_string_no_tz(self.iso_string): + else: return datetime.fromisoformat(iso_string) def has_timezone(self) -> bool: - if self.is_valid_iso_string(self.iso_string): - return False if self._is_valid_iso_string_no_tz(self.iso_string) else True + if self.is_valid_iso_string_with_tz(self.iso_string): + return False if self.is_valid_iso_string_with_tz_no_tz(self.iso_string) else True @staticmethod - def is_valid_iso_string(iso_str: str): + def is_valid_iso_string_with_tz(iso_str: str): pattern = re.compile(RegexPatterns.VALID_ISO_STRING.value) if pattern.match(iso_str): return True @@ -44,7 +42,7 @@ def is_valid_iso_string(iso_str: str): return False @staticmethod - def _is_valid_iso_string_no_tz(iso_str: str): + def is_valid_iso_string_with_tz_no_tz(iso_str: str): pattern = re.compile(RegexPatterns.VALID_ISO_STRING_NO_TZ.value) if pattern.match(iso_str): return True diff --git a/tests/services/test_track_package.py b/tests/services/test_track_package.py index ec16eda..49fd566 100644 --- a/tests/services/test_track_package.py +++ b/tests/services/test_track_package.py @@ -24,9 +24,9 @@ def assertions_on_delivered_after_exception_or_multiple_attempts( assert tracking_result.events[0].status == "accepted" assert tracking_result.events[1].status == "in_transit" assert tracking_result.events[2].status == "in_transit" - assert tracking_result.events[3].status == "exception" + assert tracking_result.events[3].status == "unknown" assert tracking_result.events[4].status == "exception" - assert tracking_result.events[5].status == "attempted_delivery" + assert tracking_result.events[5].status == "exception" assert tracking_result.events[6].status == "attempted_delivery" assert tracking_result.events[7].status == "delivered" assert tracking_result.events[-1].status == "delivered" @@ -128,13 +128,13 @@ def test_multiple_delivery_attempts(self) -> None: tracking_result = shipengine.track_package(tracking_data=package_id) track_package_assertions(tracking_result=tracking_result) - assert len(tracking_result.events) == 5 + assert len(tracking_result.events) == 9 assert_events_in_order(tracking_result.events) assert tracking_result.events[0].status == "accepted" assert tracking_result.events[1].status == "in_transit" - assert tracking_result.events[2].status == "attempted_delivery" - assert tracking_result.events[3].status == "attempted_delivery" - assert tracking_result.events[4].status == "delivered" + assert tracking_result.events[2].status == "unknown" + assert tracking_result.events[3].status == "in_transit" + assert tracking_result.events[-1].status == "delivered" def test_delivered_on_first_try(self) -> None: """DX-1091 - Test delivered on first try tracking event.""" @@ -211,7 +211,7 @@ def test_track_with_multiple_exceptions(self) -> None: assert len(tracking_result.events) == 8 assert tracking_result.events[0].status == "accepted" assert tracking_result.events[4].status == "exception" - assert tracking_result.events[5].status == "attempted_delivery" + assert tracking_result.events[5].status == "exception" assert tracking_result.events[7].status == "delivered" assert tracking_result.events[-1].status == "delivered" @@ -228,8 +228,6 @@ def test_multiple_locations_in_tracking_event(self) -> None: assert tracking_result.events[1].location.longitude is None assert type(tracking_result.events[2].location.latitude) is float assert type(tracking_result.events[2].location.longitude) is float - assert type(tracking_result.events[4].location.latitude) is float - assert type(tracking_result.events[4].location.longitude) is float def test_carrier_date_time_without_timezone(self) -> None: """DX-1098 - Test track package where carrierDateTime has no timezone.""" diff --git a/tests/util/test_iso_string.py b/tests/util/test_iso_string.py index 1ec43e7..b204828 100644 --- a/tests/util/test_iso_string.py +++ b/tests/util/test_iso_string.py @@ -9,16 +9,14 @@ class TestIsoString: def test_to_string(self) -> None: iso_str = IsoString(self._test_iso_string_no_tz).to_string() - assert type(iso_str) is str def test_to_datetime_object(self) -> None: iso_str = IsoString(self._test_iso_string_no_tz).to_datetime_object() - assert type(iso_str) is datetime.datetime def test_static_valid_iso_check(self) -> None: - assert IsoString.is_valid_iso_string(self._test_iso_string_no_tz) is True + assert IsoString.is_valid_iso_string_with_tz(self._test_iso_string_no_tz) is True def test_static_valid_iso_check_failure(self) -> None: - assert IsoString.is_valid_iso_string("2021-06-10T21:00:00.000K") is False + assert IsoString.is_valid_iso_string_with_tz("2021-06-10T21:00:00.000K") is False From 87c3c025b3e6840db84ee57286096aa543b10179 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Thu, 15 Jul 2021 16:44:18 -0500 Subject: [PATCH 32/49] AC per DX-1527 - Test config retries disabled --- tests/events/test_request_sent_event.py | 37 +++++++++++++++++++++---- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/tests/events/test_request_sent_event.py b/tests/events/test_request_sent_event.py index b0fa376..497a2d7 100644 --- a/tests/events/test_request_sent_event.py +++ b/tests/events/test_request_sent_event.py @@ -1,13 +1,40 @@ """Test that `RequestSentEvents` are emitted from the SDK properly.""" from pytest_mock import MockerFixture -# from shipengine_sdk.events import ShipEngineEventListener +from shipengine_sdk.errors import RateLimitExceededError, ShipEngineError +from shipengine_sdk.events import ( + RequestSentEvent, + ResponseReceivedEvent, + ShipEngineEventListener, +) +from shipengine_sdk.models import Endpoints -# from ..util import stub_shipengine_config +from ..util import assert_on_429_exception, configurable_stub_shipengine_instance class TestRequestSentEvent: def test_config_with_retries_disabled(self, mocker: MockerFixture) -> None: - """Tests that the SDK does not automatically retry if retries in config is set to 0.""" - # spy = mocker.spy(ShipEngineEventListener, "update") - # shipengine = stub_shipengine_config() + """DX-1527 - Tests that the SDK does not automatically retry if retries in config is set to 0.""" + request_sent_spy = mocker.spy(ShipEngineEventListener, "catch_request_sent_event") + response_received_spy = mocker.spy(ShipEngineEventListener, "catch_response_received_event") + shipengine = configurable_stub_shipengine_instance( + { + "api_key": "baz_sim", + "base_uri": Endpoints.TEST_RPC_URL.value, + "retries": 0, + "timeout": 10, + } + ) + try: + shipengine.get_carrier_accounts(carrier_code="amazon_buy_shipping") + except ShipEngineError as err: + assert_on_429_exception(err=err, error_class=RateLimitExceededError) + request_sent_return = request_sent_spy.spy_return + assert request_sent_spy.call_count == 1 + assert type(request_sent_return) == RequestSentEvent + assert request_sent_return.retry == 0 + + response_recd_return = response_received_spy.spy_return + assert request_sent_spy.call_count == 1 + assert type(response_recd_return) == ResponseReceivedEvent + assert response_recd_return.retry == 0 From b33430812c596e624195c9b6ccb8bf1ee8ae2219 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Thu, 15 Jul 2021 17:00:59 -0500 Subject: [PATCH 33/49] Work in progress :construction: --- shipengine_sdk/events/__init__.py | 16 +++++++++++--- shipengine_sdk/jsonrpc/__init__.py | 4 ++-- shipengine_sdk/util/sdk_assertions.py | 3 ++- tests/events/test_request_sent_event.py | 26 +++++++++++++++++++++++ tests/models/address/test_address.py | 3 ++- tests/util/test_helpers.py | 28 +++++++++++++++++++++++++ 6 files changed, 73 insertions(+), 7 deletions(-) diff --git a/shipengine_sdk/events/__init__.py b/shipengine_sdk/events/__init__.py index d54be51..50b06d1 100644 --- a/shipengine_sdk/events/__init__.py +++ b/shipengine_sdk/events/__init__.py @@ -96,9 +96,11 @@ def __init__(self, events: Optional[List[str]] = None) -> None: def get_subscribers(self, event: Optional[str] = None): return self.events[event] - def register(self, event, subscriber, callback: Optional[Callable] = None): - if callback is None: - callback = getattr(subscriber, "update") + def register(self, event, subscriber, callback: Union[Callable, str] = None): + if event is Events.ON_REQUEST_SENT.value and callback is None: + callback = getattr(subscriber, "catch_request_sent_event") + elif event is Events.ON_RESPONSE_RECEIVED.value and callback is None: + callback = getattr(subscriber, "catch_response_received_event") self.get_subscribers(event)[subscriber] = callback def unregister(self, event, subscriber): @@ -126,6 +128,14 @@ def __init__(self, name=None) -> None: def update(event: Union[RequestSentEvent, ResponseReceivedEvent]): return event + @staticmethod + def catch_request_sent_event(event: RequestSentEvent): + return event + + @staticmethod + def catch_response_received_event(event: ResponseReceivedEvent): + return event + def to_dict(self): return (lambda o: o.__dict__)(self) diff --git a/shipengine_sdk/jsonrpc/__init__.py b/shipengine_sdk/jsonrpc/__init__.py index e6a3e3c..a3af602 100644 --- a/shipengine_sdk/jsonrpc/__init__.py +++ b/shipengine_sdk/jsonrpc/__init__.py @@ -23,6 +23,7 @@ def rpc_request_loop( ) -> Dict[str, Any]: client: ShipEngineClient = ShipEngineClient(config=config) retry: int = 0 + api_response = None while retry <= config.retries: try: api_response = client.send_rpc_request( @@ -35,8 +36,7 @@ def rpc_request_loop( and err.retry_after < config.timeout ): time.sleep(err.retry_after) - continue else: raise err retry += 1 - return api_response + return api_response diff --git a/shipengine_sdk/util/sdk_assertions.py b/shipengine_sdk/util/sdk_assertions.py index 740fb6b..b45c2a0 100644 --- a/shipengine_sdk/util/sdk_assertions.py +++ b/shipengine_sdk/util/sdk_assertions.py @@ -199,7 +199,8 @@ def check_response_for_errors(status_code: int, response_body: Dict[str, Any], c # Check if status_code is 429 and raises an error if so. if "error" in response_body and status_code == 429: error = response_body["error"] - retry_after = error["data"]["retryAfter"] + error_data = error["data"] + retry_after = error_data["details"]["retryAfter"] if retry_after > config.timeout: raise ClientTimeoutError( retry_after=config.timeout, diff --git a/tests/events/test_request_sent_event.py b/tests/events/test_request_sent_event.py index 497a2d7..1378fa6 100644 --- a/tests/events/test_request_sent_event.py +++ b/tests/events/test_request_sent_event.py @@ -38,3 +38,29 @@ def test_config_with_retries_disabled(self, mocker: MockerFixture) -> None: assert request_sent_spy.call_count == 1 assert type(response_recd_return) == ResponseReceivedEvent assert response_recd_return.retry == 0 + + # def test_config_with_custom_retries(self, mocker: MockerFixture) -> None: + # """DX-1528 - Test config with custom retries.""" + # request_sent_spy = mocker.spy(ShipEngineEventListener, "catch_request_sent_event") + # response_received_spy = mocker.spy(ShipEngineEventListener, "catch_response_received_event") + # shipengine = configurable_stub_shipengine_instance( + # { + # "api_key": "baz_sim", + # "base_uri": Endpoints.TEST_RPC_URL.value, + # "retries": 2, + # "timeout": 3, + # } + # ) + # try: + # shipengine.get_carrier_accounts(carrier_code="amazon_buy_shipping") + # except ShipEngineError as err: + # assert_on_429_exception(err=err, error_class=RateLimitExceededError) + # request_sent_return = request_sent_spy.spy_return + # assert request_sent_spy.call_count == 3 + # assert type(request_sent_return) == RequestSentEvent + # assert request_sent_return.retry == 2 + # + # response_recd_return = response_received_spy.spy_return + # assert request_sent_spy.call_count == 3 + # assert type(response_recd_return) == ResponseReceivedEvent + # assert response_recd_return.retry == 2 diff --git a/tests/models/address/test_address.py b/tests/models/address/test_address.py index 2c0856b..9d6350c 100644 --- a/tests/models/address/test_address.py +++ b/tests/models/address/test_address.py @@ -3,7 +3,8 @@ from shipengine_sdk.errors import ValidationError from shipengine_sdk.models import ErrorCode, ErrorSource, ErrorType -from tests.util.test_helpers import address_with_too_many_lines, empty_address_lines + +from ...util.test_helpers import address_with_too_many_lines, empty_address_lines def address_line_assertions(err: ValidationError, variant: str) -> None: diff --git a/tests/util/test_helpers.py b/tests/util/test_helpers.py index 9dbf8e2..d62cbe9 100644 --- a/tests/util/test_helpers.py +++ b/tests/util/test_helpers.py @@ -2,10 +2,14 @@ from typing import Dict, Optional, Union from shipengine_sdk import ShipEngine, ShipEngineConfig +from shipengine_sdk.errors import ShipEngineError from shipengine_sdk.models import ( Address, AddressValidateResult, Endpoints, + ErrorCode, + ErrorSource, + ErrorType, TrackingQuery, ) @@ -240,6 +244,17 @@ def address_with_invalid_postal_code() -> Address: ) +def get_429_address() -> Address: + """Return an address that fetches a 429 fixture from the server.""" + return Address( + street=["429 Rate Limit Error"], + city_locality="Boston", + state_province="MA", + postal_code="02215", + country_code="US", + ) + + def get_server_side_error() -> Address: """Return an address that will cause the server to return a 500 server error.""" return Address( @@ -392,3 +407,16 @@ def canada_valid_normalize_assertions( assert normalized_address.postal_code == "M6K 3C3" assert normalized_address.country_code == original_address.country_code.upper() assert normalized_address.is_residential is expected_residential_indicator + + +def assert_on_429_exception(err: ShipEngineError, error_class: object) -> None: + error = err.to_dict() + assert type(err) == error_class + assert error["request_id"] is not None + assert error["request_id"].startswith("req_") + assert error["source"] is ErrorSource.SHIPENGINE.value + assert error["error_type"] is ErrorType.SYSTEM.value + assert error["error_code"] is ErrorCode.RATE_LIMIT_EXCEEDED.value + assert error["message"] == "You have exceeded the rate limit." + assert error["url"] is not None + assert error["url"] == "https://www.shipengine.com/docs/rate-limits" From d62cd4cd4179d1c1db9c6c1ef1ba0e56e561ba9c Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Fri, 16 Jul 2021 11:27:48 -0500 Subject: [PATCH 34/49] AC per DX-1528 and rename of test file --- ...t_sent_event.py => test_emitted_events.py} | 55 ++++++++++--------- 1 file changed, 28 insertions(+), 27 deletions(-) rename tests/events/{test_request_sent_event.py => test_emitted_events.py} (54%) diff --git a/tests/events/test_request_sent_event.py b/tests/events/test_emitted_events.py similarity index 54% rename from tests/events/test_request_sent_event.py rename to tests/events/test_emitted_events.py index 1378fa6..b9a4a41 100644 --- a/tests/events/test_request_sent_event.py +++ b/tests/events/test_emitted_events.py @@ -8,18 +8,19 @@ ShipEngineEventListener, ) from shipengine_sdk.models import Endpoints +from shipengine_sdk.models.enums import Constants from ..util import assert_on_429_exception, configurable_stub_shipengine_instance -class TestRequestSentEvent: +class TestEmittedEvents: def test_config_with_retries_disabled(self, mocker: MockerFixture) -> None: """DX-1527 - Tests that the SDK does not automatically retry if retries in config is set to 0.""" request_sent_spy = mocker.spy(ShipEngineEventListener, "catch_request_sent_event") response_received_spy = mocker.spy(ShipEngineEventListener, "catch_response_received_event") shipengine = configurable_stub_shipengine_instance( { - "api_key": "baz_sim", + "api_key": Constants.API_KEY.value, "base_uri": Endpoints.TEST_RPC_URL.value, "retries": 0, "timeout": 10, @@ -39,28 +40,28 @@ def test_config_with_retries_disabled(self, mocker: MockerFixture) -> None: assert type(response_recd_return) == ResponseReceivedEvent assert response_recd_return.retry == 0 - # def test_config_with_custom_retries(self, mocker: MockerFixture) -> None: - # """DX-1528 - Test config with custom retries.""" - # request_sent_spy = mocker.spy(ShipEngineEventListener, "catch_request_sent_event") - # response_received_spy = mocker.spy(ShipEngineEventListener, "catch_response_received_event") - # shipengine = configurable_stub_shipengine_instance( - # { - # "api_key": "baz_sim", - # "base_uri": Endpoints.TEST_RPC_URL.value, - # "retries": 2, - # "timeout": 3, - # } - # ) - # try: - # shipengine.get_carrier_accounts(carrier_code="amazon_buy_shipping") - # except ShipEngineError as err: - # assert_on_429_exception(err=err, error_class=RateLimitExceededError) - # request_sent_return = request_sent_spy.spy_return - # assert request_sent_spy.call_count == 3 - # assert type(request_sent_return) == RequestSentEvent - # assert request_sent_return.retry == 2 - # - # response_recd_return = response_received_spy.spy_return - # assert request_sent_spy.call_count == 3 - # assert type(response_recd_return) == ResponseReceivedEvent - # assert response_recd_return.retry == 2 + def test_config_with_custom_retries(self, mocker: MockerFixture) -> None: + """DX-1528 - Test config with custom retries.""" + request_sent_spy = mocker.spy(ShipEngineEventListener, "catch_request_sent_event") + response_received_spy = mocker.spy(ShipEngineEventListener, "catch_response_received_event") + shipengine = configurable_stub_shipengine_instance( + { + "api_key": Constants.API_KEY.value, + "base_uri": Endpoints.TEST_RPC_URL.value, + "retries": 3, + "timeout": 3, + } + ) + try: + shipengine.get_carrier_accounts(carrier_code="amazon_buy_shipping") + except ShipEngineError as err: + assert_on_429_exception(err=err, error_class=RateLimitExceededError) + request_sent_return = request_sent_spy.spy_return + assert request_sent_spy.call_count == 4 + assert type(request_sent_return) == RequestSentEvent + assert request_sent_return.retry == 3 + + response_recd_return = response_received_spy.spy_return + assert request_sent_spy.call_count == 4 + assert type(response_recd_return) == ResponseReceivedEvent + assert response_recd_return.retry == 3 From e8ef2a06c8b74dba26cd753e95f2a1282e2619bd Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Fri, 16 Jul 2021 11:29:35 -0500 Subject: [PATCH 35/49] Update test API_KEY and refactor tests to use it --- poetry.lock | 53 ++++++++++++++++++- pyproject.toml | 1 + requirements.txt | 2 + shipengine_sdk/models/enums/__init__.py | 7 +++ shipengine_sdk/models/package/__init__.py | 4 +- tests/models/address/test_address.py | 3 +- tests/models/track_package/test_shipment.py | 3 +- .../test_track_package_result.py | 3 +- tests/util/test_helpers.py | 3 +- 9 files changed, 71 insertions(+), 8 deletions(-) diff --git a/poetry.lock b/poetry.lock index e647c18..4e38846 100644 --- a/poetry.lock +++ b/poetry.lock @@ -562,6 +562,20 @@ pytest = ">=5.0" [package.extras] dev = ["pre-commit", "tox", "pytest-asyncio"] +[[package]] +name = "pytest-watch" +version = "4.2.0" +description = "Local continuous test runner with pytest and watchdog." +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +colorama = ">=0.3.3" +docopt = ">=0.4.0" +pytest = ">=2.6.4" +watchdog = ">=0.6.0" + [[package]] name = "python-dotenv" version = "0.15.0" @@ -848,6 +862,17 @@ six = ">=1.9.0,<2" docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"] testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)", "xonsh (>=0.9.16)"] +[[package]] +name = "watchdog" +version = "2.1.3" +description = "Filesystem events monitoring" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +watchmedo = ["PyYAML (>=3.10)", "argh (>=0.24.1)"] + [[package]] name = "yarl" version = "1.6.3" @@ -876,7 +901,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "f0261212a97f8b5644e21852153f644bf5ac5b4d65dd34b06df45d67a4ec984c" +content-hash = "44a12436d6071b62002beab90bd98605720f47f4c08c492d52f1b482efaeadbf" [metadata.files] aiohttp = [ @@ -1231,6 +1256,9 @@ pytest-mock = [ {file = "pytest-mock-3.6.1.tar.gz", hash = "sha256:40217a058c52a63f1042f0784f62009e976ba824c418cced42e88d5f40ab0e62"}, {file = "pytest_mock-3.6.1-py3-none-any.whl", hash = "sha256:30c2f2cc9759e76eee674b81ea28c9f0b94f8f0445a1b87762cadf774f0df7e3"}, ] +pytest-watch = [ + {file = "pytest-watch-4.2.0.tar.gz", hash = "sha256:06136f03d5b361718b8d0d234042f7b2f203910d8568f63df2f866b547b3d4b9"}, +] python-dotenv = [ {file = "python-dotenv-0.15.0.tar.gz", hash = "sha256:587825ed60b1711daea4832cf37524dfd404325b7db5e25ebe88c495c9f807a0"}, {file = "python_dotenv-0.15.0-py2.py3-none-any.whl", hash = "sha256:0c8d1b80d1a1e91717ea7d526178e3882732420b03f08afea0406db6402e220e"}, @@ -1418,6 +1446,29 @@ virtualenv = [ {file = "virtualenv-20.4.7-py2.py3-none-any.whl", hash = "sha256:2b0126166ea7c9c3661f5b8e06773d28f83322de7a3ff7d06f0aed18c9de6a76"}, {file = "virtualenv-20.4.7.tar.gz", hash = "sha256:14fdf849f80dbb29a4eb6caa9875d476ee2a5cf76a5f5415fa2f1606010ab467"}, ] +watchdog = [ + {file = "watchdog-2.1.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9628f3f85375a17614a2ab5eac7665f7f7be8b6b0a2a228e6f6a2e91dd4bfe26"}, + {file = "watchdog-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:acc4e2d5be6f140f02ee8590e51c002829e2c33ee199036fcd61311d558d89f4"}, + {file = "watchdog-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:85b851237cf3533fabbc034ffcd84d0fa52014b3121454e5f8b86974b531560c"}, + {file = "watchdog-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a12539ecf2478a94e4ba4d13476bb2c7a2e0a2080af2bb37df84d88b1b01358a"}, + {file = "watchdog-2.1.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6fe9c8533e955c6589cfea6f3f0a1a95fb16867a211125236c82e1815932b5d7"}, + {file = "watchdog-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d9456f0433845e7153b102fffeb767bde2406b76042f2216838af3b21707894e"}, + {file = "watchdog-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fd8c595d5a93abd441ee7c5bb3ff0d7170e79031520d113d6f401d0cf49d7c8f"}, + {file = "watchdog-2.1.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0bcfe904c7d404eb6905f7106c54873503b442e8e918cc226e1828f498bdc0ca"}, + {file = "watchdog-2.1.3-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bf84bd94cbaad8f6b9cbaeef43080920f4cb0e61ad90af7106b3de402f5fe127"}, + {file = "watchdog-2.1.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b8ddb2c9f92e0c686ea77341dcb58216fa5ff7d5f992c7278ee8a392a06e86bb"}, + {file = "watchdog-2.1.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8805a5f468862daf1e4f4447b0ccf3acaff626eaa57fbb46d7960d1cf09f2e6d"}, + {file = "watchdog-2.1.3-py3-none-manylinux2014_armv7l.whl", hash = "sha256:3e305ea2757f81d8ebd8559d1a944ed83e3ab1bdf68bcf16ec851b97c08dc035"}, + {file = "watchdog-2.1.3-py3-none-manylinux2014_i686.whl", hash = "sha256:431a3ea70b20962e6dee65f0eeecd768cd3085ea613ccb9b53c8969de9f6ebd2"}, + {file = "watchdog-2.1.3-py3-none-manylinux2014_ppc64.whl", hash = "sha256:e4929ac2aaa2e4f1a30a36751160be391911da463a8799460340901517298b13"}, + {file = "watchdog-2.1.3-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:201cadf0b8c11922f54ec97482f95b2aafca429c4c3a4bb869a14f3c20c32686"}, + {file = "watchdog-2.1.3-py3-none-manylinux2014_s390x.whl", hash = "sha256:3a7d242a7963174684206093846537220ee37ba9986b824a326a8bb4ef329a33"}, + {file = "watchdog-2.1.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:54e057727dd18bd01a3060dbf5104eb5a495ca26316487e0f32a394fd5fe725a"}, + {file = "watchdog-2.1.3-py3-none-win32.whl", hash = "sha256:b5fc5c127bad6983eecf1ad117ab3418949f18af9c8758bd10158be3647298a9"}, + {file = "watchdog-2.1.3-py3-none-win_amd64.whl", hash = "sha256:44acad6f642996a2b50bb9ce4fb3730dde08f23e79e20cd3d8e2a2076b730381"}, + {file = "watchdog-2.1.3-py3-none-win_ia64.whl", hash = "sha256:0bcdf7b99b56a3ae069866c33d247c9994ffde91b620eaf0306b27e099bd1ae0"}, + {file = "watchdog-2.1.3.tar.gz", hash = "sha256:e5236a8e8602ab6db4b873664c2d356c365ab3cac96fbdec4970ad616415dd45"}, +] yarl = [ {file = "yarl-1.6.3-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434"}, {file = "yarl-1.6.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478"}, diff --git a/pyproject.toml b/pyproject.toml index 156fadb..1270093 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ responses = "^0.13.3" coveralls = "^3.1.0" pre-commit = "2.13.0" pytest-cache = "^1.0" +pytest-watch = "^4.2.0" [tool.black] line-length = 100 diff --git a/requirements.txt b/requirements.txt index ece45c6..ec44660 100644 --- a/requirements.txt +++ b/requirements.txt @@ -49,6 +49,7 @@ pytest==6.2.4; python_version >= "3.6" pytest-cache==1.0 pytest-cov==2.12.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") pytest-mock==3.6.1; python_version >= "3.6" +pytest-watch==4.2.0 python-dotenv==0.15.0 pytz==2021.1; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5" pyyaml==5.4.1; python_full_version >= "3.6.1" @@ -72,5 +73,6 @@ typing-extensions==3.10.0.0; python_version < "3.8" and python_version >= "3.6" typing-inspect==0.7.1; python_version >= "3.6" urllib3==1.26.6; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version < "4" and python_version >= "3.5" virtualenv==20.4.7; python_full_version >= "3.6.1" +watchdog==2.1.3; python_version >= "3.6" yarl==1.6.3; python_version >= "3.6" zipp==3.5.0; python_version < "3.8" and python_version >= "3.6" diff --git a/shipengine_sdk/models/enums/__init__.py b/shipengine_sdk/models/enums/__init__.py index e0c228e..b7ec0b2 100644 --- a/shipengine_sdk/models/enums/__init__.py +++ b/shipengine_sdk/models/enums/__init__.py @@ -9,6 +9,13 @@ from .regex_patterns import RegexPatterns +class Constants(Enum): + """Test API Key for use with Simengine.""" + + API_KEY = "TEST_vMiVbICUjBz4BZjq0TRBLC/9MrxY4+yjvb1G1RMxlJs" + CARRIER_ACCOUNT_ID_STUB = "car_41GrQHn5uouiPZc2TNE6PU29tZU9ud" + + class Endpoints(Enum): """API Endpoint URI's used throughout the ShipEngine SDK.""" diff --git a/shipengine_sdk/models/package/__init__.py b/shipengine_sdk/models/package/__init__.py index b204fa9..daeeff0 100644 --- a/shipengine_sdk/models/package/__init__.py +++ b/shipengine_sdk/models/package/__init__.py @@ -17,7 +17,7 @@ class Shipment: shipment_id: Optional[str] = None account_id: Optional[str] = None carrier_account: Optional[CarrierAccount] = None - carrier: Carrier + carrier: Optional[Carrier] = None estimated_delivery_date: Union[IsoString, str] actual_delivery_date: Union[IsoString, str] @@ -77,7 +77,7 @@ def to_json(self) -> str: return json.dumps(self, default=lambda o: o.__dict__, indent=2) def __repr__(self): - return f"Shipment({self.shipment_id}, {self.account_id}, {self.carrier_account}, {self.carrier}, {self.estimated_delivery_date}, {self.actual_delivery_date})" # noqa + return f"Shipment({self.shipment_id}, {self.account_id})" class Package: diff --git a/tests/models/address/test_address.py b/tests/models/address/test_address.py index 9d6350c..150c6f7 100644 --- a/tests/models/address/test_address.py +++ b/tests/models/address/test_address.py @@ -3,8 +3,7 @@ from shipengine_sdk.errors import ValidationError from shipengine_sdk.models import ErrorCode, ErrorSource, ErrorType - -from ...util.test_helpers import address_with_too_many_lines, empty_address_lines +from tests.util import address_with_too_many_lines, empty_address_lines def address_line_assertions(err: ValidationError, variant: str) -> None: diff --git a/tests/models/track_package/test_shipment.py b/tests/models/track_package/test_shipment.py index 995a700..ba30ac7 100644 --- a/tests/models/track_package/test_shipment.py +++ b/tests/models/track_package/test_shipment.py @@ -5,6 +5,7 @@ from shipengine_sdk.errors import ShipEngineError from shipengine_sdk.models import Shipment +from shipengine_sdk.models.enums import Constants from shipengine_sdk.util.iso_string import IsoString from ...util import stub_shipengine_config @@ -17,7 +18,7 @@ def stub_valid_shipment_data() -> Dict[str, Any]: """ return { "carrierCode": "fedex", - "carrierAccountId": "car_kfUjTZSEAQ8gHeT", + "carrierAccountId": Constants.CARRIER_ACCOUNT_ID_STUB.value, "shipmentId": "shp_yuh3GkfUjTZSEAQ", "estimatedDelivery": "2021-06-15T21:00:00.000Z", } diff --git a/tests/models/track_package/test_track_package_result.py b/tests/models/track_package/test_track_package_result.py index 2e27234..f7541a0 100644 --- a/tests/models/track_package/test_track_package_result.py +++ b/tests/models/track_package/test_track_package_result.py @@ -2,6 +2,7 @@ from typing import Any, Dict from shipengine_sdk.models import TrackingEvent, TrackPackageResult +from shipengine_sdk.models.enums import Constants from ...util import stub_shipengine_config @@ -17,7 +18,7 @@ def stub_track_package_data() -> Dict[str, Any]: "result": { "shipment": { "carrierCode": "fedex", - "carrierAccountId": "car_kfUjTZSEAQ8gHeT", + "carrierAccountId": Constants.CARRIER_ACCOUNT_ID_STUB.value, "shipmentId": "shp_tJUaQJz3Twz57iL", "estimatedDelivery": "2021-06-15T21:00:00.000Z", }, diff --git a/tests/util/test_helpers.py b/tests/util/test_helpers.py index d62cbe9..75eaef3 100644 --- a/tests/util/test_helpers.py +++ b/tests/util/test_helpers.py @@ -12,6 +12,7 @@ ErrorType, TrackingQuery, ) +from shipengine_sdk.models.enums import Constants def stub_config( @@ -22,7 +23,7 @@ def stub_config( when instantiating the ShipEngine object. """ return dict( - api_key="baz_sim", + api_key=Constants.API_KEY.value, base_uri=Endpoints.TEST_RPC_URL.value, page_size=50, retries=retries, From 73351574b41afc7c10dcd7abdee7ec4ac9de3a6c Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Fri, 16 Jul 2021 11:30:03 -0500 Subject: [PATCH 36/49] Added a setup script to improve dev QOL --- bin/setup | 5 +++++ 1 file changed, 5 insertions(+) create mode 100755 bin/setup diff --git a/bin/setup b/bin/setup new file mode 100755 index 0000000..1ef36e2 --- /dev/null +++ b/bin/setup @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +poetry install +pre-commit install +poetry shell From 94b6df6df64a603297d004c0340e11902ed9981b Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Fri, 16 Jul 2021 11:30:37 -0500 Subject: [PATCH 37/49] Updated documentation to reflect the use of bin/setup --- README.md | 31 ++++++++----------------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index bd2e98d..016cc5e 100644 --- a/README.md +++ b/README.md @@ -76,18 +76,13 @@ on `osx / linux / bashonwindows`: curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python - ``` -- Once you have **Poetry** installed you need to install the projects dependencies with this command: +- Once you have **Poetry** installed you need to install the projects dependencies with this command from the project root: ```bash -poetry install +bin/setup ``` - -- After you have installed **Poetry**, you need to start the python environment managed by Poetry by - running `poetry shell` in your terminal. - -```bash -poetry shell - ``` +- This script will install all dependencies specified in `pyproject.toml` via `Poetry` and install the `pre-commit` hooks +this project uses. ## Adding dependencies to the project If your changes require you to install a python package/module using `poetry add ` or @@ -100,21 +95,11 @@ poetry export -f requirements.txt --output requirements.txt --without-hashes --d ## Pre-Commit Hooks We are using [Pre-Commit](https://pre-commit.com/) to enforce formatting, lint rules, and code analysis so that this repo is always in good health. - - If you choose not to globally install `pre-commit`, then you can skip installing via `pip` or `homebrew` directly. - You can simply run either `pip install -r requirements.txt` or `poetry install` -To be able to push a PR to the repo after making changes locally, you will need to install `pre-commit` which -is a tool that runs linting, formatting, and code analysis on your changes. -```bash -pip install pre-commit # Install via pip - -OR +- `Pre-Commit` is installed and initialized when you run `bin/setup` from the project root as outlined above. -brew install pre-commit # Install via homebrew -``` -- After you have run either `pip install -r requirements.txt`, `poetry install`, or globally installed - [Pre-Commit](https://pre-commit.com/) using the above commands you need to run the following command - in the project directory locally. This allows the pre-commit hooks to run when you are looking to commit - and push code to this repository. +- If you choose not to use `Poetry` and prefer `pip` you can simply run `pip install -r requirements.txt` +To be able to commit & push a PR to the repo after making changes locally, you will need to install `pre-commit` which +is a tool that runs tests, linting, formatting, and code analysis on your changes. ```bash pre-commit install ``` From 773bf475c3d38e664756971df579ebda1112dcc5 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Mon, 19 Jul 2021 16:58:21 -0500 Subject: [PATCH 38/49] Config and emitted event tests --- tests/events/test_emitted_events.py | 187 ++++++++++++++++++++++++-- tests/http_client/test_http_client.py | 5 +- tests/test_shipengine_config.py | 19 ++- tests/util/test_helpers.py | 4 +- 4 files changed, 190 insertions(+), 25 deletions(-) diff --git a/tests/events/test_emitted_events.py b/tests/events/test_emitted_events.py index b9a4a41..ab5a604 100644 --- a/tests/events/test_emitted_events.py +++ b/tests/events/test_emitted_events.py @@ -1,27 +1,132 @@ """Test that `RequestSentEvents` are emitted from the SDK properly.""" +from datetime import datetime + from pytest_mock import MockerFixture -from shipengine_sdk.errors import RateLimitExceededError, ShipEngineError +from shipengine_sdk import __version__ +from shipengine_sdk.errors import ( + ClientTimeoutError, + RateLimitExceededError, + ShipEngineError, +) from shipengine_sdk.events import ( RequestSentEvent, ResponseReceivedEvent, ShipEngineEventListener, ) -from shipengine_sdk.models import Endpoints +from shipengine_sdk.models import ErrorCode, ErrorSource, ErrorType from shipengine_sdk.models.enums import Constants -from ..util import assert_on_429_exception, configurable_stub_shipengine_instance +from ..util import ( + assert_on_429_exception, + configurable_stub_shipengine_instance, + valid_residential_address, +) class TestEmittedEvents: + def test_user_agent_includes_correct_sdk_version(self, mocker: MockerFixture) -> None: + """DX-1517 - Test user agent includes correct SDK version.""" + request_sent_spy = mocker.spy(ShipEngineEventListener, "catch_request_sent_event") + config = { + "api_key": Constants.STUB_API_KEY.value, + "retries": 1, + "timeout": 10, + } + shipengine = configurable_stub_shipengine_instance(config=config) + shipengine.validate_address(address=valid_residential_address()) + request_sent_return = request_sent_spy.spy_return + assert request_sent_return.headers["User-Agent"].split(" ")[0].split("/")[1] == __version__ + + def test_request_sent_event_on_retries(self, mocker: MockerFixture) -> None: + """DX-1521 - Test that a RequestSentEvent is emitted on retries.""" + request_sent_spy = mocker.spy(ShipEngineEventListener, "catch_request_sent_event") + config = { + "api_key": Constants.STUB_API_KEY.value, + "retries": 1, + "timeout": 10, + } + shipengine = configurable_stub_shipengine_instance(config=config) + try: + shipengine.get_carrier_accounts(carrier_code="amazon_buy_shipping") + except ShipEngineError as err: + assert_on_429_exception(err=err, error_class=RateLimitExceededError) + + request_sent_return = request_sent_spy.spy_return + assert request_sent_spy.call_count == 2 + assert type(request_sent_return) == RequestSentEvent + assert request_sent_return.retry == 1 + assert type(request_sent_return.timestamp) == datetime + assert request_sent_return.timeout == config["timeout"] + assert request_sent_return.body["method"] == "carrier.listAccounts.v1" + assert request_sent_return.base_uri == "https://api.shipengine.com/jsonrpc" + assert request_sent_return.headers["Api-Key"] == Constants.STUB_API_KEY.value + assert request_sent_return.headers["Content-Type"] == "application/json" + assert ( + request_sent_return.message == "Retrying the ShipEngine carrier.listAccounts.v1 API" + " at https://api.shipengine.com/jsonrpc" + ) + + def test_response_received_event_success(self, mocker: MockerFixture) -> None: + """DX-1522 Test response received event success.""" + test_start_time = datetime.now() + response_received_spy = mocker.spy(ShipEngineEventListener, "catch_response_received_event") + config = { + "api_key": Constants.STUB_API_KEY.value, + "retries": 2, + "timeout": 10, + } + shipengine = configurable_stub_shipengine_instance(config=config) + shipengine.validate_address(address=valid_residential_address()) + + response_recd_return = response_received_spy.spy_return + assert response_received_spy.call_count == 1 + assert type(response_recd_return) == ResponseReceivedEvent + assert ( + response_recd_return.message + == "Received an HTTP 200 response from the ShipEngine address.validate.v1 API" + ) + assert response_recd_return.status_code == 200 + assert response_recd_return.base_uri == "https://api.shipengine.com/jsonrpc" + assert response_recd_return.body["method"] == "address.validate.v1" + assert response_recd_return.retry == 0 + assert response_recd_return.elapsed < test_start_time.second + assert response_recd_return.headers["Content-Type"].split(";")[0] == "application/json" + + def test_response_received_on_error(self, mocker: MockerFixture) -> None: + """DX-1523 - Test response received event on error.""" + test_start_time = datetime.now() + response_received_spy = mocker.spy(ShipEngineEventListener, "catch_response_received_event") + config = { + "api_key": Constants.STUB_API_KEY.value, + "retries": 1, + "timeout": 10, + } + shipengine = configurable_stub_shipengine_instance(config=config) + try: + shipengine.get_carrier_accounts(carrier_code="amazon_buy_shipping") + except ShipEngineError as err: + assert_on_429_exception(err=err, error_class=RateLimitExceededError) + response_recd_return = response_received_spy.spy_return + assert type(response_recd_return) == ResponseReceivedEvent + assert ( + response_recd_return.message + == "Retrying the ShipEngine carrier.listAccounts.v1 API at https://api.shipengine.com/jsonrpc" + ) + assert response_recd_return.status_code == 429 + assert response_recd_return.base_uri == "https://api.shipengine.com/jsonrpc" + assert response_recd_return.body["method"] == "carrier.listAccounts.v1" + assert response_recd_return.retry == 1 + assert response_recd_return.elapsed < test_start_time.second + assert (response_recd_return.timestamp - test_start_time).total_seconds() > 1 + def test_config_with_retries_disabled(self, mocker: MockerFixture) -> None: """DX-1527 - Tests that the SDK does not automatically retry if retries in config is set to 0.""" request_sent_spy = mocker.spy(ShipEngineEventListener, "catch_request_sent_event") response_received_spy = mocker.spy(ShipEngineEventListener, "catch_response_received_event") shipengine = configurable_stub_shipengine_instance( { - "api_key": Constants.API_KEY.value, - "base_uri": Endpoints.TEST_RPC_URL.value, + "api_key": Constants.STUB_API_KEY.value, "retries": 0, "timeout": 10, } @@ -46,10 +151,9 @@ def test_config_with_custom_retries(self, mocker: MockerFixture) -> None: response_received_spy = mocker.spy(ShipEngineEventListener, "catch_response_received_event") shipengine = configurable_stub_shipengine_instance( { - "api_key": Constants.API_KEY.value, - "base_uri": Endpoints.TEST_RPC_URL.value, + "api_key": Constants.STUB_API_KEY.value, "retries": 3, - "timeout": 3, + "timeout": 21, } ) try: @@ -65,3 +169,70 @@ def test_config_with_custom_retries(self, mocker: MockerFixture) -> None: assert request_sent_spy.call_count == 4 assert type(response_recd_return) == ResponseReceivedEvent assert response_recd_return.retry == 3 + + def test_timeout_err_when_retry_greater_than_timeout(self, mocker: MockerFixture) -> None: + """DX-1529 - Test timeout error when retry_after is greater than timeout.""" + request_sent_spy = mocker.spy(ShipEngineEventListener, "catch_request_sent_event") + response_received_spy = mocker.spy(ShipEngineEventListener, "catch_response_received_event") + config = { + "api_key": Constants.STUB_API_KEY.value, + "retries": 3, + "timeout": 1, + } + shipengine = configurable_stub_shipengine_instance(config=config) + try: + shipengine.get_carrier_accounts(carrier_code="amazon_buy_shipping") + except ShipEngineError as err: + assert type(err) == ClientTimeoutError + assert err.request_id is not None + assert err.request_id.startswith("req_") + assert ( + err.message + == f"The request took longer than the {config['timeout']} seconds allowed." + ) + assert err.source is ErrorSource.SHIPENGINE.value + assert err.error_type is ErrorType.SYSTEM.value + assert err.error_code is ErrorCode.TIMEOUT.value + assert err.url == "https://www.shipengine.com/docs/rate-limits" + + request_sent_return = request_sent_spy.spy_return + assert request_sent_spy.call_count == 1 + assert type(request_sent_return) == RequestSentEvent + assert request_sent_return.retry == 0 + assert request_sent_return.timeout == 1 + + response_recd_return = response_received_spy.spy_return + assert request_sent_spy.call_count == 1 + assert type(response_recd_return) == ResponseReceivedEvent + assert response_recd_return.retry == 0 + + def test_retry_waits_correct_amount_of_time(self, mocker: MockerFixture) -> None: + """DX-1530 - retry waits the correct amount of time.""" + test_start_time = datetime.now() + request_sent_spy = mocker.spy(ShipEngineEventListener, "catch_request_sent_event") + response_received_spy = mocker.spy(ShipEngineEventListener, "catch_response_received_event") + shipengine = configurable_stub_shipengine_instance( + { + "api_key": Constants.STUB_API_KEY.value, + "retries": 2, + "timeout": 10, + } + ) + try: + shipengine.get_carrier_accounts(carrier_code="amazon_buy_shipping") + except ShipEngineError as err: + assert_on_429_exception(err=err, error_class=RateLimitExceededError) + + request_sent_return = request_sent_spy.spy_return + assert request_sent_spy.call_count == 3 + assert type(request_sent_return) == RequestSentEvent + assert request_sent_return.retry == 2 + assert request_sent_return.timeout == 10 + + response_recd_return = response_received_spy.spy_return + assert request_sent_spy.call_count == 3 + assert type(response_recd_return) == ResponseReceivedEvent + assert response_recd_return.retry == 2 + assert ( + int(str(round((test_start_time - datetime.now()).total_seconds())).strip("-")) == 5 + ) diff --git a/tests/http_client/test_http_client.py b/tests/http_client/test_http_client.py index af6884a..b41776e 100644 --- a/tests/http_client/test_http_client.py +++ b/tests/http_client/test_http_client.py @@ -13,7 +13,6 @@ def validate_address(address): shipengine = ShipEngine( dict( api_key="baz", - base_uri=Endpoints.TEST_RPC_URL.value, page_size=50, retries=2, timeout=10, @@ -47,7 +46,7 @@ class TestShipEngineClient: def test_500_server_response(self): responses.add( responses.POST, - Endpoints.TEST_RPC_URL.value, + Endpoints.SHIPENGINE_RPC_URL.value, json={ "jsonrpc": "2.0", "id": "req_DezVNUvRkAP819f3JeqiuS", @@ -74,7 +73,7 @@ def test_500_server_response(self): def test_404_server_response(self): responses.add( responses.POST, - Endpoints.TEST_RPC_URL.value, + Endpoints.SHIPENGINE_RPC_URL.value, json={ "jsonrpc": "2.0", "id": "req_DezVNUvRkAP819f3JeqiuS", diff --git a/tests/test_shipengine_config.py b/tests/test_shipengine_config.py index 5357b74..81b392f 100644 --- a/tests/test_shipengine_config.py +++ b/tests/test_shipengine_config.py @@ -14,9 +14,7 @@ def stub_config() -> dict: Return a test configuration dictionary to be used when instantiating the ShipEngine object. """ - return dict( - api_key="baz", base_uri=Endpoints.TEST_RPC_URL.value, page_size=50, retries=2, timeout=15 - ) + return dict(api_key="baz_sim", page_size=50, retries=2, timeout=15) def valid_residential_address() -> Address: @@ -59,7 +57,7 @@ def set_config_timeout(timeout: int) -> ShipEngineConfig: :raises: :class:`InvalidFieldValueError`: If invalid value is passed into `ShipEngineConfig` object at instantiation. """ - return ShipEngineConfig(dict(api_key="baz", timeout=timeout)) + return ShipEngineConfig(dict(api_key="baz_sim", timeout=timeout)) def set_config_retries(retries: int) -> ShipEngineConfig: @@ -73,7 +71,7 @@ def set_config_retries(retries: int) -> ShipEngineConfig: :raises: :class:`InvalidFieldValueError`: If invalid value is passed into `ShipEngineConfig` object at instantiation. """ - return ShipEngineConfig(dict(api_key="baz", retries=retries)) + return ShipEngineConfig(dict(api_key="baz_sim", retries=retries)) def complete_valid_config() -> ShipEngineConfig: @@ -83,8 +81,7 @@ def complete_valid_config() -> ShipEngineConfig: """ return ShipEngineConfig( dict( - api_key="baz", - base_uri=Endpoints.TEST_RPC_URL.value, + api_key="baz_sim", page_size=50, retries=2, timeout=10, @@ -99,8 +96,8 @@ def test_valid_custom_config(self): valid values for each attribute. """ valid_config: ShipEngineConfig = complete_valid_config() - assert valid_config.api_key == "baz" - assert valid_config.base_uri is Endpoints.TEST_RPC_URL.value + assert valid_config.api_key == "baz_sim" + assert valid_config.base_uri is Endpoints.SHIPENGINE_RPC_URL.value assert valid_config.page_size == 50 assert valid_config.retries == 2 assert valid_config.timeout == 10 @@ -127,7 +124,7 @@ def test_valid_retries(self): """Test case where a valid value is passed in for the retries.""" retries = 2 valid_retries = set_config_retries(retries) - assert valid_retries.api_key == "baz" + assert valid_retries.api_key == "baz_sim" assert valid_retries.retries == retries def test_invalid_retries_provided(self): @@ -195,7 +192,7 @@ def test_invalid_api_key_in_method_call(self): def test_config_defaults(self) -> None: """Test default retries.""" - config = ShipEngineConfig(dict(api_key="baz")) + config = ShipEngineConfig(dict(api_key="baz_sim")) assert config.retries == 1 assert config.page_size == 50 diff --git a/tests/util/test_helpers.py b/tests/util/test_helpers.py index 75eaef3..706155a 100644 --- a/tests/util/test_helpers.py +++ b/tests/util/test_helpers.py @@ -6,7 +6,6 @@ from shipengine_sdk.models import ( Address, AddressValidateResult, - Endpoints, ErrorCode, ErrorSource, ErrorType, @@ -23,8 +22,7 @@ def stub_config( when instantiating the ShipEngine object. """ return dict( - api_key=Constants.API_KEY.value, - base_uri=Endpoints.TEST_RPC_URL.value, + api_key=Constants.STUB_API_KEY.value, page_size=50, retries=retries, timeout=15, From cd4c92244080f33f067c56d53a2f55f9904d3da1 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Mon, 19 Jul 2021 17:00:39 -0500 Subject: [PATCH 39/49] Fixed while loop that manages request retries --- shipengine_sdk/jsonrpc/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shipengine_sdk/jsonrpc/__init__.py b/shipengine_sdk/jsonrpc/__init__.py index a3af602..7502077 100644 --- a/shipengine_sdk/jsonrpc/__init__.py +++ b/shipengine_sdk/jsonrpc/__init__.py @@ -23,7 +23,6 @@ def rpc_request_loop( ) -> Dict[str, Any]: client: ShipEngineClient = ShipEngineClient(config=config) retry: int = 0 - api_response = None while retry <= config.retries: try: api_response = client.send_rpc_request( @@ -36,7 +35,8 @@ def rpc_request_loop( and err.retry_after < config.timeout ): time.sleep(err.retry_after) + retry += 1 + continue else: raise err - retry += 1 - return api_response + return api_response From fa2265fcda80194fe37da7ce721ebe3d32db8695 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Mon, 19 Jul 2021 17:01:44 -0500 Subject: [PATCH 40/49] Updated response_headers in respnose event emission --- shipengine_sdk/events/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shipengine_sdk/events/__init__.py b/shipengine_sdk/events/__init__.py index 50b06d1..d0a5cde 100644 --- a/shipengine_sdk/events/__init__.py +++ b/shipengine_sdk/events/__init__.py @@ -173,7 +173,7 @@ def emit_event(emitted_event_type: str, event_data, dispatcher: Dispatcher): request_id=event_data.id, base_uri=event_data.base_uri, status_code=event_data.status_code, - headers=event_data.request_headers, + headers=event_data.response_headers, body=event_data.body, retry=event_data.retry, elapsed=event_data.elapsed, From e714934956df1ef615f18af9203d4e066353a581 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Mon, 19 Jul 2021 17:02:26 -0500 Subject: [PATCH 41/49] Added pytest-mock to spy on events and their methods --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 84fe137..f69bfac 100644 --- a/tox.ini +++ b/tox.ini @@ -30,6 +30,7 @@ changedir = tests deps = pytest pytest-cov + pytest-mock flake8 coverage coveralls From 956362e6e061e23c2a160aca0239c051fcca7a0c Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Mon, 19 Jul 2021 17:03:40 -0500 Subject: [PATCH 42/49] Improved the event message and event workflow --- shipengine_sdk/http_client/client.py | 31 ++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/shipengine_sdk/http_client/client.py b/shipengine_sdk/http_client/client.py index 2e1a0da..ae2bf2a 100644 --- a/shipengine_sdk/http_client/client.py +++ b/shipengine_sdk/http_client/client.py @@ -33,7 +33,19 @@ def base_url(config) -> str: return config.base_uri if os.getenv("CLIENT_BASE_URI") is None else os.getenv("CLIENT_BASE_URI") -def generate_event_message(retry: int, method: str, base_uri: str) -> str: +def generate_event_message( + retry: int, + method: str, + base_uri: str, + status_code: Optional[int] = None, + message_type: Optional[str] = None, +) -> str: + if message_type == "received": + if retry > 0: + f"Retrying the ShipEngine {method} API at {base_uri}" + else: + return f"Received an HTTP {status_code} response from the ShipEngine {method} API" + if retry == 0: return ShipEngineEvent.new_event_message( method=method, base_uri=base_uri, message_type="base_message" @@ -44,11 +56,12 @@ def generate_event_message(retry: int, method: str, base_uri: str) -> str: ) -def request_headers(user_agent: str) -> Dict[str, Any]: +def request_headers(user_agent: str, api_key: str) -> Dict[str, Any]: return { "User-Agent": user_agent, "Content-Type": "application/json", "Accept": "application/json", + "Api-Key": api_key, } @@ -86,7 +99,7 @@ def send_rpc_request( base_uri = base_url(config=config) request_body: Dict[str, Any] = wrap_request(method=method, params=params) - req_headers = request_headers(self._derive_user_agent()) + req_headers = request_headers(user_agent=self._derive_user_agent(), api_key=config.api_key) req: Request = Request( method="POST", url=base_uri, @@ -128,8 +141,15 @@ def send_rpc_request( resp_body: Dict[str, Any] = resp.json() status_code: int = resp.status_code + response_received_message = generate_event_message( + retry=retry, + method=method, + base_uri=base_uri, + status_code=status_code, + message_type="received", + ) response_event_data = EventOptions( - message=request_event_message, + message=response_received_message, id=request_body["id"], base_uri=base_uri, status_code=status_code, @@ -175,8 +195,7 @@ def _derive_user_agent() -> str: :rtype: str """ sdk_version: str = f"shipengine-python/{__version__}" - os_kernel: str = platform.platform(terse=True) python_version: str = platform.python_version() python_implementation: str = platform.python_implementation() - return f"{sdk_version} {os_kernel} {python_version} {python_implementation}" + return f"{sdk_version} {python_implementation}-v{python_version}" From 8aa3e40529197bd7ab7b679c5cf73a70777d6036 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Mon, 19 Jul 2021 17:04:36 -0500 Subject: [PATCH 43/49] Using a fake ApiKey for use with simengine and removed test rpc url member and value from Endpoints enum --- shipengine_sdk/models/enums/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shipengine_sdk/models/enums/__init__.py b/shipengine_sdk/models/enums/__init__.py index b7ec0b2..be38bf1 100644 --- a/shipengine_sdk/models/enums/__init__.py +++ b/shipengine_sdk/models/enums/__init__.py @@ -12,14 +12,13 @@ class Constants(Enum): """Test API Key for use with Simengine.""" - API_KEY = "TEST_vMiVbICUjBz4BZjq0TRBLC/9MrxY4+yjvb1G1RMxlJs" + STUB_API_KEY = "TEST_vMiVbICUjBz4BZjq0TRBLC/9MrxY4+yjvb1G1RMxlJs" CARRIER_ACCOUNT_ID_STUB = "car_41GrQHn5uouiPZc2TNE6PU29tZU9ud" class Endpoints(Enum): """API Endpoint URI's used throughout the ShipEngine SDK.""" - TEST_RPC_URL = "https://shipengine-web-api.herokuapp.com/jsonrpc" SHIPENGINE_RPC_URL = "https://api.shipengine.com/jsonrpc" From 1420c124f3d6c96cb1844ed966ee737a21ebc6c2 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Mon, 19 Jul 2021 17:05:26 -0500 Subject: [PATCH 44/49] made is_residential an optional bool with a default instead of a required field --- shipengine_sdk/models/address/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shipengine_sdk/models/address/__init__.py b/shipengine_sdk/models/address/__init__.py index b55a71a..a47a399 100644 --- a/shipengine_sdk/models/address/__init__.py +++ b/shipengine_sdk/models/address/__init__.py @@ -22,7 +22,7 @@ class Address: state_province: str postal_code: str country_code: str - is_residential: Optional[bool] = None + is_residential: Optional[bool] = False name: Optional[str] = "" phone: Optional[str] = "" company: Optional[str] = "" From dc0e2cf2f961e8148f6379150473149a1f55cbdb Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Mon, 19 Jul 2021 17:06:38 -0500 Subject: [PATCH 45/49] Removed unused python versions from coveralls action --- .github/workflows/main.yml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 05c9a41..bbd1484 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -61,16 +61,6 @@ jobs: with: python-version: 3.7 - - name: Set up Python 3.8 - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - - name: Set up Python 3.9 - uses: actions/setup-python@v2 - with: - python-version: 3.9 - - name: Install python dependencies run: | python -m pip install --upgrade pip From c05e464e562b398cbad5d6d725438cb6bf9b8005 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Mon, 19 Jul 2021 17:10:25 -0500 Subject: [PATCH 46/49] update needs statement to new value --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bbd1484..42f4edd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -78,7 +78,7 @@ jobs: flag-name: Python Test Suite coveralls_finish: - needs: tox-coveralls + needs: coveralls runs-on: ubuntu-latest steps: - name: Coveralls Finished From 5eeb0a6961721617501cfc8db95e59ea454137c4 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Mon, 19 Jul 2021 17:23:21 -0500 Subject: [PATCH 47/49] Improved CI --- .github/workflows/main.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 42f4edd..8178507 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -47,6 +47,9 @@ jobs: flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=100 --statistics + - name: Pytest + run: | + pytest - name: Run linting environment and pre-commit hooks run: | tox -e lint From 5f0f915f8d3b0c6e29fd2c5eac2f676b10bb6a51 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Mon, 19 Jul 2021 17:25:11 -0500 Subject: [PATCH 48/49] Improved CI --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8178507..504eafe 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,7 +12,7 @@ on: - main jobs: - linting: + lint_and_pytest: runs-on: ubuntu-latest strategy: matrix: From 233bd5750d6916a755853ed20d8a18eb429ab479 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Mon, 19 Jul 2021 17:28:09 -0500 Subject: [PATCH 49/49] fixed time eval --- tests/events/test_emitted_events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/events/test_emitted_events.py b/tests/events/test_emitted_events.py index ab5a604..790ea0c 100644 --- a/tests/events/test_emitted_events.py +++ b/tests/events/test_emitted_events.py @@ -234,5 +234,5 @@ def test_retry_waits_correct_amount_of_time(self, mocker: MockerFixture) -> None assert type(response_recd_return) == ResponseReceivedEvent assert response_recd_return.retry == 2 assert ( - int(str(round((test_start_time - datetime.now()).total_seconds())).strip("-")) == 5 + int(str(round((test_start_time - datetime.now()).total_seconds())).strip("-")) <= 6 )