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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ jobs:
publish:
name: publish
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
Expand All @@ -24,5 +27,3 @@ jobs:
- name: Publish to PyPI
run: |
bash ./bin/publish-pypi
env:
PYPI_TOKEN: ${{ secrets.PARTNERMAX_PYPI_TOKEN || secrets.PYPI_TOKEN }}
2 changes: 0 additions & 2 deletions .github/workflows/release-doctor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,3 @@ jobs:
- name: Check release environment
run: |
bash ./bin/check-release-environment
env:
PYPI_TOKEN: ${{ secrets.PARTNERMAX_PYPI_TOKEN || secrets.PYPI_TOKEN }}
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.12.0"
".": "0.12.1"
}
6 changes: 3 additions & 3 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 20
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/azure/partnermax-e9423fadcdede8fbb63a47e273c68c8941e6bff3f2d61257d31fe9a1a1a7f72b.yml
openapi_spec_hash: 841e984b131bfb9b83d26fdd9dbd5303
config_hash: 89f4c4e21186c377826cbe4a5685df09
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/azure/partnermax-0c2a3d56340127842c8ff928074b12d81705700b06b497d6965138738b28b656.yml
openapi_spec_hash: 515afa9ff78787ae666a539d452d5d35
config_hash: a9bb2c41f2e0cabac79eda1df90a1445
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 0.12.1 (2026-06-28)

