Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions tests/test_api_client/test_deserializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,3 +381,33 @@ def test_deserialize_model_enum(data, expected):
# then correct Shape enum expected
assert isinstance(result, Shape)
assert result == expected


def test_deserialize_contact_strips_tax_number_type_namespace():
from xero_python.accounting.models.contact import Contact

contact = deserialize_model(
Contact, {"TaxNumberType": "TAXNUMBERTYPE/SSN"}, model_finder=None
)

assert contact.tax_number_type == "SSN"


def test_deserialize_contact_keeps_valid_tax_number_type():
from xero_python.accounting.models.contact import Contact

contact = deserialize_model(Contact, {"TaxNumberType": "EIN"}, model_finder=None)

assert contact.tax_number_type == "EIN"


def test_deserialize_linked_transaction_accepts_receipt_source():
from xero_python.accounting.models.linked_transaction import LinkedTransaction

txn = deserialize_model(
LinkedTransaction,
{"SourceTransactionTypeCode": "RECEIPT"},
model_finder=None,
)

assert txn.source_transaction_type_code == "RECEIPT"
34 changes: 33 additions & 1 deletion xero_python/api_client/deserializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,5 +284,37 @@ def deserialize_model(model, data, model_finder):
value = data[attr_key]
kwargs[attr] = deserialize(attr_type, value, model_finder)

instance = model(**kwargs)
try:
return model(**kwargs)
except ValueError:
# Generated setters reject enum members the live API still returns
# (xero-python#203, #205, #206). Keep valid data; do not fail the
# whole payload because one closed enum is behind the spec.
return _deserialize_model_preserving_api_enums(model, kwargs)


def _enum_namespace_suffix(value):
if isinstance(value, str) and "/" in value:
return value.rsplit("/", 1)[-1]
return value


def _deserialize_model_preserving_api_enums(model, kwargs):
instance = model()
for attr, value in kwargs.items():
try:
setattr(instance, attr, value)
continue
except ValueError as err:
stripped = _enum_namespace_suffix(value)
if stripped != value:
try:
setattr(instance, attr, stripped)
continue
except ValueError:
pass
private = "_{}".format(attr)
if not hasattr(instance, private):
raise err
setattr(instance, private, value)
return instance