From 7244574390ad381241b9d1dacc4f518ebcc639ab Mon Sep 17 00:00:00 2001 From: Jesse Claven Date: Tue, 4 Jan 2022 16:13:22 +0000 Subject: [PATCH 1/8] Autosort imports --- duffel_api/models/order.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/duffel_api/models/order.py b/duffel_api/models/order.py index 232b1b1..1649e32 100644 --- a/duffel_api/models/order.py +++ b/duffel_api/models/order.py @@ -1,9 +1,9 @@ from ..models import ( - Airline, - Place, Aircraft, + Airline, OfferConditionChangeBeforeDeparture, OfferConditionRefundBeforeDeparture, + Place, ) from ..utils import maybe_parse_date_entries From 025e3556864295ceca316b9b780a1deb9ba2e4e7 Mon Sep 17 00:00:00 2001 From: Jesse Claven Date: Tue, 4 Jan 2022 16:13:38 +0000 Subject: [PATCH 2/8] Add type hints to util function --- duffel_api/utils.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/duffel_api/utils.py b/duffel_api/utils.py index e25a919..8ef4fdb 100644 --- a/duffel_api/utils.py +++ b/duffel_api/utils.py @@ -1,12 +1,13 @@ """Assorted auxiliary functions""" -from datetime import datetime, date +from datetime import date, datetime +from typing import Any, Union -def maybe_parse_date_entries(key, value): - """Parse datetime entries, depending on the value of `key`""" - if value is None: +def maybe_parse_date_entries(key: str, value: Any) -> Union[str, datetime, date]: + """Parse appropriate datetime or date entries, depending on the value of `key`""" + if not isinstance(value, str): + # If it's not a string, don't attempt any parsing return value - if key in [ "created_at", "updated_at", @@ -44,6 +45,7 @@ def maybe_parse_date_entries(key, value): else: return datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%fZ") + # All other strings return value From b339e413a6270efb918a893d4b8858979ff4ea33 Mon Sep 17 00:00:00 2001 From: Jesse Claven Date: Tue, 4 Jan 2022 16:21:27 +0000 Subject: [PATCH 3/8] Fix Order model type hints --- duffel_api/models/order.py | 91 +++++++++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 26 deletions(-) diff --git a/duffel_api/models/order.py b/duffel_api/models/order.py index 1649e32..4409268 100644 --- a/duffel_api/models/order.py +++ b/duffel_api/models/order.py @@ -20,22 +20,32 @@ class Order: def __init__(self, json): for key in json: value = json[key] - if value is not None: + + if isinstance(value, str): value = maybe_parse_date_entries(key, json[key]) - if key == "documents": - value = [OrderDocument(v) for v in value] - elif key == "conditions": - value = OrderConditions(value) - elif key == "owner": - value = Airline(value) - elif key == "passengers": - value = [OrderPassenger(v) for v in value] - elif key == "payment_status": - value = OrderPaymentStatus(value) - elif key == "services": - value = [OrderService(v) for v in value] - elif key == "slices": - value = [OrderSlice(v) for v in value] + setattr(self, key, value) + continue + + if isinstance(value, dict): + if key == "conditions": + value = OrderConditions(value) + elif key == "owner": + value = Airline(value) + elif key == "payment_status": + value = OrderPaymentStatus(value) + setattr(self, key, value) + continue + + if isinstance(value, list): + if key == "documents": + value = [OrderDocument(v) for v in value] + elif key == "passengers": + value = [OrderPassenger(v) for v in value] + elif key == "services": + value = [OrderService(v) for v in value] + elif key == "slices": + value = [OrderSlice(v) for v in value] + setattr(self, key, value) @@ -53,8 +63,7 @@ class OrderConditions: def __init__(self, json): for key in json: - # Bind the value before we go into the control flow for setting it - value = None + value = json[key] if key == "change_before_departure": value = OrderConditionChangeBeforeDeparture(json[key]) @@ -81,14 +90,23 @@ class InvalidPlaceType(Exception): def __init__(self, json): for key in json: - value = maybe_parse_date_entries(key, json[key]) + value = json[key] + + if isinstance(value, str): + value = maybe_parse_date_entries(key, json[key]) + setattr(self, key, value) + continue + if key in ["destination", "origin"]: value = Place(value) elif key in ["destination_type", "origin_type"]: if value not in OrderSlice.allowed_place_types: raise OrderSlice.InvalidPlaceType(value) - elif key == "segments": - value = [OrderSliceSegment(v) for v in value] + + if isinstance(value, list): + if key == "segments": + value = [OrderSliceSegment(v) for v in value] + # TODO(nlopes): maybe convert duration to a timedelta or Duration setattr(self, key, value) @@ -101,7 +119,13 @@ class OrderSliceSegment: def __init__(self, json): for key in json: - value = maybe_parse_date_entries(key, json[key]) + value = json[key] + + if isinstance(value, str): + value = maybe_parse_date_entries(key, json[key]) + setattr(self, key, value) + continue + if key == "aircraft" and value: value = Aircraft(value) elif key in ["marketing_carrier", "operating_carrier"] and value: @@ -110,6 +134,7 @@ def __init__(self, json): value = Place(value) elif key == "passengers": value = [OrderSliceSegmentPassenger(p) for p in value] + setattr(self, key, value) @@ -126,9 +151,12 @@ class InvalidCabinClass(Exception): def __init__(self, json): for key in json: value = json[key] - if key == "baggages": - value = [OrderSliceSegmentPassengerBaggage(v) for v in value] - elif key == "seat": + + if isinstance(value, list): + if key == "baggages": + value = [OrderSliceSegmentPassengerBaggage(v) for v in value] + + if key == "seat": if value is not None: value = OrderSliceSegmentPassengerSeat(value) elif key == "cabin_class": @@ -227,8 +255,12 @@ class OrderPaymentStatus: def __init__(self, json): for key in json: value = json[key] - if value is not None: + + if isinstance(value, str): value = maybe_parse_date_entries(key, json[key]) + setattr(self, key, value) + continue + setattr(self, key, value) @@ -250,13 +282,20 @@ class InvalidType(Exception): def __init__(self, json): for key in json: - value = maybe_parse_date_entries(key, json[key]) + value = json[key] + + if isinstance(value, str): + value = maybe_parse_date_entries(key, json[key]) + setattr(self, key, value) + continue + if key == "gender" and value.lower() not in OrderPassenger.allowed_genders: raise OrderPassenger.InvalidGender(value) elif key == "title" and value.lower() not in OrderPassenger.allowed_titles: raise OrderPassenger.InvalidTitle(value) elif key == "type" and value.lower() not in OrderPassenger.allowed_types: raise OrderPassenger.InvalidType(value) + setattr(self, key, value) From 32b18e89cc48bb10129be394dfb6302ba08d75ad Mon Sep 17 00:00:00 2001 From: Jesse Claven Date: Tue, 4 Jan 2022 16:21:34 +0000 Subject: [PATCH 4/8] Fix Offer model type hints --- duffel_api/models/offer.py | 97 ++++++++++++++++++++++++++------------ 1 file changed, 66 insertions(+), 31 deletions(-) diff --git a/duffel_api/models/offer.py b/duffel_api/models/offer.py index b6b73be..f467a4d 100644 --- a/duffel_api/models/offer.py +++ b/duffel_api/models/offer.py @@ -22,21 +22,35 @@ class InvalidPassengerIdentityDocumentType(Exception): def __init__(self, json): for key in json: - value = maybe_parse_date_entries(key, json[key]) - if key == "allowed_passenger_identity_document_types": - Offer._validate_passenger_identity_document_types(value) - elif key == "conditions": - value = OfferConditions(value) - elif key == "slices": - value = [OfferSlice(v) for v in value] - elif key == "passengers": - value = [Passenger(v) for v in value] - elif key == "payment_requirements": - value = PaymentRequirements(value) - elif key == "available_services": - value = [Service(v) for v in value] - elif key == "owner": - value = Airline(value) + value = json[key] + + if isinstance(value, str): + value = maybe_parse_date_entries(key, json[key]) + + if key == "allowed_passenger_identity_document_types": + Offer._validate_passenger_identity_document_types(value) + + setattr(self, key, value) + continue + + if isinstance(value, dict): + if key == "conditions": + value = OfferConditions(value) + elif key == "owner": + value = Airline(value) + setattr(self, key, value) + continue + + if isinstance(value, list): + if key == "slices": + value = [OfferSlice(v) for v in value] + elif key == "passengers": + value = [Passenger(v) for v in value] + elif key == "payment_requirements": + value = PaymentRequirements(value) + elif key == "available_services": + value = [Service(v) for v in value] + setattr(self, key, value) @staticmethod @@ -55,6 +69,8 @@ class OfferConditions: def __init__(self, json): for key in json: + value = json[key] + if key == "change_before_departure": if not json[key] is None: value = OfferConditionChangeBeforeDeparture(json[key]) @@ -166,14 +182,23 @@ class InvalidPlaceType(Exception): def __init__(self, json): for key in json: - value = maybe_parse_date_entries(key, json[key]) - if key in ["destination", "origin"]: - value = Place(value) - elif key in ["destination_type", "origin_type"]: - if value not in OfferSlice.allowed_place_types: - raise OfferSlice.InvalidPlaceType(value) - elif key == "segments": + value = json[key] + + if isinstance(value, str): + value = maybe_parse_date_entries(key, json[key]) + + if key in ["destination", "origin"]: + value = Place(value) + elif key in ["destination_type", "origin_type"]: + if value not in OfferSlice.allowed_place_types: + raise OfferSlice.InvalidPlaceType(value) + + setattr(self, key, value) + continue + + if key == "segments": value = [OfferSliceSegment(v) for v in value] + # TODO(nlopes): maybe convert duration to a timedelta or Duration setattr(self, key, value) @@ -185,15 +210,25 @@ class OfferSliceSegment: def __init__(self, json): for key in json: - value = maybe_parse_date_entries(key, json[key]) - if key == "aircraft" and value: - value = Aircraft(value) - elif key in ["marketing_carrier", "operating_carrier"] and value: - value = Airline(value) - elif key in ["destination", "origin"]: - value = Place(value) - elif key == "passengers": - value = [OfferSliceSegmentPassenger(p) for p in value] + value = json[key] + + if isinstance(value, str): + value = maybe_parse_date_entries(key, json[key]) + setattr(self, key, value) + continue + + if isinstance(value, dict): + if key == "aircraft" and value: + value = Aircraft(value) + elif key in ["marketing_carrier", "operating_carrier"] and value: + value = Airline(value) + elif key in ["destination", "origin"]: + value = Place(value) + + if isinstance(value, list): + if key == "passengers": + value = [OfferSliceSegmentPassenger(p) for p in value] + setattr(self, key, value) From 1a3f1eb0e8f63be7a3dfa92b719bcde8b76ccb58 Mon Sep 17 00:00:00 2001 From: Jesse Claven Date: Tue, 4 Jan 2022 16:27:24 +0000 Subject: [PATCH 5/8] Fix OfferRequest model type hints --- duffel_api/models/offer_request.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/duffel_api/models/offer_request.py b/duffel_api/models/offer_request.py index 17d0630..1076c29 100644 --- a/duffel_api/models/offer_request.py +++ b/duffel_api/models/offer_request.py @@ -12,13 +12,21 @@ class OfferRequest: def __init__(self, json): for key in json: - value = maybe_parse_date_entries(key, json[key]) - if key == "offers": - value = [Offer(v) for v in value] - elif key == "passengers": - value = [Passenger(v) for v in value] - elif key == "slices": - value = [Slice(v) for v in value] + value = json[key] + + if isinstance(value, str): + value = maybe_parse_date_entries(key, json[key]) + setattr(self, key, value) + continue + + if isinstance(value, list): + if key == "offers": + value = [Offer(v) for v in value] + elif key == "passengers": + value = [Passenger(v) for v in value] + elif key == "slices": + value = [Slice(v) for v in value] + setattr(self, key, value) From ad1a13b92c386176c0f5c139bbc735a594f84436 Mon Sep 17 00:00:00 2001 From: Jesse Claven Date: Tue, 4 Jan 2022 16:39:30 +0000 Subject: [PATCH 6/8] Fix OrderChangeRequest model type hints --- duffel_api/models/order_change_request.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/duffel_api/models/order_change_request.py b/duffel_api/models/order_change_request.py index 843d994..2c37a8f 100644 --- a/duffel_api/models/order_change_request.py +++ b/duffel_api/models/order_change_request.py @@ -10,12 +10,21 @@ class OrderChangeRequest: def __init__(self, json): for key in json: - value = maybe_parse_date_entries(key, json[key]) + value = json[key] + + if isinstance(value, str): + value = maybe_parse_date_entries(key, json[key]) + setattr(self, key, value) + continue + + if isinstance(value, dict): + if key == "slices": + value = OrderChangeRequestSlice(value) + + if isinstance(value, list): + if key == "order_change_offers": + value = [OrderChangeOffer(v) for v in value] - if key == "order_change_offers": - value = [OrderChangeOffer(v) for v in value] - elif key == "slices": - value = OrderChangeRequestSlice(value) setattr(self, key, value) From ef2e1f3134308f3e794645f8fa31f042650b6fac Mon Sep 17 00:00:00 2001 From: Jesse Claven Date: Wed, 5 Jan 2022 15:09:48 +0000 Subject: [PATCH 7/8] Reduce complexity of initialising classes --- duffel_api/models/offer.py | 52 ++++++++++++++++++----------------- duffel_api/models/order.py | 55 +++++++++++++++++++++----------------- tests/test_offer.py | 4 +-- 3 files changed, 60 insertions(+), 51 deletions(-) diff --git a/duffel_api/models/offer.py b/duffel_api/models/offer.py index f467a4d..42d11c8 100644 --- a/duffel_api/models/offer.py +++ b/duffel_api/models/offer.py @@ -22,36 +22,40 @@ class InvalidPassengerIdentityDocumentType(Exception): def __init__(self, json): for key in json: - value = json[key] + value = Offer.__maybe_init_value(key, json) - if isinstance(value, str): - value = maybe_parse_date_entries(key, json[key]) + setattr(self, key, value) - if key == "allowed_passenger_identity_document_types": - Offer._validate_passenger_identity_document_types(value) + @staticmethod + def __maybe_init_value(key, json): + value = json[key] - setattr(self, key, value) - continue + if isinstance(value, str): + value = maybe_parse_date_entries(key, json[key]) - if isinstance(value, dict): - if key == "conditions": - value = OfferConditions(value) - elif key == "owner": - value = Airline(value) - setattr(self, key, value) - continue + if isinstance(value, dict): + parsers = { + "conditions": OfferConditions, + "owner": Airline, + "payment_requirements": PaymentRequirements, + } - if isinstance(value, list): - if key == "slices": - value = [OfferSlice(v) for v in value] - elif key == "passengers": - value = [Passenger(v) for v in value] - elif key == "payment_requirements": - value = PaymentRequirements(value) - elif key == "available_services": - value = [Service(v) for v in value] + value = parsers[key](value) - setattr(self, key, value) + if isinstance(value, list): + if key == "allowed_passenger_identity_document_types": + Offer._validate_passenger_identity_document_types(value) + return + + parsers = { + "slices": OfferSlice, + "passengers": Passenger, + "available_services": Service, + } + + value = [parsers[key](v) for v in value] + + return value @staticmethod def _validate_passenger_identity_document_types(document_types): diff --git a/duffel_api/models/order.py b/duffel_api/models/order.py index 4409268..21299b4 100644 --- a/duffel_api/models/order.py +++ b/duffel_api/models/order.py @@ -19,34 +19,39 @@ class Order: def __init__(self, json): for key in json: - value = json[key] - - if isinstance(value, str): - value = maybe_parse_date_entries(key, json[key]) - setattr(self, key, value) - continue + value = Order.__maybe_init_value(key, json) - if isinstance(value, dict): - if key == "conditions": - value = OrderConditions(value) - elif key == "owner": - value = Airline(value) - elif key == "payment_status": - value = OrderPaymentStatus(value) - setattr(self, key, value) - continue + setattr(self, key, value) - if isinstance(value, list): - if key == "documents": - value = [OrderDocument(v) for v in value] - elif key == "passengers": - value = [OrderPassenger(v) for v in value] - elif key == "services": - value = [OrderService(v) for v in value] - elif key == "slices": - value = [OrderSlice(v) for v in value] + @staticmethod + def __maybe_init_value(key, json): + value = json[key] - setattr(self, key, value) + if isinstance(value, str): + value = maybe_parse_date_entries(key, json[key]) + elif isinstance(value, dict): + # Metadata is customer-specified data, so we don't try and parse it + if key == "metadata": + return value + + parsers = { + "conditions": OrderConditions, + "owner": Airline, + "payment_status": OrderPaymentStatus, + } + + value = parsers[key](value) + elif isinstance(value, list): + parsers = { + "documents": OrderDocument, + "passengers": OrderPassenger, + "services": OrderService, + "slices": OrderSlice, + } + + value = [parsers[key](v) for v in value] + + return value class OrderConditions: diff --git a/tests/test_offer.py b/tests/test_offer.py index 63a24f0..9dc97b1 100644 --- a/tests/test_offer.py +++ b/tests/test_offer.py @@ -5,5 +5,5 @@ def test_offer_model_parsing(): name = "get-offer-by-id" - with raw_fixture(name) as data: - assert Offer(data) + with raw_fixture(name) as fixture: + assert Offer(fixture["data"]) From ca6005f66dff920eb95399035198449a7e4fb512 Mon Sep 17 00:00:00 2001 From: Jesse Claven Date: Wed, 5 Jan 2022 15:19:41 +0000 Subject: [PATCH 8/8] Correct fixtures We don't actuall serialise `Payment`s at the moment. --- tests/fixtures/create-hold-order.json | 9 --------- tests/fixtures/create-instant-order.json | 9 --------- tests/fixtures/get-orders.json | 9 --------- 3 files changed, 27 deletions(-) diff --git a/tests/fixtures/create-hold-order.json b/tests/fixtures/create-hold-order.json index 26b6fc4..8f8e355 100644 --- a/tests/fixtures/create-hold-order.json +++ b/tests/fixtures/create-hold-order.json @@ -46,15 +46,6 @@ "payment_required_by": "2020-01-17T10:42:14Z", "price_guarantee_expires_at": "2020-01-17T10:42:14Z" }, - "payments": [ - { - "amount": "30.20", - "created_at": "2020-04-11T15:48:11.642Z", - "currency": "GBP", - "id": "pay_00009hthhsUZ8W4LxQgkjo", - "type": "balance" - } - ], "services": [ { "id": "ser_00009UhD4ongolulWd9123", diff --git a/tests/fixtures/create-instant-order.json b/tests/fixtures/create-instant-order.json index 2ac7150..8f3bdb5 100644 --- a/tests/fixtures/create-instant-order.json +++ b/tests/fixtures/create-instant-order.json @@ -46,15 +46,6 @@ "payment_required_by": null, "price_guarantee_expires_at": null }, - "payments": [ - { - "amount": "30.20", - "created_at": "2020-04-11T15:48:11.642Z", - "currency": "GBP", - "id": "pay_00009hthhsUZ8W4LxQgkjo", - "type": "balance" - } - ], "services": [ { "id": "ser_00009UhD4ongolulWd9123", diff --git a/tests/fixtures/get-orders.json b/tests/fixtures/get-orders.json index 11864ea..a498776 100644 --- a/tests/fixtures/get-orders.json +++ b/tests/fixtures/get-orders.json @@ -47,15 +47,6 @@ "payment_required_by": "2020-01-17T10:42:14Z", "price_guarantee_expires_at": "2020-01-17T10:42:14Z" }, - "payments": [ - { - "amount": "30.20", - "created_at": "2020-04-11T15:48:11.642Z", - "currency": "GBP", - "id": "pay_00009hthhsUZ8W4LxQgkjo", - "type": "balance" - } - ], "services": [ { "id": "ser_00009UhD4ongolulWd9123",