Full Changelog: [v0.12.0...v0.12.1](https://github.com/DealerMax-app/partnermax-python/compare/v0.12.0...v0.12.1)

### Bug Fixes

* regenerate SDK from openapi 1.5.10 upload schema ([05c5f4e](https://github.com/DealerMax-app/partnermax-python/commit/05c5f4e18c753cbed8f1de91918301aa161778b9))

## 0.12.0 (2026-06-28)

Full Changelog: [v0.11.1...v0.12.0](https://github.com/DealerMax-app/partnermax-python/compare/v0.11.1...v0.12.0)
Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,25 @@ nlt_settings = client.dealers.nlt_settings.update(
print(nlt_settings.down_payment_tiers)
```

## File uploads

Request parameters that correspond to file uploads can be passed as `bytes`, or a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance or a tuple of `(filename, contents, media type)`.

```python
from pathlib import Path
from partnermax import Partnermax

client = Partnermax()

client.dealers.vehicles.images.create(
vehicle_id="vehicle_id",
dealer_id="dealer_id",
file=Path("/path/to/file"),
)
```

The async client uses the exact same interface. If you pass a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance, the file contents will be read asynchronously automatically.

## Handling errors

When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `partnermax.APIConnectionError` is raised.
Expand Down
4 changes: 0 additions & 4 deletions bin/check-release-environment
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@

errors=()

if [ -z "${PYPI_TOKEN}" ]; then
errors+=("The PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.")
fi

lenErrors=${#errors[@]}

if [[ lenErrors -gt 0 ]]; then
Expand Down
6 changes: 5 additions & 1 deletion bin/publish-pypi
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@ set -eux
rm -rf dist
mkdir -p dist
uv build
uv publish --token=$PYPI_TOKEN
if [ -n "${PYPI_TOKEN:-}" ]; then
uv publish --token=$PYPI_TOKEN
else
uv publish
fi
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "partnermax"
version = "0.12.0"
version = "0.12.1"
description = "The official Python library for the partnermax API"
dynamic = ["readme"]
license = "Apache-2.0"
Expand Down
2 changes: 1 addition & 1 deletion src/partnermax/_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def assert_is_file_content(obj: object, *, key: str | None = None) -> None:
if not is_file_content(obj):
prefix = f"Expected entry at `{key}`" if key is not None else f"Expected file input `{obj!r}`"
raise RuntimeError(
f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead."
f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead. See https://github.com/DealerMax-app/partnermax-python/tree/main#file-uploads"
) from None


Expand Down
2 changes: 1 addition & 1 deletion src/partnermax/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

__title__ = "partnermax"
__version__ = "0.12.0" # x-release-please-version
__version__ = "0.12.1" # x-release-please-version
21 changes: 15 additions & 6 deletions src/partnermax/resources/dealers/vehicles/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

from __future__ import annotations

from typing import Mapping, cast

import httpx

from ...._types import Body, Query, Headers, NoneType, NotGiven, not_given
from ...._utils import path_template, maybe_transform, async_maybe_transform
from ...._files import deepcopy_with_paths
from ...._types import Body, Query, Headers, NoneType, NotGiven, FileTypes, not_given
from ...._utils import extract_files, path_template, maybe_transform, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import (
Expand Down Expand Up @@ -52,7 +55,7 @@ def create(
vehicle_id: str,
*,
dealer_id: str,
file: str,
file: FileTypes,
# 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,
Expand Down Expand Up @@ -91,6 +94,8 @@ def create(
raise ValueError(f"Expected a non-empty value for `dealer_id` but received {dealer_id!r}")
if not vehicle_id:
raise ValueError(f"Expected a non-empty value for `vehicle_id` but received {vehicle_id!r}")
body = deepcopy_with_paths({"file": file}, [["file"]])
files = extract_files(cast(Mapping[str, object], body), paths=[["file"]])
# It should be noted that the actual Content-Type header that will be
# sent to the server will contain a `boundary` parameter, e.g.
# multipart/form-data; boundary=---abc--
Expand All @@ -99,7 +104,8 @@ def create(
path_template(
"/v1/dealers/{dealer_id}/vehicles/{vehicle_id}/images", dealer_id=dealer_id, vehicle_id=vehicle_id
),
body=maybe_transform({"file": file}, image_create_params.ImageCreateParams),
body=maybe_transform(body, image_create_params.ImageCreateParams),
files=files,
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
Expand Down Expand Up @@ -234,7 +240,7 @@ async def create(
vehicle_id: str,
*,
dealer_id: str,
file: str,
file: FileTypes,
# 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,
Expand Down Expand Up @@ -273,6 +279,8 @@ async def create(
raise ValueError(f"Expected a non-empty value for `dealer_id` but received {dealer_id!r}")
if not vehicle_id:
raise ValueError(f"Expected a non-empty value for `vehicle_id` but received {vehicle_id!r}")
body = deepcopy_with_paths({"file": file}, [["file"]])
files = extract_files(cast(Mapping[str, object], body), paths=[["file"]])
# It should be noted that the actual Content-Type header that will be
# sent to the server will contain a `boundary` parameter, e.g.
# multipart/form-data; boundary=---abc--
Expand All @@ -281,7 +289,8 @@ async def create(
path_template(
"/v1/dealers/{dealer_id}/vehicles/{vehicle_id}/images", dealer_id=dealer_id, vehicle_id=vehicle_id
),
body=await async_maybe_transform({"file": file}, image_create_params.ImageCreateParams),
body=await async_maybe_transform(body, image_create_params.ImageCreateParams),
files=files,
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
Expand Down
4 changes: 2 additions & 2 deletions src/partnermax/resources/dealers/vehicles/vehicles.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def create(
certified_km: Certified odometer reading at intake, in kilometres.

motornet_code: Motornet UNI code identifying the exact vehicle configuration. Must exist in the
used-vehicle catalogue at submission time; otherwise the call returns 422
DealerMAX auto/VCOM catalogue at submission time; otherwise the call returns 422
`motornet_code_not_in_catalogue`. Partners may send a code from their own
Motornet agreement or use the paid control-plane targa/VIN resolver before
creating the vehicle.
Expand Down Expand Up @@ -560,7 +560,7 @@ async def create(
certified_km: Certified odometer reading at intake, in kilometres.

motornet_code: Motornet UNI code identifying the exact vehicle configuration. Must exist in the
used-vehicle catalogue at submission time; otherwise the call returns 422
DealerMAX auto/VCOM catalogue at submission time; otherwise the call returns 422
`motornet_code_not_in_catalogue`. Partners may send a code from their own
Motornet agreement or use the paid control-plane targa/VIN resolver before
creating the vehicle.
Expand Down
8 changes: 4 additions & 4 deletions src/partnermax/types/dealers/vehicle_bulk_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ class Vehicle(TypedDict, total=False):
motornet_code: Required[str]
"""Motornet UNI code identifying the exact vehicle configuration.

Must exist in the used-vehicle catalogue at submission time; otherwise the call
returns 422 `motornet_code_not_in_catalogue`. Partners may send a code from
their own Motornet agreement or use the paid control-plane targa/VIN resolver
before creating the vehicle.
Must exist in the DealerMAX auto/VCOM catalogue at submission time; otherwise
the call returns 422 `motornet_code_not_in_catalogue`. Partners may send a code
from their own Motornet agreement or use the paid control-plane targa/VIN
resolver before creating the vehicle.
"""

plate: Required[str]
Expand Down
8 changes: 4 additions & 4 deletions src/partnermax/types/dealers/vehicle_create_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ class VehicleCreateParams(TypedDict, total=False):
motornet_code: Required[str]
"""Motornet UNI code identifying the exact vehicle configuration.

Must exist in the used-vehicle catalogue at submission time; otherwise the call
returns 422 `motornet_code_not_in_catalogue`. Partners may send a code from
their own Motornet agreement or use the paid control-plane targa/VIN resolver
before creating the vehicle.
Must exist in the DealerMAX auto/VCOM catalogue at submission time; otherwise
the call returns 422 `motornet_code_not_in_catalogue`. Partners may send a code
from their own Motornet agreement or use the paid control-plane targa/VIN
resolver before creating the vehicle.
"""

plate: Required[str]
Expand Down
4 changes: 3 additions & 1 deletion src/partnermax/types/dealers/vehicles/image_create_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@

from typing_extensions import Required, TypedDict

from ...._types import FileTypes

__all__ = ["ImageCreateParams"]


class ImageCreateParams(TypedDict, total=False):
dealer_id: Required[str]

file: Required[str]
file: Required[FileTypes]
"""The photo file.

JPEG, PNG, or WebP, up to 15 MB. WebP is converted to PNG server-side.
Expand Down
20 changes: 10 additions & 10 deletions tests/api_resources/dealers/vehicles/test_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def test_method_create(self, client: Partnermax) -> None:
image = client.dealers.vehicles.images.create(
vehicle_id="vehicle_id",
dealer_id="dealer_id",
file="file",
file=b"Example data",
)
assert_matches_type(VehicleImage, image, path=["response"])

Expand All @@ -33,7 +33,7 @@ def test_raw_response_create(self, client: Partnermax) -> None:
response = client.dealers.vehicles.images.with_raw_response.create(
vehicle_id="vehicle_id",
dealer_id="dealer_id",
file="file",
file=b"Example data",
)

assert response.is_closed is True
Expand All @@ -47,7 +47,7 @@ def test_streaming_response_create(self, client: Partnermax) -> None:
with client.dealers.vehicles.images.with_streaming_response.create(
vehicle_id="vehicle_id",
dealer_id="dealer_id",
file="file",
file=b"Example data",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
Expand All @@ -64,14 +64,14 @@ def test_path_params_create(self, client: Partnermax) -> None:
client.dealers.vehicles.images.with_raw_response.create(
vehicle_id="vehicle_id",
dealer_id="",
file="file",
file=b"Example data",
)

with pytest.raises(ValueError, match=r"Expected a non-empty value for `vehicle_id` but received ''"):
client.dealers.vehicles.images.with_raw_response.create(
vehicle_id="",
dealer_id="dealer_id",
file="file",
file=b"Example data",
)

@pytest.mark.skip(reason="Mock server tests are disabled")
Expand Down Expand Up @@ -202,7 +202,7 @@ async def test_method_create(self, async_client: AsyncPartnermax) -> None:
image = await async_client.dealers.vehicles.images.create(
vehicle_id="vehicle_id",
dealer_id="dealer_id",
file="file",
file=b"Example data",
)
assert_matches_type(VehicleImage, image, path=["response"])

Expand All @@ -212,7 +212,7 @@ async def test_raw_response_create(self, async_client: AsyncPartnermax) -> None:
response = await async_client.dealers.vehicles.images.with_raw_response.create(
vehicle_id="vehicle_id",
dealer_id="dealer_id",
file="file",
file=b"Example data",
)

assert response.is_closed is True
Expand All @@ -226,7 +226,7 @@ async def test_streaming_response_create(self, async_client: AsyncPartnermax) ->
async with async_client.dealers.vehicles.images.with_streaming_response.create(
vehicle_id="vehicle_id",
dealer_id="dealer_id",
file="file",
file=b"Example data",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
Expand All @@ -243,14 +243,14 @@ async def test_path_params_create(self, async_client: AsyncPartnermax) -> None:
await async_client.dealers.vehicles.images.with_raw_response.create(
vehicle_id="vehicle_id",
dealer_id="",
file="file",
file=b"Example data",
)

with pytest.raises(ValueError, match=r"Expected a non-empty value for `vehicle_id` but received ''"):
await async_client.dealers.vehicles.images.with_raw_response.create(
vehicle_id="",
dealer_id="dealer_id",
file="file",
file=b"Example data",
)

@pytest.mark.skip(reason="Mock server tests are disabled")
Expand Down
Loading