adcp 2.14.0__py3-none-any.whl → 2.16.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- adcp/__init__.py +1 -1
- adcp/types/__init__.py +21 -1
- adcp/types/_ergonomic.py +464 -0
- adcp/types/coercion.py +194 -0
- {adcp-2.14.0.dist-info → adcp-2.16.0.dist-info}/METADATA +1 -1
- {adcp-2.14.0.dist-info → adcp-2.16.0.dist-info}/RECORD +10 -8
- {adcp-2.14.0.dist-info → adcp-2.16.0.dist-info}/WHEEL +0 -0
- {adcp-2.14.0.dist-info → adcp-2.16.0.dist-info}/entry_points.txt +0 -0
- {adcp-2.14.0.dist-info → adcp-2.16.0.dist-info}/licenses/LICENSE +0 -0
- {adcp-2.14.0.dist-info → adcp-2.16.0.dist-info}/top_level.txt +0 -0
adcp/__init__.py
CHANGED
adcp/types/__init__.py
CHANGED
|
@@ -6,13 +6,33 @@ Users should import from here or directly from adcp.
|
|
|
6
6
|
Examples:
|
|
7
7
|
from adcp.types import Product, CreativeFilters
|
|
8
8
|
from adcp import Product, CreativeFilters
|
|
9
|
+
|
|
10
|
+
Type Coercion:
|
|
11
|
+
For developer ergonomics, request types accept flexible input:
|
|
12
|
+
|
|
13
|
+
- Enum fields accept string values:
|
|
14
|
+
ListCreativeFormatsRequest(type="video") # Works!
|
|
15
|
+
ListCreativeFormatsRequest(type=FormatCategory.video) # Also works
|
|
16
|
+
|
|
17
|
+
- Context fields accept dicts:
|
|
18
|
+
GetProductsRequest(context={"key": "value"}) # Works!
|
|
19
|
+
|
|
20
|
+
- FieldModel lists accept strings:
|
|
21
|
+
ListCreativesRequest(fields=["creative_id", "name"]) # Works!
|
|
22
|
+
|
|
23
|
+
See adcp.types.coercion for implementation details.
|
|
9
24
|
"""
|
|
10
25
|
|
|
11
26
|
from __future__ import annotations
|
|
12
27
|
|
|
28
|
+
# Apply type coercion to generated types (must be imported before other types)
|
|
29
|
+
from adcp.types import (
|
|
30
|
+
_ergonomic, # noqa: F401
|
|
31
|
+
aliases, # noqa: F401
|
|
32
|
+
)
|
|
33
|
+
|
|
13
34
|
# Also make submodules available for advanced use
|
|
14
35
|
from adcp.types import _generated as generated # noqa: F401
|
|
15
|
-
from adcp.types import aliases # noqa: F401
|
|
16
36
|
|
|
17
37
|
# Import all types from generated code
|
|
18
38
|
from adcp.types._generated import (
|
adcp/types/_ergonomic.py
ADDED
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
# AUTO-GENERATED by scripts/generate_ergonomic_coercion.py
|
|
2
|
+
# Do not edit manually - changes will be overwritten on next type generation.
|
|
3
|
+
# To regenerate: python scripts/generate_types.py
|
|
4
|
+
"""Apply type coercion to generated types for better ergonomics.
|
|
5
|
+
|
|
6
|
+
This module patches the generated types to accept more flexible input types
|
|
7
|
+
while maintaining type safety. It uses Pydantic's model_rebuild() to add
|
|
8
|
+
BeforeValidator annotations to fields.
|
|
9
|
+
|
|
10
|
+
Why import-time patching?
|
|
11
|
+
We apply coercion at module load time rather than lazily because:
|
|
12
|
+
1. Pydantic validation runs during __init__, before any lazy access
|
|
13
|
+
2. model_rebuild() is the standard Pydantic pattern for post-hoc changes
|
|
14
|
+
3. The cost is minimal (~10-20ms for all types, once at import)
|
|
15
|
+
4. After import, there is zero runtime overhead
|
|
16
|
+
5. This approach maintains full type checker compatibility
|
|
17
|
+
|
|
18
|
+
Coercion rules applied:
|
|
19
|
+
1. Enum fields accept string values (e.g., "video" for FormatCategory.video)
|
|
20
|
+
2. List[Enum] fields accept list of strings (e.g., ["image", "video"])
|
|
21
|
+
3. ContextObject fields accept dict values
|
|
22
|
+
4. ExtensionObject fields accept dict values
|
|
23
|
+
5. FieldModel (enum) lists accept string lists
|
|
24
|
+
|
|
25
|
+
Note: List variance issues (list[Subclass] not assignable to list[BaseClass])
|
|
26
|
+
are a fundamental Python typing limitation. Users extending library types
|
|
27
|
+
should use Sequence[T] in their own code or cast() for type checker appeasement.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
from typing import Annotated, Any
|
|
33
|
+
|
|
34
|
+
from pydantic import BeforeValidator
|
|
35
|
+
|
|
36
|
+
from adcp.types.coercion import (
|
|
37
|
+
coerce_subclass_list,
|
|
38
|
+
coerce_to_enum,
|
|
39
|
+
coerce_to_enum_list,
|
|
40
|
+
coerce_to_model,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Import types that need coercion
|
|
44
|
+
from adcp.types.generated_poc.core.context import ContextObject
|
|
45
|
+
from adcp.types.generated_poc.core.creative_asset import CreativeAsset
|
|
46
|
+
from adcp.types.generated_poc.core.creative_assignment import CreativeAssignment
|
|
47
|
+
from adcp.types.generated_poc.core.error import Error
|
|
48
|
+
from adcp.types.generated_poc.core.ext import ExtensionObject
|
|
49
|
+
from adcp.types.generated_poc.core.format import Format
|
|
50
|
+
from adcp.types.generated_poc.core.package import Package
|
|
51
|
+
from adcp.types.generated_poc.core.product import Product
|
|
52
|
+
from adcp.types.generated_poc.enums.asset_content_type import AssetContentType
|
|
53
|
+
from adcp.types.generated_poc.enums.creative_sort_field import CreativeSortField
|
|
54
|
+
from adcp.types.generated_poc.enums.format_category import FormatCategory
|
|
55
|
+
from adcp.types.generated_poc.enums.pacing import Pacing
|
|
56
|
+
from adcp.types.generated_poc.enums.sort_direction import SortDirection
|
|
57
|
+
from adcp.types.generated_poc.media_buy.create_media_buy_request import (
|
|
58
|
+
CreateMediaBuyRequest,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Response types
|
|
62
|
+
from adcp.types.generated_poc.media_buy.create_media_buy_response import (
|
|
63
|
+
CreateMediaBuyResponse1,
|
|
64
|
+
)
|
|
65
|
+
from adcp.types.generated_poc.media_buy.get_media_buy_delivery_response import (
|
|
66
|
+
GetMediaBuyDeliveryResponse,
|
|
67
|
+
MediaBuyDelivery,
|
|
68
|
+
NotificationType,
|
|
69
|
+
)
|
|
70
|
+
from adcp.types.generated_poc.media_buy.get_products_request import GetProductsRequest
|
|
71
|
+
from adcp.types.generated_poc.media_buy.get_products_response import GetProductsResponse
|
|
72
|
+
from adcp.types.generated_poc.media_buy.list_creative_formats_request import (
|
|
73
|
+
ListCreativeFormatsRequest,
|
|
74
|
+
)
|
|
75
|
+
from adcp.types.generated_poc.media_buy.list_creative_formats_response import (
|
|
76
|
+
CreativeAgent,
|
|
77
|
+
ListCreativeFormatsResponse,
|
|
78
|
+
)
|
|
79
|
+
from adcp.types.generated_poc.media_buy.list_creatives_request import (
|
|
80
|
+
FieldModel,
|
|
81
|
+
ListCreativesRequest,
|
|
82
|
+
Sort,
|
|
83
|
+
)
|
|
84
|
+
from adcp.types.generated_poc.media_buy.list_creatives_response import (
|
|
85
|
+
Creative,
|
|
86
|
+
ListCreativesResponse,
|
|
87
|
+
)
|
|
88
|
+
from adcp.types.generated_poc.media_buy.package_request import PackageRequest
|
|
89
|
+
from adcp.types.generated_poc.media_buy.update_media_buy_request import (
|
|
90
|
+
Packages,
|
|
91
|
+
Packages1,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _apply_coercion() -> None:
|
|
96
|
+
"""Apply coercion validators to generated types.
|
|
97
|
+
|
|
98
|
+
This function modifies the generated types in-place to accept
|
|
99
|
+
more flexible input types.
|
|
100
|
+
"""
|
|
101
|
+
# Apply coercion to ListCreativeFormatsRequest
|
|
102
|
+
# - asset_types: list[AssetContentType | str] | None
|
|
103
|
+
# - context: ContextObject | dict | None
|
|
104
|
+
# - ext: ExtensionObject | dict | None
|
|
105
|
+
# - type: FormatCategory | str | None
|
|
106
|
+
_patch_field_annotation(
|
|
107
|
+
ListCreativeFormatsRequest,
|
|
108
|
+
"asset_types",
|
|
109
|
+
Annotated[
|
|
110
|
+
list[AssetContentType] | None,
|
|
111
|
+
BeforeValidator(coerce_to_enum_list(AssetContentType)),
|
|
112
|
+
],
|
|
113
|
+
)
|
|
114
|
+
_patch_field_annotation(
|
|
115
|
+
ListCreativeFormatsRequest,
|
|
116
|
+
"context",
|
|
117
|
+
Annotated[ContextObject | None, BeforeValidator(coerce_to_model(ContextObject))],
|
|
118
|
+
)
|
|
119
|
+
_patch_field_annotation(
|
|
120
|
+
ListCreativeFormatsRequest,
|
|
121
|
+
"ext",
|
|
122
|
+
Annotated[ExtensionObject | None, BeforeValidator(coerce_to_model(ExtensionObject))],
|
|
123
|
+
)
|
|
124
|
+
_patch_field_annotation(
|
|
125
|
+
ListCreativeFormatsRequest,
|
|
126
|
+
"type",
|
|
127
|
+
Annotated[FormatCategory | None, BeforeValidator(coerce_to_enum(FormatCategory))],
|
|
128
|
+
)
|
|
129
|
+
ListCreativeFormatsRequest.model_rebuild(force=True)
|
|
130
|
+
|
|
131
|
+
# Apply coercion to ListCreativesRequest
|
|
132
|
+
# - context: ContextObject | dict | None
|
|
133
|
+
# - ext: ExtensionObject | dict | None
|
|
134
|
+
# - fields: list[FieldModel | str] | None
|
|
135
|
+
_patch_field_annotation(
|
|
136
|
+
ListCreativesRequest,
|
|
137
|
+
"context",
|
|
138
|
+
Annotated[ContextObject | None, BeforeValidator(coerce_to_model(ContextObject))],
|
|
139
|
+
)
|
|
140
|
+
_patch_field_annotation(
|
|
141
|
+
ListCreativesRequest,
|
|
142
|
+
"ext",
|
|
143
|
+
Annotated[ExtensionObject | None, BeforeValidator(coerce_to_model(ExtensionObject))],
|
|
144
|
+
)
|
|
145
|
+
_patch_field_annotation(
|
|
146
|
+
ListCreativesRequest,
|
|
147
|
+
"fields",
|
|
148
|
+
Annotated[
|
|
149
|
+
list[FieldModel] | None,
|
|
150
|
+
BeforeValidator(coerce_to_enum_list(FieldModel)),
|
|
151
|
+
],
|
|
152
|
+
)
|
|
153
|
+
ListCreativesRequest.model_rebuild(force=True)
|
|
154
|
+
|
|
155
|
+
# Apply coercion to Sort
|
|
156
|
+
# - direction: SortDirection | str | None
|
|
157
|
+
# - field: CreativeSortField | str | None
|
|
158
|
+
_patch_field_annotation(
|
|
159
|
+
Sort,
|
|
160
|
+
"direction",
|
|
161
|
+
Annotated[SortDirection | None, BeforeValidator(coerce_to_enum(SortDirection))],
|
|
162
|
+
)
|
|
163
|
+
_patch_field_annotation(
|
|
164
|
+
Sort,
|
|
165
|
+
"field",
|
|
166
|
+
Annotated[CreativeSortField | None, BeforeValidator(coerce_to_enum(CreativeSortField))],
|
|
167
|
+
)
|
|
168
|
+
Sort.model_rebuild(force=True)
|
|
169
|
+
|
|
170
|
+
# Apply coercion to GetProductsRequest
|
|
171
|
+
# - context: ContextObject | dict | None
|
|
172
|
+
# - ext: ExtensionObject | dict | None
|
|
173
|
+
_patch_field_annotation(
|
|
174
|
+
GetProductsRequest,
|
|
175
|
+
"context",
|
|
176
|
+
Annotated[ContextObject | None, BeforeValidator(coerce_to_model(ContextObject))],
|
|
177
|
+
)
|
|
178
|
+
_patch_field_annotation(
|
|
179
|
+
GetProductsRequest,
|
|
180
|
+
"ext",
|
|
181
|
+
Annotated[ExtensionObject | None, BeforeValidator(coerce_to_model(ExtensionObject))],
|
|
182
|
+
)
|
|
183
|
+
GetProductsRequest.model_rebuild(force=True)
|
|
184
|
+
|
|
185
|
+
# Apply coercion to PackageRequest
|
|
186
|
+
# - creatives: list[CreativeAsset] (accepts subclass instances)
|
|
187
|
+
# - ext: ExtensionObject | dict | None
|
|
188
|
+
# - pacing: Pacing | str | None
|
|
189
|
+
_patch_field_annotation(
|
|
190
|
+
PackageRequest,
|
|
191
|
+
"creatives",
|
|
192
|
+
Annotated[
|
|
193
|
+
list[CreativeAsset] | None,
|
|
194
|
+
BeforeValidator(coerce_subclass_list(CreativeAsset)),
|
|
195
|
+
],
|
|
196
|
+
)
|
|
197
|
+
_patch_field_annotation(
|
|
198
|
+
PackageRequest,
|
|
199
|
+
"ext",
|
|
200
|
+
Annotated[ExtensionObject | None, BeforeValidator(coerce_to_model(ExtensionObject))],
|
|
201
|
+
)
|
|
202
|
+
_patch_field_annotation(
|
|
203
|
+
PackageRequest,
|
|
204
|
+
"pacing",
|
|
205
|
+
Annotated[Pacing | None, BeforeValidator(coerce_to_enum(Pacing))],
|
|
206
|
+
)
|
|
207
|
+
PackageRequest.model_rebuild(force=True)
|
|
208
|
+
|
|
209
|
+
# Apply coercion to CreateMediaBuyRequest
|
|
210
|
+
# - context: ContextObject | dict | None
|
|
211
|
+
# - ext: ExtensionObject | dict | None
|
|
212
|
+
# - packages: list[PackageRequest] (accepts subclass instances)
|
|
213
|
+
_patch_field_annotation(
|
|
214
|
+
CreateMediaBuyRequest,
|
|
215
|
+
"context",
|
|
216
|
+
Annotated[ContextObject | None, BeforeValidator(coerce_to_model(ContextObject))],
|
|
217
|
+
)
|
|
218
|
+
_patch_field_annotation(
|
|
219
|
+
CreateMediaBuyRequest,
|
|
220
|
+
"ext",
|
|
221
|
+
Annotated[ExtensionObject | None, BeforeValidator(coerce_to_model(ExtensionObject))],
|
|
222
|
+
)
|
|
223
|
+
_patch_field_annotation(
|
|
224
|
+
CreateMediaBuyRequest,
|
|
225
|
+
"packages",
|
|
226
|
+
Annotated[
|
|
227
|
+
list[PackageRequest],
|
|
228
|
+
BeforeValidator(coerce_subclass_list(PackageRequest)),
|
|
229
|
+
],
|
|
230
|
+
)
|
|
231
|
+
CreateMediaBuyRequest.model_rebuild(force=True)
|
|
232
|
+
|
|
233
|
+
# Apply coercion to Packages
|
|
234
|
+
# - creative_assignments: list[CreativeAssignment] (accepts subclass instances)
|
|
235
|
+
# - creatives: list[CreativeAsset] (accepts subclass instances)
|
|
236
|
+
# - pacing: Pacing | str | None
|
|
237
|
+
_patch_field_annotation(
|
|
238
|
+
Packages,
|
|
239
|
+
"creative_assignments",
|
|
240
|
+
Annotated[
|
|
241
|
+
list[CreativeAssignment] | None,
|
|
242
|
+
BeforeValidator(coerce_subclass_list(CreativeAssignment)),
|
|
243
|
+
],
|
|
244
|
+
)
|
|
245
|
+
_patch_field_annotation(
|
|
246
|
+
Packages,
|
|
247
|
+
"creatives",
|
|
248
|
+
Annotated[
|
|
249
|
+
list[CreativeAsset] | None,
|
|
250
|
+
BeforeValidator(coerce_subclass_list(CreativeAsset)),
|
|
251
|
+
],
|
|
252
|
+
)
|
|
253
|
+
_patch_field_annotation(
|
|
254
|
+
Packages,
|
|
255
|
+
"pacing",
|
|
256
|
+
Annotated[Pacing | None, BeforeValidator(coerce_to_enum(Pacing))],
|
|
257
|
+
)
|
|
258
|
+
Packages.model_rebuild(force=True)
|
|
259
|
+
|
|
260
|
+
# Apply coercion to Packages1
|
|
261
|
+
# - creative_assignments: list[CreativeAssignment] (accepts subclass instances)
|
|
262
|
+
# - creatives: list[CreativeAsset] (accepts subclass instances)
|
|
263
|
+
# - pacing: Pacing | str | None
|
|
264
|
+
_patch_field_annotation(
|
|
265
|
+
Packages1,
|
|
266
|
+
"creative_assignments",
|
|
267
|
+
Annotated[
|
|
268
|
+
list[CreativeAssignment] | None,
|
|
269
|
+
BeforeValidator(coerce_subclass_list(CreativeAssignment)),
|
|
270
|
+
],
|
|
271
|
+
)
|
|
272
|
+
_patch_field_annotation(
|
|
273
|
+
Packages1,
|
|
274
|
+
"creatives",
|
|
275
|
+
Annotated[
|
|
276
|
+
list[CreativeAsset] | None,
|
|
277
|
+
BeforeValidator(coerce_subclass_list(CreativeAsset)),
|
|
278
|
+
],
|
|
279
|
+
)
|
|
280
|
+
_patch_field_annotation(
|
|
281
|
+
Packages1,
|
|
282
|
+
"pacing",
|
|
283
|
+
Annotated[Pacing | None, BeforeValidator(coerce_to_enum(Pacing))],
|
|
284
|
+
)
|
|
285
|
+
Packages1.model_rebuild(force=True)
|
|
286
|
+
|
|
287
|
+
# Apply coercion to GetProductsResponse
|
|
288
|
+
# - context: ContextObject | dict | None
|
|
289
|
+
# - errors: list[Error] (accepts subclass instances)
|
|
290
|
+
# - ext: ExtensionObject | dict | None
|
|
291
|
+
# - products: list[Product] (accepts subclass instances)
|
|
292
|
+
_patch_field_annotation(
|
|
293
|
+
GetProductsResponse,
|
|
294
|
+
"context",
|
|
295
|
+
Annotated[ContextObject | None, BeforeValidator(coerce_to_model(ContextObject))],
|
|
296
|
+
)
|
|
297
|
+
_patch_field_annotation(
|
|
298
|
+
GetProductsResponse,
|
|
299
|
+
"errors",
|
|
300
|
+
Annotated[
|
|
301
|
+
list[Error] | None,
|
|
302
|
+
BeforeValidator(coerce_subclass_list(Error)),
|
|
303
|
+
],
|
|
304
|
+
)
|
|
305
|
+
_patch_field_annotation(
|
|
306
|
+
GetProductsResponse,
|
|
307
|
+
"ext",
|
|
308
|
+
Annotated[ExtensionObject | None, BeforeValidator(coerce_to_model(ExtensionObject))],
|
|
309
|
+
)
|
|
310
|
+
_patch_field_annotation(
|
|
311
|
+
GetProductsResponse,
|
|
312
|
+
"products",
|
|
313
|
+
Annotated[
|
|
314
|
+
list[Product],
|
|
315
|
+
BeforeValidator(coerce_subclass_list(Product)),
|
|
316
|
+
],
|
|
317
|
+
)
|
|
318
|
+
GetProductsResponse.model_rebuild(force=True)
|
|
319
|
+
|
|
320
|
+
# Apply coercion to ListCreativesResponse
|
|
321
|
+
# - context: ContextObject | dict | None
|
|
322
|
+
# - creatives: list[Creative] (accepts subclass instances)
|
|
323
|
+
# - ext: ExtensionObject | dict | None
|
|
324
|
+
_patch_field_annotation(
|
|
325
|
+
ListCreativesResponse,
|
|
326
|
+
"context",
|
|
327
|
+
Annotated[ContextObject | None, BeforeValidator(coerce_to_model(ContextObject))],
|
|
328
|
+
)
|
|
329
|
+
_patch_field_annotation(
|
|
330
|
+
ListCreativesResponse,
|
|
331
|
+
"creatives",
|
|
332
|
+
Annotated[
|
|
333
|
+
list[Creative],
|
|
334
|
+
BeforeValidator(coerce_subclass_list(Creative)),
|
|
335
|
+
],
|
|
336
|
+
)
|
|
337
|
+
_patch_field_annotation(
|
|
338
|
+
ListCreativesResponse,
|
|
339
|
+
"ext",
|
|
340
|
+
Annotated[ExtensionObject | None, BeforeValidator(coerce_to_model(ExtensionObject))],
|
|
341
|
+
)
|
|
342
|
+
ListCreativesResponse.model_rebuild(force=True)
|
|
343
|
+
|
|
344
|
+
# Apply coercion to ListCreativeFormatsResponse
|
|
345
|
+
# - context: ContextObject | dict | None
|
|
346
|
+
# - creative_agents: list[CreativeAgent] (accepts subclass instances)
|
|
347
|
+
# - errors: list[Error] (accepts subclass instances)
|
|
348
|
+
# - ext: ExtensionObject | dict | None
|
|
349
|
+
# - formats: list[Format] (accepts subclass instances)
|
|
350
|
+
_patch_field_annotation(
|
|
351
|
+
ListCreativeFormatsResponse,
|
|
352
|
+
"context",
|
|
353
|
+
Annotated[ContextObject | None, BeforeValidator(coerce_to_model(ContextObject))],
|
|
354
|
+
)
|
|
355
|
+
_patch_field_annotation(
|
|
356
|
+
ListCreativeFormatsResponse,
|
|
357
|
+
"creative_agents",
|
|
358
|
+
Annotated[
|
|
359
|
+
list[CreativeAgent] | None,
|
|
360
|
+
BeforeValidator(coerce_subclass_list(CreativeAgent)),
|
|
361
|
+
],
|
|
362
|
+
)
|
|
363
|
+
_patch_field_annotation(
|
|
364
|
+
ListCreativeFormatsResponse,
|
|
365
|
+
"errors",
|
|
366
|
+
Annotated[
|
|
367
|
+
list[Error] | None,
|
|
368
|
+
BeforeValidator(coerce_subclass_list(Error)),
|
|
369
|
+
],
|
|
370
|
+
)
|
|
371
|
+
_patch_field_annotation(
|
|
372
|
+
ListCreativeFormatsResponse,
|
|
373
|
+
"ext",
|
|
374
|
+
Annotated[ExtensionObject | None, BeforeValidator(coerce_to_model(ExtensionObject))],
|
|
375
|
+
)
|
|
376
|
+
_patch_field_annotation(
|
|
377
|
+
ListCreativeFormatsResponse,
|
|
378
|
+
"formats",
|
|
379
|
+
Annotated[
|
|
380
|
+
list[Format],
|
|
381
|
+
BeforeValidator(coerce_subclass_list(Format)),
|
|
382
|
+
],
|
|
383
|
+
)
|
|
384
|
+
ListCreativeFormatsResponse.model_rebuild(force=True)
|
|
385
|
+
|
|
386
|
+
# Apply coercion to CreateMediaBuyResponse1
|
|
387
|
+
# - context: ContextObject | dict | None
|
|
388
|
+
# - ext: ExtensionObject | dict | None
|
|
389
|
+
# - packages: list[Package] (accepts subclass instances)
|
|
390
|
+
_patch_field_annotation(
|
|
391
|
+
CreateMediaBuyResponse1,
|
|
392
|
+
"context",
|
|
393
|
+
Annotated[ContextObject | None, BeforeValidator(coerce_to_model(ContextObject))],
|
|
394
|
+
)
|
|
395
|
+
_patch_field_annotation(
|
|
396
|
+
CreateMediaBuyResponse1,
|
|
397
|
+
"ext",
|
|
398
|
+
Annotated[ExtensionObject | None, BeforeValidator(coerce_to_model(ExtensionObject))],
|
|
399
|
+
)
|
|
400
|
+
_patch_field_annotation(
|
|
401
|
+
CreateMediaBuyResponse1,
|
|
402
|
+
"packages",
|
|
403
|
+
Annotated[
|
|
404
|
+
list[Package],
|
|
405
|
+
BeforeValidator(coerce_subclass_list(Package)),
|
|
406
|
+
],
|
|
407
|
+
)
|
|
408
|
+
CreateMediaBuyResponse1.model_rebuild(force=True)
|
|
409
|
+
|
|
410
|
+
# Apply coercion to GetMediaBuyDeliveryResponse
|
|
411
|
+
# - context: ContextObject | dict | None
|
|
412
|
+
# - errors: list[Error] (accepts subclass instances)
|
|
413
|
+
# - ext: ExtensionObject | dict | None
|
|
414
|
+
# - media_buy_deliveries: list[MediaBuyDelivery] (accepts subclass instances)
|
|
415
|
+
# - notification_type: NotificationType | str | None
|
|
416
|
+
_patch_field_annotation(
|
|
417
|
+
GetMediaBuyDeliveryResponse,
|
|
418
|
+
"context",
|
|
419
|
+
Annotated[ContextObject | None, BeforeValidator(coerce_to_model(ContextObject))],
|
|
420
|
+
)
|
|
421
|
+
_patch_field_annotation(
|
|
422
|
+
GetMediaBuyDeliveryResponse,
|
|
423
|
+
"errors",
|
|
424
|
+
Annotated[
|
|
425
|
+
list[Error] | None,
|
|
426
|
+
BeforeValidator(coerce_subclass_list(Error)),
|
|
427
|
+
],
|
|
428
|
+
)
|
|
429
|
+
_patch_field_annotation(
|
|
430
|
+
GetMediaBuyDeliveryResponse,
|
|
431
|
+
"ext",
|
|
432
|
+
Annotated[ExtensionObject | None, BeforeValidator(coerce_to_model(ExtensionObject))],
|
|
433
|
+
)
|
|
434
|
+
_patch_field_annotation(
|
|
435
|
+
GetMediaBuyDeliveryResponse,
|
|
436
|
+
"media_buy_deliveries",
|
|
437
|
+
Annotated[
|
|
438
|
+
list[MediaBuyDelivery],
|
|
439
|
+
BeforeValidator(coerce_subclass_list(MediaBuyDelivery)),
|
|
440
|
+
],
|
|
441
|
+
)
|
|
442
|
+
_patch_field_annotation(
|
|
443
|
+
GetMediaBuyDeliveryResponse,
|
|
444
|
+
"notification_type",
|
|
445
|
+
Annotated[NotificationType | None, BeforeValidator(coerce_to_enum(NotificationType))],
|
|
446
|
+
)
|
|
447
|
+
GetMediaBuyDeliveryResponse.model_rebuild(force=True)
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def _patch_field_annotation(
|
|
451
|
+
model: type,
|
|
452
|
+
field_name: str,
|
|
453
|
+
new_annotation: Any,
|
|
454
|
+
) -> None:
|
|
455
|
+
"""Patch a field annotation on a Pydantic model.
|
|
456
|
+
|
|
457
|
+
This modifies the model's __annotations__ dict to add
|
|
458
|
+
BeforeValidator coercion.
|
|
459
|
+
"""
|
|
460
|
+
model.__annotations__[field_name] = new_annotation
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
# Apply coercion when module is imported
|
|
464
|
+
_apply_coercion()
|
adcp/types/coercion.py
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""Type coercion utilities for improved type ergonomics.
|
|
2
|
+
|
|
3
|
+
This module provides validators and utilities that enable flexible input types
|
|
4
|
+
while maintaining type safety. It allows developers to use natural Python
|
|
5
|
+
patterns (strings for enums, dicts for models) without explicit type construction.
|
|
6
|
+
|
|
7
|
+
Examples:
|
|
8
|
+
# With coercion, these are equivalent:
|
|
9
|
+
ListCreativeFormatsRequest(type="video")
|
|
10
|
+
ListCreativeFormatsRequest(type=FormatCategory.video)
|
|
11
|
+
|
|
12
|
+
# Dict coercion for context:
|
|
13
|
+
ListCreativeFormatsRequest(context={"key": "value"})
|
|
14
|
+
ListCreativeFormatsRequest(context=ContextObject(key="value"))
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from collections.abc import Callable
|
|
20
|
+
from enum import Enum
|
|
21
|
+
from typing import TYPE_CHECKING, Any, TypeVar
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from pydantic import BaseModel
|
|
25
|
+
|
|
26
|
+
T = TypeVar("T", bound=Enum)
|
|
27
|
+
M = TypeVar("M", bound="BaseModel")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def coerce_to_enum(enum_class: type[T]) -> Callable[[Any], T | None]:
|
|
31
|
+
"""Create a validator that coerces strings to enum values.
|
|
32
|
+
|
|
33
|
+
This allows users to pass string values where enums are expected,
|
|
34
|
+
which Pydantic will coerce at runtime anyway, but this makes it
|
|
35
|
+
type-checker friendly.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
enum_class: The enum class to coerce to.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
A validator function for use with Pydantic's BeforeValidator.
|
|
42
|
+
|
|
43
|
+
Example:
|
|
44
|
+
```python
|
|
45
|
+
from pydantic import BeforeValidator
|
|
46
|
+
from typing import Annotated
|
|
47
|
+
|
|
48
|
+
type: Annotated[
|
|
49
|
+
FormatCategory | None,
|
|
50
|
+
BeforeValidator(coerce_to_enum(FormatCategory))
|
|
51
|
+
] = None
|
|
52
|
+
```
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def validator(value: Any) -> T | None:
|
|
56
|
+
if value is None:
|
|
57
|
+
return None
|
|
58
|
+
if isinstance(value, enum_class):
|
|
59
|
+
return value
|
|
60
|
+
if isinstance(value, str):
|
|
61
|
+
try:
|
|
62
|
+
return enum_class(value)
|
|
63
|
+
except ValueError:
|
|
64
|
+
# Let Pydantic handle the validation error
|
|
65
|
+
return value # type: ignore
|
|
66
|
+
return value # type: ignore
|
|
67
|
+
|
|
68
|
+
return validator
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def coerce_to_enum_list(enum_class: type[T]) -> Callable[[Any], list[T] | None]:
|
|
72
|
+
"""Create a validator that coerces a list of strings to enum values.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
enum_class: The enum class to coerce to.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
A validator function for use with Pydantic's BeforeValidator.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def validator(value: Any) -> list[T] | None:
|
|
82
|
+
if value is None:
|
|
83
|
+
return None
|
|
84
|
+
if not isinstance(value, (list, tuple)):
|
|
85
|
+
return value # type: ignore
|
|
86
|
+
result: list[T] = []
|
|
87
|
+
for item in value:
|
|
88
|
+
if isinstance(item, enum_class):
|
|
89
|
+
result.append(item)
|
|
90
|
+
elif isinstance(item, str):
|
|
91
|
+
try:
|
|
92
|
+
result.append(enum_class(item))
|
|
93
|
+
except ValueError:
|
|
94
|
+
# Let Pydantic handle the validation error
|
|
95
|
+
result.append(item) # type: ignore
|
|
96
|
+
else:
|
|
97
|
+
result.append(item)
|
|
98
|
+
return result
|
|
99
|
+
|
|
100
|
+
return validator
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def coerce_to_model(model_class: type[M]) -> Callable[[Any], M | None]:
|
|
104
|
+
"""Create a validator that coerces dicts to Pydantic model instances.
|
|
105
|
+
|
|
106
|
+
This allows users to pass dict values where model objects are expected,
|
|
107
|
+
making the API more ergonomic.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
model_class: The Pydantic model class to coerce to.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
A validator function for use with Pydantic's BeforeValidator.
|
|
114
|
+
|
|
115
|
+
Example:
|
|
116
|
+
```python
|
|
117
|
+
from pydantic import BeforeValidator
|
|
118
|
+
from typing import Annotated
|
|
119
|
+
|
|
120
|
+
context: Annotated[
|
|
121
|
+
ContextObject | None,
|
|
122
|
+
BeforeValidator(coerce_to_model(ContextObject))
|
|
123
|
+
] = None
|
|
124
|
+
```
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
def validator(value: Any) -> M | None:
|
|
128
|
+
if value is None:
|
|
129
|
+
return None
|
|
130
|
+
if isinstance(value, model_class):
|
|
131
|
+
return value
|
|
132
|
+
if isinstance(value, dict):
|
|
133
|
+
return model_class(**value)
|
|
134
|
+
return value # type: ignore
|
|
135
|
+
|
|
136
|
+
return validator
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def coerce_subclass_list(base_class: type[M]) -> Callable[[Any], list[M] | None]:
|
|
140
|
+
"""Create a validator that accepts lists containing subclass instances.
|
|
141
|
+
|
|
142
|
+
This addresses Python's list invariance limitation where `list[Subclass]`
|
|
143
|
+
cannot be assigned to `list[BaseClass]` despite being type-safe at runtime.
|
|
144
|
+
|
|
145
|
+
The validator:
|
|
146
|
+
1. Accepts Any as input (satisfies mypy for subclass lists)
|
|
147
|
+
2. Validates each item is an instance of base_class (or subclass)
|
|
148
|
+
3. Returns list[base_class] (satisfies the field type)
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
base_class: The base Pydantic model class for list items.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
A validator function for use with Pydantic's BeforeValidator.
|
|
155
|
+
|
|
156
|
+
Example:
|
|
157
|
+
```python
|
|
158
|
+
class ExtendedCreative(CreativeAsset):
|
|
159
|
+
internal_id: str = Field(exclude=True)
|
|
160
|
+
|
|
161
|
+
# Without coercion: requires cast()
|
|
162
|
+
# PackageRequest(creatives=cast(list[CreativeAsset], [extended]))
|
|
163
|
+
|
|
164
|
+
# With coercion: just works
|
|
165
|
+
PackageRequest(creatives=[extended]) # No cast needed!
|
|
166
|
+
```
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
def validator(value: Any) -> list[M] | None:
|
|
170
|
+
if value is None:
|
|
171
|
+
return None
|
|
172
|
+
if not isinstance(value, (list, tuple)):
|
|
173
|
+
return value # type: ignore
|
|
174
|
+
# Return the list as-is - Pydantic will validate each item
|
|
175
|
+
# is an instance of base_class (including subclasses)
|
|
176
|
+
return list(value)
|
|
177
|
+
|
|
178
|
+
return validator
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# =============================================================================
|
|
182
|
+
# List Variance Notes
|
|
183
|
+
# =============================================================================
|
|
184
|
+
#
|
|
185
|
+
# The coerce_subclass_list validator above handles the common case of passing
|
|
186
|
+
# `list[Subclass]` to a field expecting `list[BaseClass]` when constructing
|
|
187
|
+
# request models.
|
|
188
|
+
#
|
|
189
|
+
# For function signatures in your own code, use Sequence[T] which is covariant:
|
|
190
|
+
#
|
|
191
|
+
# from collections.abc import Sequence
|
|
192
|
+
# def process_creatives(creatives: Sequence[CreativeAsset]) -> None:
|
|
193
|
+
# ... # Accepts list[ExtendedCreative] without cast()
|
|
194
|
+
#
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
adcp/ADCP_VERSION,sha256=cy9k2HT5B4jAGZsMGb_Zs7ZDbhQBFOU-RigyUy10xhw,6
|
|
2
|
-
adcp/__init__.py,sha256=
|
|
2
|
+
adcp/__init__.py,sha256=SVea2r6sPQTljwVo_f6PNFSDJNYnt1DQ-oSOJEohWqw,10319
|
|
3
3
|
adcp/__main__.py,sha256=8v-j_W9IIWDIcvhaR1yuZAhBCDRfehUyN0tusstzyfQ,15139
|
|
4
4
|
adcp/adagents.py,sha256=HY7vZ8QqC8Zjzc_jRA0ZMhAfvm1pJN_VJfS8a8Fbc6c,24481
|
|
5
5
|
adcp/client.py,sha256=gdDnyCyotRKMBHgxyONq3BWr4Seo7U2KZE18gqpw3nU,50839
|
|
@@ -15,10 +15,12 @@ adcp/protocols/base.py,sha256=oGUe3Ce_gupLYgZqQsgkerkiRlBKw4KBfqCe75UMszw,6485
|
|
|
15
15
|
adcp/protocols/mcp.py,sha256=kCKRDhnahRI5nCE9p7FmhrayfMcKUe0h6uYbuSZ4SDE,22642
|
|
16
16
|
adcp/testing/__init__.py,sha256=ZWp_floWjVZfy8RBG5v_FUXQ8YbN7xjXvVcX-_zl_HU,1416
|
|
17
17
|
adcp/testing/test_helpers.py,sha256=-UKuxxyKQald5EvXxguQH34b3J0JdsxKH_nRT6GTjkQ,10029
|
|
18
|
-
adcp/types/__init__.py,sha256=
|
|
18
|
+
adcp/types/__init__.py,sha256=9f_6R333b2rGA6nXHk4FeVw40nh8bOcCQ7iasAgd8aA,13746
|
|
19
|
+
adcp/types/_ergonomic.py,sha256=EYC4ltYIOGgZ8_f_eEecanr-uLyQgq8d2yAZnsnsoQU,15979
|
|
19
20
|
adcp/types/_generated.py,sha256=Nd6vMiIJ8m0wRbD79dY5XTFmh0LbHBlvHIkLGQw0u7I,23268
|
|
20
21
|
adcp/types/aliases.py,sha256=DDc-AwFJfaDce84Mkq57KFIPgfK1knoiA_OOkrmDN7A,26114
|
|
21
22
|
adcp/types/base.py,sha256=Xr0cwbjG4tRPLbDTX9lucGty6_Y_S0a5C_ve0n7umc8,8227
|
|
23
|
+
adcp/types/coercion.py,sha256=gCxMvxcj7sTx75AgEMkdN-xjlYBBH_d9xnvE76NEe4c,6081
|
|
22
24
|
adcp/types/core.py,sha256=RXkKCWCXS9BVJTNpe3Opm5O1I_LaQPMUuVwa-ipvS1Q,4839
|
|
23
25
|
adcp/types/generated_poc/__init__.py,sha256=bgFFvPK1-e04eOnyw0qmtVMzoA2V7GeAMPDVrx-VIwA,103
|
|
24
26
|
adcp/types/generated_poc/adagents.py,sha256=a9yOFnBVwmopy28eEp1CdclBRjyzrkV7OvL1wtkojBE,16069
|
|
@@ -184,9 +186,9 @@ adcp/utils/__init__.py,sha256=uetvSJB19CjQbtwEYZiTnumJG11GsafQmXm5eR3hL7E,153
|
|
|
184
186
|
adcp/utils/operation_id.py,sha256=wQX9Bb5epXzRq23xoeYPTqzu5yLuhshg7lKJZihcM2k,294
|
|
185
187
|
adcp/utils/preview_cache.py,sha256=fy792IGXX9385FGsOhyuN1ZjoagsCstmcC1a8HAwtlI,18771
|
|
186
188
|
adcp/utils/response_parser.py,sha256=uPk2vIH-RYZmq7y3i8lC4HTMQ3FfKdlgXKTjgJ1955M,6253
|
|
187
|
-
adcp-2.
|
|
188
|
-
adcp-2.
|
|
189
|
-
adcp-2.
|
|
190
|
-
adcp-2.
|
|
191
|
-
adcp-2.
|
|
192
|
-
adcp-2.
|
|
189
|
+
adcp-2.16.0.dist-info/licenses/LICENSE,sha256=PF39NR3Ae8PLgBhg3Uxw6ju7iGVIf8hfv9LRWQdii_U,629
|
|
190
|
+
adcp-2.16.0.dist-info/METADATA,sha256=eZC137xJ4Wd7Ka6kCyxPsE3iHWMMYqdmavn2mjP-Ab8,31359
|
|
191
|
+
adcp-2.16.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
192
|
+
adcp-2.16.0.dist-info/entry_points.txt,sha256=DQKpcGsJX8DtVI_SGApQ7tNvqUB4zkTLaTAEpFgmi3U,44
|
|
193
|
+
adcp-2.16.0.dist-info/top_level.txt,sha256=T1_NF0GefncFU9v_k56oDwKSJREyCqIM8lAwNZf0EOs,5
|
|
194
|
+
adcp-2.16.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|