Skip to content

Commit cd507cd

Browse files
authored
feat: restore discriminated union types (#623)
1 parent 3aca465 commit cd507cd

58 files changed

Lines changed: 22544 additions & 2005 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

codegen/layouts/partials/resource-dataclass.hbs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,17 @@
1010
{{#each nestedClasses}}
1111
{{> resource-dataclass isNested=true}}
1212

13+
{{/each}}
14+
{{#each nestedUnions}}
15+
{{../memberIndent}}{{className}} = Union[{{#each variants}}{{className}}{{#unless @last}}, {{/unless}}{{/each}}]
16+
{{../memberIndent}}_{{className}}Variants = {
17+
{{#each variants}}
18+
{{#each values}}
19+
{{../../../memberIndent}} {{pythonString this}}: {{../className}},
20+
{{/each}}
21+
{{/each}}
22+
{{../memberIndent}} }
23+
1324
{{/each}}
1425
{{#each properties}}
1526
{{../memberIndent}}{{pythonIdentifier name}}: {{type}}
@@ -22,6 +33,6 @@
2233
{{/unless}}
2334
{{memberIndent}} return cls(
2435
{{#each properties}}
25-
{{../memberIndent}} {{pythonIdentifier name}}={{#if isObject}}cls.{{nestedClassName}}.from_dict(d.get("{{name}}")) if d.get("{{name}}") is not None else None{{else}}{{#if isObjectList}}[cls.{{nestedClassName}}.from_dict(i) for i in d.get("{{name}}") or []]{{else}}{{#if isDictParam}}DeepAttrDict({{/if}}d.get("{{name}}", None){{#if isDictParam}}){{/if}}{{/if}}{{/if}},
36+
{{../memberIndent}} {{pythonIdentifier name}}={{#if isObject}}cls.{{nestedClassName}}.from_dict(d.get("{{name}}")) if d.get("{{name}}") is not None else None{{else}}{{#if isDiscriminatedObjectList}}[_from_discriminated_dict(i, cls._{{nestedClassName}}Variants, "{{discriminator}}") for i in d.get("{{name}}") or []]{{else}}{{#if isObjectList}}[cls.{{nestedClassName}}.from_dict(i) for i in d.get("{{name}}") or []]{{else}}{{#if isDictParam}}DeepAttrDict({{/if}}d.get("{{name}}", None){{#if isDictParam}}){{/if}}{{/if}}{{/if}}{{/if}},
2637
{{/each}}
2738
{{memberIndent}} )

codegen/layouts/partials/route-method.hbs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,16 @@
2424

2525
return {{#if isAsync}}await resolve_action_attempt_async{{else}}resolve_action_attempt{{/if}}(
2626
client=self.client,
27-
action_attempt=ActionAttempt.from_dict(res["action_attempt"]),
27+
action_attempt=action_attempt_from_dict(res["action_attempt"]),
2828
wait_for_action_attempt=wait_for_action_attempt
2929
)
3030
{{else if (eq returnType "None")}}
3131

3232
return None
3333
{{else if (isListType returnType)}}
3434

35-
return [{{listItemType returnType}}.from_dict(item) for item in res{{#each returnPath}}["{{this}}"]{{/each}}]
35+
return [{{fromDict (listItemType returnType)}}(item) for item in res{{#each returnPath}}["{{this}}"]{{/each}}]
3636
{{else}}
3737

38-
return {{returnType}}.from_dict(res{{#each returnPath}}["{{this}}"]{{/each}})
38+
return {{fromDict returnType}}(res{{#each returnPath}}["{{this}}"]{{/each}})
3939
{{/if}}

codegen/layouts/resource.hbs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,42 @@
1-
from typing import Any, Dict, List, Literal, Optional, Union
1+
from typing import Any, Dict, List, Literal, Optional, Union{{#if union}}, cast{{/if}}
22
from dataclasses import dataclass
33
from ..deep_attr_dict import DeepAttrDict
44
from ..resource_mapping import ResourceMapping
55

6+
{{#if hasDiscriminatedLists}}
67

8+
def _from_discriminated_dict(
9+
d: Any, variants: Dict[str, Any], discriminator: str
10+
) -> Any:
11+
variant = variants.get(d.get(discriminator))
12+
return DeepAttrDict(d) if variant is None else variant.from_dict(d)
13+
{{/if}}
14+
15+
{{#each classes}}
716
{{> resource-dataclass}}
17+
18+
19+
{{/each}}
20+
{{#if union}}
21+
{{union.className}} = Union[{{#each union.variants}}{{className}}{{#unless @last}}, {{/unless}}{{/each}}]
22+
23+
{{union.variantsName}}: Dict[str, Any] = {
24+
{{#each union.variants}}
25+
{{#each values}}
26+
{{pythonString this}}: {{../className}},
27+
{{/each}}
28+
{{/each}}
29+
}
30+
31+
32+
def {{union.fromDictName}}(d: Any) -> {{union.className}}:
33+
"""Deserialize a known {{union.discriminator}} variant.
34+
35+
Unknown discriminator values return ``DeepAttrDict`` so payloads from a
36+
newer API remain readable. The static return type covers known variants.
37+
"""
38+
variant = {{union.variantsName}}.get(d.get("{{union.discriminator}}"))
39+
if variant is None:
40+
return cast({{union.className}}, DeepAttrDict(d))
41+
return variant.from_dict(d)
42+
{{/if}}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{{#each resources}}
2-
from .{{moduleName}} import {{className}}
2+
from .{{moduleName}} import ({{#each exports}}{{this}}{{#unless @last}}, {{/unless}}{{/each}})
33
{{/each}}

codegen/lib/handlebars-helpers.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ export const indent = (value: string, spaces: number): string =>
5353
export const pythonIdentifier = (name: string): string =>
5454
PYTHON_KEYWORDS.has(name) ? `${name}_` : name
5555

56+
export const pythonString = (value: string): string => JSON.stringify(value)
57+
5658
// A param the API documents as nullable may be set to the NULL sentinel, which
5759
// the client serializes to null. Params that are merely optional may not: they
5860
// are omitted by passing None, and sending null would unset a value instead.
@@ -62,3 +64,9 @@ export const nullableType = (type: string, isNullable: boolean): string =>
6264
export const isListType = (type: string): boolean => type.startsWith('List[')
6365

6466
export const listItemType = (type: string): string => type.slice(5, -1)
67+
68+
export const fromDict = (type: string): string => {
69+
if (type === 'SeamEvent') return 'seam_event_from_dict'
70+
if (type === 'ActionAttempt') return 'action_attempt_from_dict'
71+
return `${type}.from_dict`
72+
}

0 commit comments

Comments
 (0)