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
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.268.0"
".": "0.269.0"
}
4 changes: 2 additions & 2 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 202
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/increase%2Fincrease-43d34b4805f305e7e2c71644ade73722d053018acd6009352360e8a87cbe2250.yml
openapi_spec_hash: cfff23de89960d895052350a33499cf1
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/increase%2Fincrease-f755cd0f7b44abb0c5ec9857c8ac47d778b3d753335d9a5cc3b73e4fdf9f2f96.yml
openapi_spec_hash: 16295ba5ba27744cd4f1464d640dadcf
config_hash: a185e9a72778cc4658ea73fb3a7f1354
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## 0.269.0 (2025-07-22)

Full Changelog: [v0.268.0...v0.269.0](https://github.com/Increase/increase-python/compare/v0.268.0...v0.269.0)

### Features

* **api:** api update ([887e448](https://github.com/Increase/increase-python/commit/887e44875b4811e50c3095eb11aa49b25707e6c3))


### Bug Fixes

* **parsing:** parse extra field types ([9904169](https://github.com/Increase/increase-python/commit/99041691c6c4e16d77cea36222105b7c811306e3))

## 0.268.0 (2025-07-21)

Full Changelog: [v0.267.0...v0.268.0](https://github.com/Increase/increase-python/compare/v0.267.0...v0.268.0)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "increase"
version = "0.268.0"
version = "0.269.0"
description = "The official Python library for the increase API"
dynamic = ["readme"]
license = "Apache-2.0"
Expand Down
25 changes: 23 additions & 2 deletions src/increase/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,14 +208,18 @@ def construct( # pyright: ignore[reportIncompatibleMethodOverride]
else:
fields_values[name] = field_get_default(field)

extra_field_type = _get_extra_fields_type(__cls)

_extra = {}
for key, value in values.items():
if key not in model_fields:
parsed = construct_type(value=value, type_=extra_field_type) if extra_field_type is not None else value

if PYDANTIC_V2:
_extra[key] = value
_extra[key] = parsed
else:
_fields_set.add(key)
fields_values[key] = value
fields_values[key] = parsed

object.__setattr__(m, "__dict__", fields_values)

Expand Down Expand Up @@ -370,6 +374,23 @@ def _construct_field(value: object, field: FieldInfo, key: str) -> object:
return construct_type(value=value, type_=type_, metadata=getattr(field, "metadata", None))


def _get_extra_fields_type(cls: type[pydantic.BaseModel]) -> type | None:
if not PYDANTIC_V2:
# TODO
return None

schema = cls.__pydantic_core_schema__
if schema["type"] == "model":
fields = schema["schema"]
if fields["type"] == "model-fields":
extras = fields.get("extras_schema")
if extras and "cls" in extras:
# mypy can't narrow the type
return extras["cls"] # type: ignore[no-any-return]

return None


def is_basemodel(type_: type) -> bool:
"""Returns whether or not the given type is either a `BaseModel` or a union of `BaseModel`"""
if is_union(type_):
Expand Down
2 changes: 1 addition & 1 deletion src/increase/_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__ = "increase"
__version__ = "0.268.0" # x-release-please-version
__version__ = "0.269.0" # x-release-please-version
10 changes: 7 additions & 3 deletions src/increase/types/physical_card.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ class ShipmentAddress(BaseModel):
city: str
"""The city of the shipping address."""

country: str
"""The country of the shipping address."""

line1: str
"""The first line of the shipping address."""

Expand All @@ -37,7 +40,7 @@ class ShipmentAddress(BaseModel):
"""The postal code of the shipping address."""

state: str
"""The US state of the shipping address."""
"""The state of the shipping address."""


class ShipmentTrackingUpdate(BaseModel):
Expand Down Expand Up @@ -98,12 +101,13 @@ class Shipment(BaseModel):
address: ShipmentAddress
"""The location to where the card's packing label is addressed."""

method: Literal["usps", "fedex_priority_overnight", "fedex_2_day"]
method: Literal["usps", "fedex_priority_overnight", "fedex_2_day", "dhl_worldwide_express"]
"""The shipping method.

- `usps` - USPS Post with tracking.
- `usps` - USPS Post.
- `fedex_priority_overnight` - FedEx Priority Overnight, no signature.
- `fedex_2_day` - FedEx 2-day.
- `dhl_worldwide_express` - DHL Worldwide Express, international shipping only.
"""

schedule: Literal["next_day", "same_day"]
Expand Down
15 changes: 12 additions & 3 deletions src/increase/types/physical_card_create_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,15 @@ class ShipmentAddress(TypedDict, total=False):
"""The postal code of the shipping address."""

state: Required[str]
"""The US state of the shipping address."""
"""The state of the shipping address."""

country: str
"""
The two-character ISO 3166-1 code of the country where the card should be
shipped (e.g., `US`). Please reach out to
[support@increase.com](mailto:support@increase.com) to ship cards
internationally.
"""

line2: str
"""The second line of the shipping address."""
Expand All @@ -62,12 +70,13 @@ class Shipment(TypedDict, total=False):
address: Required[ShipmentAddress]
"""The address to where the card should be shipped."""

method: Required[Literal["usps", "fedex_priority_overnight", "fedex_2_day"]]
method: Required[Literal["usps", "fedex_priority_overnight", "fedex_2_day", "dhl_worldwide_express"]]
"""The shipping method to use.

- `usps` - USPS Post with tracking.
- `usps` - USPS Post.
- `fedex_priority_overnight` - FedEx Priority Overnight, no signature.
- `fedex_2_day` - FedEx 2-day.
- `dhl_worldwide_express` - DHL Worldwide Express, international shipping only.
"""

schedule: Literal["next_day", "same_day"]
Expand Down
2 changes: 2 additions & 0 deletions tests/api_resources/test_physical_cards.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def test_method_create_with_all_params(self, client: Increase) -> None:
"name": "Ian Crease",
"postal_code": "10045",
"state": "NY",
"country": "x",
"line2": "Unit 2",
"line3": "x",
"phone_number": "x",
Expand Down Expand Up @@ -283,6 +284,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncIncrease)
"name": "Ian Crease",
"postal_code": "10045",
"state": "NY",
"country": "x",
"line2": "Unit 2",
"line3": "x",
"phone_number": "x",
Expand Down
29 changes: 28 additions & 1 deletion tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import json
from typing import Any, Dict, List, Union, Optional, cast
from typing import TYPE_CHECKING, Any, Dict, List, Union, Optional, cast
from datetime import datetime, timezone
from typing_extensions import Literal, Annotated, TypeAliasType

Expand Down Expand Up @@ -934,3 +934,30 @@ class Type2(BaseModel):
)
assert isinstance(model, Type1)
assert isinstance(model.value, InnerType2)


@pytest.mark.skipif(not PYDANTIC_V2, reason="this is only supported in pydantic v2 for now")
def test_extra_properties() -> None:
class Item(BaseModel):
prop: int

class Model(BaseModel):
__pydantic_extra__: Dict[str, Item] = Field(init=False) # pyright: ignore[reportIncompatibleVariableOverride]

other: str

if TYPE_CHECKING:

def __getattr__(self, attr: str) -> Item: ...

model = construct_type(
type_=Model,
value={
"a": {"prop": 1},
"other": "foo",
},
)
assert isinstance(model, Model)
assert model.a.prop == 1
assert isinstance(model.a, Item)
assert model.other == "foo"