Skip to content
Draft
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
61 changes: 60 additions & 1 deletion stripe/_encode.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import datetime
import time
from collections import OrderedDict
from typing import Generator, Tuple, Any
from typing import Any, Dict, Generator, Mapping, Optional, Tuple, Union


def _encode_datetime(dttime: datetime.datetime):
Expand All @@ -27,6 +27,65 @@ def _json_encode_date_callback(value):
return value


# Type for a request encoding schema node: either a leaf encoding string
# (e.g. "int64_string") or a nested dict mapping field names to sub-schemas.
_SchemaNode = Union[str, Dict[str, Any]]


def _coerce_v2_params(
params: Optional[Mapping[str, Any]],
schema: Dict[str, _SchemaNode],
) -> Optional[Mapping[str, Any]]:
"""
Coerce V2 request params according to the given encoding schema.

For fields marked as "int64_string", converts int values to str so they
are serialized as JSON strings on the wire. Recurses into nested objects
and arrays.
"""
if params is None:
return None

result: Dict[str, Any] = {}
for key, value in params.items():
field_schema = schema.get(key)
if field_schema is not None:
result[key] = _coerce_value(value, field_schema)
else:
result[key] = value
return result


def _coerce_value(value: Any, schema: _SchemaNode) -> Any:
"""Coerce a single value according to its schema node."""
if value is None:
return None

if schema == "int64_string":
# Scalar or array of int64_string
if isinstance(value, list):
return [str(v) if isinstance(v, int) else v for v in value]
if isinstance(value, int):
return str(value)
return value

if isinstance(schema, dict):
# Nested object schema
if isinstance(value, list):
# Array of objects with int64_string fields
return [
dict(_coerce_v2_params(v, schema) or {})
if isinstance(v, dict)
else v
for v in value
]
if isinstance(value, dict):
return dict(_coerce_v2_params(value, schema) or {})
return value

return value


def _api_encode(data) -> Generator[Tuple[str, Any], None, None]:
for key, value in data.items():
if value is None:
Expand Down
22 changes: 22 additions & 0 deletions stripe/_stripe_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,8 @@ def _refresh_from(
self._transient_values = self._transient_values - set(values)

for k, v in values.items():
# Apply field encoding coercion (e.g. int64_string: str → int)
v = self._coerce_field_value(k, v)
inner_class = self._get_inner_class_type(k)
is_dict = self._get_inner_class_is_beneath_dict(k)
if is_dict:
Expand Down Expand Up @@ -620,6 +622,7 @@ def __deepcopy__(self, memo: Dict[int, Any]) -> "StripeObject":

_inner_class_types: ClassVar[Dict[str, Type["StripeObject"]]] = {}
_inner_class_dicts: ClassVar[List[str]] = []
_field_encodings: ClassVar[Dict[str, str]] = {}

def _get_inner_class_type(
self, field_name: str
Expand All @@ -628,3 +631,22 @@ def _get_inner_class_type(

def _get_inner_class_is_beneath_dict(self, field_name: str):
return field_name in self._inner_class_dicts

def _coerce_field_value(self, field_name: str, value: Any) -> Any:
"""
Apply field encoding coercion based on _field_encodings metadata.

For int64_string fields, converts string values from the API response
to native Python ints.
"""
encoding = self._field_encodings.get(field_name)
if encoding is None or value is None:
return value

if encoding == "int64_string":
if isinstance(value, str):
return int(value)
if isinstance(value, list):
return [int(v) if isinstance(v, str) else v for v in value]

return value
1 change: 1 addition & 0 deletions stripe/v2/billing/_cadence.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ class MandateOptions(StripeObject):
"""
A description of the mandate that is meant to be displayed to the customer.
"""
_field_encodings = {"amount": "int64_string"}

mandate_options: Optional[MandateOptions]
"""
Expand Down
1 change: 1 addition & 0 deletions stripe/v2/billing/_collection_setting.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class MandateOptions(StripeObject):
"""
A description of the mandate that is meant to be displayed to the customer.
"""
_field_encodings = {"amount": "int64_string"}

mandate_options: Optional[MandateOptions]
"""
Expand Down
45 changes: 41 additions & 4 deletions stripe/v2/billing/_collection_setting_service.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# File generated from our OpenAPI spec
from stripe._encode import _coerce_v2_params
from stripe._stripe_service import StripeService
from stripe._util import sanitize_id
from typing import Optional, cast
Expand Down Expand Up @@ -108,7 +109,16 @@ def create(
"post",
"/v2/billing/collection_settings",
base_address="api",
params=params,
params=_coerce_v2_params(
params,
{
"payment_method_options": {
"card": {
"mandate_options": {"amount": "int64_string"},
},
},
},
),
options=options,
),
)
Expand All @@ -127,7 +137,16 @@ async def create_async(
"post",
"/v2/billing/collection_settings",
base_address="api",
params=params,
params=_coerce_v2_params(
params,
{
"payment_method_options": {
"card": {
"mandate_options": {"amount": "int64_string"},
},
},
},
),
options=options,
),
)
Expand Down Expand Up @@ -193,7 +212,16 @@ def update(
id=sanitize_id(id),
),
base_address="api",
params=params,
params=_coerce_v2_params(
params,
{
"payment_method_options": {
"card": {
"mandate_options": {"amount": "int64_string"},
},
},
},
),
options=options,
),
)
Expand All @@ -215,7 +243,16 @@ async def update_async(
id=sanitize_id(id),
),
base_address="api",
params=params,
params=_coerce_v2_params(
params,
{
"payment_method_options": {
"card": {
"mandate_options": {"amount": "int64_string"},
},
},
},
),
options=options,
),
)
1 change: 1 addition & 0 deletions stripe/v2/billing/_collection_setting_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class MandateOptions(StripeObject):
"""
A description of the mandate that is meant to be displayed to the customer.
"""
_field_encodings = {"amount": "int64_string"}

