Summary
Identifier fields are typed inconsistently across models: id and version are int in most models but str | int in FinancialMutation. The official OpenAPI spec types identifiers as string-or-integer matching ^\d+$, and real responses send them as strings of 64-bit numbers (e.g. "433546185192506620", as seen throughout tests/conftest.py). The int-typed fields currently rely on pydantic silently coercing string → int.
Decision (made): treat identifiers as str
All identifier fields (id, version, *_id) should converge on str:
- It matches what the API actually sends.
- Identifiers are opaque tokens, not quantities — nothing meaningful is gained by
int.
- It removes the silent string→int coercion and the
str | int unions.
Inputs should still accept ints for ergonomics (SalesInvoice.find_by_id(123)), coercing to str internally — pydantic can do this with a lenient annotated type, e.g.:
Identifier = Annotated[str, BeforeValidator(lambda v: str(v) if v is not None else None)]
Classmethod signatures that take ids (find_by_id, update_by_id, delete_by_id, sync_fetch, get_detail, delete_payment, ...) should widen to int | str.
⚠️ Breaking change
This changes the public type of model.id and friends: code doing invoice.id == 12345 or passing ids into int-typed downstream code will break. This should land in a major/minor version bump with a clear changelog entry, not a patch release.
Scope
moneysnake/model.py (MoneybirdModel.id, id-taking classmethods)
- Every model with
*_id / version fields (sales_invoice.py, document.py, external_sales_invoice.py, contact.py, financial_mutation.py, financial_statement.py, payment.py, tax_rate.py, detail-attribute classes)
- Tests asserting integer ids
Acceptance criteria
- All identifier fields are
str | None (via the shared annotated type); no remaining str | int unions for ids.
- Constructing a model from a spec-shaped response (string ids) and from int ids both yield string identifiers.
- URL building (
f"...{self.id}") and request bodies are unaffected in behavior.
- Tests updated; changelog notes the breaking change.
Summary
Identifier fields are typed inconsistently across models:
idandversionareintin most models butstr | intinFinancialMutation. The official OpenAPI spec types identifiers as string-or-integer matching^\d+$, and real responses send them as strings of 64-bit numbers (e.g."433546185192506620", as seen throughouttests/conftest.py). Theint-typed fields currently rely on pydantic silently coercing string → int.Decision (made): treat identifiers as
strAll identifier fields (
id,version,*_id) should converge onstr:int.str | intunions.Inputs should still accept ints for ergonomics (
SalesInvoice.find_by_id(123)), coercing tostrinternally — pydantic can do this with a lenient annotated type, e.g.:Classmethod signatures that take ids (
find_by_id,update_by_id,delete_by_id,sync_fetch,get_detail,delete_payment, ...) should widen toint | str.This changes the public type of
model.idand friends: code doinginvoice.id == 12345or passing ids into int-typed downstream code will break. This should land in a major/minor version bump with a clear changelog entry, not a patch release.Scope
moneysnake/model.py(MoneybirdModel.id, id-taking classmethods)*_id/versionfields (sales_invoice.py,document.py,external_sales_invoice.py,contact.py,financial_mutation.py,financial_statement.py,payment.py,tax_rate.py, detail-attribute classes)Acceptance criteria
str | None(via the shared annotated type); no remainingstr | intunions for ids.f"...{self.id}") and request bodies are unaffected in behavior.