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
19 changes: 19 additions & 0 deletions codegen/lib/layouts/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,25 @@ const mergeOccurrences = (occurrences: Property[], path: string): Property => {
return merged
}

if (first.format === 'record' && 'valueTypes' in first) {
const valueTypes = occurrences.some(
(occurrence) =>
!('valueTypes' in occurrence) || occurrence.valueTypes == null,
)
? undefined
: [
...new Set(
occurrences.flatMap((occurrence) =>
'valueTypes' in occurrence ? (occurrence.valueTypes ?? []) : [],
),
),
]
const merged = { ...first, ...docs }
if (valueTypes == null) delete merged.valueTypes
else merged.valueTypes = valueTypes
return merged
}

if (first.format === 'object') {
return {
...first,
Expand Down
42 changes: 42 additions & 0 deletions codegen/lib/python-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ type ListItemFormat = Extract<
{ format: 'list' }
>['itemFormat']

type RecordValueType = NonNullable<
Extract<Parameter, { format: 'record' }>['valueTypes']
>[number]

export const mapParameterToPythonType = (parameter: Parameter): string => {
if (parameter.format === 'list') {
return `List[${mapListItemFormatToPythonType(parameter.itemFormat)}]`
Expand All @@ -22,6 +26,10 @@ export const mapParameterToPythonType = (parameter: Parameter): string => {
return mapBooleanToPythonType(parameter.values)
}

if (parameter.format === 'record') {
return mapRecordToPythonType(parameter.valueTypes)
}

return mapScalarFormatToPythonType(parameter.format)
}

Expand Down Expand Up @@ -65,6 +73,10 @@ export const mapRequiredPropertyToPythonType = (
return 'List[Dict[str, Any]]'
}

if (property.format === 'record' && 'valueTypes' in property) {
return mapRecordToPythonType(property.valueTypes)
}

if (property.format === 'object' && nestedClassName != null) {
return nestedClassName
}
Expand All @@ -77,6 +89,36 @@ const mapBooleanToPythonType = (values?: boolean[]): string =>
? 'bool'
: `Literal[${values.map((value) => (value ? 'True' : 'False')).join(', ')}]`

const mapRecordToPythonType = (valueTypes?: RecordValueType[]): string => {
const types = valueTypes?.map(mapJsonSchemaTypeToPythonType) ?? []
const valueType =
types.length === 0
? 'Any'
: types.length === 1
? (types[0] ?? 'Any')
: `Union[${types.join(', ')}]`
return `Dict[str, ${valueType}]`
}

const mapJsonSchemaTypeToPythonType = (type: RecordValueType): string => {
switch (type) {
case 'string':
return 'str'
case 'number':
return 'float'
case 'integer':
return 'int'
case 'boolean':
return 'bool'
case 'object':
return 'Dict[str, Any]'
case 'array':
return 'List[Any]'
default:
throw new Error(`Unsupported JSON Schema type: ${type}`)
}
}

const mapScalarFormatToPythonType = (format: ScalarFormat): string => {
switch (format) {
case 'string':
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
}
},
"devDependencies": {
"@seamapi/blueprint": "^1.6.0",
"@seamapi/blueprint": "^1.7.0",
"@seamapi/fake-seam-connect": "1.86.0",
"@seamapi/smith": "^1.1.0",
"@seamapi/types": "1.1001.0",
Expand Down
2 changes: 1 addition & 1 deletion seam/resources/connect_webview.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion seam/resources/connected_account.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion seam/resources/device.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion seam/resources/phone.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions seam/resources/seam_event.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion seam/resources/unmanaged_device.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions seam/routes/connect_webviews.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions seam/routes/connected_accounts.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions seam/routes/devices.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions seam/routes/devices_unmanaged.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 18 additions & 2 deletions test/resource_types_test.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
# mypy: warn_unused_ignores=True

from typing import Literal, assert_type
from typing import Any, Dict, Literal, Union, assert_type

from seam.resources import AccessCode, ActionAttempt, UnmanagedAccessCode
from seam.resources import (
AccessCode,
ActionAttempt,
Device,
SeamEvent,
UnmanagedAccessCode,
)


def _assert_access_code_narrowing(
Expand All @@ -25,11 +31,21 @@ def _assert_boolean_shapes(
assert_type(credential.is_managed, Literal[True, False])


def _assert_record_value_types(device: Device, event: SeamEvent) -> None:
assert_type(device.custom_metadata, Dict[str, Union[str, bool]])
assert_type(
event.connected_account_custom_metadata,
Dict[str, Union[str, bool]] | None,
)
assert_type(event.minut_metadata, Dict[str, Any] | None)


def _assert_opposite_literal_is_rejected(code: UnmanagedAccessCode) -> None:
code.is_managed = True # type: ignore[assignment]


def test_access_code_resources_narrow_on_is_managed():
assert callable(_assert_access_code_narrowing)
assert callable(_assert_boolean_shapes)
assert callable(_assert_record_value_types)
assert callable(_assert_opposite_literal_is_rejected)
Loading