From ad4a0f5fc881fa00f3ba62b439ce77c71dcc36f9 Mon Sep 17 00:00:00 2001 From: TempoNaoTenho Date: Wed, 7 Oct 2026 17:22:24 -0300 Subject: [PATCH 1/4] fix(models): localize naive tracking timestamps to Sao Paulo --- CHANGELOG.md | 4 + docs/getting-started.md | 10 ++ pyproject.toml | 1 + scripts/generate_models.py | 21 ++- src/bling_erp_api/models/fields.py | 19 ++- .../models/generated/schemas/__init__.py | 6 +- .../models/generated/schemas/anuncios.py | 4 +- .../generated/schemas/anuncios_categorias.py | 4 +- .../models/generated/schemas/borderos.py | 4 +- .../models/generated/schemas/caixas_bancos.py | 4 +- .../generated/schemas/calculos_impostos.py | 4 +- .../generated/schemas/campos_customizados.py | 4 +- .../models/generated/schemas/canais_venda.py | 4 +- .../generated/schemas/categorias_lojas.py | 4 +- .../generated/schemas/categorias_produtos.py | 4 +- .../schemas/categorias_receitas_despesas.py | 4 +- .../models/generated/schemas/common.py | 4 +- .../models/generated/schemas/configuracoes.py | 4 +- .../models/generated/schemas/contas.py | 4 +- .../generated/schemas/contas_contabeis.py | 4 +- .../generated/schemas/contas_financeiras.py | 4 +- .../models/generated/schemas/contas_pagar.py | 4 +- .../generated/schemas/contas_receber.py | 4 +- .../models/generated/schemas/contatos.py | 4 +- .../models/generated/schemas/contratos.py | 4 +- .../models/generated/schemas/depositos.py | 4 +- .../models/generated/schemas/empresas.py | 4 +- .../models/generated/schemas/estoques.py | 4 +- .../generated/schemas/formas_pagamentos.py | 4 +- .../generated/schemas/grupos_produtos.py | 4 +- .../models/generated/schemas/homologacao.py | 4 +- .../models/generated/schemas/logisticas.py | 4 +- .../generated/schemas/logisticas_etiquetas.py | 4 +- .../generated/schemas/logisticas_objetos.py | 8 +- .../generated/schemas/logisticas_remessas.py | 8 +- .../generated/schemas/logisticas_servicos.py | 4 +- .../models/generated/schemas/lotes.py | 4 +- .../generated/schemas/lotes_lancamentos.py | 4 +- .../generated/schemas/naturezas_operacoes.py | 4 +- .../models/generated/schemas/notas_fiscais.py | 4 +- .../schemas/notas_fiscais_consumidor.py | 4 +- .../generated/schemas/notas_servicos.py | 4 +- .../models/generated/schemas/notificacoes.py | 4 +- .../models/generated/schemas/orcamentos.py | 4 +- .../generated/schemas/ordens_producao.py | 4 +- .../generated/schemas/pedidos_compras.py | 4 +- .../generated/schemas/pedidos_vendas.py | 4 +- .../models/generated/schemas/produtos.py | 4 +- .../generated/schemas/produtos_estruturas.py | 4 +- .../schemas/produtos_fornecedores.py | 4 +- .../generated/schemas/produtos_lojas.py | 4 +- .../generated/schemas/produtos_variacoes.py | 4 +- .../generated/schemas/propostas_comerciais.py | 4 +- .../models/generated/schemas/situacoes.py | 4 +- .../models/generated/schemas/usuarios.py | 4 +- .../models/generated/schemas/vendedores.py | 4 +- .../responses/logisticas_objetos_get.json | 24 ++++ tests/unit/test_bling_datetime.py | 135 ++++++++++++++++++ tests/unit/test_generate_models_overrides.py | 2 +- uv.lock | 11 ++ 60 files changed, 320 insertions(+), 121 deletions(-) create mode 100644 tests/fixtures/responses/logisticas_objetos_get.json create mode 100644 tests/unit/test_bling_datetime.py diff --git a/CHANGELOG.md b/CHANGELOG.md index e315132..3f3ff78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Tracking timestamps in logistics objects and shipment objects now accept offset-free Bling values through the generated `BlingDatetime` type. Naive timestamps use `America/Sao_Paulo` (including historical daylight saving time); explicit offsets are preserved, and JSON serialization emits an ISO timestamp with its offset. The generator and shared model namespace use the same type, with portable timezone data supplied by `tzdata`. + ## [0.3.1] - 2026-10-06 ### Fixed diff --git a/docs/getting-started.md b/docs/getting-started.md index 36b4b82..c88ed07 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -114,6 +114,16 @@ with BlingClient.from_env() as client: print(contato.get("data", {}).get("nome")) ``` +### Datas de rastreamento + +Os campos `rastreamento.ultima_alteracao` de objetos e remessas usam +`BlingDatetime`. Datas sem fuso, como `"2026-10-07 11:24:02"`, são interpretadas +no fuso IANA `America/Sao_Paulo`, incluindo suas regras históricas de horário +de verão. Datas que já possuem fuso mantêm o offset informado pelo Bling. +Na serialização JSON, o resultado inclui o offset: +`"2026-10-07T11:24:02-03:00"`. A dependência `tzdata` fornece essas regras +também em sistemas sem uma base de fusos instalada. + ### Paginação Endpoints de listagem aceitam `page` e `limit`: diff --git a/pyproject.toml b/pyproject.toml index e2c9d41..e49e29a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ dependencies = [ "pydantic>=2.13.4,<3.0.0", "pydantic-settings>=2.14.1,<3.0.0", "python-dotenv>=1.2.2,<2.0.0", + "tzdata>=2025.2", ] [project.urls] diff --git a/scripts/generate_models.py b/scripts/generate_models.py index ca39690..7201f8c 100644 --- a/scripts/generate_models.py +++ b/scripts/generate_models.py @@ -117,8 +117,8 @@ ) BUILTIN_TYPE_NAMES = { "Any", - "AwareDatetime", "BlingDate", + "BlingDatetime", "BlingModel", "Field", "None", @@ -181,7 +181,7 @@ # Type that replaces bare ``date`` in all generated annotations. DATE_TYPE_REWRITE = "BlingDate" -DATE_IMPORT = "from bling_erp_api.models.fields import BlingDate" +DATE_IMPORT = "from bling_erp_api.models.fields import BlingDate, BlingDatetime" # ``Field(...)`` keywords that affect the wire format. Inserted redeclarations # of parent-inherited fields only clone the parent's ``Field(...)`` call when @@ -604,10 +604,10 @@ def _schema_module_content( "", "from __future__ import annotations", "", - "from bling_erp_api.models.fields import BlingDate", + DATE_IMPORT, "from typing import TYPE_CHECKING, Any", "", - "from pydantic import AliasChoices, AwareDatetime, Field, RootModel", + "from pydantic import AliasChoices, Field, RootModel", "", "from bling_erp_api.models.base import BlingModel", ] @@ -653,11 +653,9 @@ def _schema_init_content( "", "from __future__ import annotations", "", - "from bling_erp_api.models.fields import BlingDate", + DATE_IMPORT, "from typing import Any", "", - "from pydantic import AwareDatetime", - "", ] for module, names in sorted(by_module.items()): imports = ", ".join(names) @@ -671,7 +669,7 @@ def _schema_init_content( exports + "]", "", "_MODEL_NAMESPACE = {name: globals()[name] for name in __all__}", - "_MODEL_NAMESPACE.update({'Any': Any, 'AwareDatetime': AwareDatetime, 'BlingDate': BlingDate})", + "_MODEL_NAMESPACE.update({'Any': Any, 'BlingDate': BlingDate, 'BlingDatetime': BlingDatetime})", "for _model in _MODEL_NAMESPACE.values():", " if isinstance(_model, type) and hasattr(_model, 'model_rebuild'):", " _model.model_rebuild(_types_namespace=_MODEL_NAMESPACE)", @@ -912,10 +910,11 @@ def _remove_pass_if_empty(node: ast.ClassDef) -> None: def _rewrite_date_types(node: ast.ClassDef) -> None: - """Replace ``date`` with ``BlingDate`` in all field annotations.""" + """Use Bling date and datetime parsing in generated field annotations.""" + replacements = {"date": "BlingDate", "AwareDatetime": "BlingDatetime"} for stmt in ast.walk(node): - if isinstance(stmt, ast.Name) and stmt.id == "date": - stmt.id = "BlingDate" + if isinstance(stmt, ast.Name) and stmt.id in replacements: + stmt.id = replacements[stmt.id] def _inherited_field_value(parent_decl: ast.AnnAssign | None, field_name: str) -> ast.expr: diff --git a/src/bling_erp_api/models/fields.py b/src/bling_erp_api/models/fields.py index f98f5e6..e77def8 100644 --- a/src/bling_erp_api/models/fields.py +++ b/src/bling_erp_api/models/fields.py @@ -2,9 +2,11 @@ from __future__ import annotations -from datetime import date +from datetime import date, datetime from typing import Annotated +from zoneinfo import ZoneInfo +from pydantic import AfterValidator from pydantic.functional_serializers import PlainSerializer from pydantic.functional_validators import PlainValidator @@ -44,3 +46,18 @@ def _serialize_bling_date(value: date | None) -> str | None: PlainValidator(_parse_bling_date), PlainSerializer(_serialize_bling_date, return_type=str | None, when_used="json"), ] + + +def _localize_bling_datetime(value: datetime) -> datetime: + """Interpret offset-free Bling timestamps in America/Sao_Paulo. + + Preserve explicit offsets and use the IANA timezone rules for naive values, + including historical daylight saving time. Invalid timestamps still fail + Pydantic's normal datetime validation before this validator is called. + """ + if value.tzinfo is None or value.utcoffset() is None: + return value.replace(tzinfo=ZoneInfo("America/Sao_Paulo")) + return value + + +BlingDatetime = Annotated[datetime, AfterValidator(_localize_bling_datetime)] diff --git a/src/bling_erp_api/models/generated/schemas/__init__.py b/src/bling_erp_api/models/generated/schemas/__init__.py index e167880..950ed4c 100644 --- a/src/bling_erp_api/models/generated/schemas/__init__.py +++ b/src/bling_erp_api/models/generated/schemas/__init__.py @@ -4,11 +4,9 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import Any -from pydantic import AwareDatetime - from .anuncios import ( AnunciosAtributoDTO, AnunciosCategoriaDTO, @@ -1484,7 +1482,7 @@ ] _MODEL_NAMESPACE = {name: globals()[name] for name in __all__} -_MODEL_NAMESPACE.update({"Any": Any, "AwareDatetime": AwareDatetime, "BlingDate": BlingDate}) +_MODEL_NAMESPACE.update({"Any": Any, "BlingDate": BlingDate, "BlingDatetime": BlingDatetime}) for _model in _MODEL_NAMESPACE.values(): if isinstance(_model, type) and hasattr(_model, "model_rebuild"): _model.model_rebuild(_types_namespace=_MODEL_NAMESPACE) diff --git a/src/bling_erp_api/models/generated/schemas/anuncios.py b/src/bling_erp_api/models/generated/schemas/anuncios.py index 883e0b5..80271cf 100644 --- a/src/bling_erp_api/models/generated/schemas/anuncios.py +++ b/src/bling_erp_api/models/generated/schemas/anuncios.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/anuncios_categorias.py b/src/bling_erp_api/models/generated/schemas/anuncios_categorias.py index 7569a87..a7c8852 100644 --- a/src/bling_erp_api/models/generated/schemas/anuncios_categorias.py +++ b/src/bling_erp_api/models/generated/schemas/anuncios_categorias.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/borderos.py b/src/bling_erp_api/models/generated/schemas/borderos.py index 76f7575..582fe01 100644 --- a/src/bling_erp_api/models/generated/schemas/borderos.py +++ b/src/bling_erp_api/models/generated/schemas/borderos.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/caixas_bancos.py b/src/bling_erp_api/models/generated/schemas/caixas_bancos.py index 32706f6..4ba5be6 100644 --- a/src/bling_erp_api/models/generated/schemas/caixas_bancos.py +++ b/src/bling_erp_api/models/generated/schemas/caixas_bancos.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/calculos_impostos.py b/src/bling_erp_api/models/generated/schemas/calculos_impostos.py index 83338ad..23357e6 100644 --- a/src/bling_erp_api/models/generated/schemas/calculos_impostos.py +++ b/src/bling_erp_api/models/generated/schemas/calculos_impostos.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/campos_customizados.py b/src/bling_erp_api/models/generated/schemas/campos_customizados.py index 403fe66..aad13bd 100644 --- a/src/bling_erp_api/models/generated/schemas/campos_customizados.py +++ b/src/bling_erp_api/models/generated/schemas/campos_customizados.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/canais_venda.py b/src/bling_erp_api/models/generated/schemas/canais_venda.py index 868e480..961e596 100644 --- a/src/bling_erp_api/models/generated/schemas/canais_venda.py +++ b/src/bling_erp_api/models/generated/schemas/canais_venda.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/categorias_lojas.py b/src/bling_erp_api/models/generated/schemas/categorias_lojas.py index 276468d..000eee3 100644 --- a/src/bling_erp_api/models/generated/schemas/categorias_lojas.py +++ b/src/bling_erp_api/models/generated/schemas/categorias_lojas.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/categorias_produtos.py b/src/bling_erp_api/models/generated/schemas/categorias_produtos.py index 1e92d86..706f013 100644 --- a/src/bling_erp_api/models/generated/schemas/categorias_produtos.py +++ b/src/bling_erp_api/models/generated/schemas/categorias_produtos.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/categorias_receitas_despesas.py b/src/bling_erp_api/models/generated/schemas/categorias_receitas_despesas.py index e4f7422..c3ed5b4 100644 --- a/src/bling_erp_api/models/generated/schemas/categorias_receitas_despesas.py +++ b/src/bling_erp_api/models/generated/schemas/categorias_receitas_despesas.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/common.py b/src/bling_erp_api/models/generated/schemas/common.py index 888a1fa..2e60abe 100644 --- a/src/bling_erp_api/models/generated/schemas/common.py +++ b/src/bling_erp_api/models/generated/schemas/common.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/configuracoes.py b/src/bling_erp_api/models/generated/schemas/configuracoes.py index 20d15f5..52f7e0b 100644 --- a/src/bling_erp_api/models/generated/schemas/configuracoes.py +++ b/src/bling_erp_api/models/generated/schemas/configuracoes.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/contas.py b/src/bling_erp_api/models/generated/schemas/contas.py index ed77166..32ace0d 100644 --- a/src/bling_erp_api/models/generated/schemas/contas.py +++ b/src/bling_erp_api/models/generated/schemas/contas.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/contas_contabeis.py b/src/bling_erp_api/models/generated/schemas/contas_contabeis.py index 8dfc91b..0ad5d6d 100644 --- a/src/bling_erp_api/models/generated/schemas/contas_contabeis.py +++ b/src/bling_erp_api/models/generated/schemas/contas_contabeis.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/contas_financeiras.py b/src/bling_erp_api/models/generated/schemas/contas_financeiras.py index c4b3163..f996b1b 100644 --- a/src/bling_erp_api/models/generated/schemas/contas_financeiras.py +++ b/src/bling_erp_api/models/generated/schemas/contas_financeiras.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/contas_pagar.py b/src/bling_erp_api/models/generated/schemas/contas_pagar.py index ddc134e..3e66b03 100644 --- a/src/bling_erp_api/models/generated/schemas/contas_pagar.py +++ b/src/bling_erp_api/models/generated/schemas/contas_pagar.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/contas_receber.py b/src/bling_erp_api/models/generated/schemas/contas_receber.py index 0aa6163..98d678a 100644 --- a/src/bling_erp_api/models/generated/schemas/contas_receber.py +++ b/src/bling_erp_api/models/generated/schemas/contas_receber.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/contatos.py b/src/bling_erp_api/models/generated/schemas/contatos.py index 300533b..4489366 100644 --- a/src/bling_erp_api/models/generated/schemas/contatos.py +++ b/src/bling_erp_api/models/generated/schemas/contatos.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/contratos.py b/src/bling_erp_api/models/generated/schemas/contratos.py index 2630c79..45563f8 100644 --- a/src/bling_erp_api/models/generated/schemas/contratos.py +++ b/src/bling_erp_api/models/generated/schemas/contratos.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/depositos.py b/src/bling_erp_api/models/generated/schemas/depositos.py index f78ce41..324a164 100644 --- a/src/bling_erp_api/models/generated/schemas/depositos.py +++ b/src/bling_erp_api/models/generated/schemas/depositos.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/empresas.py b/src/bling_erp_api/models/generated/schemas/empresas.py index c9580fb..397e363 100644 --- a/src/bling_erp_api/models/generated/schemas/empresas.py +++ b/src/bling_erp_api/models/generated/schemas/empresas.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/estoques.py b/src/bling_erp_api/models/generated/schemas/estoques.py index c2bf859..575b879 100644 --- a/src/bling_erp_api/models/generated/schemas/estoques.py +++ b/src/bling_erp_api/models/generated/schemas/estoques.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/formas_pagamentos.py b/src/bling_erp_api/models/generated/schemas/formas_pagamentos.py index 6d573bb..b247a70 100644 --- a/src/bling_erp_api/models/generated/schemas/formas_pagamentos.py +++ b/src/bling_erp_api/models/generated/schemas/formas_pagamentos.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/grupos_produtos.py b/src/bling_erp_api/models/generated/schemas/grupos_produtos.py index 5d22f3c..4bc15ae 100644 --- a/src/bling_erp_api/models/generated/schemas/grupos_produtos.py +++ b/src/bling_erp_api/models/generated/schemas/grupos_produtos.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/homologacao.py b/src/bling_erp_api/models/generated/schemas/homologacao.py index 5bd38ba..7441715 100644 --- a/src/bling_erp_api/models/generated/schemas/homologacao.py +++ b/src/bling_erp_api/models/generated/schemas/homologacao.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/logisticas.py b/src/bling_erp_api/models/generated/schemas/logisticas.py index a7f09a8..0ded4e5 100644 --- a/src/bling_erp_api/models/generated/schemas/logisticas.py +++ b/src/bling_erp_api/models/generated/schemas/logisticas.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/logisticas_etiquetas.py b/src/bling_erp_api/models/generated/schemas/logisticas_etiquetas.py index 2a4e16c..8b466bf 100644 --- a/src/bling_erp_api/models/generated/schemas/logisticas_etiquetas.py +++ b/src/bling_erp_api/models/generated/schemas/logisticas_etiquetas.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/logisticas_objetos.py b/src/bling_erp_api/models/generated/schemas/logisticas_objetos.py index b4a9485..876a742 100644 --- a/src/bling_erp_api/models/generated/schemas/logisticas_objetos.py +++ b/src/bling_erp_api/models/generated/schemas/logisticas_objetos.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel @@ -92,7 +92,7 @@ class LogisticasObjetosRastreamentoDTO(BlingModel): situacao: Bling ``situacao``; type ``int``; obrigatório. `0` Postado
`1` Em andamento
`2` Não entregue
`3` Entregue
`4` Aguardando retirada
`5` Etiqueta comprada
`6` Vinculado
`7` Atrasado
`8` Não... origem: Bling ``origem``; type ``str``; obrigatório. Cidade e estado de origem destino: Bling ``destino``; type ``str``; obrigatório. Cidade e estado de destino - ultima_alteracao: Bling ``ultimaAlteracao``; type ``AwareDatetime``; obrigatório. Data e hora em que ocorreu a atualização de rastreio. + ultima_alteracao: Bling ``ultimaAlteracao``; type ``BlingDatetime``; obrigatório. Data e hora em que ocorreu a atualização de rastreio. url: Bling ``url``; type ``str``; obrigatório. URL de rastreamento""" codigo: str = Field(..., examples=["EC272330554BR"]) @@ -100,7 +100,7 @@ class LogisticasObjetosRastreamentoDTO(BlingModel): situacao: int = Field(..., examples=["8"]) origem: str = Field(..., examples=["São Paulo, SP"]) destino: str = Field(..., examples=["São Paulo, SP"]) - ultima_alteracao: AwareDatetime = Field( + ultima_alteracao: BlingDatetime = Field( ..., validation_alias=AliasChoices("ultima_alteracao", "ultimaAlteracao"), examples=["2020-11-11 16:40:33"], diff --git a/src/bling_erp_api/models/generated/schemas/logisticas_remessas.py b/src/bling_erp_api/models/generated/schemas/logisticas_remessas.py index aef41a0..f3f5c4a 100644 --- a/src/bling_erp_api/models/generated/schemas/logisticas_remessas.py +++ b/src/bling_erp_api/models/generated/schemas/logisticas_remessas.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel @@ -132,7 +132,7 @@ class LogisticasRemessasRastreamentoDTO(BlingModel): situacao: Bling ``situacao``; type ``int``; obrigatório. `0` Postado
`1` Em andamento
`2` Não entregue
`3` Entregue
`4` Aguardando retirada
`5` Em aberto
`6` Vinculado
`7` Atrasado
`8` Não postado... origem: Bling ``origem``; type ``str``; obrigatório. Cidade e estado de origem destino: Bling ``destino``; type ``str``; obrigatório. Cidade e estado de destino - ultima_alteracao: Bling ``ultimaAlteracao``; type ``AwareDatetime``; obrigatório. Data e hora em que ocorreu a atualização de rastreio. + ultima_alteracao: Bling ``ultimaAlteracao``; type ``BlingDatetime``; obrigatório. Data e hora em que ocorreu a atualização de rastreio. url: Bling ``url``; type ``str``; obrigatório. URL de rastreamento""" codigo: str = Field(..., examples=["EC272330554BR"]) @@ -140,7 +140,7 @@ class LogisticasRemessasRastreamentoDTO(BlingModel): situacao: int = Field(..., examples=[1]) origem: str = Field(..., examples=["São Paulo, SP"]) destino: str = Field(..., examples=["São Paulo, SP"]) - ultima_alteracao: AwareDatetime = Field( + ultima_alteracao: BlingDatetime = Field( ..., validation_alias=AliasChoices("ultima_alteracao", "ultimaAlteracao"), examples=["2020-11-11 16:40:33"], diff --git a/src/bling_erp_api/models/generated/schemas/logisticas_servicos.py b/src/bling_erp_api/models/generated/schemas/logisticas_servicos.py index f2dd1c4..dae5471 100644 --- a/src/bling_erp_api/models/generated/schemas/logisticas_servicos.py +++ b/src/bling_erp_api/models/generated/schemas/logisticas_servicos.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/lotes.py b/src/bling_erp_api/models/generated/schemas/lotes.py index 74ba238..5544047 100644 --- a/src/bling_erp_api/models/generated/schemas/lotes.py +++ b/src/bling_erp_api/models/generated/schemas/lotes.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/lotes_lancamentos.py b/src/bling_erp_api/models/generated/schemas/lotes_lancamentos.py index 0da96ec..cd8a22e 100644 --- a/src/bling_erp_api/models/generated/schemas/lotes_lancamentos.py +++ b/src/bling_erp_api/models/generated/schemas/lotes_lancamentos.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/naturezas_operacoes.py b/src/bling_erp_api/models/generated/schemas/naturezas_operacoes.py index 4df623b..456b386 100644 --- a/src/bling_erp_api/models/generated/schemas/naturezas_operacoes.py +++ b/src/bling_erp_api/models/generated/schemas/naturezas_operacoes.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/notas_fiscais.py b/src/bling_erp_api/models/generated/schemas/notas_fiscais.py index 9426903..8816e3d 100644 --- a/src/bling_erp_api/models/generated/schemas/notas_fiscais.py +++ b/src/bling_erp_api/models/generated/schemas/notas_fiscais.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/notas_fiscais_consumidor.py b/src/bling_erp_api/models/generated/schemas/notas_fiscais_consumidor.py index 996cd28..0a10238 100644 --- a/src/bling_erp_api/models/generated/schemas/notas_fiscais_consumidor.py +++ b/src/bling_erp_api/models/generated/schemas/notas_fiscais_consumidor.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/notas_servicos.py b/src/bling_erp_api/models/generated/schemas/notas_servicos.py index 32ac19e..dfecc6b 100644 --- a/src/bling_erp_api/models/generated/schemas/notas_servicos.py +++ b/src/bling_erp_api/models/generated/schemas/notas_servicos.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/notificacoes.py b/src/bling_erp_api/models/generated/schemas/notificacoes.py index f4fc3e7..ad2a9dc 100644 --- a/src/bling_erp_api/models/generated/schemas/notificacoes.py +++ b/src/bling_erp_api/models/generated/schemas/notificacoes.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/orcamentos.py b/src/bling_erp_api/models/generated/schemas/orcamentos.py index afd1a1d..59c6be3 100644 --- a/src/bling_erp_api/models/generated/schemas/orcamentos.py +++ b/src/bling_erp_api/models/generated/schemas/orcamentos.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/ordens_producao.py b/src/bling_erp_api/models/generated/schemas/ordens_producao.py index c3415b8..b1e1005 100644 --- a/src/bling_erp_api/models/generated/schemas/ordens_producao.py +++ b/src/bling_erp_api/models/generated/schemas/ordens_producao.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/pedidos_compras.py b/src/bling_erp_api/models/generated/schemas/pedidos_compras.py index b6bbdb5..8ad641c 100644 --- a/src/bling_erp_api/models/generated/schemas/pedidos_compras.py +++ b/src/bling_erp_api/models/generated/schemas/pedidos_compras.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/pedidos_vendas.py b/src/bling_erp_api/models/generated/schemas/pedidos_vendas.py index 44fa44b..fbfe167 100644 --- a/src/bling_erp_api/models/generated/schemas/pedidos_vendas.py +++ b/src/bling_erp_api/models/generated/schemas/pedidos_vendas.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/produtos.py b/src/bling_erp_api/models/generated/schemas/produtos.py index 5d17f71..a645374 100644 --- a/src/bling_erp_api/models/generated/schemas/produtos.py +++ b/src/bling_erp_api/models/generated/schemas/produtos.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/produtos_estruturas.py b/src/bling_erp_api/models/generated/schemas/produtos_estruturas.py index 9609483..54fd8b7 100644 --- a/src/bling_erp_api/models/generated/schemas/produtos_estruturas.py +++ b/src/bling_erp_api/models/generated/schemas/produtos_estruturas.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/produtos_fornecedores.py b/src/bling_erp_api/models/generated/schemas/produtos_fornecedores.py index 88803b8..c2bdd3d 100644 --- a/src/bling_erp_api/models/generated/schemas/produtos_fornecedores.py +++ b/src/bling_erp_api/models/generated/schemas/produtos_fornecedores.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/produtos_lojas.py b/src/bling_erp_api/models/generated/schemas/produtos_lojas.py index a6bbba1..a46c608 100644 --- a/src/bling_erp_api/models/generated/schemas/produtos_lojas.py +++ b/src/bling_erp_api/models/generated/schemas/produtos_lojas.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/produtos_variacoes.py b/src/bling_erp_api/models/generated/schemas/produtos_variacoes.py index f47e843..be5695d 100644 --- a/src/bling_erp_api/models/generated/schemas/produtos_variacoes.py +++ b/src/bling_erp_api/models/generated/schemas/produtos_variacoes.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/propostas_comerciais.py b/src/bling_erp_api/models/generated/schemas/propostas_comerciais.py index cdca098..a6bec52 100644 --- a/src/bling_erp_api/models/generated/schemas/propostas_comerciais.py +++ b/src/bling_erp_api/models/generated/schemas/propostas_comerciais.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/situacoes.py b/src/bling_erp_api/models/generated/schemas/situacoes.py index 444beb6..d5fef87 100644 --- a/src/bling_erp_api/models/generated/schemas/situacoes.py +++ b/src/bling_erp_api/models/generated/schemas/situacoes.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/usuarios.py b/src/bling_erp_api/models/generated/schemas/usuarios.py index 01081db..9206a38 100644 --- a/src/bling_erp_api/models/generated/schemas/usuarios.py +++ b/src/bling_erp_api/models/generated/schemas/usuarios.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/src/bling_erp_api/models/generated/schemas/vendedores.py b/src/bling_erp_api/models/generated/schemas/vendedores.py index 9db9d1d..f309336 100644 --- a/src/bling_erp_api/models/generated/schemas/vendedores.py +++ b/src/bling_erp_api/models/generated/schemas/vendedores.py @@ -4,10 +4,10 @@ from __future__ import annotations -from bling_erp_api.models.fields import BlingDate +from bling_erp_api.models.fields import BlingDate, BlingDatetime from typing import TYPE_CHECKING, Any -from pydantic import AliasChoices, AwareDatetime, Field, RootModel +from pydantic import AliasChoices, Field, RootModel from bling_erp_api.models.base import BlingModel diff --git a/tests/fixtures/responses/logisticas_objetos_get.json b/tests/fixtures/responses/logisticas_objetos_get.json new file mode 100644 index 0000000..949eae6 --- /dev/null +++ b/tests/fixtures/responses/logisticas_objetos_get.json @@ -0,0 +1,24 @@ +{ + "data": { + "pedidoVenda": {"id": 101}, + "notaFiscal": {"id": 201}, + "servico": {"id": 301}, + "rastreamento": { + "codigo": "SYNTHETIC123", + "descricao": "Em andamento", + "situacao": 1, + "origem": "São Paulo, SP", + "destino": "Curitiba, PR", + "ultimaAlteracao": "2026-10-07 11:24:02", + "url": "https://example.com/tracking" + }, + "dimensao": {"altura": 10, "largura": 10, "comprimento": 10, "diametro": 0, "peso": 1}, + "embalagem": {"id": 401}, + "dataSaida": "2026-10-07", + "prazoEntregaPrevisto": 5, + "fretePrevisto": 10, + "valorDeclarado": 50, + "avisoRecebimento": false, + "maoPropria": false + } +} diff --git a/tests/unit/test_bling_datetime.py b/tests/unit/test_bling_datetime.py new file mode 100644 index 0000000..81eb58e --- /dev/null +++ b/tests/unit/test_bling_datetime.py @@ -0,0 +1,135 @@ +"""Regression tests for Bling timestamps without UTC offsets.""" + +from __future__ import annotations + +import inspect +import json +from datetime import UTC, datetime, timedelta +from pathlib import Path +from typing import TYPE_CHECKING, cast + +import httpx +import pytest +from pydantic import TypeAdapter, ValidationError + +from bling_erp_api import BlingClient +from bling_erp_api.models.fields import BlingDatetime +from bling_erp_api.models.generated.schemas.logisticas_objetos import ( + LogisticasObjetosIdObjetoGetResponse200, + LogisticasObjetosRastreamentoDTO, +) +from bling_erp_api.models.generated.schemas.logisticas_remessas import ( + LogisticasRemessasIdRemessaGetResponse200, + LogisticasRemessasRastreamentoDTO, +) +from bling_erp_api.utils.serialization import to_json_object + +if TYPE_CHECKING: + from pytest_httpx import HTTPXMock + + from bling_erp_api.types import JsonObject + +FIXTURE = Path(__file__).parents[1] / "fixtures/responses/logisticas_objetos_get.json" +TRACKING_MODELS = (LogisticasObjetosRastreamentoDTO, LogisticasRemessasRastreamentoDTO) + + +def _object_payload() -> JsonObject: + return cast("JsonObject", json.loads(FIXTURE.read_text(encoding="utf-8"))) + + +@pytest.mark.parametrize( + ("value", "offset"), + [ + ("2026-10-07 11:24:02", -3), + ("2026-10-07T11:24:02.123456", -3), + (datetime(2026, 10, 7, 11, 24, 2), -3), # noqa: DTZ001 + ("2018-12-01 12:00:00", -2), + ("2026-10-07T14:24:02Z", 0), + ("2026-10-07T11:24:02-04:00", -4), + (datetime(2026, 10, 7, 14, 24, 2, tzinfo=UTC), 0), + ], +) +def test_bling_datetime_assigns_local_timezone_and_preserves_offsets( + value: str | datetime, offset: int +) -> None: + """Offset-free dates use Sao Paulo rules; explicit offsets are preserved.""" + parsed = TypeAdapter[datetime](BlingDatetime).validate_python(value) + assert parsed.utcoffset() == timedelta(hours=offset) + if isinstance(value, datetime) and value.tzinfo is not None: + assert parsed is value + + +@pytest.mark.parametrize("value", ["not-a-date", "2026-02-30 12:00:00", "", None]) +def test_bling_datetime_rejects_invalid_timestamps(value: object) -> None: + """Do not turn malformed timestamps into silently accepted data.""" + with pytest.raises(ValidationError): + TypeAdapter[datetime](BlingDatetime).validate_python(value) + + +@pytest.mark.parametrize("model_type", TRACKING_MODELS) +def test_tracking_models_accept_wire_and_python_field_names( + model_type: type[LogisticasObjetosRastreamentoDTO | LogisticasRemessasRastreamentoDTO], +) -> None: + """Both affected DTOs parse aliases and serialize aware ISO timestamps.""" + payload = cast("JsonObject", cast("JsonObject", _object_payload()["data"])["rastreamento"]) + wire = model_type.model_validate(payload) + python_payload = {**payload} + python_payload["ultima_alteracao"] = python_payload.pop("ultimaAlteracao") + python_model = model_type.model_validate(python_payload) + assert python_model == wire + assert wire.ultima_alteracao.utcoffset() == timedelta(hours=-3) + assert "ultima_alteracao" in inspect.signature(model_type).parameters + assert "ultimaAlteracao" not in inspect.signature(model_type).parameters + serialized = to_json_object(wire) + assert serialized["ultimaAlteracao"] == "2026-10-07T11:24:02-03:00" + assert "ultima_alteracao" not in serialized + assert model_type.model_validate(serialized) == wire + with pytest.raises(ValidationError, match="Conflicting values"): + model_type.model_validate({**payload, "ultima_alteracao": "2026-10-08 11:24:02"}) + + +@pytest.mark.parametrize("english_alias", [False, True]) +def test_logistics_object_resource_parses_naive_datetime( + httpx_mock: HTTPXMock, *, english_alias: bool +) -> None: + """Exercise the public resource's two validation passes without live API calls.""" + httpx_mock.add_response(json=_object_payload()) + with BlingClient(auth=httpx.Auth()) as client: + response = ( + client.logisticas_objetos.get(object_id=701) + if english_alias + else client.logisticas_objetos.obter(id_objeto=701) + ) + assert isinstance(response, LogisticasObjetosIdObjetoGetResponse200) + assert response.data is not None + assert response.data.rastreamento is not None + assert response.data.rastreamento.ultima_alteracao.isoformat() == "2026-10-07T11:24:02-03:00" + request = httpx_mock.get_request() + assert request is not None + assert request.method == "GET" + assert request.url.path == "/Api/v3/logisticas/objetos/701" + assert not request.url.query + assert request.content == b"" + + +def test_logistics_shipment_resource_parses_nested_naive_datetime(httpx_mock: HTTPXMock) -> None: + """Remessas share the same timestamp issue inside objetos[].rastreamento.""" + obj = cast("JsonObject", _object_payload()["data"]) + obj["servico"] = {"id": 301, "nome": "Synthetic service", "codigo": "TEST"} + payload = { + "data": { + "id": 501, + "numeroPlp": "PLP-TEST", + "situacao": 1, + "descricao": "Synthetic shipment", + "dataCriacao": "2026-10-07", + "logistica": {"id": 301}, + "objetos": [{"id": 701, **obj}], + }, + } + httpx_mock.add_response(json=payload) + with BlingClient(auth=httpx.Auth()) as client: + response = client.logisticas_remessas.obter(id_remessa=501) + assert isinstance(response, LogisticasRemessasIdRemessaGetResponse200) + assert response.data is not None + assert response.data.objetos[0].rastreamento.ultima_alteracao.utcoffset() == timedelta(hours=-3) diff --git a/tests/unit/test_generate_models_overrides.py b/tests/unit/test_generate_models_overrides.py index 4f4e4df..052d7ab 100644 --- a/tests/unit/test_generate_models_overrides.py +++ b/tests/unit/test_generate_models_overrides.py @@ -472,7 +472,7 @@ def test_schema_module_content_imports_alias_choices_for_inserted_field( {"ParentDTO": "exemplo", "ChildDTO": "exemplo"}, ) - assert "from pydantic import AliasChoices, AwareDatetime, Field, RootModel" in content + assert "from pydantic import AliasChoices, Field, RootModel" in content assert "validation_alias=AliasChoices('x', 'xStr')" in content assert "serialization_alias='xStr'" in content # ``alias='xStr'`` is a substring of ``serialization_alias='xStr'``; the diff --git a/uv.lock b/uv.lock index df317e2..473efa6 100644 --- a/uv.lock +++ b/uv.lock @@ -112,6 +112,7 @@ dependencies = [ { name = "pydantic" }, { name = "pydantic-settings" }, { name = "python-dotenv" }, + { name = "tzdata" }, ] [package.dev-dependencies] @@ -139,6 +140,7 @@ requires-dist = [ { name = "pydantic", specifier = ">=2.13.4,<3.0.0" }, { name = "pydantic-settings", specifier = ">=2.14.1,<3.0.0" }, { name = "python-dotenv", specifier = ">=1.2.2,<2.0.0" }, + { name = "tzdata", specifier = ">=2025.2" }, ] [package.metadata.requires-dev] @@ -1124,6 +1126,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, ] +[[package]] +name = "tzdata" +version = "2026.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/68/f1b440335057bfce71b6e50a9d09445aa2ecbd08359a337976627b8409e7/tzdata-2026.5.tar.gz", hash = "sha256:8cc73c0a0bfca7dbfa59235d60b2eff82231dee33f53d206db1acd9173cfc0a7", size = 200404, upload-time = "2026-10-03T09:23:14.143Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/21/1e5995a1c920cce14e4bffae20c665ec10e7ed03ab25e006cd741092b718/tzdata-2026.5-py2.py3-none-any.whl", hash = "sha256:b683bd1b6659ddcd810ff02ad09ba821d4bf1065072805063eb35c49617905ac", size = 347996, upload-time = "2026-10-03T09:23:12.535Z" }, +] + [[package]] name = "urllib3" version = "2.7.0" From a79782a00ed61915f3ad95bfc5b51dc7821f74e3 Mon Sep 17 00:00:00 2001 From: TempoNaoTenho Date: Wed, 7 Oct 2026 17:26:35 -0300 Subject: [PATCH 2/4] fix(models): normalize absent tracking datetime sentinels --- CHANGELOG.md | 1 + docs/getting-started.md | 7 ++++ src/bling_erp_api/models/fields.py | 27 +++++++++++--- tests/unit/test_bling_datetime.py | 56 ++++++++++++++++++++++++++++-- 4 files changed, 84 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f3ff78..516cb66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Tracking timestamps in logistics objects and shipment objects now accept offset-free Bling values through the generated `BlingDatetime` type. Naive timestamps use `America/Sao_Paulo` (including historical daylight saving time); explicit offsets are preserved, and JSON serialization emits an ISO timestamp with its offset. The generator and shared model namespace use the same type, with portable timezone data supplied by `tzdata`. +- Tracking dates now normalize MySQL zero-date sentinels, empty strings and null to `None`, matching absent-date handling in `BlingDate`. This fixes a second failure confirmed in read-only live logistics objects. Other malformed dates still raise validation errors; the tracking timestamp key remains required, and unrelated DTO requirements are unchanged. ## [0.3.1] - 2026-10-06 diff --git a/docs/getting-started.md b/docs/getting-started.md index c88ed07..4d6624f 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -124,6 +124,13 @@ Na serialização JSON, o resultado inclui o offset: `"2026-10-07T11:24:02-03:00"`. A dependência `tzdata` fornece essas regras também em sistemas sem uma base de fusos instalada. +Sentinelas de data ausente (`"0000-00-00 00:00:00"`, `"0000-00-00"`, +`"0001-01-01"`, `"0001-01-01 00:00:00"`), strings vazias e `null` são +representadas por `None`. `model_dump(mode="json")` preserva `null`; +`to_json_object()` omite esses valores. Outras datas malformadas continuam +causando erro de validação, e a chave `ultimaAlteracao` continua obrigatória +no objeto de rastreamento. + ### Paginação Endpoints de listagem aceitam `page` e `limit`: diff --git a/src/bling_erp_api/models/fields.py b/src/bling_erp_api/models/fields.py index e77def8..6f50ab3 100644 --- a/src/bling_erp_api/models/fields.py +++ b/src/bling_erp_api/models/fields.py @@ -6,7 +6,7 @@ from typing import Annotated from zoneinfo import ZoneInfo -from pydantic import AfterValidator +from pydantic import AfterValidator, BeforeValidator from pydantic.functional_serializers import PlainSerializer from pydantic.functional_validators import PlainValidator @@ -48,16 +48,35 @@ def _serialize_bling_date(value: date | None) -> str | None: ] -def _localize_bling_datetime(value: datetime) -> datetime: +def _normalize_bling_datetime(value: object) -> object: + """Map Bling's absent timestamp sentinels to None before datetime parsing.""" + if isinstance(value, str): + stripped = value.strip() + if not stripped or stripped in { + "0000-00-00", + "0000-00-00 00:00:00", + "0001-01-01", + "0001-01-01 00:00:00", + }: + return None + return stripped + return value + + +def _localize_bling_datetime(value: datetime | None) -> datetime | None: """Interpret offset-free Bling timestamps in America/Sao_Paulo. Preserve explicit offsets and use the IANA timezone rules for naive values, including historical daylight saving time. Invalid timestamps still fail Pydantic's normal datetime validation before this validator is called. """ - if value.tzinfo is None or value.utcoffset() is None: + if value is not None and (value.tzinfo is None or value.utcoffset() is None): return value.replace(tzinfo=ZoneInfo("America/Sao_Paulo")) return value -BlingDatetime = Annotated[datetime, AfterValidator(_localize_bling_datetime)] +BlingDatetime = Annotated[ + datetime | None, + BeforeValidator(_normalize_bling_datetime), + AfterValidator(_localize_bling_datetime), +] diff --git a/tests/unit/test_bling_datetime.py b/tests/unit/test_bling_datetime.py index 81eb58e..733bd32 100644 --- a/tests/unit/test_bling_datetime.py +++ b/tests/unit/test_bling_datetime.py @@ -53,17 +53,18 @@ def test_bling_datetime_assigns_local_timezone_and_preserves_offsets( value: str | datetime, offset: int ) -> None: """Offset-free dates use Sao Paulo rules; explicit offsets are preserved.""" - parsed = TypeAdapter[datetime](BlingDatetime).validate_python(value) + parsed = TypeAdapter[datetime | None](BlingDatetime).validate_python(value) + assert parsed is not None assert parsed.utcoffset() == timedelta(hours=offset) if isinstance(value, datetime) and value.tzinfo is not None: assert parsed is value -@pytest.mark.parametrize("value", ["not-a-date", "2026-02-30 12:00:00", "", None]) +@pytest.mark.parametrize("value", ["not-a-date", "2026-02-30 12:00:00", "2026-00-07 12:00:00"]) def test_bling_datetime_rejects_invalid_timestamps(value: object) -> None: """Do not turn malformed timestamps into silently accepted data.""" with pytest.raises(ValidationError): - TypeAdapter[datetime](BlingDatetime).validate_python(value) + TypeAdapter[datetime | None](BlingDatetime).validate_python(value) @pytest.mark.parametrize("model_type", TRACKING_MODELS) @@ -77,6 +78,7 @@ def test_tracking_models_accept_wire_and_python_field_names( python_payload["ultima_alteracao"] = python_payload.pop("ultimaAlteracao") python_model = model_type.model_validate(python_payload) assert python_model == wire + assert wire.ultima_alteracao is not None assert wire.ultima_alteracao.utcoffset() == timedelta(hours=-3) assert "ultima_alteracao" in inspect.signature(model_type).parameters assert "ultimaAlteracao" not in inspect.signature(model_type).parameters @@ -103,6 +105,7 @@ def test_logistics_object_resource_parses_naive_datetime( assert isinstance(response, LogisticasObjetosIdObjetoGetResponse200) assert response.data is not None assert response.data.rastreamento is not None + assert response.data.rastreamento.ultima_alteracao is not None assert response.data.rastreamento.ultima_alteracao.isoformat() == "2026-10-07T11:24:02-03:00" request = httpx_mock.get_request() assert request is not None @@ -132,4 +135,51 @@ def test_logistics_shipment_resource_parses_nested_naive_datetime(httpx_mock: HT response = client.logisticas_remessas.obter(id_remessa=501) assert isinstance(response, LogisticasRemessasIdRemessaGetResponse200) assert response.data is not None + assert response.data.objetos[0].rastreamento.ultima_alteracao is not None assert response.data.objetos[0].rastreamento.ultima_alteracao.utcoffset() == timedelta(hours=-3) + + +@pytest.mark.parametrize("model_type", TRACKING_MODELS) +@pytest.mark.parametrize( + "value", + [None, "", " ", "0000-00-00", "0000-00-00 00:00:00", "0001-01-01", "0001-01-01 00:00:00"], +) +def test_tracking_models_accept_absent_timestamp_sentinels( + model_type: type[LogisticasObjetosRastreamentoDTO | LogisticasRemessasRastreamentoDTO], + value: str | None, +) -> None: + """Absent tracking dates retain other fields and serialize without warnings.""" + payload = cast("JsonObject", cast("JsonObject", _object_payload()["data"])["rastreamento"]) + payload["ultimaAlteracao"] = value + model = model_type.model_validate(payload) + assert model.ultima_alteracao is None + assert model.codigo == "SYNTHETIC123" + assert model.situacao == 1 + assert model.model_dump(mode="json", by_alias=True)["ultimaAlteracao"] is None + assert "ultimaAlteracao" not in to_json_object(model) + payload.pop("ultimaAlteracao") + with pytest.raises(ValidationError): + model_type.model_validate(payload) + + +def test_logistics_object_resource_accepts_zero_timestamp(httpx_mock: HTTPXMock) -> None: + """The zero datetime observed in live objects no longer aborts resource reads.""" + payload = _object_payload() + data = cast("JsonObject", payload["data"]) + tracking = cast("JsonObject", data["rastreamento"]) + tracking["ultimaAlteracao"] = "0000-00-00 00:00:00" + httpx_mock.add_response(json=payload) + with BlingClient(auth=httpx.Auth()) as client: + response = client.logisticas_objetos.obter(id_objeto=701) + assert response.data is not None + assert response.data.rastreamento is not None + assert response.data.rastreamento.ultima_alteracao is None + assert response.data.nota_fiscal.id == 201 + assert to_json_object(response.data)["rastreamento"] == { + "codigo": "SYNTHETIC123", + "descricao": "Em andamento", + "situacao": 1, + "origem": "São Paulo, SP", + "destino": "Curitiba, PR", + "url": "https://example.com/tracking", + } From 3205ce62772bff9aaa6d1378684576b657d561e5 Mon Sep 17 00:00:00 2001 From: TempoNaoTenho Date: Wed, 7 Oct 2026 17:30:12 -0300 Subject: [PATCH 3/4] test(models): audit datetime validation against OpenAPI and live reads --- docs/response-validation-audit.md | 65 +++++++++++++++++++++++++++++++ mkdocs.yml | 1 + tests/unit/test_bling_datetime.py | 30 ++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 docs/response-validation-audit.md diff --git a/docs/response-validation-audit.md b/docs/response-validation-audit.md new file mode 100644 index 0000000..4b7e8c7 --- /dev/null +++ b/docs/response-validation-audit.md @@ -0,0 +1,65 @@ +# Auditoria de validação de respostas — 2026-10-07 + +Esta investigação partiu da falha de `GET /logisticas/objetos/{idObjeto}` no +SDK 0.3.1: o Bling envia `rastreamento.ultimaAlteracao` sem offset, enquanto +o modelo gerado exigia `AwareDatetime`. + +## Problemas confirmados e correções + +| Problema | Evidência | Correção | +| --- | --- | --- | +| Timestamp sem fuso rejeitado | Reprodução com `2026-10-07 11:24:02`; o próprio OpenAPI contém exemplo sem offset | `BlingDatetime` interpreta valores sem fuso como `America/Sao_Paulo` e preserva offsets explícitos | +| Mesmo problema em remessas | `LogisticasRemessasRastreamentoDTO.ultimaAlteracao` também era `AwareDatetime` | Mesmo tipo aplicado pelo gerador; teste de leitura com rastreamento dentro de `objetos[]` | +| Data zerada rejeitada | Cinco objetos reais apresentaram sentinela de data zerada durante a investigação | Sentinelas de ausência viram `None`; datas malformadas comuns continuam falhando | + +O tipo usa as regras IANA de São Paulo, incluindo o horário de verão histórico. +A dependência `tzdata` permite a mesma interpretação em sistemas sem base de +fusos. As chaves obrigatórias, os aliases e a proteção contra valores +conflitantes continuam sendo validados. + +## Varredura dos modelos e do OpenAPI + +- Os dois campos acima eram os únicos campos gerados com `AwareDatetime`. +- Há 52 propriedades com formato `date` nos componentes do OpenAPI. Seus + exemplos passaram pelo parser `BlingDate`, que já trata datas zeradas. +- Quatro propriedades usam o formato não padronizado `datetime`: + `NotasFiscaisDadosBaseDTO.dataEmissao`, `dataOperacao` e + `NotificacoesDadosBaseDTO.dataEnvio`, `dataLeitura`. O gerador as mantém como + texto; elas não exigem timezone e não foram convertidas nesta correção. +- Um teste percorre os exemplos `date-time` do OpenAPI e os valida com os + campos dos modelos gerados, para detectar novamente uma incompatibilidade + entre o exemplo oficial e o parser. + +## Amostragem real, somente leitura + +As consultas usaram `doppler run --project bling-sdk --config dev` e o +transporte do SDK com `rate_limit_max_requests=1` e +`rate_limit_period_seconds=2.1`. O limitador permaneceu habilitado, inclusive +para retries. Os relatórios exibiram somente rotas sem IDs, tipos e locais +de erros e resultados de validação. Payloads, identificadores, valores +financeiros e credenciais não foram salvos. As fixtures são sintéticas. + +| Recurso | Resultado observado | +| --- | --- | +| Pedidos de venda, listagem e detalhe | Validação e serialização passaram | +| Produtos, listagem e detalhe | Validação e serialização passaram | +| Contatos, listagem e detalhe | Validação e serialização passaram | +| NF-e, listagem e detalhe | Validação e serialização passaram | +| Contas a pagar e a receber, listagem e detalhe | Validação e serialização passaram | +| Logísticas, listagem | Validação e serialização passaram | +| Objetos de logística | Oito leituras pelo método público passaram após as duas correções, com timestamp ausente representado por `None` | +| NFS-e | Listagem validada; sem registros para verificar detalhe | +| NFC-e | API retornou erro de autenticação/acesso; modelos não foram validados com payload real | +| Remessas por logística | API retornou erro de validação nas duas logísticas consultadas; detalhes permanecem cobertos por fixture sintética e teste do método público | + +As amostras reais de rastreamento verificadas nesta execução tinham data +zerada. Datas sem fuso válidas foram verificadas com o valor relatado, os +exemplos oficiais e testes locais. A amostragem não prova que todos os +payloads possíveis de todos os endpoints sejam aceitos. + +## Limite em relação ao Django + +A atualização do Django foi excluída desta etapa a pedido do usuário. O SDK +corrige as duas falhas confirmadas de leitura. A persistência de NF-e/DANFE +em `sync_order()` e a recuperação de fichas antigas dependem do aplicativo +consumidor e não foram alteradas ou validadas aqui. diff --git a/mkdocs.yml b/mkdocs.yml index 7085292..dbeeb7a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -64,6 +64,7 @@ nav: - Autenticação: authentication.md - Paginação: pagination.md - Erros: errors.md + - Validação de respostas: response-validation-audit.md - Recursos: - resources/sales_orders.md - resources/products.md diff --git a/tests/unit/test_bling_datetime.py b/tests/unit/test_bling_datetime.py index 733bd32..b8fd6c3 100644 --- a/tests/unit/test_bling_datetime.py +++ b/tests/unit/test_bling_datetime.py @@ -14,6 +14,7 @@ from bling_erp_api import BlingClient from bling_erp_api.models.fields import BlingDatetime +from bling_erp_api.models.generated import schemas from bling_erp_api.models.generated.schemas.logisticas_objetos import ( LogisticasObjetosIdObjetoGetResponse200, LogisticasObjetosRastreamentoDTO, @@ -27,9 +28,11 @@ if TYPE_CHECKING: from pytest_httpx import HTTPXMock + from bling_erp_api.models.base import BlingModel from bling_erp_api.types import JsonObject FIXTURE = Path(__file__).parents[1] / "fixtures/responses/logisticas_objetos_get.json" +SPEC = Path(__file__).parents[2] / "specs/bling-openapi-reference.json" TRACKING_MODELS = (LogisticasObjetosRastreamentoDTO, LogisticasRemessasRastreamentoDTO) @@ -37,6 +40,33 @@ def _object_payload() -> JsonObject: return cast("JsonObject", json.loads(FIXTURE.read_text(encoding="utf-8"))) +def test_generated_datetime_fields_accept_openapi_examples() -> None: + """Every component date-time example must pass its generated field validator.""" + spec = cast("JsonObject", json.loads(SPEC.read_text(encoding="utf-8"))) + components = cast("JsonObject", spec["components"]) + definitions = cast("JsonObject", components["schemas"]) + checked = 0 + for schema_name, definition in definitions.items(): + properties = cast("JsonObject", cast("JsonObject", definition).get("properties", {})) + for wire_name, property_definition in properties.items(): + prop = cast("JsonObject", property_definition) + if prop.get("format") != "date-time" or "example" not in prop: + continue + model_type = cast("type[BlingModel]", getattr(schemas, schema_name)) + field = next( + field + for name, field in model_type.model_fields.items() + if (field.serialization_alias or name) == wire_name + ) + parsed = TypeAdapter[datetime | None](field.rebuild_annotation()).validate_python( + prop["example"], + ) + assert parsed is not None + assert parsed.utcoffset() is not None + checked += 1 + assert checked >= 2 + + @pytest.mark.parametrize( ("value", "offset"), [ From a3de11bdf2418ae6c7573f88ef3fedba994b40b4 Mon Sep 17 00:00:00 2001 From: TempoNaoTenho Date: Wed, 7 Oct 2026 17:31:24 -0300 Subject: [PATCH 4/4] chore(release): prepare 0.3.2 tracking validation fixes --- CHANGELOG.md | 2 ++ src/bling_erp_api/_version.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 516cb66..8d85fb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.3.2] - 2026-10-07 + ### Fixed - Tracking timestamps in logistics objects and shipment objects now accept offset-free Bling values through the generated `BlingDatetime` type. Naive timestamps use `America/Sao_Paulo` (including historical daylight saving time); explicit offsets are preserved, and JSON serialization emits an ISO timestamp with its offset. The generator and shared model namespace use the same type, with portable timezone data supplied by `tzdata`. diff --git a/src/bling_erp_api/_version.py b/src/bling_erp_api/_version.py index a86b5c4..a79cb0d 100644 --- a/src/bling_erp_api/_version.py +++ b/src/bling_erp_api/_version.py @@ -1,3 +1,3 @@ """Package version.""" -__version__ = "0.3.1" +__version__ = "0.3.2"