diff --git a/CHANGELOG.md b/CHANGELOG.md index e315132..8d85fb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ 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`. +- 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 ### Fixed diff --git a/docs/getting-started.md b/docs/getting-started.md index 36b4b82..4d6624f 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -114,6 +114,23 @@ 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. + +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/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/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/_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" diff --git a/src/bling_erp_api/models/fields.py b/src/bling_erp_api/models/fields.py index f98f5e6..6f50ab3 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, BeforeValidator from pydantic.functional_serializers import PlainSerializer from pydantic.functional_validators import PlainValidator @@ -44,3 +46,37 @@ 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 _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 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 | None, + BeforeValidator(_normalize_bling_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..b8fd6c3 --- /dev/null +++ b/tests/unit/test_bling_datetime.py @@ -0,0 +1,215 @@ +"""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 import schemas +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.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) + + +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"), + [ + ("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 | 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", "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 | None](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 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 + 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 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 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", + } 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"