From d724f3f5628726af853d9a08c5fb2601e23c7823 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Aug 2026 01:12:49 +0000 Subject: [PATCH 1/3] feat: Generate a status-discriminated action attempt union Bump @seamapi/blueprint to 1.10.0 and consume the new actionAttemptStatuses property annotation. Each action attempt now generates one dataclass per status from its status enum: the status field is typed as the status literal, a property whose annotation lists the status keeps its schema-declared requiredness (so the success class exposes a non-optional result and the error class a non-optional error), and a property whose annotation does not list the status is typed None. action_attempt_from_dict dispatches on the (action_type, status) pair, and per-action-type and per-status Union aliases (e.g. LockDoorActionAttempt, SuccessActionAttempt, PendingActionAttempt, ErrorActionAttempt) cover each single discriminator value. poll_until_ready returns the success union, and the action attempt failed and timeout errors take the error and pending unions. Code that dereferences error or result without narrowing on status stops typechecking; runtime API behavior is unchanged. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01EdWS7o3htQ9cNxhCWL5Frp --- .../layouts/partials/resource-dataclass.hbs | 2 +- codegen/layouts/resource.hbs | 14 +- codegen/lib/layouts/resources.ts | 252 +- package-lock.json | 8 +- package.json | 2 +- seam/exceptions.py | 14 +- seam/modules/action_attempts.py | 6 +- seam/resources/__init__.py | 66 + seam/resources/action_attempt.py | 2751 +++++++++++++---- test/action_attempt_types_test.py | 161 + test/nested_resource_test.py | 34 +- test/resource_types_test.py | 10 +- 12 files changed, 2594 insertions(+), 726 deletions(-) create mode 100644 test/action_attempt_types_test.py diff --git a/codegen/layouts/partials/resource-dataclass.hbs b/codegen/layouts/partials/resource-dataclass.hbs index e52750c6..d9f4966e 100644 --- a/codegen/layouts/partials/resource-dataclass.hbs +++ b/codegen/layouts/partials/resource-dataclass.hbs @@ -33,6 +33,6 @@ {{/unless}} {{memberIndent}} return cls( {{#each properties}} -{{../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}}, +{{../memberIndent}} {{pythonIdentifier name}}={{#if isRequiredObject}}cls.{{nestedClassName}}.from_dict(d.get("{{name}}") or {}){{else}}{{#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}}{{/if}}, {{/each}} {{memberIndent}} ) diff --git a/codegen/layouts/resource.hbs b/codegen/layouts/resource.hbs index 72c9250b..dd7306cd 100644 --- a/codegen/layouts/resource.hbs +++ b/codegen/layouts/resource.hbs @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Literal, Optional, Union{{#if union}}, cast{{/if}} +from typing import Any, Dict, List, Literal, Optional, {{#if union.secondaryDiscriminator}}Tuple, {{/if}}Union{{#if union}}, cast{{/if}} from dataclasses import dataclass from ..deep_attr_dict import DeepAttrDict from ..resource_mapping import ResourceMapping @@ -20,22 +20,26 @@ def _from_discriminated_dict( {{#if union}} {{union.className}} = Union[{{#each union.variants}}{{className}}{{#unless @last}}, {{/unless}}{{/each}}] -{{union.variantsName}}: Dict[str, Any] = { +{{#each union.aliases}} +{{className}} = Union[{{#each variantClassNames}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}] + +{{/each}} +{{union.variantsName}}: Dict[{{#if union.secondaryDiscriminator}}Tuple[str, str]{{else}}str{{/if}}, Any] = { {{#each union.variants}} {{#each values}} - {{pythonString this}}: {{../className}}, + {{#if @root.union.secondaryDiscriminator}}({{pythonString this}}, {{pythonString ../secondaryValue}}){{else}}{{pythonString this}}{{/if}}: {{../className}}, {{/each}} {{/each}} } def {{union.fromDictName}}(d: Any) -> {{union.className}}: - """Deserialize a known {{union.discriminator}} variant. + """Deserialize a known {{union.discriminator}}{{#if union.secondaryDiscriminator}} and {{union.secondaryDiscriminator}}{{/if}} variant. Unknown discriminator values return ``DeepAttrDict`` so payloads from a newer API remain readable. The static return type covers known variants. """ - variant = {{union.variantsName}}.get(d.get("{{union.discriminator}}")) + variant = {{union.variantsName}}.get({{#if union.secondaryDiscriminator}}(d.get("{{union.discriminator}}"), d.get("{{union.secondaryDiscriminator}}")){{else}}d.get("{{union.discriminator}}"){{/if}}) if variant is None: return cast({{union.className}}, DeepAttrDict(d)) return variant.from_dict(d) diff --git a/codegen/lib/layouts/resources.ts b/codegen/lib/layouts/resources.ts index 53ffbcb1..fa61d5f3 100644 --- a/codegen/lib/layouts/resources.ts +++ b/codegen/lib/layouts/resources.ts @@ -2,7 +2,12 @@ // Each blueprint resource, along with events, action attempts, and pagination, // becomes a dataclass in its own module, re-exported from seam/resources/__init__.py. -import type { Blueprint, Property } from '@seamapi/blueprint' +import type { + ActionAttemptStatus, + Blueprint, + EnumProperty, + Property, +} from '@seamapi/blueprint' import { pascalCase, snakeCase } from 'change-case' import { convertCustomResourceName } from '../custom-resource-name-conversions.js' @@ -38,9 +43,15 @@ interface ResourceClassLayoutContext { interface DiscriminatedUnionLayoutContext { className: string discriminator: string + secondaryDiscriminator?: string fromDictName: string variantsName: string - variants: Array<{ className: string; values: string[] }> + variants: Array<{ + className: string + values: string[] + secondaryValue?: string + }> + aliases?: Array<{ className: string; variantClassNames: string[] }> } interface ResourcePropertyLayoutContext { @@ -52,6 +63,7 @@ interface ResourcePropertyLayoutContext { nestedClassName: string isDictParam: boolean isObject: boolean + isRequiredObject: boolean isObjectList: boolean isDiscriminatedObjectList: boolean discriminator: string @@ -248,6 +260,22 @@ const reservedClassNames = new Set([ 'dataclass', ]) +// Markers set when an action attempt is expanded into per-status variants. +// A property whose actionAttemptStatuses annotation does not list the +// variant's status is rendered as None; one whose annotation does list it is +// genuinely present for that status, so it sheds the forced optionality that +// nested objects otherwise get. +type StatusAnnotatedProperty = Property & { + renderAsNone?: boolean + presentForStatus?: boolean +} + +const isRenderedAsNone = (property: Property): boolean => + (property as StatusAnnotatedProperty).renderAsNone === true + +const isPresentForStatus = (property: Property): boolean => + (property as StatusAnnotatedProperty).presentForStatus === true + const getNestedProperties = (property: Property): Property[] | undefined => { if (property.format === 'object') return property.properties if (property.format === 'list' && property.itemFormat === 'object') { @@ -296,6 +324,25 @@ const buildClass = ( const takenClassNames = new Set() const properties = classProperties.map((property) => { + if (isRenderedAsNone(property)) { + // The property is typed None in this status variant, so no nested + // classes are generated for it: nothing could reference them. + return { + name: property.name, + description: property.description, + isDeprecated: property.isDeprecated, + deprecationMessage: property.deprecationMessage, + type: 'None', + nestedClassName: '', + isDictParam: false, + isObject: false, + isRequiredObject: false, + isObjectList: false, + isDiscriminatedObjectList: false, + discriminator: '', + } + } + const nestedProperties = getNestedProperties(property) const nestedPath = `${path}.${property.name}` const isDiscriminatedObjectList = @@ -380,11 +427,23 @@ const buildClass = ( const isObject = nestedClassName != null && property.format === 'object' // A nested object is read as None whenever the payload omits it, and the - // schema is not a reliable guide to when that happens: an action attempt - // documents both error and result as required, yet a pending one carries - // neither. Constructing them unconditionally would fail on those payloads, - // so from_dict keeps its None fallback and the field stays Optional. - const type = mapPropertyToPythonType(property, nestedClassName, isObject) + // schema alone is not a reliable guide to when that happens: an action + // attempt documents both error and result as required, yet a pending one + // carries neither. Constructing them unconditionally would fail on those + // payloads, so from_dict keeps its None fallback and the field stays + // Optional. The exception is a status-annotated property in a variant + // whose status the annotation lists: the annotation guarantees it is + // present there, so it keeps the optionality the schema declares. + const isRequiredObject = + isObject && + isPresentForStatus(property) && + !property.isOptional && + !property.isNullable + const type = mapPropertyToPythonType( + property, + nestedClassName, + isObject && !isRequiredObject, + ) const requiredType = mapRequiredPropertyToPythonType( property, nestedClassName, @@ -400,6 +459,7 @@ const buildClass = ( nestedClassName: nestedClassName ?? '', isDictParam: requiredType.startsWith('Dict'), isObject, + isRequiredObject, isObjectList: !isDiscriminatedObjectList && nestedClassName != null && @@ -427,41 +487,113 @@ const hasDiscriminatedLists = ( resourceClass.nestedUnions.length > 0 || resourceClass.nestedClasses.some(hasDiscriminatedLists) +interface UnionVariant { + value: string + secondaryValue?: string + description: string + properties: Property[] + isDeprecated: boolean + deprecationMessage: string +} + +// Group the class names of a union's variants by one of the discriminator +// values, so each group becomes a Union alias over the classes that share it. +const buildUnionAliases = ( + variants: Array<{ className: string; groupValue: string | undefined }>, + suffix: string, +): Array<{ className: string; variantClassNames: string[] }> => { + const groups = new Map() + for (const { className, groupValue } of variants) { + if (groupValue == null) continue + const group = groups.get(groupValue) + if (group == null) { + groups.set(groupValue, [className]) + } else { + group.push(className) + } + } + return [...groups.entries()].map(([value, variantClassNames]) => ({ + className: `${pythonClassName(value)}${suffix}`, + variantClassNames, + })) +} + const buildUnionResource = ( className: string, discriminator: string, fromDictName: string, - variants: Array<{ - value: string - description: string - properties: Property[] - isDeprecated: boolean - deprecationMessage: string - }>, + variants: UnionVariant[], isDeprecated: boolean, deprecationMessage: string, + secondaryDiscriminator?: string, ): ResourceLayoutContext => { const suffix = className === 'SeamEvent' ? 'Event' : 'ActionAttempt' - const classes = variants.map((variant) => ({ - ...buildClass( - `${pythonClassName(variant.value)}${suffix}`, - variant.description, - variant.properties, - `${snakeCase(className)}.${variant.value}`, - rootIndentation, - ), - isDeprecated: variant.isDeprecated, - deprecationMessage: variant.deprecationMessage, - })) + const classes = variants.map((variant) => { + const secondaryName = + variant.secondaryValue == null + ? '' + : pythonClassName(variant.secondaryValue) + const secondaryPath = + variant.secondaryValue == null ? '' : `.${variant.secondaryValue}` + return { + ...buildClass( + `${pythonClassName(variant.value)}${secondaryName}${suffix}`, + variant.description, + variant.properties, + `${snakeCase(className)}.${variant.value}${secondaryPath}`, + rootIndentation, + ), + isDeprecated: variant.isDeprecated, + deprecationMessage: variant.deprecationMessage, + } + }) + + // With a secondary discriminator, a class covers one value pair, so aliases + // name the unions over each single value: one per primary value and one per + // secondary value. + const aliases = + secondaryDiscriminator == null + ? [] + : [ + ...buildUnionAliases( + classes.map(({ className: name }, index) => ({ + className: name, + groupValue: variants[index]?.value, + })), + suffix, + ), + ...buildUnionAliases( + classes.map(({ className: name }, index) => ({ + className: name, + groupValue: variants[index]?.secondaryValue, + })), + suffix, + ), + ] + const classNames = new Set(classes.map(({ className: name }) => name)) + for (const alias of aliases) { + if (classNames.has(alias.className) || alias.className === className) { + throw new Error( + `The union alias ${alias.className} collides with a generated class name.`, + ) + } + } + const union = { className, discriminator, + ...(secondaryDiscriminator == null ? {} : { secondaryDiscriminator }), fromDictName, variantsName: `_${snakeCase(className).toUpperCase()}_VARIANTS`, - variants: classes.map((variantClass, index) => ({ - className: variantClass.className, - values: [variants[index]?.value ?? ''], - })), + variants: classes.map((variantClass, index) => { + const secondaryValue = variants[index]?.secondaryValue + return { + className: variantClass.className, + values: [variants[index]?.value ?? ''], + ...(secondaryValue == null ? {} : { secondaryValue }), + } + }), + aliases, } return { @@ -474,12 +606,65 @@ const buildUnionResource = ( hasDiscriminatedLists: classes.some(hasDiscriminatedLists), exports: [ ...classes.map(({ className: name }) => name), + ...aliases.map(({ className: name }) => name), className, fromDictName, ], } } +// Expand an action attempt into one union variant per status from its status +// enum. In each variant, the status enum is filtered to the single status; a +// property whose actionAttemptStatuses annotation lists the status is marked +// present, and one whose annotation does not list it is rendered as None. +// Properties without the annotation are rendered unchanged for every status. +const expandActionAttemptByStatus = ( + attempt: Blueprint['actionAttempts'][number], +): UnionVariant[] => { + const statusProperty = attempt.properties.find( + (property): property is EnumProperty => + property.name === 'status' && property.format === 'enum', + ) + if (statusProperty == null || statusProperty.values.length === 0) { + throw new Error( + `The ${attempt.actionAttemptType} action attempt must have a status enum property to expand into per-status variants.`, + ) + } + + return statusProperty.values.map(({ name: status }) => ({ + value: attempt.actionAttemptType, + secondaryValue: status, + description: attempt.description, + isDeprecated: attempt.isDeprecated, + deprecationMessage: attempt.deprecationMessage, + properties: attempt.properties.map((property): Property => { + if (property === statusProperty) { + return { + ...statusProperty, + values: statusProperty.values.filter( + (value) => value.name === status, + ), + } + } + const { actionAttemptStatuses } = property + if (actionAttemptStatuses == null) return property + if (actionAttemptStatuses.includes(status as ActionAttemptStatus)) { + const presentProperty: StatusAnnotatedProperty = { + ...property, + presentForStatus: true, + } + return presentProperty + } + const noneProperty: StatusAnnotatedProperty = { + ...property, + isNullable: false, + renderAsNone: true, + } + return noneProperty + }), + })) +} + export const getResourceLayoutContexts = ( blueprint: Blueprint, ): ResourceLayoutContext[] => { @@ -568,15 +753,10 @@ export const getResourceLayoutContexts = ( 'ActionAttempt', 'action_type', 'action_attempt_from_dict', - blueprint.actionAttempts.map((attempt) => ({ - value: attempt.actionAttemptType, - description: attempt.description, - properties: attempt.properties, - isDeprecated: attempt.isDeprecated, - deprecationMessage: attempt.deprecationMessage, - })), + blueprint.actionAttempts.flatMap(expandActionAttemptByStatus), actionAttemptModel?.isDeprecated ?? false, actionAttemptModel?.deprecationMessage ?? '', + 'status', ), ) diff --git a/package-lock.json b/package-lock.json index 71372b48..fa376186 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "@seamapi/python", "devDependencies": { - "@seamapi/blueprint": "^1.9.1", + "@seamapi/blueprint": "^1.10.0", "@seamapi/fake-seam-connect": "2.0.5", "@seamapi/smith": "^1.1.0", "@seamapi/types": "1.1047.0", @@ -787,9 +787,9 @@ "license": "MIT" }, "node_modules/@seamapi/blueprint": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@seamapi/blueprint/-/blueprint-1.9.1.tgz", - "integrity": "sha512-A9H3dZE9f+ZEEnOAlMl5jSlL5F/RIKkjOF8NDFbnuahAiu/trDz/RzHOGhbQd6sd0RErIyx3eG1p4HCOJDKDCA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@seamapi/blueprint/-/blueprint-1.10.0.tgz", + "integrity": "sha512-XyP6zvbhv5naWEa9T9utNLi3FVVmvUB1Htih/IjUqS3Uz0FrIIBWy7Myk5+s4uylEt+wTdtSBaA1fGOF/6ILmA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index c42fcef3..ec8a307c 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ }, "packageManager": "npm@11.19.0", "devDependencies": { - "@seamapi/blueprint": "^1.9.1", + "@seamapi/blueprint": "^1.10.0", "@seamapi/fake-seam-connect": "2.0.5", "@seamapi/smith": "^1.1.0", "@seamapi/types": "1.1047.0", diff --git a/seam/exceptions.py b/seam/exceptions.py index afdec1e8..ef9fbeb1 100644 --- a/seam/exceptions.py +++ b/seam/exceptions.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from typing import Any, Dict, List, Optional -from .resources import ActionAttempt +from .resources import ActionAttempt, ErrorActionAttempt, PendingActionAttempt @dataclass(frozen=True) @@ -156,10 +156,10 @@ class SeamActionAttemptFailedError(SeamActionAttemptError): :vartype code: str """ - def __init__(self, action_attempt: ActionAttempt): + def __init__(self, action_attempt: ErrorActionAttempt): """ - :param action_attempt: The ActionAttempt object associated with this error - :type action_attempt: ActionAttempt + :param action_attempt: The failed ActionAttempt object associated with this error + :type action_attempt: ErrorActionAttempt """ # A failed action attempt carries an error, but reading through it @@ -186,10 +186,10 @@ class SeamActionAttemptTimeoutError(SeamActionAttemptError): :vartype name: str """ - def __init__(self, action_attempt: ActionAttempt, timeout: float): + def __init__(self, action_attempt: PendingActionAttempt, timeout: float): """ - :param action_attempt: The ActionAttempt object associated with this error - :type action_attempt: ActionAttempt + :param action_attempt: The still-pending ActionAttempt object associated with this error + :type action_attempt: PendingActionAttempt :param timeout: The timeout duration in seconds :type timeout: float """ diff --git a/seam/modules/action_attempts.py b/seam/modules/action_attempts.py index e87b0b59..461517dd 100644 --- a/seam/modules/action_attempts.py +++ b/seam/modules/action_attempts.py @@ -4,7 +4,7 @@ from ..client import AsyncSeamHttpClient, SeamHttpClient from ..exceptions import SeamActionAttemptFailedError, SeamActionAttemptTimeoutError -from ..resources import ActionAttempt, action_attempt_from_dict +from ..resources import ActionAttempt, SuccessActionAttempt, action_attempt_from_dict TIMEOUT = 5.0 POLLING_INTERVAL = 0.5 @@ -24,7 +24,7 @@ def poll_until_ready( action_attempt_id: str, timeout: float = TIMEOUT, polling_interval: float = POLLING_INTERVAL, -) -> ActionAttempt: +) -> SuccessActionAttempt: time_waiting = 0.0 action_attempt = get_action_attempt(client, action_attempt_id) @@ -84,7 +84,7 @@ async def poll_until_ready_async( action_attempt_id: str, timeout: float = TIMEOUT, polling_interval: float = POLLING_INTERVAL, -) -> ActionAttempt: +) -> SuccessActionAttempt: time_waiting = 0.0 action_attempt = await get_action_attempt_async(client, action_attempt_id) diff --git a/seam/resources/__init__.py b/seam/resources/__init__.py index a981676b..14aac489 100644 --- a/seam/resources/__init__.py +++ b/seam/resources/__init__.py @@ -8,6 +8,69 @@ from .acs_system import AcsSystem from .acs_user import AcsUser from .action_attempt import ( + LockDoorSuccessActionAttempt, + LockDoorPendingActionAttempt, + LockDoorErrorActionAttempt, + UnlockDoorSuccessActionAttempt, + UnlockDoorPendingActionAttempt, + UnlockDoorErrorActionAttempt, + ScanCredentialSuccessActionAttempt, + ScanCredentialPendingActionAttempt, + ScanCredentialErrorActionAttempt, + EncodeCredentialSuccessActionAttempt, + EncodeCredentialPendingActionAttempt, + EncodeCredentialErrorActionAttempt, + ScanToAssignCredentialSuccessActionAttempt, + ScanToAssignCredentialPendingActionAttempt, + ScanToAssignCredentialErrorActionAttempt, + AssignCredentialSuccessActionAttempt, + AssignCredentialPendingActionAttempt, + AssignCredentialErrorActionAttempt, + ResetSandboxWorkspaceSuccessActionAttempt, + ResetSandboxWorkspacePendingActionAttempt, + ResetSandboxWorkspaceErrorActionAttempt, + SetFanModeSuccessActionAttempt, + SetFanModePendingActionAttempt, + SetFanModeErrorActionAttempt, + SetHvacModeSuccessActionAttempt, + SetHvacModePendingActionAttempt, + SetHvacModeErrorActionAttempt, + ActivateClimatePresetSuccessActionAttempt, + ActivateClimatePresetPendingActionAttempt, + ActivateClimatePresetErrorActionAttempt, + SimulateKeypadCodeEntrySuccessActionAttempt, + SimulateKeypadCodeEntryPendingActionAttempt, + SimulateKeypadCodeEntryErrorActionAttempt, + SimulateManualLockViaKeypadSuccessActionAttempt, + SimulateManualLockViaKeypadPendingActionAttempt, + SimulateManualLockViaKeypadErrorActionAttempt, + PushThermostatProgramsSuccessActionAttempt, + PushThermostatProgramsPendingActionAttempt, + PushThermostatProgramsErrorActionAttempt, + ConfigureAutoLockSuccessActionAttempt, + ConfigureAutoLockPendingActionAttempt, + ConfigureAutoLockErrorActionAttempt, + SyncAccessCodesSuccessActionAttempt, + SyncAccessCodesPendingActionAttempt, + SyncAccessCodesErrorActionAttempt, + CreateAccessCodeSuccessActionAttempt, + CreateAccessCodePendingActionAttempt, + CreateAccessCodeErrorActionAttempt, + DeleteAccessCodeSuccessActionAttempt, + DeleteAccessCodePendingActionAttempt, + DeleteAccessCodeErrorActionAttempt, + UpdateAccessCodeSuccessActionAttempt, + UpdateAccessCodePendingActionAttempt, + UpdateAccessCodeErrorActionAttempt, + CreateNoiseThresholdSuccessActionAttempt, + CreateNoiseThresholdPendingActionAttempt, + CreateNoiseThresholdErrorActionAttempt, + DeleteNoiseThresholdSuccessActionAttempt, + DeleteNoiseThresholdPendingActionAttempt, + DeleteNoiseThresholdErrorActionAttempt, + UpdateNoiseThresholdSuccessActionAttempt, + UpdateNoiseThresholdPendingActionAttempt, + UpdateNoiseThresholdErrorActionAttempt, LockDoorActionAttempt, UnlockDoorActionAttempt, ScanCredentialActionAttempt, @@ -29,6 +92,9 @@ CreateNoiseThresholdActionAttempt, DeleteNoiseThresholdActionAttempt, UpdateNoiseThresholdActionAttempt, + SuccessActionAttempt, + PendingActionAttempt, + ErrorActionAttempt, ActionAttempt, action_attempt_from_dict, ) diff --git a/seam/resources/action_attempt.py b/seam/resources/action_attempt.py index 4a6cc6ed..162d4492 100644 --- a/seam/resources/action_attempt.py +++ b/seam/resources/action_attempt.py @@ -1,11 +1,11 @@ -from typing import Any, Dict, List, Literal, Optional, Union, cast +from typing import Any, Dict, List, Literal, Optional, Tuple, Union, cast from dataclasses import dataclass from ..deep_attr_dict import DeepAttrDict from ..resource_mapping import ResourceMapping @dataclass -class LockDoorActionAttempt: +class LockDoorSuccessActionAttempt: """Locking a door is pending. :ivar action_attempt_id: ID of the action attempt. @@ -18,24 +18,6 @@ class LockDoorActionAttempt: :ivar status:""" - @dataclass - class Error(ResourceMapping): - """Error associated with the action. - - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - - :ivar type: Type of the error.""" - - message: str - type: str - - @classmethod - def from_dict(cls, d: Any): - return cls( - message=d.get("message", None), - type=d.get("type", None), - ) - @dataclass class Result(ResourceMapping): """Result of the action. @@ -53,36 +35,59 @@ def from_dict(cls, d: Any): action_attempt_id: str action_type: Literal["LOCK_DOOR"] - error: Optional[Error] - result: Optional[Result] - status: Literal["success", "pending", "error"] + error: None + result: Result + status: Literal["success"] @classmethod def from_dict(cls, d: Any): return cls( action_attempt_id=d.get("action_attempt_id", None), action_type=d.get("action_type", None), - error=( - cls.Error.from_dict(d.get("error")) - if d.get("error") is not None - else None - ), - result=( - cls.Result.from_dict(d.get("result")) - if d.get("result") is not None - else None - ), + error=d.get("error", None), + result=cls.Result.from_dict(d.get("result") or {}), status=d.get("status", None), ) @dataclass -class UnlockDoorActionAttempt: - """Unlocking a door is pending. +class LockDoorPendingActionAttempt: + """Locking a door is pending. :ivar action_attempt_id: ID of the action attempt. - :ivar action_type: Action attempt to track the status of unlocking a door. + :ivar action_type: Action attempt to track the status of locking a door. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + + action_attempt_id: str + action_type: Literal["LOCK_DOOR"] + error: None + result: None + status: Literal["pending"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=d.get("error", None), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class LockDoorErrorActionAttempt: + """Locking a door is pending. + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Action attempt to track the status of locking a door. :ivar error: Error associated with the action. @@ -108,6 +113,37 @@ def from_dict(cls, d: Any): type=d.get("type", None), ) + action_attempt_id: str + action_type: Literal["LOCK_DOOR"] + error: Error + result: None + status: Literal["error"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=cls.Error.from_dict(d.get("error") or {}), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class UnlockDoorSuccessActionAttempt: + """Unlocking a door is pending. + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Action attempt to track the status of unlocking a door. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + @dataclass class Result(ResourceMapping): """Result of the action. @@ -125,61 +161,76 @@ def from_dict(cls, d: Any): action_attempt_id: str action_type: Literal["UNLOCK_DOOR"] - error: Optional[Error] - result: Optional[Result] - status: Literal["success", "pending", "error"] + error: None + result: Result + status: Literal["success"] @classmethod def from_dict(cls, d: Any): return cls( action_attempt_id=d.get("action_attempt_id", None), action_type=d.get("action_type", None), - error=( - cls.Error.from_dict(d.get("error")) - if d.get("error") is not None - else None - ), - result=( - cls.Result.from_dict(d.get("result")) - if d.get("result") is not None - else None - ), + error=d.get("error", None), + result=cls.Result.from_dict(d.get("result") or {}), status=d.get("status", None), ) @dataclass -class ScanCredentialActionAttempt: - """Reading credential data from the physical encoder is pending. +class UnlockDoorPendingActionAttempt: + """Unlocking a door is pending. :ivar action_attempt_id: ID of the action attempt. - :ivar action_type: Action attempt to track the status of scanning a credential. + :ivar action_type: Action attempt to track the status of unlocking a door. - :ivar error: + :ivar error: Error associated with the action. - :ivar result: Result of scanning a card. If the attempt was successful, includes a snapshot of credential data read from the physical encoder, the corresponding data stored on Seam and the access system, and any associated warnings. + :ivar result: Result of the action. + + :ivar status:""" + + action_attempt_id: str + action_type: Literal["UNLOCK_DOOR"] + error: None + result: None + status: Literal["pending"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=d.get("error", None), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class UnlockDoorErrorActionAttempt: + """Unlocking a door is pending. + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Action attempt to track the status of unlocking a door. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. :ivar status:""" @dataclass class Error(ResourceMapping): - """ + """Error associated with the action. :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - :ivar type: Error type to indicate that the Seam Bridge is disconnected or cannot reach the access control system. - """ + :ivar type: Type of the error.""" message: str - type: Literal[ - "uncategorized_error", - "action_attempt_expired", - "no_credential_on_encoder", - "encoder_not_online", - "encoder_communication_timeout", - "bridge_disconnected", - ] + type: str @classmethod def from_dict(cls, d: Any): @@ -188,6 +239,37 @@ def from_dict(cls, d: Any): type=d.get("type", None), ) + action_attempt_id: str + action_type: Literal["UNLOCK_DOOR"] + error: Error + result: None + status: Literal["error"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=cls.Error.from_dict(d.get("error") or {}), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class ScanCredentialSuccessActionAttempt: + """Reading credential data from the physical encoder is pending. + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Action attempt to track the status of scanning a credential. + + :ivar error: + + :ivar result: Result of scanning a card. If the attempt was successful, includes a snapshot of credential data read from the physical encoder, the corresponding data stored on Seam and the access system, and any associated warnings. + + :ivar status:""" + @dataclass class Result(ResourceMapping): """Result of scanning a card. If the attempt was successful, includes a snapshot of credential data read from the physical encoder, the corresponding data stored on Seam and the access system, and any associated warnings. @@ -668,40 +750,63 @@ def from_dict(cls, d: Any): action_attempt_id: str action_type: Literal["SCAN_CREDENTIAL"] - error: Optional[Error] - result: Optional[Result] - status: Literal["success", "pending", "error"] + error: None + result: Result + status: Literal["success"] @classmethod def from_dict(cls, d: Any): return cls( action_attempt_id=d.get("action_attempt_id", None), action_type=d.get("action_type", None), - error=( - cls.Error.from_dict(d.get("error")) - if d.get("error") is not None - else None - ), - result=( - cls.Result.from_dict(d.get("result")) - if d.get("result") is not None - else None - ), + error=d.get("error", None), + result=cls.Result.from_dict(d.get("result") or {}), status=d.get("status", None), ) @dataclass -class EncodeCredentialActionAttempt: - """Encoding credential data from the physical encoder onto a card is pending. +class ScanCredentialPendingActionAttempt: + """Reading credential data from the physical encoder is pending. :ivar action_attempt_id: ID of the action attempt. - :ivar action_type: Action attempt to track the status of encoding credential data from the physical encoder onto a card. + :ivar action_type: Action attempt to track the status of scanning a credential. :ivar error: - :ivar result: Result of an encoding attempt. If the attempt was successful, includes the credential data that was encoded onto the card. + :ivar result: Result of scanning a card. If the attempt was successful, includes a snapshot of credential data read from the physical encoder, the corresponding data stored on Seam and the access system, and any associated warnings. + + :ivar status:""" + + action_attempt_id: str + action_type: Literal["SCAN_CREDENTIAL"] + error: None + result: None + status: Literal["pending"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=d.get("error", None), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class ScanCredentialErrorActionAttempt: + """Reading credential data from the physical encoder is pending. + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Action attempt to track the status of scanning a credential. + + :ivar error: + + :ivar result: Result of scanning a card. If the attempt was successful, includes a snapshot of credential data read from the physical encoder, the corresponding data stored on Seam and the access system, and any associated warnings. :ivar status:""" @@ -711,7 +816,7 @@ class Error(ResourceMapping): :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - :ivar type: Error type to indicate that the credential was deleted and can no longer be encoded. + :ivar type: Error type to indicate that the Seam Bridge is disconnected or cannot reach the access control system. """ message: str @@ -719,13 +824,9 @@ class Error(ResourceMapping): "uncategorized_error", "action_attempt_expired", "no_credential_on_encoder", - "incompatible_card_format", - "credential_cannot_be_reissued", "encoder_not_online", "encoder_communication_timeout", "bridge_disconnected", - "encoding_interrupted", - "credential_deleted", ] @classmethod @@ -735,6 +836,37 @@ def from_dict(cls, d: Any): type=d.get("type", None), ) + action_attempt_id: str + action_type: Literal["SCAN_CREDENTIAL"] + error: Error + result: None + status: Literal["error"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=cls.Error.from_dict(d.get("error") or {}), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class EncodeCredentialSuccessActionAttempt: + """Encoding credential data from the physical encoder onto a card is pending. + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Action attempt to track the status of encoding credential data from the physical encoder onto a card. + + :ivar error: + + :ivar result: Result of an encoding attempt. If the attempt was successful, includes the credential data that was encoded onto the card. + + :ivar status:""" + @dataclass class Result(ResourceMapping): """Result of an encoding attempt. If the attempt was successful, includes the credential data that was encoded onto the card. @@ -1056,40 +1188,63 @@ def from_dict(cls, d: Any): action_attempt_id: str action_type: Literal["ENCODE_CREDENTIAL"] - error: Optional[Error] - result: Optional[Result] - status: Literal["success", "pending", "error"] + error: None + result: Result + status: Literal["success"] @classmethod def from_dict(cls, d: Any): return cls( action_attempt_id=d.get("action_attempt_id", None), action_type=d.get("action_type", None), - error=( - cls.Error.from_dict(d.get("error")) - if d.get("error") is not None - else None - ), - result=( - cls.Result.from_dict(d.get("result")) - if d.get("result") is not None - else None - ), + error=d.get("error", None), + result=cls.Result.from_dict(d.get("result") or {}), status=d.get("status", None), ) @dataclass -class ScanToAssignCredentialActionAttempt: - """Scanning a physical card and assigning the credential is pending. +class EncodeCredentialPendingActionAttempt: + """Encoding credential data from the physical encoder onto a card is pending. :ivar action_attempt_id: ID of the action attempt. - :ivar action_type: Action attempt to track the status of scanning a physical card and assigning the credential to an ACS user. + :ivar action_type: Action attempt to track the status of encoding credential data from the physical encoder onto a card. :ivar error: - :ivar result: Result of a scan to assign attempt. If the attempt was successful, includes the credential data that was scanned and assigned. + :ivar result: Result of an encoding attempt. If the attempt was successful, includes the credential data that was encoded onto the card. + + :ivar status:""" + + action_attempt_id: str + action_type: Literal["ENCODE_CREDENTIAL"] + error: None + result: None + status: Literal["pending"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=d.get("error", None), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class EncodeCredentialErrorActionAttempt: + """Encoding credential data from the physical encoder onto a card is pending. + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Action attempt to track the status of encoding credential data from the physical encoder onto a card. + + :ivar error: + + :ivar result: Result of an encoding attempt. If the attempt was successful, includes the credential data that was encoded onto the card. :ivar status:""" @@ -1099,12 +1254,21 @@ class Error(ResourceMapping): :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - :ivar type: Error type to indicate that there is no credential on the encoder. + :ivar type: Error type to indicate that the credential was deleted and can no longer be encoded. """ message: str type: Literal[ - "uncategorized_error", "action_attempt_expired", "no_credential_on_encoder" + "uncategorized_error", + "action_attempt_expired", + "no_credential_on_encoder", + "incompatible_card_format", + "credential_cannot_be_reissued", + "encoder_not_online", + "encoder_communication_timeout", + "bridge_disconnected", + "encoding_interrupted", + "credential_deleted", ] @classmethod @@ -1114,11 +1278,42 @@ def from_dict(cls, d: Any): type=d.get("type", None), ) - @dataclass - class Result(ResourceMapping): - """Result of a scan to assign attempt. If the attempt was successful, includes the credential data that was scanned and assigned. + action_attempt_id: str + action_type: Literal["ENCODE_CREDENTIAL"] + error: Error + result: None + status: Literal["error"] - :ivar access_method: Access method for the `credential `_. Supported values: ``code``, ``card``, ``mobile_key``, ``cloud_key``. + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=cls.Error.from_dict(d.get("error") or {}), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class ScanToAssignCredentialSuccessActionAttempt: + """Scanning a physical card and assigning the credential is pending. + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Action attempt to track the status of scanning a physical card and assigning the credential to an ACS user. + + :ivar error: + + :ivar result: Result of a scan to assign attempt. If the attempt was successful, includes the credential data that was scanned and assigned. + + :ivar status:""" + + @dataclass + class Result(ResourceMapping): + """Result of a scan to assign attempt. If the attempt was successful, includes the credential data that was scanned and assigned. + + :ivar access_method: Access method for the `credential `_. Supported values: ``code``, ``card``, ``mobile_key``, ``cloud_key``. :ivar acs_credential_id: ID of the `credential `_. @@ -1435,40 +1630,63 @@ def from_dict(cls, d: Any): action_attempt_id: str action_type: Literal["SCAN_TO_ASSIGN_CREDENTIAL"] - error: Optional[Error] - result: Optional[Result] - status: Literal["success", "pending", "error"] + error: None + result: Result + status: Literal["success"] @classmethod def from_dict(cls, d: Any): return cls( action_attempt_id=d.get("action_attempt_id", None), action_type=d.get("action_type", None), - error=( - cls.Error.from_dict(d.get("error")) - if d.get("error") is not None - else None - ), - result=( - cls.Result.from_dict(d.get("result")) - if d.get("result") is not None - else None - ), + error=d.get("error", None), + result=cls.Result.from_dict(d.get("result") or {}), status=d.get("status", None), ) @dataclass -class AssignCredentialActionAttempt: - """Assigning a credential to an access method is pending. +class ScanToAssignCredentialPendingActionAttempt: + """Scanning a physical card and assigning the credential is pending. :ivar action_attempt_id: ID of the action attempt. - :ivar action_type: Action attempt to track the status of assigning a pre-registered card credential to an access method. + :ivar action_type: Action attempt to track the status of scanning a physical card and assigning the credential to an ACS user. :ivar error: - :ivar result: Result of assigning a credential. If successful, includes the updated access method with the assigned credential. + :ivar result: Result of a scan to assign attempt. If the attempt was successful, includes the credential data that was scanned and assigned. + + :ivar status:""" + + action_attempt_id: str + action_type: Literal["SCAN_TO_ASSIGN_CREDENTIAL"] + error: None + result: None + status: Literal["pending"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=d.get("error", None), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class ScanToAssignCredentialErrorActionAttempt: + """Scanning a physical card and assigning the credential is pending. + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Action attempt to track the status of scanning a physical card and assigning the credential to an ACS user. + + :ivar error: + + :ivar result: Result of a scan to assign attempt. If the attempt was successful, includes the credential data that was scanned and assigned. :ivar status:""" @@ -1478,11 +1696,12 @@ class Error(ResourceMapping): :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - :ivar type: Error type to indicate that no matching credential was found.""" + :ivar type: Error type to indicate that there is no credential on the encoder. + """ message: str type: Literal[ - "uncategorized_error", "action_attempt_expired", "credential_not_found" + "uncategorized_error", "action_attempt_expired", "no_credential_on_encoder" ] @classmethod @@ -1492,6 +1711,37 @@ def from_dict(cls, d: Any): type=d.get("type", None), ) + action_attempt_id: str + action_type: Literal["SCAN_TO_ASSIGN_CREDENTIAL"] + error: Error + result: None + status: Literal["error"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=cls.Error.from_dict(d.get("error") or {}), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class AssignCredentialSuccessActionAttempt: + """Assigning a credential to an access method is pending. + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Action attempt to track the status of assigning a pre-registered card credential to an access method. + + :ivar error: + + :ivar result: Result of assigning a credential. If successful, includes the updated access method with the assigned credential. + + :ivar status:""" + @dataclass class Result(ResourceMapping): """Result of assigning a credential. If successful, includes the updated access method with the assigned credential. @@ -1715,119 +1965,78 @@ def from_dict(cls, d: Any): action_attempt_id: str action_type: Literal["ASSIGN_CREDENTIAL"] - error: Optional[Error] - result: Optional[Result] - status: Literal["success", "pending", "error"] + error: None + result: Result + status: Literal["success"] @classmethod def from_dict(cls, d: Any): return cls( action_attempt_id=d.get("action_attempt_id", None), action_type=d.get("action_type", None), - error=( - cls.Error.from_dict(d.get("error")) - if d.get("error") is not None - else None - ), - result=( - cls.Result.from_dict(d.get("result")) - if d.get("result") is not None - else None - ), + error=d.get("error", None), + result=cls.Result.from_dict(d.get("result") or {}), status=d.get("status", None), ) @dataclass -class ResetSandboxWorkspaceActionAttempt: - """Resetting a sandbox workspace is pending. +class AssignCredentialPendingActionAttempt: + """Assigning a credential to an access method is pending. :ivar action_attempt_id: ID of the action attempt. - :ivar action_type: Action attempt to track the status of resetting a sandbox workspace. + :ivar action_type: Action attempt to track the status of assigning a pre-registered card credential to an access method. - :ivar error: Error associated with the action. + :ivar error: - :ivar result: Result of the action. + :ivar result: Result of assigning a credential. If successful, includes the updated access method with the assigned credential. :ivar status:""" - @dataclass - class Error(ResourceMapping): - """Error associated with the action. - - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - - :ivar type: Type of the error.""" - - message: str - type: str - - @classmethod - def from_dict(cls, d: Any): - return cls( - message=d.get("message", None), - type=d.get("type", None), - ) - - @dataclass - class Result(ResourceMapping): - """Result of the action.""" - - @classmethod - def from_dict(cls, d: Any): - # pylint: disable=unused-argument - return cls() - action_attempt_id: str - action_type: Literal["RESET_SANDBOX_WORKSPACE"] - error: Optional[Error] - result: Optional[Result] - status: Literal["success", "pending", "error"] + action_type: Literal["ASSIGN_CREDENTIAL"] + error: None + result: None + status: Literal["pending"] @classmethod def from_dict(cls, d: Any): return cls( action_attempt_id=d.get("action_attempt_id", None), action_type=d.get("action_type", None), - error=( - cls.Error.from_dict(d.get("error")) - if d.get("error") is not None - else None - ), - result=( - cls.Result.from_dict(d.get("result")) - if d.get("result") is not None - else None - ), + error=d.get("error", None), + result=d.get("result", None), status=d.get("status", None), ) @dataclass -class SetFanModeActionAttempt: - """Setting the fan mode is pending. +class AssignCredentialErrorActionAttempt: + """Assigning a credential to an access method is pending. :ivar action_attempt_id: ID of the action attempt. - :ivar action_type: Action attempt to track the status of setting the fan mode on a thermostat. + :ivar action_type: Action attempt to track the status of assigning a pre-registered card credential to an access method. - :ivar error: Error associated with the action. + :ivar error: - :ivar result: Result of the action. + :ivar result: Result of assigning a credential. If successful, includes the updated access method with the assigned credential. :ivar status:""" @dataclass class Error(ResourceMapping): - """Error associated with the action. + """ :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - :ivar type: Type of the error.""" + :ivar type: Error type to indicate that no matching credential was found.""" message: str - type: str + type: Literal[ + "uncategorized_error", "action_attempt_expired", "credential_not_found" + ] @classmethod def from_dict(cls, d: Any): @@ -1836,47 +2045,30 @@ def from_dict(cls, d: Any): type=d.get("type", None), ) - @dataclass - class Result(ResourceMapping): - """Result of the action.""" - - @classmethod - def from_dict(cls, d: Any): - # pylint: disable=unused-argument - return cls() - action_attempt_id: str - action_type: Literal["SET_FAN_MODE"] - error: Optional[Error] - result: Optional[Result] - status: Literal["success", "pending", "error"] + action_type: Literal["ASSIGN_CREDENTIAL"] + error: Error + result: None + status: Literal["error"] @classmethod def from_dict(cls, d: Any): return cls( action_attempt_id=d.get("action_attempt_id", None), action_type=d.get("action_type", None), - error=( - cls.Error.from_dict(d.get("error")) - if d.get("error") is not None - else None - ), - result=( - cls.Result.from_dict(d.get("result")) - if d.get("result") is not None - else None - ), + error=cls.Error.from_dict(d.get("error") or {}), + result=d.get("result", None), status=d.get("status", None), ) @dataclass -class SetHvacModeActionAttempt: - """Setting the HVAC mode is pending. +class ResetSandboxWorkspaceSuccessActionAttempt: + """Resetting a sandbox workspace is pending. :ivar action_attempt_id: ID of the action attempt. - :ivar action_type: Action attempt to track the status of setting the HVAC mode on a thermostat. + :ivar action_type: Action attempt to track the status of resetting a sandbox workspace. :ivar error: Error associated with the action. @@ -1884,24 +2076,6 @@ class SetHvacModeActionAttempt: :ivar status:""" - @dataclass - class Error(ResourceMapping): - """Error associated with the action. - - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - - :ivar type: Type of the error.""" - - message: str - type: str - - @classmethod - def from_dict(cls, d: Any): - return cls( - message=d.get("message", None), - type=d.get("type", None), - ) - @dataclass class Result(ResourceMapping): """Result of the action.""" @@ -1912,37 +2086,29 @@ def from_dict(cls, d: Any): return cls() action_attempt_id: str - action_type: Literal["SET_HVAC_MODE"] - error: Optional[Error] - result: Optional[Result] - status: Literal["success", "pending", "error"] + action_type: Literal["RESET_SANDBOX_WORKSPACE"] + error: None + result: Result + status: Literal["success"] @classmethod def from_dict(cls, d: Any): return cls( action_attempt_id=d.get("action_attempt_id", None), action_type=d.get("action_type", None), - error=( - cls.Error.from_dict(d.get("error")) - if d.get("error") is not None - else None - ), - result=( - cls.Result.from_dict(d.get("result")) - if d.get("result") is not None - else None - ), + error=d.get("error", None), + result=cls.Result.from_dict(d.get("result") or {}), status=d.get("status", None), ) @dataclass -class ActivateClimatePresetActionAttempt: - """Activating a climate preset is pending. +class ResetSandboxWorkspacePendingActionAttempt: + """Resetting a sandbox workspace is pending. :ivar action_attempt_id: ID of the action attempt. - :ivar action_type: Action attempt to track the status of a climate preset activation. + :ivar action_type: Action attempt to track the status of resetting a sandbox workspace. :ivar error: Error associated with the action. @@ -1950,65 +2116,30 @@ class ActivateClimatePresetActionAttempt: :ivar status:""" - @dataclass - class Error(ResourceMapping): - """Error associated with the action. - - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - - :ivar type: Type of the error.""" - - message: str - type: str - - @classmethod - def from_dict(cls, d: Any): - return cls( - message=d.get("message", None), - type=d.get("type", None), - ) - - @dataclass - class Result(ResourceMapping): - """Result of the action.""" - - @classmethod - def from_dict(cls, d: Any): - # pylint: disable=unused-argument - return cls() - action_attempt_id: str - action_type: Literal["ACTIVATE_CLIMATE_PRESET"] - error: Optional[Error] - result: Optional[Result] - status: Literal["success", "pending", "error"] + action_type: Literal["RESET_SANDBOX_WORKSPACE"] + error: None + result: None + status: Literal["pending"] @classmethod def from_dict(cls, d: Any): return cls( action_attempt_id=d.get("action_attempt_id", None), action_type=d.get("action_type", None), - error=( - cls.Error.from_dict(d.get("error")) - if d.get("error") is not None - else None - ), - result=( - cls.Result.from_dict(d.get("result")) - if d.get("result") is not None - else None - ), + error=d.get("error", None), + result=d.get("result", None), status=d.get("status", None), ) @dataclass -class SimulateKeypadCodeEntryActionAttempt: - """Simulating a keypad code entry is pending. +class ResetSandboxWorkspaceErrorActionAttempt: + """Resetting a sandbox workspace is pending. :ivar action_attempt_id: ID of the action attempt. - :ivar action_type: Action attempt to track the status of simulating a keypad code entry. + :ivar action_type: Action attempt to track the status of resetting a sandbox workspace. :ivar error: Error associated with the action. @@ -2034,47 +2165,30 @@ def from_dict(cls, d: Any): type=d.get("type", None), ) - @dataclass - class Result(ResourceMapping): - """Result of the action.""" - - @classmethod - def from_dict(cls, d: Any): - # pylint: disable=unused-argument - return cls() - action_attempt_id: str - action_type: Literal["SIMULATE_KEYPAD_CODE_ENTRY"] - error: Optional[Error] - result: Optional[Result] - status: Literal["success", "pending", "error"] + action_type: Literal["RESET_SANDBOX_WORKSPACE"] + error: Error + result: None + status: Literal["error"] @classmethod def from_dict(cls, d: Any): return cls( action_attempt_id=d.get("action_attempt_id", None), action_type=d.get("action_type", None), - error=( - cls.Error.from_dict(d.get("error")) - if d.get("error") is not None - else None - ), - result=( - cls.Result.from_dict(d.get("result")) - if d.get("result") is not None - else None - ), + error=cls.Error.from_dict(d.get("error") or {}), + result=d.get("result", None), status=d.get("status", None), ) @dataclass -class SimulateManualLockViaKeypadActionAttempt: - """Simulating a manual lock action using a keypad is pending. +class SetFanModeSuccessActionAttempt: + """Setting the fan mode is pending. :ivar action_attempt_id: ID of the action attempt. - :ivar action_type: Action attempt to track the status of simulating a manual lock action using a keypad. + :ivar action_type: Action attempt to track the status of setting the fan mode on a thermostat. :ivar error: Error associated with the action. @@ -2082,24 +2196,6 @@ class SimulateManualLockViaKeypadActionAttempt: :ivar status:""" - @dataclass - class Error(ResourceMapping): - """Error associated with the action. - - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - - :ivar type: Type of the error.""" - - message: str - type: str - - @classmethod - def from_dict(cls, d: Any): - return cls( - message=d.get("message", None), - type=d.get("type", None), - ) - @dataclass class Result(ResourceMapping): """Result of the action.""" @@ -2110,37 +2206,29 @@ def from_dict(cls, d: Any): return cls() action_attempt_id: str - action_type: Literal["SIMULATE_MANUAL_LOCK_VIA_KEYPAD"] - error: Optional[Error] - result: Optional[Result] - status: Literal["success", "pending", "error"] + action_type: Literal["SET_FAN_MODE"] + error: None + result: Result + status: Literal["success"] @classmethod def from_dict(cls, d: Any): return cls( action_attempt_id=d.get("action_attempt_id", None), action_type=d.get("action_type", None), - error=( - cls.Error.from_dict(d.get("error")) - if d.get("error") is not None - else None - ), - result=( - cls.Result.from_dict(d.get("result")) - if d.get("result") is not None - else None - ), + error=d.get("error", None), + result=cls.Result.from_dict(d.get("result") or {}), status=d.get("status", None), ) @dataclass -class PushThermostatProgramsActionAttempt: - """Pushing thermostat weekly programs is pending. +class SetFanModePendingActionAttempt: + """Setting the fan mode is pending. :ivar action_attempt_id: ID of the action attempt. - :ivar action_type: Action attempt to track the status of pushing thermostat programs. + :ivar action_type: Action attempt to track the status of setting the fan mode on a thermostat. :ivar error: Error associated with the action. @@ -2148,10 +2236,41 @@ class PushThermostatProgramsActionAttempt: :ivar status:""" - @dataclass - class Error(ResourceMapping): - """Error associated with the action. - + action_attempt_id: str + action_type: Literal["SET_FAN_MODE"] + error: None + result: None + status: Literal["pending"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=d.get("error", None), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class SetFanModeErrorActionAttempt: + """Setting the fan mode is pending. + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Action attempt to track the status of setting the fan mode on a thermostat. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + + @dataclass + class Error(ResourceMapping): + """Error associated with the action. + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. :ivar type: Type of the error.""" @@ -2166,6 +2285,37 @@ def from_dict(cls, d: Any): type=d.get("type", None), ) + action_attempt_id: str + action_type: Literal["SET_FAN_MODE"] + error: Error + result: None + status: Literal["error"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=cls.Error.from_dict(d.get("error") or {}), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class SetHvacModeSuccessActionAttempt: + """Setting the HVAC mode is pending. + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Action attempt to track the status of setting the HVAC mode on a thermostat. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + @dataclass class Result(ResourceMapping): """Result of the action.""" @@ -2176,37 +2326,180 @@ def from_dict(cls, d: Any): return cls() action_attempt_id: str - action_type: Literal["PUSH_THERMOSTAT_PROGRAMS"] - error: Optional[Error] - result: Optional[Result] - status: Literal["success", "pending", "error"] + action_type: Literal["SET_HVAC_MODE"] + error: None + result: Result + status: Literal["success"] @classmethod def from_dict(cls, d: Any): return cls( action_attempt_id=d.get("action_attempt_id", None), action_type=d.get("action_type", None), - error=( - cls.Error.from_dict(d.get("error")) - if d.get("error") is not None - else None - ), - result=( - cls.Result.from_dict(d.get("result")) - if d.get("result") is not None - else None - ), + error=d.get("error", None), + result=cls.Result.from_dict(d.get("result") or {}), status=d.get("status", None), ) @dataclass -class ConfigureAutoLockActionAttempt: - """Configuring the auto-lock is pending. +class SetHvacModePendingActionAttempt: + """Setting the HVAC mode is pending. :ivar action_attempt_id: ID of the action attempt. - :ivar action_type: Action attempt to track the status of configuring the auto-lock on a lock. + :ivar action_type: Action attempt to track the status of setting the HVAC mode on a thermostat. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + + action_attempt_id: str + action_type: Literal["SET_HVAC_MODE"] + error: None + result: None + status: Literal["pending"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=d.get("error", None), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class SetHvacModeErrorActionAttempt: + """Setting the HVAC mode is pending. + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Action attempt to track the status of setting the HVAC mode on a thermostat. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + + @dataclass + class Error(ResourceMapping): + """Error associated with the action. + + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + + :ivar type: Type of the error.""" + + message: str + type: str + + @classmethod + def from_dict(cls, d: Any): + return cls( + message=d.get("message", None), + type=d.get("type", None), + ) + + action_attempt_id: str + action_type: Literal["SET_HVAC_MODE"] + error: Error + result: None + status: Literal["error"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=cls.Error.from_dict(d.get("error") or {}), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class ActivateClimatePresetSuccessActionAttempt: + """Activating a climate preset is pending. + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Action attempt to track the status of a climate preset activation. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + + @dataclass + class Result(ResourceMapping): + """Result of the action.""" + + @classmethod + def from_dict(cls, d: Any): + # pylint: disable=unused-argument + return cls() + + action_attempt_id: str + action_type: Literal["ACTIVATE_CLIMATE_PRESET"] + error: None + result: Result + status: Literal["success"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=d.get("error", None), + result=cls.Result.from_dict(d.get("result") or {}), + status=d.get("status", None), + ) + + +@dataclass +class ActivateClimatePresetPendingActionAttempt: + """Activating a climate preset is pending. + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Action attempt to track the status of a climate preset activation. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + + action_attempt_id: str + action_type: Literal["ACTIVATE_CLIMATE_PRESET"] + error: None + result: None + status: Literal["pending"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=d.get("error", None), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class ActivateClimatePresetErrorActionAttempt: + """Activating a climate preset is pending. + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Action attempt to track the status of a climate preset activation. :ivar error: Error associated with the action. @@ -2232,47 +2525,951 @@ def from_dict(cls, d: Any): type=d.get("type", None), ) + action_attempt_id: str + action_type: Literal["ACTIVATE_CLIMATE_PRESET"] + error: Error + result: None + status: Literal["error"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=cls.Error.from_dict(d.get("error") or {}), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class SimulateKeypadCodeEntrySuccessActionAttempt: + """Simulating a keypad code entry is pending. + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Action attempt to track the status of simulating a keypad code entry. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + @dataclass class Result(ResourceMapping): """Result of the action.""" - @classmethod - def from_dict(cls, d: Any): - # pylint: disable=unused-argument - return cls() + @classmethod + def from_dict(cls, d: Any): + # pylint: disable=unused-argument + return cls() + + action_attempt_id: str + action_type: Literal["SIMULATE_KEYPAD_CODE_ENTRY"] + error: None + result: Result + status: Literal["success"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=d.get("error", None), + result=cls.Result.from_dict(d.get("result") or {}), + status=d.get("status", None), + ) + + +@dataclass +class SimulateKeypadCodeEntryPendingActionAttempt: + """Simulating a keypad code entry is pending. + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Action attempt to track the status of simulating a keypad code entry. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + + action_attempt_id: str + action_type: Literal["SIMULATE_KEYPAD_CODE_ENTRY"] + error: None + result: None + status: Literal["pending"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=d.get("error", None), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class SimulateKeypadCodeEntryErrorActionAttempt: + """Simulating a keypad code entry is pending. + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Action attempt to track the status of simulating a keypad code entry. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + + @dataclass + class Error(ResourceMapping): + """Error associated with the action. + + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + + :ivar type: Type of the error.""" + + message: str + type: str + + @classmethod + def from_dict(cls, d: Any): + return cls( + message=d.get("message", None), + type=d.get("type", None), + ) + + action_attempt_id: str + action_type: Literal["SIMULATE_KEYPAD_CODE_ENTRY"] + error: Error + result: None + status: Literal["error"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=cls.Error.from_dict(d.get("error") or {}), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class SimulateManualLockViaKeypadSuccessActionAttempt: + """Simulating a manual lock action using a keypad is pending. + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Action attempt to track the status of simulating a manual lock action using a keypad. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + + @dataclass + class Result(ResourceMapping): + """Result of the action.""" + + @classmethod + def from_dict(cls, d: Any): + # pylint: disable=unused-argument + return cls() + + action_attempt_id: str + action_type: Literal["SIMULATE_MANUAL_LOCK_VIA_KEYPAD"] + error: None + result: Result + status: Literal["success"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=d.get("error", None), + result=cls.Result.from_dict(d.get("result") or {}), + status=d.get("status", None), + ) + + +@dataclass +class SimulateManualLockViaKeypadPendingActionAttempt: + """Simulating a manual lock action using a keypad is pending. + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Action attempt to track the status of simulating a manual lock action using a keypad. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + + action_attempt_id: str + action_type: Literal["SIMULATE_MANUAL_LOCK_VIA_KEYPAD"] + error: None + result: None + status: Literal["pending"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=d.get("error", None), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class SimulateManualLockViaKeypadErrorActionAttempt: + """Simulating a manual lock action using a keypad is pending. + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Action attempt to track the status of simulating a manual lock action using a keypad. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + + @dataclass + class Error(ResourceMapping): + """Error associated with the action. + + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + + :ivar type: Type of the error.""" + + message: str + type: str + + @classmethod + def from_dict(cls, d: Any): + return cls( + message=d.get("message", None), + type=d.get("type", None), + ) + + action_attempt_id: str + action_type: Literal["SIMULATE_MANUAL_LOCK_VIA_KEYPAD"] + error: Error + result: None + status: Literal["error"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=cls.Error.from_dict(d.get("error") or {}), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class PushThermostatProgramsSuccessActionAttempt: + """Pushing thermostat weekly programs is pending. + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Action attempt to track the status of pushing thermostat programs. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + + @dataclass + class Result(ResourceMapping): + """Result of the action.""" + + @classmethod + def from_dict(cls, d: Any): + # pylint: disable=unused-argument + return cls() + + action_attempt_id: str + action_type: Literal["PUSH_THERMOSTAT_PROGRAMS"] + error: None + result: Result + status: Literal["success"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=d.get("error", None), + result=cls.Result.from_dict(d.get("result") or {}), + status=d.get("status", None), + ) + + +@dataclass +class PushThermostatProgramsPendingActionAttempt: + """Pushing thermostat weekly programs is pending. + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Action attempt to track the status of pushing thermostat programs. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + + action_attempt_id: str + action_type: Literal["PUSH_THERMOSTAT_PROGRAMS"] + error: None + result: None + status: Literal["pending"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=d.get("error", None), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class PushThermostatProgramsErrorActionAttempt: + """Pushing thermostat weekly programs is pending. + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Action attempt to track the status of pushing thermostat programs. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + + @dataclass + class Error(ResourceMapping): + """Error associated with the action. + + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + + :ivar type: Type of the error.""" + + message: str + type: str + + @classmethod + def from_dict(cls, d: Any): + return cls( + message=d.get("message", None), + type=d.get("type", None), + ) + + action_attempt_id: str + action_type: Literal["PUSH_THERMOSTAT_PROGRAMS"] + error: Error + result: None + status: Literal["error"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=cls.Error.from_dict(d.get("error") or {}), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class ConfigureAutoLockSuccessActionAttempt: + """Configuring the auto-lock is pending. + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Action attempt to track the status of configuring the auto-lock on a lock. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + + @dataclass + class Result(ResourceMapping): + """Result of the action.""" + + @classmethod + def from_dict(cls, d: Any): + # pylint: disable=unused-argument + return cls() + + action_attempt_id: str + action_type: Literal["CONFIGURE_AUTO_LOCK"] + error: None + result: Result + status: Literal["success"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=d.get("error", None), + result=cls.Result.from_dict(d.get("result") or {}), + status=d.get("status", None), + ) + + +@dataclass +class ConfigureAutoLockPendingActionAttempt: + """Configuring the auto-lock is pending. + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Action attempt to track the status of configuring the auto-lock on a lock. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + + action_attempt_id: str + action_type: Literal["CONFIGURE_AUTO_LOCK"] + error: None + result: None + status: Literal["pending"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=d.get("error", None), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class ConfigureAutoLockErrorActionAttempt: + """Configuring the auto-lock is pending. + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Action attempt to track the status of configuring the auto-lock on a lock. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + + @dataclass + class Error(ResourceMapping): + """Error associated with the action. + + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + + :ivar type: Type of the error.""" + + message: str + type: str + + @classmethod + def from_dict(cls, d: Any): + return cls( + message=d.get("message", None), + type=d.get("type", None), + ) + + action_attempt_id: str + action_type: Literal["CONFIGURE_AUTO_LOCK"] + error: Error + result: None + status: Literal["error"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=cls.Error.from_dict(d.get("error") or {}), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class SyncAccessCodesSuccessActionAttempt: + """ + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Syncing access codes is pending. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + + @dataclass + class Result(ResourceMapping): + """Result of the action.""" + + @classmethod + def from_dict(cls, d: Any): + # pylint: disable=unused-argument + return cls() + + action_attempt_id: str + action_type: Literal["SYNC_ACCESS_CODES"] + error: None + result: Result + status: Literal["success"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=d.get("error", None), + result=cls.Result.from_dict(d.get("result") or {}), + status=d.get("status", None), + ) + + +@dataclass +class SyncAccessCodesPendingActionAttempt: + """ + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Syncing access codes is pending. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + + action_attempt_id: str + action_type: Literal["SYNC_ACCESS_CODES"] + error: None + result: None + status: Literal["pending"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=d.get("error", None), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class SyncAccessCodesErrorActionAttempt: + """ + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Syncing access codes is pending. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + + @dataclass + class Error(ResourceMapping): + """Error associated with the action. + + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + + :ivar type: Type of the error.""" + + message: str + type: str + + @classmethod + def from_dict(cls, d: Any): + return cls( + message=d.get("message", None), + type=d.get("type", None), + ) + + action_attempt_id: str + action_type: Literal["SYNC_ACCESS_CODES"] + error: Error + result: None + status: Literal["error"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=cls.Error.from_dict(d.get("error") or {}), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class CreateAccessCodeSuccessActionAttempt: + """ + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Creating an access code is pending. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + + @dataclass + class Result(ResourceMapping): + """Result of the action. + + :ivar access_code: Created access code.""" + + access_code: Dict[str, Any] + + @classmethod + def from_dict(cls, d: Any): + return cls( + access_code=DeepAttrDict(d.get("access_code", None)), + ) + + action_attempt_id: str + action_type: Literal["CREATE_ACCESS_CODE"] + error: None + result: Result + status: Literal["success"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=d.get("error", None), + result=cls.Result.from_dict(d.get("result") or {}), + status=d.get("status", None), + ) + + +@dataclass +class CreateAccessCodePendingActionAttempt: + """ + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Creating an access code is pending. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + + action_attempt_id: str + action_type: Literal["CREATE_ACCESS_CODE"] + error: None + result: None + status: Literal["pending"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=d.get("error", None), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class CreateAccessCodeErrorActionAttempt: + """ + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Creating an access code is pending. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + + @dataclass + class Error(ResourceMapping): + """Error associated with the action. + + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + + :ivar type: Type of the error.""" + + message: str + type: str + + @classmethod + def from_dict(cls, d: Any): + return cls( + message=d.get("message", None), + type=d.get("type", None), + ) + + action_attempt_id: str + action_type: Literal["CREATE_ACCESS_CODE"] + error: Error + result: None + status: Literal["error"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=cls.Error.from_dict(d.get("error") or {}), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class DeleteAccessCodeSuccessActionAttempt: + """ + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Deleting an access code is pending. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + + @dataclass + class Result(ResourceMapping): + """Result of the action.""" + + @classmethod + def from_dict(cls, d: Any): + # pylint: disable=unused-argument + return cls() + + action_attempt_id: str + action_type: Literal["DELETE_ACCESS_CODE"] + error: None + result: Result + status: Literal["success"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=d.get("error", None), + result=cls.Result.from_dict(d.get("result") or {}), + status=d.get("status", None), + ) + + +@dataclass +class DeleteAccessCodePendingActionAttempt: + """ + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Deleting an access code is pending. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + + action_attempt_id: str + action_type: Literal["DELETE_ACCESS_CODE"] + error: None + result: None + status: Literal["pending"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=d.get("error", None), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class DeleteAccessCodeErrorActionAttempt: + """ + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Deleting an access code is pending. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + + @dataclass + class Error(ResourceMapping): + """Error associated with the action. + + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + + :ivar type: Type of the error.""" + + message: str + type: str + + @classmethod + def from_dict(cls, d: Any): + return cls( + message=d.get("message", None), + type=d.get("type", None), + ) + + action_attempt_id: str + action_type: Literal["DELETE_ACCESS_CODE"] + error: Error + result: None + status: Literal["error"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=cls.Error.from_dict(d.get("error") or {}), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class UpdateAccessCodeSuccessActionAttempt: + """ + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Updating an access code is pending. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + + @dataclass + class Result(ResourceMapping): + """Result of the action. + + :ivar access_code: Updated access code.""" + + access_code: Optional[Dict[str, Any]] + + @classmethod + def from_dict(cls, d: Any): + return cls( + access_code=DeepAttrDict(d.get("access_code", None)), + ) + + action_attempt_id: str + action_type: Literal["UPDATE_ACCESS_CODE"] + error: None + result: Result + status: Literal["success"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=d.get("error", None), + result=cls.Result.from_dict(d.get("result") or {}), + status=d.get("status", None), + ) + + +@dataclass +class UpdateAccessCodePendingActionAttempt: + """ + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Updating an access code is pending. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" action_attempt_id: str - action_type: Literal["CONFIGURE_AUTO_LOCK"] - error: Optional[Error] - result: Optional[Result] - status: Literal["success", "pending", "error"] + action_type: Literal["UPDATE_ACCESS_CODE"] + error: None + result: None + status: Literal["pending"] @classmethod def from_dict(cls, d: Any): return cls( action_attempt_id=d.get("action_attempt_id", None), action_type=d.get("action_type", None), - error=( - cls.Error.from_dict(d.get("error")) - if d.get("error") is not None - else None - ), - result=( - cls.Result.from_dict(d.get("result")) - if d.get("result") is not None - else None - ), + error=d.get("error", None), + result=d.get("result", None), status=d.get("status", None), ) @dataclass -class SyncAccessCodesActionAttempt: +class UpdateAccessCodeErrorActionAttempt: """ :ivar action_attempt_id: ID of the action attempt. - :ivar action_type: Syncing access codes is pending. + :ivar action_type: Updating an access code is pending. :ivar error: Error associated with the action. @@ -2298,47 +3495,30 @@ def from_dict(cls, d: Any): type=d.get("type", None), ) - @dataclass - class Result(ResourceMapping): - """Result of the action.""" - - @classmethod - def from_dict(cls, d: Any): - # pylint: disable=unused-argument - return cls() - action_attempt_id: str - action_type: Literal["SYNC_ACCESS_CODES"] - error: Optional[Error] - result: Optional[Result] - status: Literal["success", "pending", "error"] + action_type: Literal["UPDATE_ACCESS_CODE"] + error: Error + result: None + status: Literal["error"] @classmethod def from_dict(cls, d: Any): return cls( action_attempt_id=d.get("action_attempt_id", None), action_type=d.get("action_type", None), - error=( - cls.Error.from_dict(d.get("error")) - if d.get("error") is not None - else None - ), - result=( - cls.Result.from_dict(d.get("result")) - if d.get("result") is not None - else None - ), + error=cls.Error.from_dict(d.get("error") or {}), + result=d.get("result", None), status=d.get("status", None), ) @dataclass -class CreateAccessCodeActionAttempt: +class CreateNoiseThresholdSuccessActionAttempt: """ :ivar action_attempt_id: ID of the action attempt. - :ivar action_type: Creating an access code is pending. + :ivar action_type: Creating a noise threshold is pending. :ivar error: Error associated with the action. @@ -2347,69 +3527,74 @@ class CreateAccessCodeActionAttempt: :ivar status:""" @dataclass - class Error(ResourceMapping): - """Error associated with the action. - - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + class Result(ResourceMapping): + """Result of the action. - :ivar type: Type of the error.""" + :ivar noise_threshold: Created noise threshold.""" - message: str - type: str + noise_threshold: Dict[str, Any] @classmethod def from_dict(cls, d: Any): return cls( - message=d.get("message", None), - type=d.get("type", None), + noise_threshold=DeepAttrDict(d.get("noise_threshold", None)), ) - @dataclass - class Result(ResourceMapping): - """Result of the action. + action_attempt_id: str + action_type: Literal["CREATE_NOISE_THRESHOLD"] + error: None + result: Result + status: Literal["success"] - :ivar access_code: Created access code.""" + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=d.get("error", None), + result=cls.Result.from_dict(d.get("result") or {}), + status=d.get("status", None), + ) - access_code: Dict[str, Any] - @classmethod - def from_dict(cls, d: Any): - return cls( - access_code=DeepAttrDict(d.get("access_code", None)), - ) +@dataclass +class CreateNoiseThresholdPendingActionAttempt: + """ + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Creating a noise threshold is pending. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" action_attempt_id: str - action_type: Literal["CREATE_ACCESS_CODE"] - error: Optional[Error] - result: Optional[Result] - status: Literal["success", "pending", "error"] + action_type: Literal["CREATE_NOISE_THRESHOLD"] + error: None + result: None + status: Literal["pending"] @classmethod def from_dict(cls, d: Any): return cls( action_attempt_id=d.get("action_attempt_id", None), action_type=d.get("action_type", None), - error=( - cls.Error.from_dict(d.get("error")) - if d.get("error") is not None - else None - ), - result=( - cls.Result.from_dict(d.get("result")) - if d.get("result") is not None - else None - ), + error=d.get("error", None), + result=d.get("result", None), status=d.get("status", None), ) @dataclass -class DeleteAccessCodeActionAttempt: +class CreateNoiseThresholdErrorActionAttempt: """ :ivar action_attempt_id: ID of the action attempt. - :ivar action_type: Deleting an access code is pending. + :ivar action_type: Creating a noise threshold is pending. :ivar error: Error associated with the action. @@ -2435,47 +3620,30 @@ def from_dict(cls, d: Any): type=d.get("type", None), ) - @dataclass - class Result(ResourceMapping): - """Result of the action.""" - - @classmethod - def from_dict(cls, d: Any): - # pylint: disable=unused-argument - return cls() - action_attempt_id: str - action_type: Literal["DELETE_ACCESS_CODE"] - error: Optional[Error] - result: Optional[Result] - status: Literal["success", "pending", "error"] + action_type: Literal["CREATE_NOISE_THRESHOLD"] + error: Error + result: None + status: Literal["error"] @classmethod def from_dict(cls, d: Any): return cls( action_attempt_id=d.get("action_attempt_id", None), action_type=d.get("action_type", None), - error=( - cls.Error.from_dict(d.get("error")) - if d.get("error") is not None - else None - ), - result=( - cls.Result.from_dict(d.get("result")) - if d.get("result") is not None - else None - ), + error=cls.Error.from_dict(d.get("error") or {}), + result=d.get("result", None), status=d.get("status", None), ) @dataclass -class UpdateAccessCodeActionAttempt: +class DeleteNoiseThresholdSuccessActionAttempt: """ :ivar action_attempt_id: ID of the action attempt. - :ivar action_type: Updating an access code is pending. + :ivar action_type: Deleting a noise threshold is pending. :ivar error: Error associated with the action. @@ -2484,69 +3652,69 @@ class UpdateAccessCodeActionAttempt: :ivar status:""" @dataclass - class Error(ResourceMapping): - """Error associated with the action. + class Result(ResourceMapping): + """Result of the action.""" - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + @classmethod + def from_dict(cls, d: Any): + # pylint: disable=unused-argument + return cls() - :ivar type: Type of the error.""" + action_attempt_id: str + action_type: Literal["DELETE_NOISE_THRESHOLD"] + error: None + result: Result + status: Literal["success"] - message: str - type: str + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=d.get("error", None), + result=cls.Result.from_dict(d.get("result") or {}), + status=d.get("status", None), + ) - @classmethod - def from_dict(cls, d: Any): - return cls( - message=d.get("message", None), - type=d.get("type", None), - ) - @dataclass - class Result(ResourceMapping): - """Result of the action. +@dataclass +class DeleteNoiseThresholdPendingActionAttempt: + """ - :ivar access_code: Updated access code.""" + :ivar action_attempt_id: ID of the action attempt. - access_code: Optional[Dict[str, Any]] + :ivar action_type: Deleting a noise threshold is pending. - @classmethod - def from_dict(cls, d: Any): - return cls( - access_code=DeepAttrDict(d.get("access_code", None)), - ) + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" action_attempt_id: str - action_type: Literal["UPDATE_ACCESS_CODE"] - error: Optional[Error] - result: Optional[Result] - status: Literal["success", "pending", "error"] + action_type: Literal["DELETE_NOISE_THRESHOLD"] + error: None + result: None + status: Literal["pending"] @classmethod def from_dict(cls, d: Any): return cls( action_attempt_id=d.get("action_attempt_id", None), action_type=d.get("action_type", None), - error=( - cls.Error.from_dict(d.get("error")) - if d.get("error") is not None - else None - ), - result=( - cls.Result.from_dict(d.get("result")) - if d.get("result") is not None - else None - ), + error=d.get("error", None), + result=d.get("result", None), status=d.get("status", None), ) @dataclass -class CreateNoiseThresholdActionAttempt: +class DeleteNoiseThresholdErrorActionAttempt: """ :ivar action_attempt_id: ID of the action attempt. - :ivar action_type: Creating a noise threshold is pending. + :ivar action_type: Deleting a noise threshold is pending. :ivar error: Error associated with the action. @@ -2572,11 +3740,42 @@ def from_dict(cls, d: Any): type=d.get("type", None), ) + action_attempt_id: str + action_type: Literal["DELETE_NOISE_THRESHOLD"] + error: Error + result: None + status: Literal["error"] + + @classmethod + def from_dict(cls, d: Any): + return cls( + action_attempt_id=d.get("action_attempt_id", None), + action_type=d.get("action_type", None), + error=cls.Error.from_dict(d.get("error") or {}), + result=d.get("result", None), + status=d.get("status", None), + ) + + +@dataclass +class UpdateNoiseThresholdSuccessActionAttempt: + """ + + :ivar action_attempt_id: ID of the action attempt. + + :ivar action_type: Updating a noise threshold is pending. + + :ivar error: Error associated with the action. + + :ivar result: Result of the action. + + :ivar status:""" + @dataclass class Result(ResourceMapping): """Result of the action. - :ivar noise_threshold: Created noise threshold.""" + :ivar noise_threshold: Updated noise threshold.""" noise_threshold: Dict[str, Any] @@ -2587,37 +3786,29 @@ def from_dict(cls, d: Any): ) action_attempt_id: str - action_type: Literal["CREATE_NOISE_THRESHOLD"] - error: Optional[Error] - result: Optional[Result] - status: Literal["success", "pending", "error"] + action_type: Literal["UPDATE_NOISE_THRESHOLD"] + error: None + result: Result + status: Literal["success"] @classmethod def from_dict(cls, d: Any): return cls( action_attempt_id=d.get("action_attempt_id", None), action_type=d.get("action_type", None), - error=( - cls.Error.from_dict(d.get("error")) - if d.get("error") is not None - else None - ), - result=( - cls.Result.from_dict(d.get("result")) - if d.get("result") is not None - else None - ), + error=d.get("error", None), + result=cls.Result.from_dict(d.get("result") or {}), status=d.get("status", None), ) @dataclass -class DeleteNoiseThresholdActionAttempt: +class UpdateNoiseThresholdPendingActionAttempt: """ :ivar action_attempt_id: ID of the action attempt. - :ivar action_type: Deleting a noise threshold is pending. + :ivar action_type: Updating a noise threshold is pending. :ivar error: Error associated with the action. @@ -2625,60 +3816,25 @@ class DeleteNoiseThresholdActionAttempt: :ivar status:""" - @dataclass - class Error(ResourceMapping): - """Error associated with the action. - - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - - :ivar type: Type of the error.""" - - message: str - type: str - - @classmethod - def from_dict(cls, d: Any): - return cls( - message=d.get("message", None), - type=d.get("type", None), - ) - - @dataclass - class Result(ResourceMapping): - """Result of the action.""" - - @classmethod - def from_dict(cls, d: Any): - # pylint: disable=unused-argument - return cls() - action_attempt_id: str - action_type: Literal["DELETE_NOISE_THRESHOLD"] - error: Optional[Error] - result: Optional[Result] - status: Literal["success", "pending", "error"] + action_type: Literal["UPDATE_NOISE_THRESHOLD"] + error: None + result: None + status: Literal["pending"] @classmethod def from_dict(cls, d: Any): return cls( action_attempt_id=d.get("action_attempt_id", None), action_type=d.get("action_type", None), - error=( - cls.Error.from_dict(d.get("error")) - if d.get("error") is not None - else None - ), - result=( - cls.Result.from_dict(d.get("result")) - if d.get("result") is not None - else None - ), + error=d.get("error", None), + result=d.get("result", None), status=d.get("status", None), ) @dataclass -class UpdateNoiseThresholdActionAttempt: +class UpdateNoiseThresholdErrorActionAttempt: """ :ivar action_attempt_id: ID of the action attempt. @@ -2709,101 +3865,382 @@ def from_dict(cls, d: Any): type=d.get("type", None), ) - @dataclass - class Result(ResourceMapping): - """Result of the action. - - :ivar noise_threshold: Updated noise threshold.""" - - noise_threshold: Dict[str, Any] - - @classmethod - def from_dict(cls, d: Any): - return cls( - noise_threshold=DeepAttrDict(d.get("noise_threshold", None)), - ) - action_attempt_id: str action_type: Literal["UPDATE_NOISE_THRESHOLD"] - error: Optional[Error] - result: Optional[Result] - status: Literal["success", "pending", "error"] + error: Error + result: None + status: Literal["error"] @classmethod def from_dict(cls, d: Any): return cls( action_attempt_id=d.get("action_attempt_id", None), action_type=d.get("action_type", None), - error=( - cls.Error.from_dict(d.get("error")) - if d.get("error") is not None - else None - ), - result=( - cls.Result.from_dict(d.get("result")) - if d.get("result") is not None - else None - ), + error=cls.Error.from_dict(d.get("error") or {}), + result=d.get("result", None), status=d.get("status", None), ) ActionAttempt = Union[ - LockDoorActionAttempt, - UnlockDoorActionAttempt, - ScanCredentialActionAttempt, - EncodeCredentialActionAttempt, - ScanToAssignCredentialActionAttempt, - AssignCredentialActionAttempt, - ResetSandboxWorkspaceActionAttempt, - SetFanModeActionAttempt, - SetHvacModeActionAttempt, - ActivateClimatePresetActionAttempt, - SimulateKeypadCodeEntryActionAttempt, - SimulateManualLockViaKeypadActionAttempt, - PushThermostatProgramsActionAttempt, - ConfigureAutoLockActionAttempt, - SyncAccessCodesActionAttempt, - CreateAccessCodeActionAttempt, - DeleteAccessCodeActionAttempt, - UpdateAccessCodeActionAttempt, - CreateNoiseThresholdActionAttempt, - DeleteNoiseThresholdActionAttempt, - UpdateNoiseThresholdActionAttempt, + LockDoorSuccessActionAttempt, + LockDoorPendingActionAttempt, + LockDoorErrorActionAttempt, + UnlockDoorSuccessActionAttempt, + UnlockDoorPendingActionAttempt, + UnlockDoorErrorActionAttempt, + ScanCredentialSuccessActionAttempt, + ScanCredentialPendingActionAttempt, + ScanCredentialErrorActionAttempt, + EncodeCredentialSuccessActionAttempt, + EncodeCredentialPendingActionAttempt, + EncodeCredentialErrorActionAttempt, + ScanToAssignCredentialSuccessActionAttempt, + ScanToAssignCredentialPendingActionAttempt, + ScanToAssignCredentialErrorActionAttempt, + AssignCredentialSuccessActionAttempt, + AssignCredentialPendingActionAttempt, + AssignCredentialErrorActionAttempt, + ResetSandboxWorkspaceSuccessActionAttempt, + ResetSandboxWorkspacePendingActionAttempt, + ResetSandboxWorkspaceErrorActionAttempt, + SetFanModeSuccessActionAttempt, + SetFanModePendingActionAttempt, + SetFanModeErrorActionAttempt, + SetHvacModeSuccessActionAttempt, + SetHvacModePendingActionAttempt, + SetHvacModeErrorActionAttempt, + ActivateClimatePresetSuccessActionAttempt, + ActivateClimatePresetPendingActionAttempt, + ActivateClimatePresetErrorActionAttempt, + SimulateKeypadCodeEntrySuccessActionAttempt, + SimulateKeypadCodeEntryPendingActionAttempt, + SimulateKeypadCodeEntryErrorActionAttempt, + SimulateManualLockViaKeypadSuccessActionAttempt, + SimulateManualLockViaKeypadPendingActionAttempt, + SimulateManualLockViaKeypadErrorActionAttempt, + PushThermostatProgramsSuccessActionAttempt, + PushThermostatProgramsPendingActionAttempt, + PushThermostatProgramsErrorActionAttempt, + ConfigureAutoLockSuccessActionAttempt, + ConfigureAutoLockPendingActionAttempt, + ConfigureAutoLockErrorActionAttempt, + SyncAccessCodesSuccessActionAttempt, + SyncAccessCodesPendingActionAttempt, + SyncAccessCodesErrorActionAttempt, + CreateAccessCodeSuccessActionAttempt, + CreateAccessCodePendingActionAttempt, + CreateAccessCodeErrorActionAttempt, + DeleteAccessCodeSuccessActionAttempt, + DeleteAccessCodePendingActionAttempt, + DeleteAccessCodeErrorActionAttempt, + UpdateAccessCodeSuccessActionAttempt, + UpdateAccessCodePendingActionAttempt, + UpdateAccessCodeErrorActionAttempt, + CreateNoiseThresholdSuccessActionAttempt, + CreateNoiseThresholdPendingActionAttempt, + CreateNoiseThresholdErrorActionAttempt, + DeleteNoiseThresholdSuccessActionAttempt, + DeleteNoiseThresholdPendingActionAttempt, + DeleteNoiseThresholdErrorActionAttempt, + UpdateNoiseThresholdSuccessActionAttempt, + UpdateNoiseThresholdPendingActionAttempt, + UpdateNoiseThresholdErrorActionAttempt, +] + +LockDoorActionAttempt = Union[ + LockDoorSuccessActionAttempt, + LockDoorPendingActionAttempt, + LockDoorErrorActionAttempt, +] + +UnlockDoorActionAttempt = Union[ + UnlockDoorSuccessActionAttempt, + UnlockDoorPendingActionAttempt, + UnlockDoorErrorActionAttempt, +] + +ScanCredentialActionAttempt = Union[ + ScanCredentialSuccessActionAttempt, + ScanCredentialPendingActionAttempt, + ScanCredentialErrorActionAttempt, +] + +EncodeCredentialActionAttempt = Union[ + EncodeCredentialSuccessActionAttempt, + EncodeCredentialPendingActionAttempt, + EncodeCredentialErrorActionAttempt, +] + +ScanToAssignCredentialActionAttempt = Union[ + ScanToAssignCredentialSuccessActionAttempt, + ScanToAssignCredentialPendingActionAttempt, + ScanToAssignCredentialErrorActionAttempt, +] + +AssignCredentialActionAttempt = Union[ + AssignCredentialSuccessActionAttempt, + AssignCredentialPendingActionAttempt, + AssignCredentialErrorActionAttempt, +] + +ResetSandboxWorkspaceActionAttempt = Union[ + ResetSandboxWorkspaceSuccessActionAttempt, + ResetSandboxWorkspacePendingActionAttempt, + ResetSandboxWorkspaceErrorActionAttempt, +] + +SetFanModeActionAttempt = Union[ + SetFanModeSuccessActionAttempt, + SetFanModePendingActionAttempt, + SetFanModeErrorActionAttempt, +] + +SetHvacModeActionAttempt = Union[ + SetHvacModeSuccessActionAttempt, + SetHvacModePendingActionAttempt, + SetHvacModeErrorActionAttempt, +] + +ActivateClimatePresetActionAttempt = Union[ + ActivateClimatePresetSuccessActionAttempt, + ActivateClimatePresetPendingActionAttempt, + ActivateClimatePresetErrorActionAttempt, +] + +SimulateKeypadCodeEntryActionAttempt = Union[ + SimulateKeypadCodeEntrySuccessActionAttempt, + SimulateKeypadCodeEntryPendingActionAttempt, + SimulateKeypadCodeEntryErrorActionAttempt, +] + +SimulateManualLockViaKeypadActionAttempt = Union[ + SimulateManualLockViaKeypadSuccessActionAttempt, + SimulateManualLockViaKeypadPendingActionAttempt, + SimulateManualLockViaKeypadErrorActionAttempt, +] + +PushThermostatProgramsActionAttempt = Union[ + PushThermostatProgramsSuccessActionAttempt, + PushThermostatProgramsPendingActionAttempt, + PushThermostatProgramsErrorActionAttempt, +] + +ConfigureAutoLockActionAttempt = Union[ + ConfigureAutoLockSuccessActionAttempt, + ConfigureAutoLockPendingActionAttempt, + ConfigureAutoLockErrorActionAttempt, +] + +SyncAccessCodesActionAttempt = Union[ + SyncAccessCodesSuccessActionAttempt, + SyncAccessCodesPendingActionAttempt, + SyncAccessCodesErrorActionAttempt, +] + +CreateAccessCodeActionAttempt = Union[ + CreateAccessCodeSuccessActionAttempt, + CreateAccessCodePendingActionAttempt, + CreateAccessCodeErrorActionAttempt, +] + +DeleteAccessCodeActionAttempt = Union[ + DeleteAccessCodeSuccessActionAttempt, + DeleteAccessCodePendingActionAttempt, + DeleteAccessCodeErrorActionAttempt, +] + +UpdateAccessCodeActionAttempt = Union[ + UpdateAccessCodeSuccessActionAttempt, + UpdateAccessCodePendingActionAttempt, + UpdateAccessCodeErrorActionAttempt, +] + +CreateNoiseThresholdActionAttempt = Union[ + CreateNoiseThresholdSuccessActionAttempt, + CreateNoiseThresholdPendingActionAttempt, + CreateNoiseThresholdErrorActionAttempt, +] + +DeleteNoiseThresholdActionAttempt = Union[ + DeleteNoiseThresholdSuccessActionAttempt, + DeleteNoiseThresholdPendingActionAttempt, + DeleteNoiseThresholdErrorActionAttempt, +] + +UpdateNoiseThresholdActionAttempt = Union[ + UpdateNoiseThresholdSuccessActionAttempt, + UpdateNoiseThresholdPendingActionAttempt, + UpdateNoiseThresholdErrorActionAttempt, +] + +SuccessActionAttempt = Union[ + LockDoorSuccessActionAttempt, + UnlockDoorSuccessActionAttempt, + ScanCredentialSuccessActionAttempt, + EncodeCredentialSuccessActionAttempt, + ScanToAssignCredentialSuccessActionAttempt, + AssignCredentialSuccessActionAttempt, + ResetSandboxWorkspaceSuccessActionAttempt, + SetFanModeSuccessActionAttempt, + SetHvacModeSuccessActionAttempt, + ActivateClimatePresetSuccessActionAttempt, + SimulateKeypadCodeEntrySuccessActionAttempt, + SimulateManualLockViaKeypadSuccessActionAttempt, + PushThermostatProgramsSuccessActionAttempt, + ConfigureAutoLockSuccessActionAttempt, + SyncAccessCodesSuccessActionAttempt, + CreateAccessCodeSuccessActionAttempt, + DeleteAccessCodeSuccessActionAttempt, + UpdateAccessCodeSuccessActionAttempt, + CreateNoiseThresholdSuccessActionAttempt, + DeleteNoiseThresholdSuccessActionAttempt, + UpdateNoiseThresholdSuccessActionAttempt, +] + +PendingActionAttempt = Union[ + LockDoorPendingActionAttempt, + UnlockDoorPendingActionAttempt, + ScanCredentialPendingActionAttempt, + EncodeCredentialPendingActionAttempt, + ScanToAssignCredentialPendingActionAttempt, + AssignCredentialPendingActionAttempt, + ResetSandboxWorkspacePendingActionAttempt, + SetFanModePendingActionAttempt, + SetHvacModePendingActionAttempt, + ActivateClimatePresetPendingActionAttempt, + SimulateKeypadCodeEntryPendingActionAttempt, + SimulateManualLockViaKeypadPendingActionAttempt, + PushThermostatProgramsPendingActionAttempt, + ConfigureAutoLockPendingActionAttempt, + SyncAccessCodesPendingActionAttempt, + CreateAccessCodePendingActionAttempt, + DeleteAccessCodePendingActionAttempt, + UpdateAccessCodePendingActionAttempt, + CreateNoiseThresholdPendingActionAttempt, + DeleteNoiseThresholdPendingActionAttempt, + UpdateNoiseThresholdPendingActionAttempt, +] + +ErrorActionAttempt = Union[ + LockDoorErrorActionAttempt, + UnlockDoorErrorActionAttempt, + ScanCredentialErrorActionAttempt, + EncodeCredentialErrorActionAttempt, + ScanToAssignCredentialErrorActionAttempt, + AssignCredentialErrorActionAttempt, + ResetSandboxWorkspaceErrorActionAttempt, + SetFanModeErrorActionAttempt, + SetHvacModeErrorActionAttempt, + ActivateClimatePresetErrorActionAttempt, + SimulateKeypadCodeEntryErrorActionAttempt, + SimulateManualLockViaKeypadErrorActionAttempt, + PushThermostatProgramsErrorActionAttempt, + ConfigureAutoLockErrorActionAttempt, + SyncAccessCodesErrorActionAttempt, + CreateAccessCodeErrorActionAttempt, + DeleteAccessCodeErrorActionAttempt, + UpdateAccessCodeErrorActionAttempt, + CreateNoiseThresholdErrorActionAttempt, + DeleteNoiseThresholdErrorActionAttempt, + UpdateNoiseThresholdErrorActionAttempt, ] -_ACTION_ATTEMPT_VARIANTS: Dict[str, Any] = { - "LOCK_DOOR": LockDoorActionAttempt, - "UNLOCK_DOOR": UnlockDoorActionAttempt, - "SCAN_CREDENTIAL": ScanCredentialActionAttempt, - "ENCODE_CREDENTIAL": EncodeCredentialActionAttempt, - "SCAN_TO_ASSIGN_CREDENTIAL": ScanToAssignCredentialActionAttempt, - "ASSIGN_CREDENTIAL": AssignCredentialActionAttempt, - "RESET_SANDBOX_WORKSPACE": ResetSandboxWorkspaceActionAttempt, - "SET_FAN_MODE": SetFanModeActionAttempt, - "SET_HVAC_MODE": SetHvacModeActionAttempt, - "ACTIVATE_CLIMATE_PRESET": ActivateClimatePresetActionAttempt, - "SIMULATE_KEYPAD_CODE_ENTRY": SimulateKeypadCodeEntryActionAttempt, - "SIMULATE_MANUAL_LOCK_VIA_KEYPAD": SimulateManualLockViaKeypadActionAttempt, - "PUSH_THERMOSTAT_PROGRAMS": PushThermostatProgramsActionAttempt, - "CONFIGURE_AUTO_LOCK": ConfigureAutoLockActionAttempt, - "SYNC_ACCESS_CODES": SyncAccessCodesActionAttempt, - "CREATE_ACCESS_CODE": CreateAccessCodeActionAttempt, - "DELETE_ACCESS_CODE": DeleteAccessCodeActionAttempt, - "UPDATE_ACCESS_CODE": UpdateAccessCodeActionAttempt, - "CREATE_NOISE_THRESHOLD": CreateNoiseThresholdActionAttempt, - "DELETE_NOISE_THRESHOLD": DeleteNoiseThresholdActionAttempt, - "UPDATE_NOISE_THRESHOLD": UpdateNoiseThresholdActionAttempt, +_ACTION_ATTEMPT_VARIANTS: Dict[Tuple[str, str], Any] = { + ("LOCK_DOOR", "success"): LockDoorSuccessActionAttempt, + ("LOCK_DOOR", "pending"): LockDoorPendingActionAttempt, + ("LOCK_DOOR", "error"): LockDoorErrorActionAttempt, + ("UNLOCK_DOOR", "success"): UnlockDoorSuccessActionAttempt, + ("UNLOCK_DOOR", "pending"): UnlockDoorPendingActionAttempt, + ("UNLOCK_DOOR", "error"): UnlockDoorErrorActionAttempt, + ("SCAN_CREDENTIAL", "success"): ScanCredentialSuccessActionAttempt, + ("SCAN_CREDENTIAL", "pending"): ScanCredentialPendingActionAttempt, + ("SCAN_CREDENTIAL", "error"): ScanCredentialErrorActionAttempt, + ("ENCODE_CREDENTIAL", "success"): EncodeCredentialSuccessActionAttempt, + ("ENCODE_CREDENTIAL", "pending"): EncodeCredentialPendingActionAttempt, + ("ENCODE_CREDENTIAL", "error"): EncodeCredentialErrorActionAttempt, + ( + "SCAN_TO_ASSIGN_CREDENTIAL", + "success", + ): ScanToAssignCredentialSuccessActionAttempt, + ( + "SCAN_TO_ASSIGN_CREDENTIAL", + "pending", + ): ScanToAssignCredentialPendingActionAttempt, + ("SCAN_TO_ASSIGN_CREDENTIAL", "error"): ScanToAssignCredentialErrorActionAttempt, + ("ASSIGN_CREDENTIAL", "success"): AssignCredentialSuccessActionAttempt, + ("ASSIGN_CREDENTIAL", "pending"): AssignCredentialPendingActionAttempt, + ("ASSIGN_CREDENTIAL", "error"): AssignCredentialErrorActionAttempt, + ("RESET_SANDBOX_WORKSPACE", "success"): ResetSandboxWorkspaceSuccessActionAttempt, + ("RESET_SANDBOX_WORKSPACE", "pending"): ResetSandboxWorkspacePendingActionAttempt, + ("RESET_SANDBOX_WORKSPACE", "error"): ResetSandboxWorkspaceErrorActionAttempt, + ("SET_FAN_MODE", "success"): SetFanModeSuccessActionAttempt, + ("SET_FAN_MODE", "pending"): SetFanModePendingActionAttempt, + ("SET_FAN_MODE", "error"): SetFanModeErrorActionAttempt, + ("SET_HVAC_MODE", "success"): SetHvacModeSuccessActionAttempt, + ("SET_HVAC_MODE", "pending"): SetHvacModePendingActionAttempt, + ("SET_HVAC_MODE", "error"): SetHvacModeErrorActionAttempt, + ("ACTIVATE_CLIMATE_PRESET", "success"): ActivateClimatePresetSuccessActionAttempt, + ("ACTIVATE_CLIMATE_PRESET", "pending"): ActivateClimatePresetPendingActionAttempt, + ("ACTIVATE_CLIMATE_PRESET", "error"): ActivateClimatePresetErrorActionAttempt, + ( + "SIMULATE_KEYPAD_CODE_ENTRY", + "success", + ): SimulateKeypadCodeEntrySuccessActionAttempt, + ( + "SIMULATE_KEYPAD_CODE_ENTRY", + "pending", + ): SimulateKeypadCodeEntryPendingActionAttempt, + ("SIMULATE_KEYPAD_CODE_ENTRY", "error"): SimulateKeypadCodeEntryErrorActionAttempt, + ( + "SIMULATE_MANUAL_LOCK_VIA_KEYPAD", + "success", + ): SimulateManualLockViaKeypadSuccessActionAttempt, + ( + "SIMULATE_MANUAL_LOCK_VIA_KEYPAD", + "pending", + ): SimulateManualLockViaKeypadPendingActionAttempt, + ( + "SIMULATE_MANUAL_LOCK_VIA_KEYPAD", + "error", + ): SimulateManualLockViaKeypadErrorActionAttempt, + ("PUSH_THERMOSTAT_PROGRAMS", "success"): PushThermostatProgramsSuccessActionAttempt, + ("PUSH_THERMOSTAT_PROGRAMS", "pending"): PushThermostatProgramsPendingActionAttempt, + ("PUSH_THERMOSTAT_PROGRAMS", "error"): PushThermostatProgramsErrorActionAttempt, + ("CONFIGURE_AUTO_LOCK", "success"): ConfigureAutoLockSuccessActionAttempt, + ("CONFIGURE_AUTO_LOCK", "pending"): ConfigureAutoLockPendingActionAttempt, + ("CONFIGURE_AUTO_LOCK", "error"): ConfigureAutoLockErrorActionAttempt, + ("SYNC_ACCESS_CODES", "success"): SyncAccessCodesSuccessActionAttempt, + ("SYNC_ACCESS_CODES", "pending"): SyncAccessCodesPendingActionAttempt, + ("SYNC_ACCESS_CODES", "error"): SyncAccessCodesErrorActionAttempt, + ("CREATE_ACCESS_CODE", "success"): CreateAccessCodeSuccessActionAttempt, + ("CREATE_ACCESS_CODE", "pending"): CreateAccessCodePendingActionAttempt, + ("CREATE_ACCESS_CODE", "error"): CreateAccessCodeErrorActionAttempt, + ("DELETE_ACCESS_CODE", "success"): DeleteAccessCodeSuccessActionAttempt, + ("DELETE_ACCESS_CODE", "pending"): DeleteAccessCodePendingActionAttempt, + ("DELETE_ACCESS_CODE", "error"): DeleteAccessCodeErrorActionAttempt, + ("UPDATE_ACCESS_CODE", "success"): UpdateAccessCodeSuccessActionAttempt, + ("UPDATE_ACCESS_CODE", "pending"): UpdateAccessCodePendingActionAttempt, + ("UPDATE_ACCESS_CODE", "error"): UpdateAccessCodeErrorActionAttempt, + ("CREATE_NOISE_THRESHOLD", "success"): CreateNoiseThresholdSuccessActionAttempt, + ("CREATE_NOISE_THRESHOLD", "pending"): CreateNoiseThresholdPendingActionAttempt, + ("CREATE_NOISE_THRESHOLD", "error"): CreateNoiseThresholdErrorActionAttempt, + ("DELETE_NOISE_THRESHOLD", "success"): DeleteNoiseThresholdSuccessActionAttempt, + ("DELETE_NOISE_THRESHOLD", "pending"): DeleteNoiseThresholdPendingActionAttempt, + ("DELETE_NOISE_THRESHOLD", "error"): DeleteNoiseThresholdErrorActionAttempt, + ("UPDATE_NOISE_THRESHOLD", "success"): UpdateNoiseThresholdSuccessActionAttempt, + ("UPDATE_NOISE_THRESHOLD", "pending"): UpdateNoiseThresholdPendingActionAttempt, + ("UPDATE_NOISE_THRESHOLD", "error"): UpdateNoiseThresholdErrorActionAttempt, } def action_attempt_from_dict(d: Any) -> ActionAttempt: - """Deserialize a known action_type variant. + """Deserialize a known action_type and status variant. Unknown discriminator values return ``DeepAttrDict`` so payloads from a newer API remain readable. The static return type covers known variants. """ - variant = _ACTION_ATTEMPT_VARIANTS.get(d.get("action_type")) + variant = _ACTION_ATTEMPT_VARIANTS.get((d.get("action_type"), d.get("status"))) if variant is None: return cast(ActionAttempt, DeepAttrDict(d)) return variant.from_dict(d) diff --git a/test/action_attempt_types_test.py b/test/action_attempt_types_test.py new file mode 100644 index 00000000..bbf50459 --- /dev/null +++ b/test/action_attempt_types_test.py @@ -0,0 +1,161 @@ +# mypy: warn_unused_ignores=True + +"""Static and runtime checks for status-discriminated action attempts. + +The ``_assert_*`` functions are compile-time tests: mypy checks that +narrowing on ``status`` recovers the per-status classes, and the +``type: ignore`` comments assert that the flagged expressions are type +errors, since an ignore that suppresses nothing fails the mypy run here. +""" + +from typing import Any, Literal, assert_type, cast + +from seam.client import SeamHttpClient +from seam.modules.action_attempts import poll_until_ready +from seam.resources import ( + ErrorActionAttempt, + LockDoorActionAttempt, + LockDoorErrorActionAttempt, + LockDoorPendingActionAttempt, + LockDoorSuccessActionAttempt, + PendingActionAttempt, + SuccessActionAttempt, + action_attempt_from_dict, +) + + +def _assert_unnarrowed_dereference_is_rejected( + attempt: LockDoorActionAttempt, +) -> None: + _ = attempt.result.was_confirmed_by_device # type: ignore[union-attr] + _ = attempt.error.message # type: ignore[union-attr] + + +def _assert_success_narrowing_needs_no_none_check( + attempt: LockDoorActionAttempt, +) -> None: + if attempt.status == "success": + assert_type(attempt, LockDoorSuccessActionAttempt) + assert_type(attempt.result, LockDoorSuccessActionAttempt.Result) + assert_type(attempt.result.was_confirmed_by_device, bool | None) + assert_type(attempt.error, None) + + +def _assert_error_narrowing_needs_no_none_check( + attempt: LockDoorActionAttempt, +) -> None: + if attempt.status == "error": + assert_type(attempt, LockDoorErrorActionAttempt) + assert_type(attempt.error, LockDoorErrorActionAttempt.Error) + assert_type(attempt.error.message, str) + assert_type(attempt.error.type, str) + assert_type(attempt.result, None) + + +def _assert_isinstance_narrowing_needs_no_none_check( + attempt: LockDoorActionAttempt, +) -> None: + if isinstance(attempt, LockDoorSuccessActionAttempt): + assert_type(attempt.result, LockDoorSuccessActionAttempt.Result) + if isinstance(attempt, LockDoorErrorActionAttempt): + assert_type(attempt.error, LockDoorErrorActionAttempt.Error) + + +def _assert_waiting_returns_the_success_union(client: SeamHttpClient) -> None: + attempt = poll_until_ready(client, action_attempt_id="attempt-id") + assert_type(attempt, SuccessActionAttempt) + assert_type(attempt.status, Literal["success"]) + if attempt.action_type == "LOCK_DOOR": + assert_type(attempt, LockDoorSuccessActionAttempt) + assert_type(attempt.result, LockDoorSuccessActionAttempt.Result) + assert_type(attempt.error, None) + + +def _assert_pending_members_type_dependents_as_none( + attempt: LockDoorPendingActionAttempt, +) -> None: + assert_type(attempt.status, Literal["pending"]) + assert_type(attempt.error, None) + assert_type(attempt.result, None) + + +def _assert_status_unions_cover_every_action_type( + pending: PendingActionAttempt, + failed: ErrorActionAttempt, +) -> None: + assert_type(pending.status, Literal["pending"]) + assert_type(pending.error, None) + assert_type(pending.result, None) + assert_type(failed.status, Literal["error"]) + assert_type(failed.result, None) + _ = failed.error.message + + +def test_from_dict_parses_a_pending_action_attempt(): + attempt = action_attempt_from_dict( + { + "action_attempt_id": "attempt-id", + "action_type": "LOCK_DOOR", + "status": "pending", + } + ) + + assert isinstance(attempt, LockDoorPendingActionAttempt) + assert attempt.status == "pending" + assert attempt.result is None + assert attempt.error is None + + +def test_from_dict_parses_a_successful_action_attempt(): + attempt = action_attempt_from_dict( + { + "action_attempt_id": "attempt-id", + "action_type": "LOCK_DOOR", + "status": "success", + "result": {"was_confirmed_by_device": True}, + } + ) + + assert isinstance(attempt, LockDoorSuccessActionAttempt) + assert attempt.status == "success" + assert isinstance(attempt.result, LockDoorSuccessActionAttempt.Result) + assert attempt.result.was_confirmed_by_device is True + assert attempt.error is None + + +def test_from_dict_parses_a_failed_action_attempt(): + attempt = action_attempt_from_dict( + { + "action_attempt_id": "attempt-id", + "action_type": "LOCK_DOOR", + "status": "error", + "error": {"message": "failed", "type": "device_error"}, + } + ) + + assert isinstance(attempt, LockDoorErrorActionAttempt) + assert attempt.status == "error" + assert isinstance(attempt.error, LockDoorErrorActionAttempt.Error) + assert attempt.error.message == "failed" + assert attempt.error.type == "device_error" + assert attempt.result is None + + +def test_from_dict_keeps_unknown_statuses_readable(): + unknown = cast( + Any, + action_attempt_from_dict({"action_type": "LOCK_DOOR", "status": "cancelled"}), + ) + + assert unknown.action_type == "LOCK_DOOR" + assert unknown.status == "cancelled" + + +def test_action_attempt_types_narrow_on_status(): + assert callable(_assert_unnarrowed_dereference_is_rejected) + assert callable(_assert_success_narrowing_needs_no_none_check) + assert callable(_assert_error_narrowing_needs_no_none_check) + assert callable(_assert_isinstance_narrowing_needs_no_none_check) + assert callable(_assert_waiting_returns_the_success_union) + assert callable(_assert_pending_members_type_dependents_as_none) + assert callable(_assert_status_unions_cover_every_action_type) diff --git a/test/nested_resource_test.py b/test/nested_resource_test.py index f0654b20..6772ff85 100644 --- a/test/nested_resource_test.py +++ b/test/nested_resource_test.py @@ -8,8 +8,10 @@ import seam.resources.device as device_module from seam.resources.acs_user import AcsUser from seam.resources.action_attempt import ( - LockDoorActionAttempt, - ScanCredentialActionAttempt, + LockDoorErrorActionAttempt, + LockDoorPendingActionAttempt, + LockDoorSuccessActionAttempt, + ScanCredentialSuccessActionAttempt, action_attempt_from_dict, ) from seam.resources.device import Device @@ -75,28 +77,42 @@ def test_action_attempt_union_hydrates_nested_result_and_error(): attempt = action_attempt_from_dict( { "action_type": "LOCK_DOOR", + "status": "success", "result": {"was_confirmed_by_device": True}, - "error": {"message": "failed", "type": "device_error"}, } ) - assert isinstance(attempt, LockDoorActionAttempt) - assert isinstance(attempt.result, LockDoorActionAttempt.Result) + assert isinstance(attempt, LockDoorSuccessActionAttempt) + assert isinstance(attempt.result, LockDoorSuccessActionAttempt.Result) assert attempt.result.was_confirmed_by_device is True - assert isinstance(attempt.error, LockDoorActionAttempt.Error) - assert attempt.error.message == "failed" + assert attempt.error is None + + failed = action_attempt_from_dict( + { + "action_type": "LOCK_DOOR", + "status": "error", + "error": {"message": "failed", "type": "device_error"}, + } + ) + assert isinstance(failed, LockDoorErrorActionAttempt) + assert isinstance(failed.error, LockDoorErrorActionAttempt.Error) + assert failed.error.message == "failed" + assert failed.result is None pending = action_attempt_from_dict( {"action_type": "LOCK_DOOR", "status": "pending"} ) + assert isinstance(pending, LockDoorPendingActionAttempt) assert pending.error is None assert pending.result is None def test_action_attempt_variants_keep_distinct_result_shapes(): - lock_fields = {f.name for f in dataclasses.fields(LockDoorActionAttempt.Result)} + lock_fields = { + f.name for f in dataclasses.fields(LockDoorSuccessActionAttempt.Result) + } scan_fields = { - f.name for f in dataclasses.fields(ScanCredentialActionAttempt.Result) + f.name for f in dataclasses.fields(ScanCredentialSuccessActionAttempt.Result) } assert "was_confirmed_by_device" in lock_fields diff --git a/test/resource_types_test.py b/test/resource_types_test.py index e2b29c9e..3c9cc9ca 100644 --- a/test/resource_types_test.py +++ b/test/resource_types_test.py @@ -8,8 +8,9 @@ ActionAttempt, Device, LockDoorActionAttempt, + LockDoorSuccessActionAttempt, NoiseSensorNoiseThresholdTriggeredEvent, - ScanCredentialActionAttempt, + ScanCredentialSuccessActionAttempt, SeamEvent, UnmanagedAccessCode, ) @@ -27,7 +28,7 @@ def _assert_access_code_narrowing( def _assert_boolean_shapes( code: AccessCode, unmanaged_code: UnmanagedAccessCode, - credential: ScanCredentialActionAttempt.Result.AcsCredentialOnSeam, + credential: ScanCredentialSuccessActionAttempt.Result.AcsCredentialOnSeam, ) -> None: assert_type(code.is_backup_access_code_available, bool) error = code.errors[0] @@ -63,8 +64,11 @@ def _assert_action_attempt_narrowing(attempt: ActionAttempt) -> None: assert_type(attempt, LockDoorActionAttempt) assert_type( attempt.result, - LockDoorActionAttempt.Result | None, + LockDoorSuccessActionAttempt.Result | None, ) + if attempt.status == "success": + assert_type(attempt, LockDoorSuccessActionAttempt) + assert_type(attempt.result, LockDoorSuccessActionAttempt.Result) def _assert_record_value_types(device: Device) -> None: From aceafcd618829dd16e9adc353a6119d71c49135c Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Aug 2026 02:58:14 +0000 Subject: [PATCH 2/3] refactor: remove narration comments Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01EdWS7o3htQ9cNxhCWL5Frp --- codegen/lib/layouts/resources.ts | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/codegen/lib/layouts/resources.ts b/codegen/lib/layouts/resources.ts index fa61d5f3..f1eb4968 100644 --- a/codegen/lib/layouts/resources.ts +++ b/codegen/lib/layouts/resources.ts @@ -260,11 +260,6 @@ const reservedClassNames = new Set([ 'dataclass', ]) -// Markers set when an action attempt is expanded into per-status variants. -// A property whose actionAttemptStatuses annotation does not list the -// variant's status is rendered as None; one whose annotation does list it is -// genuinely present for that status, so it sheds the forced optionality that -// nested objects otherwise get. type StatusAnnotatedProperty = Property & { renderAsNone?: boolean presentForStatus?: boolean @@ -325,8 +320,6 @@ const buildClass = ( const properties = classProperties.map((property) => { if (isRenderedAsNone(property)) { - // The property is typed None in this status variant, so no nested - // classes are generated for it: nothing could reference them. return { name: property.name, description: property.description, @@ -426,14 +419,6 @@ const buildClass = ( } const isObject = nestedClassName != null && property.format === 'object' - // A nested object is read as None whenever the payload omits it, and the - // schema alone is not a reliable guide to when that happens: an action - // attempt documents both error and result as required, yet a pending one - // carries neither. Constructing them unconditionally would fail on those - // payloads, so from_dict keeps its None fallback and the field stays - // Optional. The exception is a status-annotated property in a variant - // whose status the annotation lists: the annotation guarantees it is - // present there, so it keeps the optionality the schema declares. const isRequiredObject = isObject && isPresentForStatus(property) && @@ -496,8 +481,6 @@ interface UnionVariant { deprecationMessage: string } -// Group the class names of a union's variants by one of the discriminator -// values, so each group becomes a Union alias over the classes that share it. const buildUnionAliases = ( variants: Array<{ className: string; groupValue: string | undefined }>, suffix: string, @@ -548,9 +531,6 @@ const buildUnionResource = ( } }) - // With a secondary discriminator, a class covers one value pair, so aliases - // name the unions over each single value: one per primary value and one per - // secondary value. const aliases = secondaryDiscriminator == null ? [] @@ -613,11 +593,6 @@ const buildUnionResource = ( } } -// Expand an action attempt into one union variant per status from its status -// enum. In each variant, the status enum is filtered to the single status; a -// property whose actionAttemptStatuses annotation lists the status is marked -// present, and one whose annotation does not list it is rendered as None. -// Properties without the annotation are rendered unchanged for every status. const expandActionAttemptByStatus = ( attempt: Blueprint['actionAttempts'][number], ): UnionVariant[] => { From 57c0ee4f68bf8cc3c59eb0846162096090a4d8ac Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Aug 2026 03:04:00 +0000 Subject: [PATCH 3/3] refactor: remove test module docstring Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01EdWS7o3htQ9cNxhCWL5Frp --- test/action_attempt_types_test.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/test/action_attempt_types_test.py b/test/action_attempt_types_test.py index bbf50459..34425576 100644 --- a/test/action_attempt_types_test.py +++ b/test/action_attempt_types_test.py @@ -1,13 +1,5 @@ # mypy: warn_unused_ignores=True -"""Static and runtime checks for status-discriminated action attempts. - -The ``_assert_*`` functions are compile-time tests: mypy checks that -narrowing on ``status`` recovers the per-status classes, and the -``type: ignore`` comments assert that the flagged expressions are type -errors, since an ignore that suppresses nothing fails the mypy run here. -""" - from typing import Any, Literal, assert_type, cast from seam.client import SeamHttpClient