From 0aabc2c77be975a277b4e312846d4756c2c3d416 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 17 May 2026 22:37:42 +0000 Subject: [PATCH 1/2] feat(api): api update --- .stats.yml | 4 +- .../resources/dealers/nlt/offers.py | 20 +- .../resources/dealers/nlt_settings.py | 16 +- .../types/dealers/nlt/nlt_offer_summary.py | 32 ++- .../types/dealers/nlt/offer_list_params.py | 13 +- .../dealers/nlt/offer_retrieve_response.py | 231 ++++++++++-------- .../api_resources/dealers/nlt/test_offers.py | 8 +- 7 files changed, 190 insertions(+), 134 deletions(-) diff --git a/.stats.yml b/.stats.yml index 4f9f81e..db6c6af 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 12 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/azure/partnermax-eb7585f84c961b4388b2cfe057e3dba9edf92af55cd9cb6fa097f0d1ca21c55b.yml -openapi_spec_hash: fa2c2ed67b11bd033589562913a61a41 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/azure/partnermax-4426eaa7a8352dde34f30097e25554f3361f148d5d424c0f4d4f95a619c5e34c.yml +openapi_spec_hash: 868381cc42d1ff37e163dd9f243f7762 config_hash: bf921bb2f4db038ffed15141c74cd018 diff --git a/src/partnermax/resources/dealers/nlt/offers.py b/src/partnermax/resources/dealers/nlt/offers.py index cfc34b7..40c7cbf 100644 --- a/src/partnermax/resources/dealers/nlt/offers.py +++ b/src/partnermax/resources/dealers/nlt/offers.py @@ -95,10 +95,10 @@ def list( canone_max_eur: int | Omit = omit, cursor: str | Omit = omit, duration_months: Literal[24, 36, 48] | Omit = omit, - fuel_type: Literal["electric", "hybrid", "plugin_hybrid", "petrol", "diesel", "lpg", "methane"] | Omit = omit, + fuel_type: str | Omit = omit, km_per_year: Literal[10000, 15000, 20000, 25000, 30000, 40000] | Omit = omit, limit: int | Omit = omit, - segment: Literal["A", "B", "C", "D", "E", "SUV", "VAN"] | Omit = omit, + segment: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -118,6 +118,12 @@ def list( cursor: Opaque pagination cursor. + fuel_type: Raw Italian label (case-insensitive ILIKE match). Examples: "Benzina", "Diesel", + "Ibrido benzina", "Ibrido diesel", "Elettrica", "GPL", "Metano". + + segment: Raw Italian label (case-insensitive ILIKE substring match). Examples: "SUV + piccoli", "SUV medi", "Superiori", "Medie", "Utilitarie". + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -224,10 +230,10 @@ async def list( canone_max_eur: int | Omit = omit, cursor: str | Omit = omit, duration_months: Literal[24, 36, 48] | Omit = omit, - fuel_type: Literal["electric", "hybrid", "plugin_hybrid", "petrol", "diesel", "lpg", "methane"] | Omit = omit, + fuel_type: str | Omit = omit, km_per_year: Literal[10000, 15000, 20000, 25000, 30000, 40000] | Omit = omit, limit: int | Omit = omit, - segment: Literal["A", "B", "C", "D", "E", "SUV", "VAN"] | Omit = omit, + segment: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -247,6 +253,12 @@ async def list( cursor: Opaque pagination cursor. + fuel_type: Raw Italian label (case-insensitive ILIKE match). Examples: "Benzina", "Diesel", + "Ibrido benzina", "Ibrido diesel", "Elettrica", "GPL", "Metano". + + segment: Raw Italian label (case-insensitive ILIKE substring match). Examples: "SUV + piccoli", "SUV medi", "Superiori", "Medie", "Utilitarie". + extra_headers: Send extra headers extra_query: Add additional query parameters to the request diff --git a/src/partnermax/resources/dealers/nlt_settings.py b/src/partnermax/resources/dealers/nlt_settings.py index cd6e5fc..4ba75a8 100644 --- a/src/partnermax/resources/dealers/nlt_settings.py +++ b/src/partnermax/resources/dealers/nlt_settings.py @@ -104,12 +104,12 @@ def update( ``` listino_imponibile = prezzo_listino / 1.22 provvigione = listino_imponibile × (agency_markup_percent / 100) - canon = base_canon + provvigione / durata - anticipo_eur / durata - if offer.vat_treatment == "private": canon *= 1.22 + canon = base_canon + provvigione / duration_months - down_payment_eur / duration_months + if offer.private_only: canon *= 1.22 ``` - VAT treatment is a property of each offer (`NltOfferSummary.vat_treatment`), not - of the dealer. + VAT treatment is a property of each offer (`NltOfferDetail.private_only` / + `NltOfferSummary.vat_treatment`), not of the dealer. Args: down_payment_tiers: Three down-payment scenarios shown to consumers, in strictly ascending order @@ -223,12 +223,12 @@ async def update( ``` listino_imponibile = prezzo_listino / 1.22 provvigione = listino_imponibile × (agency_markup_percent / 100) - canon = base_canon + provvigione / durata - anticipo_eur / durata - if offer.vat_treatment == "private": canon *= 1.22 + canon = base_canon + provvigione / duration_months - down_payment_eur / duration_months + if offer.private_only: canon *= 1.22 ``` - VAT treatment is a property of each offer (`NltOfferSummary.vat_treatment`), not - of the dealer. + VAT treatment is a property of each offer (`NltOfferDetail.private_only` / + `NltOfferSummary.vat_treatment`), not of the dealer. Args: down_payment_tiers: Three down-payment scenarios shown to consumers, in strictly ascending order diff --git a/src/partnermax/types/dealers/nlt/nlt_offer_summary.py b/src/partnermax/types/dealers/nlt/nlt_offer_summary.py index 42bdf44..0615904 100644 --- a/src/partnermax/types/dealers/nlt/nlt_offer_summary.py +++ b/src/partnermax/types/dealers/nlt/nlt_offer_summary.py @@ -12,13 +12,12 @@ class NltOfferSummary(BaseModel): brand: str dealer_id: str + """Dealer id (prefixed `dlr_`).""" - duration_months: Literal[24, 36, 48] + duration_months: Literal[36, 48, 60] """Duration corresponding to the `monthly_canon_from_eur` quote.""" - fuel_type: Literal["electric", "hybrid", "plugin_hybrid", "petrol", "diesel", "lpg", "methane"] - - km_per_year_at_quote: int + km_per_year_at_quote: Literal[10000, 15000, 20000, 25000, 30000, 40000] """Km/year corresponding to the `monthly_canon_from_eur` quote.""" model: str @@ -30,8 +29,13 @@ class NltOfferSummary(BaseModel): """ offer_id: str + """Numeric `nlt_offerte.id_offerta` as string. + + Use as the path parameter for the detail endpoint. + """ - segment: Literal["A", "B", "C", "D", "E", "SUV", "VAN"] + slug: str + """Offer slug used in canonical URLs (`/noleggio-lungo-termine/{slug}`).""" vat_treatment: Literal["private", "business"] """VAT treatment of this offer (not the dealer). @@ -41,10 +45,26 @@ class NltOfferSummary(BaseModel): """ canonical_url: Optional[str] = None - """Consumer-facing URL on the dealer's public site.""" + """ + Consumer-facing URL on the dealer's public site: + `https://{primary_domain}/noleggio-lungo-termine/{slug}`. Null when the dealer + has no site row. + """ + + fuel_type: Optional[str] = None + """Raw Italian label from `nlt_offerte.alimentazione` (e.g. + + "Benzina", "Ibrido diesel"). Apimax-aligned, no enum normalization. + """ has_promo: Optional[bool] = None image_url: Optional[str] = None + segment: Optional[str] = None + """Raw Italian label from `nlt_offerte.segmento` (e.g. + + "SUV piccoli", "Superiori"). Apimax-aligned, no enum normalization. + """ + trim: Optional[str] = None diff --git a/src/partnermax/types/dealers/nlt/offer_list_params.py b/src/partnermax/types/dealers/nlt/offer_list_params.py index b8475ce..e335d2b 100644 --- a/src/partnermax/types/dealers/nlt/offer_list_params.py +++ b/src/partnermax/types/dealers/nlt/offer_list_params.py @@ -19,10 +19,19 @@ class OfferListParams(TypedDict, total=False): duration_months: Literal[24, 36, 48] - fuel_type: Literal["electric", "hybrid", "plugin_hybrid", "petrol", "diesel", "lpg", "methane"] + fuel_type: str + """Raw Italian label (case-insensitive ILIKE match). + + Examples: "Benzina", "Diesel", "Ibrido benzina", "Ibrido diesel", "Elettrica", + "GPL", "Metano". + """ km_per_year: Literal[10000, 15000, 20000, 25000, 30000, 40000] limit: int - segment: Literal["A", "B", "C", "D", "E", "SUV", "VAN"] + segment: str + """Raw Italian label (case-insensitive ILIKE substring match). + + Examples: "SUV piccoli", "SUV medi", "Superiori", "Medie", "Utilitarie". + """ diff --git a/src/partnermax/types/dealers/nlt/offer_retrieve_response.py b/src/partnermax/types/dealers/nlt/offer_retrieve_response.py index 0ce6d92..dfa71df 100644 --- a/src/partnermax/types/dealers/nlt/offer_retrieve_response.py +++ b/src/partnermax/types/dealers/nlt/offer_retrieve_response.py @@ -8,71 +8,63 @@ __all__ = [ "OfferRetrieveResponse", - "AccessoriInclusi", - "AddonsDisponibili", - "AddonsDisponibiliAutoSostitutiva", - "AddonsDisponibiliPneumatici", - "AnticipoScenariEur", - "AnticipoScenariLabels", + "AvailableAddons", + "AvailableAddonsReplacementVehicle", + "AvailableAddonsTires", + "DownPaymentScenariosEur", + "DownPaymentScenariosLabels", "Gallery", + "IncludedAccessory", + "IncludedService", "NetworkOffer", - "Quotazioni", - "ServiziInclusi", + "Quotation", "Tag", ] -class AccessoriInclusi(BaseModel): - codice: str +class AvailableAddonsReplacementVehicle(BaseModel): + category_description: str - descrizione: str - - prezzo_extra_eur: float - - -class AddonsDisponibiliAutoSostitutiva(BaseModel): - categoria_default: str + default_category: str """Replacement vehicle category (B fixed).""" - categoria_descrizione: str + monthly_cost_eur: float - costo_mensile_eur: float - - -class AddonsDisponibiliPneumatici(BaseModel): - costo_treno_eur: float - """Cost of one set of 4 tyres, EUR.""" - diametro_in: int +class AvailableAddonsTires(BaseModel): + diameter_in: int """Tyre diameter in inches.""" - regola_cambio: str + replacement_rule: str """Replacement rule (e.g. one set every 30 000 km, rounded up).""" + set_cost_eur: float + """Cost of one set of 4 tyres, EUR.""" + -class AddonsDisponibili(BaseModel): - auto_sostitutiva: Optional[AddonsDisponibiliAutoSostitutiva] = None +class AvailableAddons(BaseModel): + replacement_vehicle: Optional[AvailableAddonsReplacementVehicle] = None - pneumatici: Optional[AddonsDisponibiliPneumatici] = None + tires: Optional[AvailableAddonsTires] = None -class AnticipoScenariEur(BaseModel): - anticipo_medio: int +class DownPaymentScenariosEur(BaseModel): + medium: int """12.5% down-payment scenario, whole EUR.""" - anticipo_standard: int + standard: int """25% down-payment scenario, whole EUR (matches vetrina canon).""" - anticipo_zero: int + zero: int """Zero down-payment scenario, whole EUR.""" -class AnticipoScenariLabels(BaseModel): - anticipo_medio: str +class DownPaymentScenariosLabels(BaseModel): + medium: str - anticipo_standard: str + standard: str - anticipo_zero: str + zero: str class Gallery(BaseModel): @@ -81,13 +73,29 @@ class Gallery(BaseModel): url: str -class NetworkOffer(BaseModel): - canone_mensile_min_eur: float +class IncludedAccessory(BaseModel): + code: str + + description: str + + extra_price_eur: float + +class IncludedService(BaseModel): + name: str + """Service name (e.g. "Assicurazione RCA", "Manutenzione").""" + + description: Optional[str] = None + """Short human description (e.g. "Responsabilità Civile Auto").""" + + +class NetworkOffer(BaseModel): dealer_id: int dealer_name: str + min_monthly_canon_eur: float + city: Optional[str] = None contact_url: Optional[str] = None @@ -103,25 +111,17 @@ class NetworkOffer(BaseModel): review_count: Optional[int] = None -class Quotazioni(BaseModel): - canone_mensile_eur: float - """Displayed monthly canon for this (durata, km) cell. +class Quotation(BaseModel): + duration_months: Literal[36, 48, 60] - Computed by `calcola_canone_vetrina` for the primary dealer of the partner - network; VAT-inclusive when `solo_privati=true`. - """ - - durata_mesi: Literal[36, 48, 60] - - km_inclusi_anno: Literal[10000, 15000, 20000, 25000, 30000, 40000] + km_per_year: Literal[10000, 15000, 20000, 25000, 30000, 40000] + monthly_canon_eur: float + """Displayed monthly canon for this (duration, km) cell. -class ServiziInclusi(BaseModel): - name: str - """Service name (e.g. "Assicurazione RCA", "Manutenzione").""" - - description: Optional[str] = None - """Short human description (e.g. "Responsabilità Civile Auto").""" + Computed by `calcola_canone_vetrina` for the primary dealer of the partner + network; VAT-inclusive when `private_only=true`. + """ class Tag(BaseModel): @@ -137,107 +137,122 @@ class Tag(BaseModel): class OfferRetrieveResponse(BaseModel): """Full offer detail. - Shape mirrors apimax MCP `get_nlt_offer_details` (`apimax/app/api/mcp_server.py::_tool_get_nlt_offer_details`) bit-for-bit so partnermax SDK consumers stay aligned with the Custom GPT / MCP clients on the DealerMAX network. + Field names: American English snake_case (Stripe-style SDK contract). Values: raw Italian, apimax-aligned. Shape mirrors apimax MCP `get_nlt_offer_details` (`apimax/app/api/mcp_server.py::_tool_get_nlt_offer_details`). """ found: bool - iva_inclusa: bool - """True when canon is VAT-inclusive (i.e. `solo_privati=true`).""" - network_dealer_count: int - slug: str - """Offer slug (stable identifier shared with apimax surfaces).""" - - title: str - - accessori_di_serie: Optional[List[str]] = None - """Standard equipment list (one entry per item). + offer_id: str + """Numeric `nlt_offerte.id_offerta` as string. - Sourced from `mnet_dettagli.equipaggiamento` split on newlines/semicolons. - Currently empty on every live offer (upstream column unpopulated); will - auto-fill when the data flows in. + Same value returned by the listing endpoint. """ - accessori_inclusi: Optional[List[AccessoriInclusi]] = None + slug: str + """ + Offer slug (stable identifier shared with apimax surfaces and used in canonical + URLs). + """ - addons_disponibili: Optional[AddonsDisponibili] = None + title: str - anticipo_scenari_eur: Optional[AnticipoScenariEur] = None + vat_included: bool + """True when canon is VAT-inclusive (i.e. `private_only=true`).""" - anticipo_scenari_labels: Optional[AnticipoScenariLabels] = None + available_addons: Optional[AvailableAddons] = None - canone_mensile_min_eur: Optional[float] = None - """ - Lowest canon across the partner network for this offer (primary dealer's price). - """ + brand: Optional[str] = None description_full: Optional[str] = None """AI-generated long-form description.""" description_short: Optional[str] = None - dettagli_tecnici: Optional[Dict[str, object]] = None - """ - Full Motornet technical sheet — apimax: `_get_dettagli_motornet` - (`nlt_resolver.py:752`). Every non-null `mnet_dettagli` column for this - `codice_motornet_uni` flattened into a plain dict (~30-40 keys typically - populated out of 90 columns). Native units preserved: cilindrata (cc), kw, hp, - coppia, accelerazione (s), velocita (km/h), lunghezza/larghezza/altezza/passo - (cm), peso (kg), bagagliaio (L, free-text), emissioni_co2 (g/km, free-text), - pneumatici_anteriori ("205/55 R17"), trazione, alimentazione, cambio, euro, - autonomia_media, capacita_nominale_batteria, etc. Keys are stable across offers; - values are int/float/bool/string (timestamps ISO-formatted). - """ + down_payment_scenarios_eur: Optional[DownPaymentScenariosEur] = None + + down_payment_scenarios_labels: Optional[DownPaymentScenariosLabels] = None fuel_type: Optional[str] = None - """Raw Italian label from `nlt_offerte.alimentazione` (e.g. "Benzina", "Ibrida").""" + """Raw Italian label from `nlt_offerte.alimentazione` (e.g. + + "Benzina", "Ibrido diesel"). + """ gallery: Optional[List[Gallery]] = None image_url: Optional[str] = None + included_accessories: Optional[List[IncludedAccessory]] = None + + included_services: Optional[List[IncludedService]] = None + """Services normally included in the canone. + + apimax: `_get_services_included` (`nlt_resolver.py:719`) reads global + `nlt_services` is_active table. + """ + last_modified: Optional[datetime] = None - marca: Optional[str] = None + min_monthly_canon_eur: Optional[float] = None + """ + Lowest canon across the partner network for this offer (primary dealer's price). + """ - modello: Optional[str] = None + model: Optional[str] = None network_offers: Optional[List[NetworkOffer]] = None """All the partner's dealers that can fulfil this offer, sorted by canon ASC.""" - prezzo_totale_eur: Optional[float] = None - """List price IVA-inclusive (vehicle + accessories + MSS).""" - primary_dealer_city: Optional[str] = None primary_dealer_name: Optional[str] = None primary_dealer_province: Optional[str] = None - quotazioni: Optional[List[Quotazioni]] = None + private_only: Optional[bool] = None + """Per-offer VAT scope: true → consumer-facing (B2C, VAT-inclusive). - segmento: Optional[str] = None + false → business (B2B, VAT-exclusive). Sourced from `nlt_offerte.solo_privati`. + """ - servizi_inclusi: Optional[List[ServiziInclusi]] = None - """Services normally included in the canone. + quotations: Optional[List[Quotation]] = None - apimax: `_get_services_included` (`nlt_resolver.py:719`) reads global - `nlt_services` is_active table — same 8 services across the network - (Assicurazione RCA / Kasco / Incendio-Furto, Manutenzione, Assistenza Stradale, - Bollo, Pneumatici, Veicolo in anticipo). Not per-offer. + segment: Optional[str] = None + """Raw Italian label from `nlt_offerte.segmento` (e.g. + + "SUV piccoli", "Superiori"). """ - solo_privati: Optional[bool] = None - """Per-offer VAT scope: true → consumer-facing (B2C, VAT-inclusive). + standard_equipment: Optional[List[str]] = None + """Standard equipment list (one entry per item). - false → business (B2B, VAT-exclusive). + Sourced from `mnet_dettagli.equipaggiamento` split on newlines/semicolons. + Currently empty on every live offer (upstream column unpopulated); will + auto-fill when the data flows in. """ tags: Optional[List[Tag]] = None + technical_details: Optional[Dict[str, object]] = None + """ + Full Motornet technical sheet — apimax: `_get_dettagli_motornet` + (`nlt_resolver.py:752`). Every non-null `mnet_dettagli` column for this + `codice_motornet_uni` flattened into a plain dict (~30-40 keys typically + populated out of 90 columns). KEYS stay Italian because they are raw SQL column + names: cilindrata (cc), kw, hp, coppia, accelerazione (s), velocita (km/h), + lunghezza/larghezza/altezza/passo (cm), peso (kg), bagagliaio (L, free-text), + emissioni_co2 (g/km, free-text), pneumatici_anteriori ("205/55 R17"), trazione, + alimentazione, cambio, euro, autonomia_media, capacita_nominale_batteria, etc. + Native units preserved; values are int/float/bool/string (timestamps + ISO-formatted). + """ + + total_price_eur: Optional[float] = None + """List price IVA-inclusive (vehicle + accessories + MSS).""" + transmission: Optional[str] = None - """Raw Italian label from `nlt_offerte.cambio`.""" + """Raw Italian label from `nlt_offerte.cambio` (e.g. "Automatico sequenziale").""" - versione: Optional[str] = None + trim: Optional[str] = None diff --git a/tests/api_resources/dealers/nlt/test_offers.py b/tests/api_resources/dealers/nlt/test_offers.py index dffafb9..47df2f3 100644 --- a/tests/api_resources/dealers/nlt/test_offers.py +++ b/tests/api_resources/dealers/nlt/test_offers.py @@ -86,10 +86,10 @@ def test_method_list_with_all_params(self, client: Partnermax) -> None: canone_max_eur=50, cursor="cursor", duration_months=24, - fuel_type="electric", + fuel_type="fuel_type", km_per_year=10000, limit=1, - segment="A", + segment="segment", ) assert_matches_type(OfferListResponse, offer, path=["response"]) @@ -202,10 +202,10 @@ async def test_method_list_with_all_params(self, async_client: AsyncPartnermax) canone_max_eur=50, cursor="cursor", duration_months=24, - fuel_type="electric", + fuel_type="fuel_type", km_per_year=10000, limit=1, - segment="A", + segment="segment", ) assert_matches_type(OfferListResponse, offer, path=["response"]) From b8f05a7489cc728641fc9d10c48284427ffd5bde Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 17 May 2026 22:37:57 +0000 Subject: [PATCH 2/2] release: 0.5.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 8 ++++++++ pyproject.toml | 2 +- src/partnermax/_version.py | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index da59f99..2aca35a 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.4.0" + ".": "0.5.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b3972b..a0a8fc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.5.0 (2026-05-17) + +Full Changelog: [v0.4.0...v0.5.0](https://github.com/DealerMax-app/partnermax-python/compare/v0.4.0...v0.5.0) + +### Features + +* **api:** api update ([0aabc2c](https://github.com/DealerMax-app/partnermax-python/commit/0aabc2c77be975a277b4e312846d4756c2c3d416)) + ## 0.4.0 (2026-05-17) Full Changelog: [v0.3.0...v0.4.0](https://github.com/DealerMax-app/partnermax-python/compare/v0.3.0...v0.4.0) diff --git a/pyproject.toml b/pyproject.toml index 692b799..993efde 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "partnermax" -version = "0.4.0" +version = "0.5.0" description = "The official Python library for the partnermax API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/partnermax/_version.py b/src/partnermax/_version.py index a702f98..fe67b16 100644 --- a/src/partnermax/_version.py +++ b/src/partnermax/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "partnermax" -__version__ = "0.4.0" # x-release-please-version +__version__ = "0.5.0" # x-release-please-version