mandate_options: Optional[MandateOptions]
"""
Expand Down
1 change: 1 addition & 0 deletions stripe/v2/billing/_license_fee.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class TransformQuantity(StripeObject):
"""
After division, round the result up or down.
"""
_field_encodings = {"divide_by": "int64_string"}

active: bool
"""
Expand Down
21 changes: 17 additions & 4 deletions stripe/v2/billing/_license_fee_service.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# File generated from our OpenAPI spec
from stripe._encode import _coerce_v2_params
from stripe._stripe_service import StripeService
from stripe._util import sanitize_id
from typing import Optional, cast
Expand Down Expand Up @@ -106,7 +107,10 @@ def create(
"post",
"/v2/billing/license_fees",
base_address="api",
params=params,
params=_coerce_v2_params(
params,
{"transform_quantity": {"divide_by": "int64_string"}},
),
options=options,
),
)
Expand All @@ -125,7 +129,10 @@ async def create_async(
"post",
"/v2/billing/license_fees",
base_address="api",
params=params,
params=_coerce_v2_params(
params,
{"transform_quantity": {"divide_by": "int64_string"}},
),
options=options,
),
)
Expand Down Expand Up @@ -185,7 +192,10 @@ def update(
"post",
"/v2/billing/license_fees/{id}".format(id=sanitize_id(id)),
base_address="api",
params=params,
params=_coerce_v2_params(
params,
{"transform_quantity": {"divide_by": "int64_string"}},
),
options=options,
),
)
Expand All @@ -205,7 +215,10 @@ async def update_async(
"post",
"/v2/billing/license_fees/{id}".format(id=sanitize_id(id)),
base_address="api",
params=params,
params=_coerce_v2_params(
params,
{"transform_quantity": {"divide_by": "int64_string"}},
),
options=options,
),
)
1 change: 1 addition & 0 deletions stripe/v2/billing/_license_fee_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class TransformQuantity(StripeObject):
"""
After division, round the result up or down.
"""
_field_encodings = {"divide_by": "int64_string"}

created: str
"""
Expand Down
1 change: 1 addition & 0 deletions stripe/v2/billing/_rate_card_rate.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class TransformQuantity(StripeObject):
"""
After division, round the result up or down.
"""
_field_encodings = {"divide_by": "int64_string"}

created: str
"""
Expand Down
11 changes: 9 additions & 2 deletions stripe/v2/billing/rate_cards/_rate_service.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# File generated from our OpenAPI spec
from stripe._encode import _coerce_v2_params
from stripe._stripe_service import StripeService
from stripe._util import sanitize_id
from typing import Optional, cast
Expand Down Expand Up @@ -87,7 +88,10 @@ def create(
rate_card_id=sanitize_id(rate_card_id),
),
base_address="api",
params=params,
params=_coerce_v2_params(
params,
{"transform_quantity": {"divide_by": "int64_string"}},
),
options=options,
),
)
Expand All @@ -110,7 +114,10 @@ async def create_async(
rate_card_id=sanitize_id(rate_card_id),
),
base_address="api",
params=params,
params=_coerce_v2_params(
params,
{"transform_quantity": {"divide_by": "int64_string"}},
),
options=options,
),
)
Expand Down
1 change: 1 addition & 0 deletions stripe/v2/reporting/_report_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class DownloadUrl(StripeObject):
The total size of the file in bytes.
"""
_inner_class_types = {"download_url": DownloadUrl}
_field_encodings = {"size": "int64_string"}

file: File
"""
Expand Down
Loading
Loading