From f3fac17d55b7af5ef73307f475fdbf58da162b84 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Aug 2026 16:22:27 +0000 Subject: [PATCH 01/11] feat: implement the URL search params serialization standard Port @seamapi/url-search-params-serializer to Python so the SDK can serialize objects to URL search params for HTTP GET requests. Output is byte-for-byte identical to the reference implementation: - Values are encoded with the application/x-www-form-urlencoded serializer, which differs from urllib in its treatment of "*" and "~". - Params are sorted by name, compared by UTF-16 code unit. - Floats are formatted using the ECMAScript Number::toString algorithm, which differs from repr for integral floats and around the exponent notation thresholds. Python has no undefined, so UNDEFINED is provided as the sentinel for a removed param, while None serializes to an empty value as null does. Temporal.Instant and Date both map to datetime, where a naive datetime is interpreted as UTC and microseconds are truncated to millisecond precision. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PpqwDhZmyikGbjmCPqFW2A --- README.rst | 45 ++ seam/__init__.py | 7 + seam/utils/url_search_params_serializer.py | 460 +++++++++++++++++++++ test/url_search_params_serializer_test.py | 448 ++++++++++++++++++++ 4 files changed, 960 insertions(+) create mode 100644 seam/utils/url_search_params_serializer.py create mode 100644 test/url_search_params_serializer_test.py diff --git a/README.rst b/README.rst index a031c5ce..e28551e0 100644 --- a/README.rst +++ b/README.rst @@ -73,6 +73,8 @@ Contents * `Configuring the httpx client`_ + * `Serializing URL search params`_ + * `Development and Testing`_ * `Quickstart`_ @@ -518,6 +520,49 @@ precedence over the defaults the SDK sets: }, ) +Serializing URL search params +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The Seam API parses URL search params as complex types. +This SDK implements the `Seam URL search params serialization standard`_, +which defines how the Seam SDKs serialize objects to URL search params. +Use it directly when building requests to the Seam API by hand: + +.. code-block:: python + + from seam import serialize_url_search_params + + serialize_url_search_params( + { + "name": "Dax", + "age": 27, + "is_admin": True, + "tags": ["cars", "planes"], + } + ) + # => 'age=27&is_admin=true&name=Dax&tags=cars&tags=planes' + +Params are sorted by name, so equivalent input always produces the same query string. +Nested dicts are serialized to dot-path keys, e.g., ``{"a": {"b": 1}}`` becomes ``a.b=1``. +Params set to ``None`` are serialized to an empty value, e.g., ``a=``, +while params set to ``seam.UNDEFINED`` are removed. +A param that cannot be represented raises a ``seam.UnserializableParamError``. + +To merge serialized params into existing params, use ``update_url_search_params``: + +.. code-block:: python + + from seam import UrlSearchParams, update_url_search_params + + search_params = UrlSearchParams("?foo=bar") + + update_url_search_params(search_params, {"name": "Dax"}) + + str(search_params) + # => 'foo=bar&name=Dax' + +.. _Seam URL search params serialization standard: https://github.com/seamapi/url-search-params-serializer + Development and Testing ----------------------- diff --git a/seam/__init__.py b/seam/__init__.py index 30e17b6c..4e77cc35 100644 --- a/seam/__init__.py +++ b/seam/__init__.py @@ -15,3 +15,10 @@ ) from .seam_webhook import SeamWebhook from svix.webhooks import WebhookVerificationError as SeamWebhookVerificationError +from .utils.url_search_params_serializer import ( + UNDEFINED, + UnserializableParamError, + UrlSearchParams, + serialize_url_search_params, + update_url_search_params, +) diff --git a/seam/utils/url_search_params_serializer.py b/seam/utils/url_search_params_serializer.py new file mode 100644 index 00000000..2c6e5588 --- /dev/null +++ b/seam/utils/url_search_params_serializer.py @@ -0,0 +1,460 @@ +"""Serializes Python objects to URL search params. + +This is a Python port of the `@seamapi/url-search-params-serializer +`_ reference +implementation, which defines the standard for how the Seam SDKs and other +Seam API consumers serialize objects to URL search params in HTTP GET requests. + +Output is byte-for-byte identical to the reference implementation: +values are encoded with the ``application/x-www-form-urlencoded`` serializer, +params are sorted by name, and numbers are formatted using the +ECMAScript ``Number::toString`` algorithm. + +Type mapping between the reference implementation and this port: + +- JavaScript ``undefined`` is :data:`UNDEFINED`, or simply an absent key. +- JavaScript ``null`` is ``None``. +- JavaScript ``string`` is ``str``. +- JavaScript ``boolean`` is ``bool``. +- JavaScript ``number`` is ``float`` or ``int``. +- JavaScript ``bigint`` is ``int``. + Python integers are arbitrary precision, so ``int`` covers both cases + and is always serialized in full without exponent notation. +- JavaScript ``Date`` and ``Temporal.Instant`` are + :class:`datetime.datetime`. + A naive ``datetime`` is interpreted as UTC. + Since ``Date`` has millisecond precision, microseconds are truncated. +- JavaScript ``Array`` is ``list`` or ``tuple``. + Unordered collections such as ``set`` are unsupported + because they would not serialize deterministically. +- A JavaScript plain object is any ``Mapping``, e.g., a ``dict``. +""" + +import datetime +import math +import string +from collections.abc import Mapping +from decimal import Decimal +from typing import Any, Iterator, List, Optional, Sequence, Tuple, Union +from urllib.parse import parse_qsl + +Params = Mapping[str, Any] + + +class UnserializableParamError(Exception): + """Exception raised when a param could not be serialized. + + :ivar name: Name of the param that could not be serialized + :vartype name: str + """ + + def __init__(self, name: str, message: str): + """ + :param name: Name of the param that could not be serialized + :type name: str + :param message: Description of why the param could not be serialized + :type message: str + """ + + super().__init__(f"Could not serialize parameter: '{name}' {message}") + self.name = name + + +class _Undefined: + """Type of the :data:`UNDEFINED` sentinel.""" + + _instance = None + + def __new__(cls): + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + + def __repr__(self): + return "UNDEFINED" + + def __bool__(self): + return False + + +UNDEFINED = _Undefined() +"""Sentinel for the absence of a value, equivalent to JavaScript ``undefined``. + +Params set to this sentinel are removed, whereas params set to ``None`` +are serialized to an empty value. Omitting the key entirely is equivalent. +""" + + +class UrlSearchParams: + """A mutable collection of URL search params. + + Implements the parts of the `URLSearchParams + `_ + interface needed to serialize params to a query string. + Unlike a ``dict``, a name may appear more than once, + which is how arrays are serialized. + """ + + def __init__( + self, + init: Optional[Union[str, Params, Sequence[Tuple[str, str]]]] = None, + ): + """ + :param init: A query string, a mapping of names to values, + or a sequence of name-value pairs + :type init: Optional[Union[str, Mapping[str, Any], Sequence[Tuple[str, str]]]] + """ + + self._pairs: List[Tuple[str, str]] = [] + + if init is None: + return + + if isinstance(init, str): + query = init[1:] if init.startswith("?") else init + self._pairs = list(parse_qsl(query, keep_blank_values=True)) + return + + items = init.items() if isinstance(init, Mapping) else init + self._pairs = [(str(name), str(value)) for name, value in items] + + def append(self, name: str, value: str) -> None: + """Appends a name-value pair, keeping any existing pairs with this name. + + :param name: Name of the param + :type name: str + :param value: Value of the param + :type value: str + """ + + self._pairs.append((name, value)) + + def set(self, name: str, value: str) -> None: + """Sets the value associated with a name. + + Replaces the first pair with this name and removes any others. + Appends a new pair if no pair with this name exists. + + :param name: Name of the param + :type name: str + :param value: Value of the param + :type value: str + """ + + if not self.has(name): + self.append(name, value) + return + + pairs: List[Tuple[str, str]] = [] + is_set = False + + for pair in self._pairs: + if pair[0] != name: + pairs.append(pair) + elif not is_set: + pairs.append((name, value)) + is_set = True + + self._pairs = pairs + + def get(self, name: str) -> Optional[str]: + """Returns the value of the first pair with this name. + + :param name: Name of the param + :type name: str + + :returns: The value, or ``None`` if no pair with this name exists + """ + + for existing_name, value in self._pairs: + if existing_name == name: + return value + + return None + + def get_all(self, name: str) -> List[str]: + """Returns the values of all pairs with this name, in insertion order. + + :param name: Name of the param + :type name: str + + :returns: The values""" + + return [value for existing_name, value in self._pairs if existing_name == name] + + def has(self, name: str) -> bool: + """Returns whether a pair with this name exists. + + :param name: Name of the param + :type name: str + + :returns: Whether a pair with this name exists""" + + return any(existing_name == name for existing_name, _ in self._pairs) + + def delete(self, name: str) -> None: + """Removes all pairs with this name. + + :param name: Name of the param + :type name: str + """ + + self._pairs = [pair for pair in self._pairs if pair[0] != name] + + def sort(self) -> None: + """Sorts all pairs by name. + + Sorting is stable, so the relative order of pairs + with the same name is preserved. + Names are compared by UTF-16 code units to match the + `URLSearchParams.sort() + `_ + specification. + """ + + self._pairs.sort(key=lambda pair: pair[0].encode("utf-16-be")) + + def to_string(self) -> str: + """Serializes all pairs to a query string. + + :returns: The query string, without a leading ``?``""" + + return "&".join( + f"{_encode_form_component(name)}={_encode_form_component(value)}" + for name, value in self._pairs + ) + + def __str__(self) -> str: + return self.to_string() + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.to_string()!r})" + + def __len__(self) -> int: + return len(self._pairs) + + def __iter__(self) -> Iterator[Tuple[str, str]]: + return iter(self._pairs) + + +def serialize_url_search_params(params: Params) -> str: + """Serializes params to a URL search param query string. + + :param params: The params to serialize + :type params: Mapping[str, Any] + + :returns: The query string, without a leading ``?`` + + :raises UnserializableParamError: If any param could not be serialized + """ + + search_params = UrlSearchParams() + update_url_search_params(search_params, params) + + return search_params.to_string() + + +def update_url_search_params(search_params: UrlSearchParams, params: Params) -> None: + """Updates existing URL search params with serialized params. + + Existing params are preserved unless overwritten by a serialized param. + All params are sorted by name. + + :param search_params: The URL search params to update + :type search_params: UrlSearchParams + :param params: The params to serialize + :type params: Mapping[str, Any] + + :raises UnserializableParamError: If any param could not be serialized + """ + + _nested_update_url_search_params(search_params, params, []) + search_params.sort() + + +def _nested_update_url_search_params( + search_params: UrlSearchParams, params: Params, path: List[str] +) -> None: + for key, value in params.items(): + if not isinstance(key, str): + raise UnserializableParamError( + repr(key), + f"is a {type(key).__name__} which is unsupported as a parameter name", + ) + + if "." in key: + raise UnserializableParamError( + key, + 'contains one or more dots "." in its name which is unsupported', + ) + + current_path = [*path, key] + + if isinstance(value, Mapping): + _nested_update_url_search_params(search_params, value, current_path) + continue + + name = ".".join(current_path) + + if _is_undefined(value): + continue + + if isinstance(value, str) and len(value) == 0: + continue + + if isinstance(value, (list, tuple)): + _update_url_search_params_from_array(search_params, name, value) + continue + + search_params.set(name, _serialize(name, value)) + + +def _update_url_search_params_from_array( + search_params: UrlSearchParams, name: str, values: Sequence[Any] +) -> None: + if len(values) == 0: + search_params.set(name, "") + return + + if len(values) == 1 and _is_empty_string(values[0]): + raise UnserializableParamError( + name, + "is a single element array containing the empty string which is unsupported", + ) + + if any(_is_empty_string(value) for value in values): + raise UnserializableParamError( + name, + "is an array containing the empty string which is unsupported", + ) + + if any(value is None or _is_undefined(value) for value in values): + raise UnserializableParamError( + name, + "is an array containing null or undefined values which is unsupported", + ) + + for value in values: + search_params.append(name, _serialize(name, value)) + + +def _serialize(name: str, value: Any) -> str: + if value is None: + return "" + + if isinstance(value, str): + return value + + if isinstance(value, bool): + return "true" if value else "false" + + if isinstance(value, int): + return str(value) + + if isinstance(value, float): + return _format_number(name, value) + + if isinstance(value, datetime.datetime): + return _format_datetime(value) + + raise UnserializableParamError(name, f"is a {type(value).__name__}") + + +def _is_empty_string(value: Any) -> bool: + return isinstance(value, str) and len(value) == 0 + + +def _is_undefined(value: Any) -> bool: + return isinstance(value, _Undefined) + + +def _format_datetime(value: datetime.datetime) -> str: + if value.tzinfo is None: + value = value.replace(tzinfo=datetime.timezone.utc) + + utc_value = value.astimezone(datetime.timezone.utc) + milliseconds = utc_value.microsecond // 1000 + + return ( + f"{utc_value.year:04d}-{utc_value.month:02d}-{utc_value.day:02d}" + f"T{utc_value.hour:02d}:{utc_value.minute:02d}:{utc_value.second:02d}" + f".{milliseconds:03d}Z" + ) + + +def _format_number(name: str, value: float) -> str: + if math.isnan(value): + raise UnserializableParamError(name, "is NaN") + + if math.isinf(value): + raise UnserializableParamError( + name, "is Infinity" if value > 0 else "is -Infinity" + ) + + if value == 0: + return "0" + + sign = "-" if value < 0 else "" + _, digit_tuple, exponent = Decimal(repr(abs(value))).as_tuple() + + # The shortest digit string that round-trips, and the position of the + # decimal point relative to it, as required by the ECMAScript + # Number::toString algorithm. + digits = "".join(str(digit) for digit in digit_tuple) + point = int(exponent) + len(digits) + digits = digits.rstrip("0") + + return sign + _format_digits(digits, point) + + +def _format_digits(digits: str, point: int) -> str: + """Formats digits and a decimal point position per ECMAScript Number::toString. + + :param digits: Significant digits, without trailing zeros + :type digits: str + :param point: Position of the decimal point relative to the digits + :type point: int + + :returns: The formatted number""" + + count = len(digits) + + if count <= point <= 21: + return digits + "0" * (point - count) + + if 0 < point <= 21: + return f"{digits[:point]}.{digits[point:]}" + + if -6 < point <= 0: + return f"0.{'0' * -point}{digits}" + + exponent = point - 1 + exponent_sign = "+" if exponent >= 0 else "-" + mantissa = digits if count == 1 else f"{digits[0]}.{digits[1:]}" + + return f"{mantissa}e{exponent_sign}{abs(exponent)}" + + +_FORM_SAFE_CHARACTERS = frozenset(f"{string.ascii_letters}{string.digits}*-._") + + +def _encode_form_component(value: str) -> str: + """Percent-encodes a string using the ``application/x-www-form-urlencoded`` serializer. + + :param value: The string to encode + :type value: str + + :returns: The encoded string""" + + encoded = [] + + for byte in value.encode("utf-8"): + character = chr(byte) + if character in _FORM_SAFE_CHARACTERS: + encoded.append(character) + elif character == " ": + encoded.append("+") + else: + encoded.append(f"%{byte:02X}") + + return "".join(encoded) diff --git a/test/url_search_params_serializer_test.py b/test/url_search_params_serializer_test.py new file mode 100644 index 00000000..08459ad8 --- /dev/null +++ b/test/url_search_params_serializer_test.py @@ -0,0 +1,448 @@ +from collections import OrderedDict +from datetime import date, datetime, timedelta, timezone + +import pytest + +from seam.utils.url_search_params_serializer import ( + UNDEFINED, + UnserializableParamError, + UrlSearchParams, + serialize_url_search_params, + update_url_search_params, +) + + +def test_serializes_empty_object(): + assert serialize_url_search_params({}) == "" + + +def test_serializes_string(): + assert serialize_url_search_params({"foo": "d"}) == "foo=d" + assert serialize_url_search_params({"foo": "null"}) == "foo=null" + assert serialize_url_search_params({"foo": "None"}) == "foo=None" + assert serialize_url_search_params({"foo": "undefined"}) == "foo=undefined" + assert serialize_url_search_params({"foo": "0"}) == "foo=0" + + +def test_serializes_the_empty_string_to_undefined(): + assert serialize_url_search_params({"foo": ""}) == "" + assert serialize_url_search_params({"foo": "d", "bar": ""}) == "foo=d" + + +def test_serializes_int(): + assert serialize_url_search_params({"foo": 1}) == "foo=1" + assert serialize_url_search_params({"foo": 0}) == "foo=0" + assert serialize_url_search_params({"foo": -42}) == "foo=-42" + + +def test_serializes_arbitrary_precision_int(): + assert ( + serialize_url_search_params({"foo": 9007199254740993}) == "foo=9007199254740993" + ) + assert ( + serialize_url_search_params({"foo": 123456789012345678901234567890}) + == "foo=123456789012345678901234567890" + ) + + +def test_serializes_float(): + assert serialize_url_search_params({"foo": 23.8}) == "foo=23.8" + assert serialize_url_search_params({"foo": -23.8}) == "foo=-23.8" + assert serialize_url_search_params({"foo": 0.30000000000000004}) == ( + "foo=0.30000000000000004" + ) + + +def test_serializes_float_using_the_ecmascript_number_format(): + # A float is serialized exactly as JavaScript would serialize the number, + # which is not always the same as the Python repr. + assert serialize_url_search_params({"foo": 1.0}) == "foo=1" + assert serialize_url_search_params({"foo": -0.0}) == "foo=0" + assert serialize_url_search_params({"foo": 100.0}) == "foo=100" + assert serialize_url_search_params({"foo": 1e16}) == "foo=10000000000000000" + assert serialize_url_search_params({"foo": 1e20}) == "foo=100000000000000000000" + assert serialize_url_search_params({"foo": 1e21}) == "foo=1e%2B21" + assert serialize_url_search_params({"foo": 0.0001}) == "foo=0.0001" + assert serialize_url_search_params({"foo": 1e-6}) == "foo=0.000001" + assert serialize_url_search_params({"foo": 1e-7}) == "foo=1e-7" + assert serialize_url_search_params({"foo": 5e-324}) == "foo=5e-324" + assert serialize_url_search_params({"foo": 1.7976931348623157e308}) == ( + "foo=1.7976931348623157e%2B308" + ) + + +def test_serializes_bool(): + assert serialize_url_search_params({"foo": True}) == "foo=true" + assert serialize_url_search_params({"foo": False}) == "foo=false" + assert serialize_url_search_params({"foo": True, "bar": False}) == ( + "bar=false&foo=true" + ) + + +def test_removes_undefined_params(): + assert serialize_url_search_params({"bar": UNDEFINED}) == "" + assert serialize_url_search_params({"foo": 1, "bar": UNDEFINED}) == "foo=1" + + +def test_serializes_none_params(): + assert serialize_url_search_params({"bar": None}) == "bar=" + assert serialize_url_search_params({"foo": 1, "bar": None}) == "bar=&foo=1" + + +def test_serializes_empty_array_params(): + assert serialize_url_search_params({"bar": []}) == "bar=" + assert serialize_url_search_params({"foo": 1, "bar": []}) == "bar=&foo=1" + assert serialize_url_search_params({"bar": ()}) == "bar=" + + +def test_serializes_array_params_with_one_value(): + assert serialize_url_search_params({"bar": ["a"]}) == "bar=a" + assert serialize_url_search_params({"foo": 1, "bar": ["a"]}) == "bar=a&foo=1" + + +def test_serializes_array_params_with_many_values(): + assert serialize_url_search_params({"foo": 1, "bar": ["a", "2"]}) == ( + "bar=a&bar=2&foo=1" + ) + assert serialize_url_search_params( + {"foo": 1, "bar": ["null", "2", "undefined"]} + ) == ("bar=null&bar=2&bar=undefined&foo=1") + + +def test_serializes_tuple_params(): + assert serialize_url_search_params({"bar": ("a", "2")}) == "bar=a&bar=2" + + +def test_serializes_array_params_with_mixed_values(): + assert serialize_url_search_params( + {"bar": [1, "a", True, datetime(1970, 1, 1, tzinfo=timezone.utc)]} + ) == ("bar=1&bar=a&bar=true&bar=1970-01-01T00%3A00%3A00.000Z") + + +def test_serializes_datetime(): + assert serialize_url_search_params( + {"foo": 1, "now": datetime(2025, 2, 24, 18, 44, 39, tzinfo=timezone.utc)} + ) == ("foo=1&now=2025-02-24T18%3A44%3A39.000Z") + + +def test_serializes_datetime_with_milliseconds(): + assert serialize_url_search_params( + { + "now": datetime( + 2025, 2, 24, 18, 44, 39, microsecond=123000, tzinfo=timezone.utc + ) + } + ) == ("now=2025-02-24T18%3A44%3A39.123Z") + + +def test_truncates_datetime_microseconds(): + assert serialize_url_search_params( + { + "now": datetime( + 2025, 2, 24, 18, 44, 39, microsecond=123999, tzinfo=timezone.utc + ) + } + ) == ("now=2025-02-24T18%3A44%3A39.123Z") + + +def test_serializes_datetime_as_utc(): + assert serialize_url_search_params( + {"now": datetime(2025, 2, 24, 13, 44, 39, tzinfo=timezone(timedelta(hours=-5)))} + ) == ("now=2025-02-24T18%3A44%3A39.000Z") + + +def test_serializes_naive_datetime_as_utc(): + assert serialize_url_search_params({"now": datetime(2025, 2, 24, 18, 44, 39)}) == ( + "now=2025-02-24T18%3A44%3A39.000Z" + ) + + +def test_serializes_datetime_before_the_epoch(): + assert serialize_url_search_params( + {"then": datetime(1969, 12, 31, 23, 59, 59, tzinfo=timezone.utc)} + ) == ("then=1969-12-31T23%3A59%3A59.000Z") + + +def test_serializes_dicts(): + assert serialize_url_search_params({"foo": 1, "bar": {"baz": "a"}}) == ( + "bar.baz=a&foo=1" + ) + + assert serialize_url_search_params({"foo": 1, "bar": {"baz": {"x": {"z": 1}}}}) == ( + "bar.baz.x.z=1&foo=1" + ) + + assert serialize_url_search_params( + {"foo": 1, "bar": {"baz": {"x": {"z": None}}}} + ) == ("bar.baz.x.z=&foo=1") + + assert serialize_url_search_params({"foo": 1, "bar": {"baz": [1, "a"]}}) == ( + "bar.baz=1&bar.baz=a&foo=1" + ) + + assert serialize_url_search_params({"foo": {}, "bar": 2}) == "bar=2" + + assert serialize_url_search_params({"foo": {"x": {}}, "bar": 2}) == "bar=2" + + assert serialize_url_search_params( + {"foo": {}, "bar": {"baz": {"x": {"z": None, "t": {}}, "q": {}}}} + ) == ("bar.baz.x.z=") + + +def test_serializes_dict_subclasses(): + assert serialize_url_search_params( + {"foo": OrderedDict([("bar", 1), ("baz", 2)])} + ) == ("foo.bar=1&foo.baz=2") + + +def test_sorts_params_by_name(): + assert serialize_url_search_params({"b": 1, "a": 2, "c": 3}) == "a=2&b=1&c=3" + assert serialize_url_search_params({"b": 1, "A": 2, "a": 3, "B": 4}) == ( + "A=2&B=4&a=3&b=1" + ) + assert serialize_url_search_params({"a10": 1, "a2": 2, "a1": 3}) == ( + "a1=3&a10=1&a2=2" + ) + assert serialize_url_search_params({"zz": 1, "a": {"z": 2, "b": 3}}) == ( + "a.b=3&a.z=2&zz=1" + ) + assert serialize_url_search_params({"ab": 1, "a": {"b": 2}}) == "a.b=2&ab=1" + + +def test_sorts_params_by_utf_16_code_unit(): + assert serialize_url_search_params({"￿": 1, "\U0001f600": 2}) == ( + "%F0%9F%98%80=2&%EF%BF%BF=1" + ) + + +def test_sorting_preserves_array_order(): + assert serialize_url_search_params({"b": ["3", "1", "2"], "a": 1}) == ( + "a=1&b=3&b=1&b=2" + ) + + +def test_encodes_params_as_form_urlencoded(): + assert serialize_url_search_params({"foo": "a b"}) == "foo=a+b" + assert serialize_url_search_params({"foo": "a+b"}) == "foo=a%2Bb" + assert serialize_url_search_params({"foo": "a~b"}) == "foo=a%7Eb" + assert serialize_url_search_params({"foo": "a*b"}) == "foo=a*b" + assert serialize_url_search_params({"foo": "abcXYZ019*-._"}) == "foo=abcXYZ019*-._" + assert serialize_url_search_params({"foo": "a&b=c?d#e/f"}) == ( + "foo=a%26b%3Dc%3Fd%23e%2Ff" + ) + assert serialize_url_search_params({"foo": "100%"}) == "foo=100%25" + assert serialize_url_search_params({"foo": "a\nb"}) == "foo=a%0Ab" + + +def test_encodes_unicode_params(): + assert serialize_url_search_params({"foo": "héllo wörld"}) == ( + "foo=h%C3%A9llo+w%C3%B6rld" + ) + assert serialize_url_search_params({"foo": "日本語"}) == ( + "foo=%E6%97%A5%E6%9C%AC%E8%AA%9E" + ) + assert serialize_url_search_params({"🔒": "a"}) == "%F0%9F%94%92=a" + assert serialize_url_search_params({"a b": 1}) == "a+b=1" + + +def test_cannot_serialize_keys_containing_a_dot(): + with pytest.raises(UnserializableParamError): + serialize_url_search_params({"foo.bar": 1}) + + with pytest.raises(UnserializableParamError): + serialize_url_search_params({"foo": {"bar.baz": 1}}) + + +def test_cannot_serialize_non_string_keys(): + with pytest.raises(UnserializableParamError): + serialize_url_search_params({1: "a"}) + + +def test_cannot_serialize_functions(): + with pytest.raises(UnserializableParamError): + serialize_url_search_params({"foo": lambda: None}) + + +def test_cannot_serialize_number_pointers(): + with pytest.raises(UnserializableParamError): + serialize_url_search_params({"foo": float("inf")}) + + with pytest.raises(UnserializableParamError): + serialize_url_search_params({"foo": float("-inf")}) + + with pytest.raises(UnserializableParamError): + serialize_url_search_params({"foo": float("nan")}) + + +def test_cannot_serialize_arbitrary_objects(): + class Device: + def __init__(self): + self.device_id = "a" + + with pytest.raises(UnserializableParamError): + serialize_url_search_params({"foo": Device()}) + + +def test_cannot_serialize_date(): + # A date is not an instant, so it has no unambiguous serialization. + with pytest.raises(UnserializableParamError): + serialize_url_search_params({"foo": date(2025, 2, 24)}) + + +def test_cannot_serialize_sets(): + # A set would not serialize deterministically. + with pytest.raises(UnserializableParamError): + serialize_url_search_params({"foo": {"a", "b"}}) + + +def test_cannot_serialize_array_params_with_unserializable_values(): + with pytest.raises(UnserializableParamError): + serialize_url_search_params({"foo": [""]}) + + with pytest.raises(UnserializableParamError): + serialize_url_search_params({"bar": ["a", None]}) + + with pytest.raises(UnserializableParamError): + serialize_url_search_params({"bar": ["a", UNDEFINED]}) + + with pytest.raises(UnserializableParamError): + serialize_url_search_params({"bar": ["a", ["s"]]}) + + with pytest.raises(UnserializableParamError): + serialize_url_search_params({"bar": ["a", []]}) + + with pytest.raises(UnserializableParamError): + serialize_url_search_params({"bar": ["a", [""]]}) + + with pytest.raises(UnserializableParamError): + serialize_url_search_params({"bar": ["a", {}]}) + + with pytest.raises(UnserializableParamError): + serialize_url_search_params({"bar": ["a", {"x": 2}]}) + + with pytest.raises(UnserializableParamError): + serialize_url_search_params({"bar": ["a", lambda: None]}) + + with pytest.raises(UnserializableParamError): + serialize_url_search_params({"foo": 1, "bar": ["", "a", ""]}) + + with pytest.raises(UnserializableParamError): + serialize_url_search_params({"foo": 1, "bar": ["", "a", "2"]}) + + with pytest.raises(UnserializableParamError): + serialize_url_search_params({"foo": 1, "bar": ["", "", ""]}) + + with pytest.raises(UnserializableParamError): + serialize_url_search_params({"foo": [1, float("nan")]}) + + +def test_unserializable_param_error_message(): + with pytest.raises(UnserializableParamError) as error: + serialize_url_search_params({"foo": {"bar.baz": 1}}) + + assert str(error.value) == ( + "Could not serialize parameter: 'bar.baz' contains one or more dots" + ' "." in its name which is unsupported' + ) + assert error.value.name == "bar.baz" + + +def test_unserializable_param_error_message_uses_the_full_path(): + with pytest.raises(UnserializableParamError) as error: + serialize_url_search_params({"foo": {"bar": float("nan")}}) + + assert str(error.value) == "Could not serialize parameter: 'foo.bar' is NaN" + + +def test_update_url_search_params(): + search_params = UrlSearchParams() + update_url_search_params(search_params, {"foo": "d", "bar": 2}) + + assert search_params.to_string() == "bar=2&foo=d" + + +def test_update_url_search_params_preserves_existing_params(): + search_params = UrlSearchParams([("foo", "bar")]) + update_url_search_params( + search_params, + {"name": "Dax", "age": 27, "isAdmin": True, "tags": ["cars", "planes"]}, + ) + + assert search_params.to_string() == ( + "age=27&foo=bar&isAdmin=true&name=Dax&tags=cars&tags=planes" + ) + + +def test_update_url_search_params_overwrites_existing_params(): + search_params = UrlSearchParams([("foo", "a"), ("bar", "x"), ("foo", "b")]) + update_url_search_params(search_params, {"foo": "new"}) + + assert search_params.to_string() == "bar=x&foo=new" + + +def test_update_url_search_params_appends_array_params(): + search_params = UrlSearchParams([("foo", "old")]) + update_url_search_params(search_params, {"foo": [1, 2]}) + + assert search_params.to_string() == "foo=old&foo=1&foo=2" + + +def test_update_url_search_params_keeps_existing_params_for_absent_values(): + for value in [UNDEFINED, "", {}]: + search_params = UrlSearchParams([("foo", "a")]) + update_url_search_params(search_params, {"foo": value}) + + assert search_params.to_string() == "foo=a" + + +def test_url_search_params_from_query_string(): + search_params = UrlSearchParams("?a=1&b=hello+world&c=%F0%9F%94%92&d") + + assert search_params.get("a") == "1" + assert search_params.get("b") == "hello world" + assert search_params.get("c") == "🔒" + assert search_params.get("d") == "" + assert search_params.to_string() == "a=1&b=hello+world&c=%F0%9F%94%92&d=" + + +def test_url_search_params_from_dict(): + assert UrlSearchParams({"a": "1", "b": "2"}).to_string() == "a=1&b=2" + + +def test_url_search_params_append_and_get(): + search_params = UrlSearchParams() + search_params.append("foo", "a") + search_params.append("foo", "b") + + assert search_params.get("foo") == "a" + assert search_params.get_all("foo") == ["a", "b"] + assert search_params.get("bar") is None + assert search_params.get_all("bar") == [] + assert len(search_params) == 2 + assert list(search_params) == [("foo", "a"), ("foo", "b")] + + +def test_url_search_params_set(): + search_params = UrlSearchParams([("foo", "a"), ("bar", "x"), ("foo", "b")]) + search_params.set("foo", "c") + + assert list(search_params) == [("foo", "c"), ("bar", "x")] + + search_params.set("baz", "y") + + assert search_params.get("baz") == "y" + + +def test_url_search_params_has_and_delete(): + search_params = UrlSearchParams([("foo", "a"), ("foo", "b")]) + + assert search_params.has("foo") + + search_params.delete("foo") + + assert not search_params.has("foo") + assert len(search_params) == 0 + + +def test_url_search_params_str(): + assert str(UrlSearchParams([("foo", "a b")])) == "foo=a+b" From ee5c7c8c065dc5f981b49d3ce6848967b93945d5 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Aug 2026 17:18:35 +0000 Subject: [PATCH 02/11] feat: send explicit null request params with seam.NULL The Seam API distinguishes an omitted param from a param explicitly set to null: in an update request, an omitted param leaves the current value unchanged while a null param unsets it, and some endpoints accept null as a meaningful filter value. Python has a single absence value, so route methods omitted both cases and there was no way to send null. For example, access_grants.list documents null as a filter for Access Grants without an access_grant_key, but passing None dropped the filter and returned every Access Grant. Add the NULL sentinel for a param explicitly set to null. Since sending null is rarely intended and unsetting a value cannot be undone, None keeps meaning the safe option of omitting the param, so this adds the capability without changing the behavior of any existing call. The existing generated route methods need no change: they already omit params set to None, and the client now replaces any remaining NULL sentinel with None so that json serializes it to null. NULL works at any depth, e.g., to clear a single key of an object param. Bind the URL search params serializer to the same convention, replacing its UNDEFINED sentinel: None is JavaScript undefined and is removed, while NULL is JavaScript null and serializes to an empty value. NULL is typed as Any so it may be passed to any param without a type error. Once blueprint exposes isNullable on Parameter, codegen can type nullable params precisely instead. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PpqwDhZmyikGbjmCPqFW2A --- README.rst | 46 ++++++++- seam/__init__.py | 2 +- seam/client.py | 6 ++ seam/null.py | 95 ++++++++++++++++++ seam/utils/url_search_params_serializer.py | 43 ++------ test/null_test.py | 110 +++++++++++++++++++++ test/url_search_params_serializer_test.py | 30 +++--- 7 files changed, 283 insertions(+), 49 deletions(-) create mode 100644 seam/null.py create mode 100644 test/null_test.py diff --git a/README.rst b/README.rst index e28551e0..587e9a7f 100644 --- a/README.rst +++ b/README.rst @@ -63,6 +63,8 @@ Contents * `Webhooks`_ + * `Omitted params and null params`_ + * `Advanced Usage`_ * `Setting the endpoint`_ @@ -449,6 +451,45 @@ see the `Svix docs for more examples in specific frameworks Any: return self.request("DELETE", url, json=json, **kwargs) def request(self, method, url, *args, **kwargs) -> Any: + # Route methods omit params set to None, so any remaining NULL sentinel + # is an explicit null and becomes None for JSON serialization. + if "json" in kwargs: + kwargs["json"] = replace_null(kwargs["json"]) + response = super().request(method, url, *args, **kwargs) return self._handle_response(response) diff --git a/seam/null.py b/seam/null.py new file mode 100644 index 00000000..c732d63f --- /dev/null +++ b/seam/null.py @@ -0,0 +1,95 @@ +"""The explicit null sentinel used by request params. + +Python has a single absence value, ``None``, but the Seam API distinguishes +an omitted param from a param explicitly set to null. For example, in an +update request, an omitted param leaves the current value unchanged, +while a null param unsets the current value. + +Since sending null is rarely intended and unsetting a value cannot be undone, +``None`` means the safe option of omitting the param. +Sending null is explicit and always spelled :data:`NULL`. +""" + +from collections.abc import Mapping +from typing import Any + + +class Null: + """Type of the :data:`NULL` sentinel.""" + + _instance = None + + def __new__(cls): + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + + def __repr__(self): + return "NULL" + + def __bool__(self): + return False + + +NULL: Any = Null() +"""Sentinel for a param explicitly set to null. + +Params set to this sentinel are sent as null, +whereas params set to ``None`` are omitted from the request. + +Use it wherever the Seam API documents null as a meaningful value, e.g., +to unset a value in an update request, or to filter by an unset value: + +.. code-block:: python + + from seam import NULL, Seam + + seam = Seam() + + # Unsets the name, leaving custom_metadata unchanged. + seam.devices.update(device_id=device_id, name=NULL) + + # Lists only the Access Grants which have no access_grant_key. + seam.access_grants.list(access_grant_key=NULL) + +This sentinel is typed as ``Any`` so that it may be passed +to any param without a type error. +""" + + +def is_null(value: Any) -> bool: + """Returns whether a value is the :data:`NULL` sentinel. + + :param value: The value to check + :type value: Any + + :returns: Whether the value is the ``NULL`` sentinel""" + + return isinstance(value, Null) + + +def replace_null(value: Any) -> Any: + """Recursively replaces the :data:`NULL` sentinel with ``None``. + + Returns a copy, so the given value is never modified. + Use this to prepare a request payload for JSON serialization, + where ``None`` is serialized to null. + + :param value: The value to convert + :type value: Any + + :returns: A copy of the value with every ``NULL`` sentinel replaced""" + + if is_null(value): + return None + + if isinstance(value, Mapping): + return {key: replace_null(item) for key, item in value.items()} + + if isinstance(value, list): + return [replace_null(item) for item in value] + + if isinstance(value, tuple): + return tuple(replace_null(item) for item in value) + + return value diff --git a/seam/utils/url_search_params_serializer.py b/seam/utils/url_search_params_serializer.py index 2c6e5588..0bdfcdeb 100644 --- a/seam/utils/url_search_params_serializer.py +++ b/seam/utils/url_search_params_serializer.py @@ -12,8 +12,10 @@ Type mapping between the reference implementation and this port: -- JavaScript ``undefined`` is :data:`UNDEFINED`, or simply an absent key. -- JavaScript ``null`` is ``None``. +- JavaScript ``undefined`` is ``None``, or simply an absent key. +- JavaScript ``null`` is :data:`seam.NULL `. + Python has a single absence value, so ``None`` means the safe option of + omitting the param and sending null is always explicit. - JavaScript ``string`` is ``str``. - JavaScript ``boolean`` is ``bool``. - JavaScript ``number`` is ``float`` or ``int``. @@ -38,6 +40,8 @@ from typing import Any, Iterator, List, Optional, Sequence, Tuple, Union from urllib.parse import parse_qsl +from ..null import is_null + Params = Mapping[str, Any] @@ -60,31 +64,6 @@ def __init__(self, name: str, message: str): self.name = name -class _Undefined: - """Type of the :data:`UNDEFINED` sentinel.""" - - _instance = None - - def __new__(cls): - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance - - def __repr__(self): - return "UNDEFINED" - - def __bool__(self): - return False - - -UNDEFINED = _Undefined() -"""Sentinel for the absence of a value, equivalent to JavaScript ``undefined``. - -Params set to this sentinel are removed, whereas params set to ``None`` -are serialized to an empty value. Omitting the key entirely is equivalent. -""" - - class UrlSearchParams: """A mutable collection of URL search params. @@ -296,7 +275,7 @@ def _nested_update_url_search_params( name = ".".join(current_path) - if _is_undefined(value): + if value is None: continue if isinstance(value, str) and len(value) == 0: @@ -328,7 +307,7 @@ def _update_url_search_params_from_array( "is an array containing the empty string which is unsupported", ) - if any(value is None or _is_undefined(value) for value in values): + if any(value is None or is_null(value) for value in values): raise UnserializableParamError( name, "is an array containing null or undefined values which is unsupported", @@ -339,7 +318,7 @@ def _update_url_search_params_from_array( def _serialize(name: str, value: Any) -> str: - if value is None: + if is_null(value): return "" if isinstance(value, str): @@ -364,10 +343,6 @@ def _is_empty_string(value: Any) -> bool: return isinstance(value, str) and len(value) == 0 -def _is_undefined(value: Any) -> bool: - return isinstance(value, _Undefined) - - def _format_datetime(value: datetime.datetime) -> str: if value.tzinfo is None: value = value.replace(tzinfo=datetime.timezone.utc) diff --git a/test/null_test.py b/test/null_test.py new file mode 100644 index 00000000..36b1a452 --- /dev/null +++ b/test/null_test.py @@ -0,0 +1,110 @@ +from collections import OrderedDict + +import niquests +import pytest + +from seam.client import SeamHttpClient +from seam.null import NULL, Null, is_null, replace_null + + +def test_null_is_a_singleton(): + assert Null() is NULL + assert is_null(NULL) + assert is_null(Null()) + + +def test_null_is_not_none(): + assert NULL is not None + assert not is_null(None) + assert not is_null("") + assert not is_null(0) + + +def test_null_is_falsy(): + assert not NULL + + +def test_null_repr(): + assert repr(NULL) == "NULL" + + +def test_replace_null(): + assert replace_null(NULL) is None + assert replace_null(None) is None + assert replace_null("a") == "a" + assert replace_null(0) == 0 + assert replace_null(False) is False + + +def test_replace_null_in_dict(): + assert replace_null({"a": NULL, "b": 1, "c": None}) == { + "a": None, + "b": 1, + "c": None, + } + + +def test_replace_null_in_nested_dict(): + assert replace_null({"a": {"b": {"c": NULL}}}) == {"a": {"b": {"c": None}}} + + +def test_replace_null_in_lists_and_tuples(): + assert replace_null(["a", NULL]) == ["a", None] + assert replace_null(("a", NULL)) == ("a", None) + assert replace_null({"a": [{"b": NULL}]}) == {"a": [{"b": None}]} + + +def test_replace_null_does_not_modify_the_given_value(): + params = {"a": NULL, "b": [NULL]} + replace_null(params) + + assert params == {"a": NULL, "b": [NULL]} + + +def test_replace_null_normalizes_mappings_to_dicts(): + result = replace_null(OrderedDict([("a", NULL)])) + + assert result == {"a": None} + + +class StubResponse: + status_code = 200 + headers = {"content-type": "application/json"} + + def json(self): + return {} + + +@pytest.fixture(name="sent_payloads") +def sent_payloads_fixture(monkeypatch): + payloads = [] + + # pylint: disable=unused-argument + def request(self, method, url, *args, **kwargs): + payloads.append(kwargs.get("json")) + return StubResponse() + + monkeypatch.setattr(niquests.Session, "request", request) + + return payloads + + +def test_client_sends_null_params_as_json_null(sent_payloads): + client = SeamHttpClient(base_url="https://example.com", auth_headers={}) + client.post("/devices/update", json={"device_id": "a", "name": NULL}) + + assert sent_payloads == [{"device_id": "a", "name": None}] + + +def test_client_sends_nested_null_params_as_json_null(sent_payloads): + client = SeamHttpClient(base_url="https://example.com", auth_headers={}) + client.post("/spaces/update", json={"customer_data": {"check_in": NULL}}) + + assert sent_payloads == [{"customer_data": {"check_in": None}}] + + +def test_client_passes_through_payloads_without_null_params(sent_payloads): + client = SeamHttpClient(base_url="https://example.com", auth_headers={}) + client.post("/devices/update", json={"device_id": "a", "name": "Front Door"}) + + assert sent_payloads == [{"device_id": "a", "name": "Front Door"}] diff --git a/test/url_search_params_serializer_test.py b/test/url_search_params_serializer_test.py index 08459ad8..6ce3a8e2 100644 --- a/test/url_search_params_serializer_test.py +++ b/test/url_search_params_serializer_test.py @@ -3,8 +3,8 @@ import pytest +from seam.null import NULL from seam.utils.url_search_params_serializer import ( - UNDEFINED, UnserializableParamError, UrlSearchParams, serialize_url_search_params, @@ -24,7 +24,8 @@ def test_serializes_string(): assert serialize_url_search_params({"foo": "0"}) == "foo=0" -def test_serializes_the_empty_string_to_undefined(): +def test_removes_the_empty_string(): + # Serializing the empty string would conflict with NULL. assert serialize_url_search_params({"foo": ""}) == "" assert serialize_url_search_params({"foo": "d", "bar": ""}) == "foo=d" @@ -79,14 +80,19 @@ def test_serializes_bool(): ) -def test_removes_undefined_params(): - assert serialize_url_search_params({"bar": UNDEFINED}) == "" - assert serialize_url_search_params({"foo": 1, "bar": UNDEFINED}) == "foo=1" +def test_removes_none_params(): + assert serialize_url_search_params({"bar": None}) == "" + assert serialize_url_search_params({"foo": 1, "bar": None}) == "foo=1" -def test_serializes_none_params(): - assert serialize_url_search_params({"bar": None}) == "bar=" - assert serialize_url_search_params({"foo": 1, "bar": None}) == "bar=&foo=1" +def test_serializes_null_params(): + assert serialize_url_search_params({"bar": NULL}) == "bar=" + assert serialize_url_search_params({"foo": 1, "bar": NULL}) == "bar=&foo=1" + + +def test_removes_none_params_at_any_depth(): + assert serialize_url_search_params({"foo": {"bar": None, "baz": 1}}) == "foo.baz=1" + assert serialize_url_search_params({"foo": {"bar": None}}) == "" def test_serializes_empty_array_params(): @@ -173,7 +179,7 @@ def test_serializes_dicts(): ) assert serialize_url_search_params( - {"foo": 1, "bar": {"baz": {"x": {"z": None}}}} + {"foo": 1, "bar": {"baz": {"x": {"z": NULL}}}} ) == ("bar.baz.x.z=&foo=1") assert serialize_url_search_params({"foo": 1, "bar": {"baz": [1, "a"]}}) == ( @@ -185,7 +191,7 @@ def test_serializes_dicts(): assert serialize_url_search_params({"foo": {"x": {}}, "bar": 2}) == "bar=2" assert serialize_url_search_params( - {"foo": {}, "bar": {"baz": {"x": {"z": None, "t": {}}, "q": {}}}} + {"foo": {}, "bar": {"baz": {"x": {"z": NULL, "t": {}}, "q": {}}}} ) == ("bar.baz.x.z=") @@ -303,7 +309,7 @@ def test_cannot_serialize_array_params_with_unserializable_values(): serialize_url_search_params({"bar": ["a", None]}) with pytest.raises(UnserializableParamError): - serialize_url_search_params({"bar": ["a", UNDEFINED]}) + serialize_url_search_params({"bar": ["a", NULL]}) with pytest.raises(UnserializableParamError): serialize_url_search_params({"bar": ["a", ["s"]]}) @@ -388,7 +394,7 @@ def test_update_url_search_params_appends_array_params(): def test_update_url_search_params_keeps_existing_params_for_absent_values(): - for value in [UNDEFINED, "", {}]: + for value in [None, "", {}]: search_params = UrlSearchParams([("foo", "a")]) update_url_search_params(search_params, {"foo": value}) From c95f986dfa29a1d35a4fbbb07f3d33b3f17fb1d8 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Aug 2026 22:53:41 +0000 Subject: [PATCH 03/11] feat: type nullable request params precisely Blueprint now reports isNullable for request parameters, so codegen can distinguish the params the Seam API documents as nullable from the rest. Type a nullable param as Union[T, Null] so it accepts the NULL sentinel, and leave every other param as it was. This makes NULL checkable. Previously NULL had to be typed as Any to be passed anywhere, which meant a type checker could not report sending null to a param that does not accept it. NULL is now typed as Null, so passing it to a non-nullable param such as devices.update(is_managed=...) is an error while access_grants.list(access_grant_key=NULL) is accepted. Reading isNullable requires blueprint 1.4.0 or later, which turns an untyped property from a warning into an error. The pinned types release leaves submit_args untyped for /seam/connect_webview/v1/submit, so generation fails against it; bump types to the next release, which defines that type and adds the between parameter to events.list. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PpqwDhZmyikGbjmCPqFW2A --- README.rst | 4 +- codegen/layouts/partials/method-signature.hbs | 2 +- codegen/layouts/route.hbs | 1 + codegen/lib/class-model.ts | 1 + codegen/lib/handlebars-helpers.ts | 5 + codegen/lib/layouts/route.ts | 2 + codegen/lib/routes.ts | 1 + seam/null.py | 6 +- seam/resources/access_code.py | 71 +- seam/resources/access_grant.py | 38 +- seam/resources/access_method.py | 27 +- seam/resources/acs_access_group.py | 43 +- seam/resources/acs_credential.py | 58 +- seam/resources/acs_encoder.py | 16 +- seam/resources/acs_entrance.py | 103 +-- seam/resources/acs_system.py | 36 +- seam/resources/acs_user.py | 62 +- seam/resources/action_attempt.py | 201 ++--- seam/resources/batch.py | 77 +- seam/resources/client_session.py | 8 +- seam/resources/connect_webview.py | 14 +- seam/resources/connected_account.py | 59 +- seam/resources/customer_portal.py | 4 +- seam/resources/device.py | 820 ++++-------------- seam/resources/device_provider.py | 31 +- seam/resources/instant_key.py | 8 +- seam/resources/noise_threshold.py | 3 +- seam/resources/phone.py | 43 +- seam/resources/seam_event.py | 136 +-- seam/resources/space.py | 12 +- seam/resources/thermostat_daily_program.py | 6 +- seam/resources/thermostat_schedule.py | 3 +- seam/resources/unmanaged_access_code.py | 48 +- seam/resources/unmanaged_access_grant.py | 38 +- seam/resources/unmanaged_access_method.py | 27 +- seam/resources/unmanaged_device.py | 118 +-- seam/resources/unmanaged_user_identity.py | 6 +- seam/resources/user_identity.py | 6 +- seam/resources/workspace.py | 17 +- seam/routes/access_codes.py | 419 +++------ seam/routes/access_codes_simulate.py | 25 +- seam/routes/access_codes_unmanaged.py | 155 +--- seam/routes/access_grants.py | 216 +---- seam/routes/access_grants_unmanaged.py | 75 +- seam/routes/access_methods.py | 194 +---- seam/routes/access_methods_unmanaged.py | 41 +- seam/routes/acs.py | 1 + seam/routes/acs_access_groups.py | 131 +-- seam/routes/acs_credentials.py | 197 +---- seam/routes/acs_encoders.py | 125 +-- seam/routes/acs_encoders_simulate.py | 125 +-- seam/routes/acs_entrances.py | 121 +-- seam/routes/acs_systems.py | 83 +- seam/routes/acs_users.py | 279 +----- seam/routes/action_attempts.py | 49 +- seam/routes/client_sessions.py | 163 +--- seam/routes/connect_webviews.py | 109 +-- seam/routes/connected_accounts.py | 120 +-- seam/routes/connected_accounts_simulate.py | 11 +- seam/routes/customers.py | 161 +--- seam/routes/devices.py | 135 +-- seam/routes/devices_simulate.py | 69 +- seam/routes/devices_unmanaged.py | 103 +-- seam/routes/events.py | 93 +- seam/routes/instant_keys.py | 33 +- seam/routes/locks.py | 115 +-- seam/routes/locks_simulate.py | 61 +- seam/routes/noise_sensors.py | 38 +- seam/routes/noise_sensors_noise_thresholds.py | 103 +-- seam/routes/noise_sensors_simulate.py | 15 +- seam/routes/phones.py | 33 +- seam/routes/phones_simulate.py | 35 +- seam/routes/spaces.py | 213 +---- seam/routes/thermostats.py | 428 ++------- seam/routes/thermostats_daily_programs.py | 61 +- seam/routes/thermostats_schedules.py | 115 +-- seam/routes/thermostats_simulate.py | 59 +- seam/routes/user_identities.py | 279 ++---- seam/routes/user_identities_unmanaged.py | 73 +- seam/routes/webhooks.py | 24 +- seam/routes/workspaces.py | 116 +-- 81 files changed, 1504 insertions(+), 5658 deletions(-) diff --git a/README.rst b/README.rst index 587e9a7f..0994f8bf 100644 --- a/README.rst +++ b/README.rst @@ -466,7 +466,9 @@ Python has a single absence value, so this SDK maps the two cases as follows: Sending null is rarely intended and unsetting a value cannot be undone, so ``None`` means the safe option of omitting the param -and sending null is always explicit: +and sending null is always explicit. +Route methods accept ``NULL`` only for the params the Seam API documents as +nullable, so a type checker reports passing it to any other param as an error: .. code-block:: python diff --git a/codegen/layouts/partials/method-signature.hbs b/codegen/layouts/partials/method-signature.hbs index 10977db0..4e58dbd1 100644 --- a/codegen/layouts/partials/method-signature.hbs +++ b/codegen/layouts/partials/method-signature.hbs @@ -1 +1 @@ -{{name}}(self{{#if params}}, *{{else}}{{#if (eq returnType "ActionAttempt")}}, *{{/if}}{{/if}}{{#each params}}, {{name}}: {{#if required}}{{type}}{{else}}Optional[{{type}}] = None{{/if}}{{/each}}{{#if (eq returnType "ActionAttempt")}}, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None{{/if}}) -> {{returnType}} \ No newline at end of file +{{name}}(self{{#if params}}, *{{else}}{{#if (eq returnType "ActionAttempt")}}, *{{/if}}{{/if}}{{#each params}}, {{name}}: {{#if required}}{{nullableType type isNullable}}{{else}}Optional[{{nullableType type isNullable}}] = None{{/if}}{{/each}}{{#if (eq returnType "ActionAttempt")}}, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None{{/if}}) -> {{returnType}} \ No newline at end of file diff --git a/codegen/layouts/route.hbs b/codegen/layouts/route.hbs index afaf2b43..f705b81d 100644 --- a/codegen/layouts/route.hbs +++ b/codegen/layouts/route.hbs @@ -2,6 +2,7 @@ from typing import Optional, Any, List, Dict, Union import abc from ..client import SeamHttpClient from ..route import route_metadata +from ..null import Null {{#if resourceClasses}} from ..resources import ({{#each resourceClasses}}{{this}}{{#unless @last}},{{/unless}}{{/each}}) {{/if}} diff --git a/codegen/lib/class-model.ts b/codegen/lib/class-model.ts index 9ecca230..3fd3cf17 100644 --- a/codegen/lib/class-model.ts +++ b/codegen/lib/class-model.ts @@ -9,6 +9,7 @@ export interface ClassMethodParameter { deprecationMessage: string position?: number | undefined required?: boolean | undefined + isNullable?: boolean | undefined } export interface ClassMethod { diff --git a/codegen/lib/handlebars-helpers.ts b/codegen/lib/handlebars-helpers.ts index dd4abeda..d7d9adc4 100644 --- a/codegen/lib/handlebars-helpers.ts +++ b/codegen/lib/handlebars-helpers.ts @@ -56,3 +56,8 @@ export const pythonIdentifier = (name: string): string => export const isListType = (type: string): boolean => type.startsWith('List[') export const listItemType = (type: string): string => type.slice(5, -1) + +// A nullable param accepts the NULL sentinel, which is sent as null. +// A param set to None is omitted from the request instead. +export const nullableType = (type: string, isNullable: boolean): string => + isNullable ? `Union[${type}, Null]` : type diff --git a/codegen/lib/layouts/route.ts b/codegen/lib/layouts/route.ts index be784fdc..53c623e1 100644 --- a/codegen/lib/layouts/route.ts +++ b/codegen/lib/layouts/route.ts @@ -27,6 +27,7 @@ export interface MethodLayoutContext { isDeprecated: boolean deprecationMessage: string required: boolean + isNullable: boolean }> returnPath: string[] returnType: string @@ -87,6 +88,7 @@ export const getMethodLayoutContext = ( isDeprecated: parameter.isDeprecated, deprecationMessage: parameter.deprecationMessage, required: parameter.required ?? false, + isNullable: parameter.isNullable ?? false, })), returnPath: method.returnPath, returnType: method.returnResource, diff --git a/codegen/lib/routes.ts b/codegen/lib/routes.ts index 2d5f33d1..d1d197ea 100644 --- a/codegen/lib/routes.ts +++ b/codegen/lib/routes.ts @@ -104,6 +104,7 @@ export const routes = ( deprecationMessage: parameter.deprecationMessage, position: parameter.name === idParameterName ? 0 : undefined, required: parameter.isRequired, + isNullable: parameter.isNullable, })), ...resolveResponse(response), }) diff --git a/seam/null.py b/seam/null.py index c732d63f..fe8bc4d8 100644 --- a/seam/null.py +++ b/seam/null.py @@ -31,7 +31,7 @@ def __bool__(self): return False -NULL: Any = Null() +NULL = Null() """Sentinel for a param explicitly set to null. Params set to this sentinel are sent as null, @@ -52,8 +52,8 @@ def __bool__(self): # Lists only the Access Grants which have no access_grant_key. seam.access_grants.list(access_grant_key=NULL) -This sentinel is typed as ``Any`` so that it may be passed -to any param without a type error. +Route methods accept this sentinel only for params the Seam API +documents as nullable, so passing it to any other param is a type error. """ diff --git a/seam/resources/access_code.py b/seam/resources/access_code.py index 7991193d..e77d6bfe 100644 --- a/seam/resources/access_code.py +++ b/seam/resources/access_code.py @@ -7,13 +7,13 @@ @dataclass class AccessCode: """Represents a smart lock `access code `_. - + An access code is a code used for a keypad or pinpad device. Unlike physical keys, which can easily be lost or duplicated, PIN codes can be customized, tracked, and altered on the fly. Using the Seam Access Code API, you can easily generate access codes on the hundreds of door lock models with which we integrate. - + Seam supports programming two types of access codes: `ongoing `_ and `time-bound `_. To differentiate between the two, refer to the ``type`` property of the access code. Ongoing codes display as ``ongoing``, whereas time-bound codes are labeled ``time_bound``. An ongoing access code is active, until it has been removed from the device. To specify an ongoing access code, leave both ``starts_at`` and ``ends_at`` empty. A time-bound access code will be programmed at the ``starts_at`` time and removed at the ``ends_at`` time. - + In addition, for certain devices, Seam also supports `offline access codes `_. Offline access (PIN) codes are designed for door locks that might not always maintain an internet connection. For this type of access code, the device manufacturer uses encryption keys (tokens) to create server-based registries of algorithmically-generated offline PIN codes. Because the tokens remain synchronized with the managed devices, the locks do not require an active internet connection—and you do not need to be near the locks—to create an offline access code. Then, owners or managers can share these offline codes with users through a variety of mechanisms, such as messaging applications. That is, lock users do not need to install a smartphone application to receive an offline access code. - + For granting a person access to a space, `Access Grants `_ are the default and recommended approach and work across both standalone smart locks and access systems. Use the lower-level Access Codes API directly only when you specifically need to manage individual PIN codes. :ivar access_code_id: Unique identifier for the access code. @@ -62,8 +62,7 @@ class AccessCode: :ivar warnings: Warnings associated with the `access code `_. - :ivar workspace_id: Unique identifier for the Seam workspace associated with the access code. - """ + :ivar workspace_id: Unique identifier for the Seam workspace associated with the access code.""" @dataclass class DormakabaOracodeMetadata(ResourceMapping): @@ -83,8 +82,7 @@ class DormakabaOracodeMetadata(ResourceMapping): :ivar user_level_id: Dormakaba Oracode user level ID associated with this access code. - :ivar user_level_name: Dormakaba Oracode user level name associated with this access code. - """ + :ivar user_level_name: Dormakaba Oracode user level name associated with this access code.""" is_cancellable: Optional[bool] is_early_checkin_able: Optional[bool] @@ -128,12 +126,11 @@ class Errors(ResourceMapping): :ivar modified_fields: List of fields that were changed externally, with their previous and new values. - :ivar is_connected_account_error: + :ivar is_connected_account_error: - :ivar is_device_error: + :ivar is_device_error: - :ivar is_bridge_error: Indicates whether the error is related to `Seam Bridge `_. - """ + :ivar is_bridge_error: Indicates whether the error is related to `Seam Bridge `_.""" @dataclass class ModifiedFields(ResourceMapping): @@ -179,10 +176,7 @@ def from_dict(cls, d: Any): managed_access_code_id=d.get("managed_access_code_id", None), unmanaged_access_code_id=d.get("unmanaged_access_code_id", None), change_type=d.get("change_type", None), - modified_fields=[ - cls.ModifiedFields.from_dict(i) - for i in d.get("modified_fields") or [] - ], + modified_fields=[cls.ModifiedFields.from_dict(i) for i in d.get("modified_fields") or []], is_connected_account_error=d.get("is_connected_account_error", None), is_device_error=d.get("is_device_error", None), is_bridge_error=d.get("is_bridge_error", None), @@ -196,13 +190,13 @@ class PendingMutations(ResourceMapping): :ivar message: Detailed description of the mutation. - :ivar mutation_code: + :ivar mutation_code: :ivar scheduled_at: Date and time at which Seam will attempt to program this access code on the device. - :ivar from_: + :ivar from_: - :ivar to:""" + :ivar to: """ @dataclass class From(ResourceMapping): @@ -270,11 +264,7 @@ def from_dict(cls, d: Any): message=d.get("message", None), mutation_code=d.get("mutation_code", None), scheduled_at=d.get("scheduled_at", None), - from_=( - cls.From.from_dict(d.get("from")) - if d.get("from") is not None - else None - ), + from_=cls.From.from_dict(d.get("from")) if d.get("from") is not None else None, to=cls.To.from_dict(d.get("to")) if d.get("to") is not None else None, ) @@ -290,8 +280,7 @@ class Warnings(ResourceMapping): :ivar change_type: Indicates the type of external modification. ``modified`` means the code's PIN or schedule was changed. ``removed`` means the code was deleted from the device. - :ivar modified_fields: List of fields that were changed externally, with their previous and new values. - """ + :ivar modified_fields: List of fields that were changed externally, with their previous and new values.""" @dataclass class ModifiedFields(ResourceMapping): @@ -328,10 +317,7 @@ def from_dict(cls, d: Any): message=d.get("message", None), warning_code=d.get("warning_code", None), change_type=d.get("change_type", None), - modified_fields=[ - cls.ModifiedFields.from_dict(i) - for i in d.get("modified_fields") or [] - ], + modified_fields=[cls.ModifiedFields.from_dict(i) for i in d.get("modified_fields") or []], ) access_code_id: str @@ -367,34 +353,19 @@ def from_dict(cls, d: Any): common_code_key=d.get("common_code_key", None), created_at=d.get("created_at", None), device_id=d.get("device_id", None), - dormakaba_oracode_metadata=( - cls.DormakabaOracodeMetadata.from_dict( - d.get("dormakaba_oracode_metadata") - ) - if d.get("dormakaba_oracode_metadata") is not None - else None - ), + dormakaba_oracode_metadata=cls.DormakabaOracodeMetadata.from_dict(d.get("dormakaba_oracode_metadata")) if d.get("dormakaba_oracode_metadata") is not None else None, ends_at=d.get("ends_at", None), errors=[cls.Errors.from_dict(i) for i in d.get("errors") or []], is_backup=d.get("is_backup", None), - is_backup_access_code_available=d.get( - "is_backup_access_code_available", None - ), - is_external_modification_allowed=d.get( - "is_external_modification_allowed", None - ), + is_backup_access_code_available=d.get("is_backup_access_code_available", None), + is_external_modification_allowed=d.get("is_external_modification_allowed", None), is_managed=d.get("is_managed", None), is_offline_access_code=d.get("is_offline_access_code", None), is_one_time_use=d.get("is_one_time_use", None), is_scheduled_on_device=d.get("is_scheduled_on_device", None), - is_waiting_for_code_assignment=d.get( - "is_waiting_for_code_assignment", None - ), + is_waiting_for_code_assignment=d.get("is_waiting_for_code_assignment", None), name=d.get("name", None), - pending_mutations=[ - cls.PendingMutations.from_dict(i) - for i in d.get("pending_mutations") or [] - ], + pending_mutations=[cls.PendingMutations.from_dict(i) for i in d.get("pending_mutations") or []], pulled_backup_access_code_id=d.get("pulled_backup_access_code_id", None), starts_at=d.get("starts_at", None), status=d.get("status", None), diff --git a/seam/resources/access_grant.py b/seam/resources/access_grant.py index 1d442c5f..8bb1a57f 100644 --- a/seam/resources/access_grant.py +++ b/seam/resources/access_grant.py @@ -58,8 +58,7 @@ class Errors(ResourceMapping): :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - :ivar missing_device_ids: IDs of the devices that did not receive an access code at grant creation. Use these to identify which specific devices failed when the message reports a partial failure. - """ + :ivar missing_device_ids: IDs of the devices that did not receive an access code at grant creation. Use these to identify which specific devices failed when the message reports a partial failure.""" created_at: str error_code: str @@ -81,13 +80,13 @@ class PendingMutations(ResourceMapping): :ivar created_at: Date and time at which the mutation was created. - :ivar from_: + :ivar from_: :ivar message: Detailed description of the mutation. - :ivar mutation_code: + :ivar mutation_code: - :ivar to: + :ivar to: :ivar access_method_ids: IDs of the access methods being updated.""" @@ -150,11 +149,7 @@ def from_dict(cls, d: Any): def from_dict(cls, d: Any): return cls( created_at=d.get("created_at", None), - from_=( - cls.From.from_dict(d.get("from")) - if d.get("from") is not None - else None - ), + from_=cls.From.from_dict(d.get("from")) if d.get("from") is not None else None, message=d.get("message", None), mutation_code=d.get("mutation_code", None), to=cls.To.from_dict(d.get("to")) if d.get("to") is not None else None, @@ -175,8 +170,7 @@ class RequestedAccessMethods(ResourceMapping): :ivar instant_key_max_use_count: Maximum number of times the instant key can be used. Only applicable when mode is 'mobile_key'. Defaults to 1 if not specified. - :ivar mode: Access method mode. Supported values: ``code``, ``card``, ``mobile_key``, ``cloud_key``. - """ + :ivar mode: Access method mode. Supported values: ``code``, ``card``, ``mobile_key``, ``cloud_key``.""" code: Optional[str] created_access_method_ids: List[str] @@ -210,14 +204,13 @@ class Warnings(ResourceMapping): :ivar access_method_ids: IDs of the access methods being updated. - :ivar device_id: + :ivar device_id: :ivar new_code: The new PIN code that was assigned instead. :ivar original_code: The originally requested PIN code that was unavailable. - :ivar reason: Specific reason why the grant's times are not programmable on the device. - """ + :ivar reason: Specific reason why the grant's times are not programmable on the device.""" @dataclass class FailedDevices(ResourceMapping): @@ -257,10 +250,7 @@ def from_dict(cls, d: Any): created_at=d.get("created_at", None), message=d.get("message", None), warning_code=d.get("warning_code", None), - failed_devices=[ - cls.FailedDevices.from_dict(i) - for i in d.get("failed_devices") or [] - ], + failed_devices=[cls.FailedDevices.from_dict(i) for i in d.get("failed_devices") or []], access_method_ids=d.get("access_method_ids", None), device_id=d.get("device_id", None), new_code=d.get("new_code", None), @@ -304,14 +294,8 @@ def from_dict(cls, d: Any): instant_key_url=d.get("instant_key_url", None), location_ids=d.get("location_ids", None), name=d.get("name", None), - pending_mutations=[ - cls.PendingMutations.from_dict(i) - for i in d.get("pending_mutations") or [] - ], - requested_access_methods=[ - cls.RequestedAccessMethods.from_dict(i) - for i in d.get("requested_access_methods") or [] - ], + pending_mutations=[cls.PendingMutations.from_dict(i) for i in d.get("pending_mutations") or []], + requested_access_methods=[cls.RequestedAccessMethods.from_dict(i) for i in d.get("requested_access_methods") or []], reservation_key=d.get("reservation_key", None), space_ids=d.get("space_ids", None), starts_at=d.get("starts_at", None), diff --git a/seam/resources/access_method.py b/seam/resources/access_method.py index d7614ada..b5f1aae6 100644 --- a/seam/resources/access_method.py +++ b/seam/resources/access_method.py @@ -52,8 +52,7 @@ class Errors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - """ + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" created_at: str error_code: str @@ -73,19 +72,19 @@ class PendingMutations(ResourceMapping): :ivar created_at: Date and time at which the mutation was created. - :ivar from_: + :ivar from_: :ivar message: Detailed description of the mutation. - :ivar mutation_code: + :ivar mutation_code: - :ivar to:""" + :ivar to: """ @dataclass class From(ResourceMapping): """ - :ivar device_ids: + :ivar device_ids: :ivar ends_at: Previous end time for access. @@ -107,7 +106,7 @@ def from_dict(cls, d: Any): class To(ResourceMapping): """ - :ivar device_ids: + :ivar device_ids: :ivar ends_at: New end time for access. @@ -135,11 +134,7 @@ def from_dict(cls, d: Any): def from_dict(cls, d: Any): return cls( created_at=d.get("created_at", None), - from_=( - cls.From.from_dict(d.get("from")) - if d.get("from") is not None - else None - ), + from_=cls.From.from_dict(d.get("from")) if d.get("from") is not None else None, message=d.get("message", None), mutation_code=d.get("mutation_code", None), to=cls.To.from_dict(d.get("to")) if d.get("to") is not None else None, @@ -155,8 +150,7 @@ class Warnings(ResourceMapping): :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - :ivar original_access_method_id: ID of the original access method from which this backup access method was split, if applicable. - """ + :ivar original_access_method_id: ID of the original access method from which this backup access method was split, if applicable.""" created_at: str message: str @@ -209,10 +203,7 @@ def from_dict(cls, d: Any): is_ready_for_encoding=d.get("is_ready_for_encoding", None), issued_at=d.get("issued_at", None), mode=d.get("mode", None), - pending_mutations=[ - cls.PendingMutations.from_dict(i) - for i in d.get("pending_mutations") or [] - ], + pending_mutations=[cls.PendingMutations.from_dict(i) for i in d.get("pending_mutations") or []], warnings=[cls.Warnings.from_dict(i) for i in d.get("warnings") or []], workspace_id=d.get("workspace_id", None), ) diff --git a/seam/resources/acs_access_group.py b/seam/resources/acs_access_group.py index ca32527b..c9ce5840 100644 --- a/seam/resources/acs_access_group.py +++ b/seam/resources/acs_access_group.py @@ -7,9 +7,9 @@ @dataclass class AcsAccessGroup: """Group that defines the entrances to which a set of users has access and, in some cases, the access schedule for these entrances and users. - + Some access control systems use `access group `_, which are sets of users, combined with sets of permissions. These permissions include both the set of areas or assets that the users can access and the schedule during which the users can access these areas or assets. Instead of assigning access rights individually to each access control system user, which can be time-consuming and error-prone, administrators can assign users to an access group, thereby ensuring that the users inherit all the permissions associated with the access group. Using access groups streamlines the process of managing large numbers of access control system users, especially in bigger organizations or complexes. - + To learn whether your access control system supports access groups, see the corresponding `system integration guide `_. :ivar access_group_type: Deprecated: Use ``external_type``. @@ -50,8 +50,7 @@ class AccessSchedule(ResourceMapping): :ivar ends_at: Date and time at which the user's access ends, in `ISO 8601 `_ format. - :ivar starts_at: Date and time at which the user's access starts, in `ISO 8601 `_ format. - """ + :ivar starts_at: Date and time at which the user's access starts, in `ISO 8601 `_ format.""" ends_at: Optional[str] starts_at: str @@ -71,8 +70,7 @@ class Errors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - """ + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" created_at: str error_code: str @@ -94,16 +92,15 @@ class PendingMutations(ResourceMapping): :ivar message: Detailed description of the mutation. - :ivar mutation_code: + :ivar mutation_code: - :ivar from_: + :ivar from_: - :ivar to: + :ivar to: :ivar acs_user_id: ID of the user involved in the scheduled change. - :ivar variant: Whether the user is scheduled to be added to or removed from this access group. - """ + :ivar variant: Whether the user is scheduled to be added to or removed from this access group.""" @dataclass class From(ResourceMapping): @@ -179,11 +176,7 @@ def from_dict(cls, d: Any): created_at=d.get("created_at", None), message=d.get("message", None), mutation_code=d.get("mutation_code", None), - from_=( - cls.From.from_dict(d.get("from")) - if d.get("from") is not None - else None - ), + from_=cls.From.from_dict(d.get("from")) if d.get("from") is not None else None, to=cls.To.from_dict(d.get("to")) if d.get("to") is not None else None, acs_user_id=d.get("acs_user_id", None), variant=d.get("variant", None), @@ -197,8 +190,7 @@ class Warnings(ResourceMapping): :ivar message: Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - """ + :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue.""" created_at: str message: str @@ -233,14 +225,8 @@ def from_dict(cls, d: Any): def from_dict(cls, d: Any): return cls( access_group_type=d.get("access_group_type", None), - access_group_type_display_name=d.get( - "access_group_type_display_name", None - ), - access_schedule=( - cls.AccessSchedule.from_dict(d.get("access_schedule")) - if d.get("access_schedule") is not None - else None - ), + access_group_type_display_name=d.get("access_group_type_display_name", None), + access_schedule=cls.AccessSchedule.from_dict(d.get("access_schedule")) if d.get("access_schedule") is not None else None, acs_access_group_id=d.get("acs_access_group_id", None), acs_system_id=d.get("acs_system_id", None), connected_account_id=d.get("connected_account_id", None), @@ -251,10 +237,7 @@ def from_dict(cls, d: Any): external_type_display_name=d.get("external_type_display_name", None), is_managed=d.get("is_managed", None), name=d.get("name", None), - pending_mutations=[ - cls.PendingMutations.from_dict(i) - for i in d.get("pending_mutations") or [] - ], + pending_mutations=[cls.PendingMutations.from_dict(i) for i in d.get("pending_mutations") or []], warnings=[cls.Warnings.from_dict(i) for i in d.get("warnings") or []], workspace_id=d.get("workspace_id", None), ) diff --git a/seam/resources/acs_credential.py b/seam/resources/acs_credential.py index b8f09b54..a29f5b81 100644 --- a/seam/resources/acs_credential.py +++ b/seam/resources/acs_credential.py @@ -7,11 +7,11 @@ @dataclass class AcsCredential: """Means by which an `access control system user `_ gains access at an `entrance `_. The ``acs_credential`` object represents a `credential `_ that provides an ACS user access within an `access control system `_. - + An access control system generally uses digital means of access to authorize a user trying to get through a specific entrance. Examples of credentials include plastic key cards, mobile keys, biometric identifiers, and PIN codes. The electronic nature of these credentials, as well as the fact that access is centralized, enables both the rapid provisioning and rescinding of access and the ability to compile access audit logs. - + For each ``acs_credential``, you define the access method. You can also specify additional properties, such as a PIN code, depending on the credential type. - + For granting a person access to a space, `Access Grants `_ are the default and recommended approach. Use the lower-level ACS credential API directly only when you specifically need to manage individual credentials. :ivar access_method: Access method for the `credential `_. Supported values: ``code``, ``card``, ``mobile_key``, ``cloud_key``. @@ -70,8 +70,7 @@ class AcsCredential: :ivar warnings: Warnings associated with the `credential `_. - :ivar workspace_id: ID of the workspace that contains the `credential `_. - """ + :ivar workspace_id: ID of the workspace that contains the `credential `_.""" @dataclass class AkilesMetadata(ResourceMapping): @@ -101,8 +100,7 @@ class AssaAbloyVostioMetadata(ResourceMapping): :ivar key_issuing_request_id: Key issuing request ID in the Vostio access system. - :ivar override_guest_acs_entrance_ids: IDs of the guest entrances to override in the Vostio access system. - """ + :ivar override_guest_acs_entrance_ids: IDs of the guest entrances to override in the Vostio access system.""" auto_join: Optional[bool] door_names: Optional[List[str]] @@ -119,9 +117,7 @@ def from_dict(cls, d: Any): endpoint_id=d.get("endpoint_id", None), key_id=d.get("key_id", None), key_issuing_request_id=d.get("key_issuing_request_id", None), - override_guest_acs_entrance_ids=d.get( - "override_guest_acs_entrance_ids", None - ), + override_guest_acs_entrance_ids=d.get("override_guest_acs_entrance_ids", None), ) @dataclass @@ -130,9 +126,9 @@ class Errors(ResourceMapping): :ivar created_at: Date and time at which Seam created the error. - :ivar error_code: + :ivar error_code: - :ivar message:""" + :ivar message: """ created_at: str error_code: str @@ -164,8 +160,7 @@ class VisionlineMetadata(ResourceMapping): :ivar is_valid: Indicates whether the credential is valid. - :ivar joiner_acs_credential_ids: IDs of the credentials to which you want to join. - """ + :ivar joiner_acs_credential_ids: IDs of the credentials to which you want to join.""" auto_join: Optional[bool] card_function_type: Optional[str] @@ -201,8 +196,7 @@ class Warnings(ResourceMapping): :ivar new_code: The PIN code that was assigned instead. - :ivar original_code: The originally requested PIN code that could not be used. - """ + :ivar original_code: The originally requested PIN code that could not be used.""" created_at: str message: str @@ -258,18 +252,8 @@ def from_dict(cls, d: Any): acs_credential_pool_id=d.get("acs_credential_pool_id", None), acs_system_id=d.get("acs_system_id", None), acs_user_id=d.get("acs_user_id", None), - akiles_metadata=( - cls.AkilesMetadata.from_dict(d.get("akiles_metadata")) - if d.get("akiles_metadata") is not None - else None - ), - assa_abloy_vostio_metadata=( - cls.AssaAbloyVostioMetadata.from_dict( - d.get("assa_abloy_vostio_metadata") - ) - if d.get("assa_abloy_vostio_metadata") is not None - else None - ), + akiles_metadata=cls.AkilesMetadata.from_dict(d.get("akiles_metadata")) if d.get("akiles_metadata") is not None else None, + assa_abloy_vostio_metadata=cls.AssaAbloyVostioMetadata.from_dict(d.get("assa_abloy_vostio_metadata")) if d.get("assa_abloy_vostio_metadata") is not None else None, card_number=d.get("card_number", None), code=d.get("code", None), connected_account_id=d.get("connected_account_id", None), @@ -280,26 +264,16 @@ def from_dict(cls, d: Any): external_type=d.get("external_type", None), external_type_display_name=d.get("external_type_display_name", None), is_issued=d.get("is_issued", None), - is_latest_desired_state_synced_with_provider=d.get( - "is_latest_desired_state_synced_with_provider", None - ), + is_latest_desired_state_synced_with_provider=d.get("is_latest_desired_state_synced_with_provider", None), is_managed=d.get("is_managed", None), - is_multi_phone_sync_credential=d.get( - "is_multi_phone_sync_credential", None - ), + is_multi_phone_sync_credential=d.get("is_multi_phone_sync_credential", None), is_one_time_use=d.get("is_one_time_use", None), issued_at=d.get("issued_at", None), - latest_desired_state_synced_with_provider_at=d.get( - "latest_desired_state_synced_with_provider_at", None - ), + latest_desired_state_synced_with_provider_at=d.get("latest_desired_state_synced_with_provider_at", None), parent_acs_credential_id=d.get("parent_acs_credential_id", None), starts_at=d.get("starts_at", None), user_identity_id=d.get("user_identity_id", None), - visionline_metadata=( - cls.VisionlineMetadata.from_dict(d.get("visionline_metadata")) - if d.get("visionline_metadata") is not None - else None - ), + visionline_metadata=cls.VisionlineMetadata.from_dict(d.get("visionline_metadata")) if d.get("visionline_metadata") is not None else None, warnings=[cls.Warnings.from_dict(i) for i in d.get("warnings") or []], workspace_id=d.get("workspace_id", None), ) diff --git a/seam/resources/acs_encoder.py b/seam/resources/acs_encoder.py index c1e4f936..ba7c2d3b 100644 --- a/seam/resources/acs_encoder.py +++ b/seam/resources/acs_encoder.py @@ -7,18 +7,18 @@ @dataclass class AcsEncoder: """Represents a hardware device that encodes `credential `_ data onto physical cards within an `access control system `_. - + Some access control systems require credentials to be encoded onto plastic key cards using a card encoder. This process involves the following two key steps: - + 1. Credential creation Configure the access parameters for the credential. 2. Card encoding Write the credential data onto the card using a compatible card encoder. - + Separately, the Seam API also supports card scanning, which enables you to scan and read the encoded data on a card. You can use this action to confirm consistency with access control system records or diagnose discrepancies if needed. - + See `Working with Card Encoders and Scanners `_. - + To verify if your access control system requires a card encoder, see the corresponding `system integration guide `_. :ivar acs_encoder_id: ID of the `encoder `_. @@ -33,8 +33,7 @@ class AcsEncoder: :ivar errors: Errors associated with the `encoder `_. - :ivar workspace_id: ID of the workspace that contains the `encoder `_. - """ + :ivar workspace_id: ID of the workspace that contains the `encoder `_.""" @dataclass class Errors(ResourceMapping): @@ -44,8 +43,7 @@ class Errors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - """ + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" created_at: str error_code: str diff --git a/seam/resources/acs_entrance.py b/seam/resources/acs_entrance.py index 491e6677..ff1f82ca 100644 --- a/seam/resources/acs_entrance.py +++ b/seam/resources/acs_entrance.py @@ -7,7 +7,7 @@ @dataclass class AcsEntrance: """Represents an `entrance `_ within an `access control system `_. - + In an access control system, an entrance is a secured door, gate, zone, or other method of entry. You can list details for all the ``acs_entrance`` resources in your workspace or get these details for a specific ``acs_entrance``. You can also list all entrances associated with a specific credential, and you can list all credentials associated with a specific entrance. :ivar acs_entrance_id: ID of the `entrance `_. @@ -58,8 +58,7 @@ class AcsEntrance: :ivar visionline_metadata: Visionline-specific metadata associated with the `entrance `_. - :ivar warnings: Warnings associated with the `entrance `_. - """ + :ivar warnings: Warnings associated with the `entrance `_.""" @dataclass class AkilesMetadata(ResourceMapping): @@ -117,8 +116,7 @@ class AssaAbloyVostioMetadata(ResourceMapping): :ivar pms_id: PMS ID of the door in the Vostio access system. - :ivar stand_open: Indicates whether keys are allowed to set the door in stand open mode in the Vostio access system. - """ + :ivar stand_open: Indicates whether keys are allowed to set the door in stand open mode in the Vostio access system.""" door_name: Optional[str] door_number: Optional[float] @@ -200,8 +198,7 @@ def from_dict(cls, d: Any): class DormakabaAmbianceMetadata(ResourceMapping): """dormakaba Ambiance-specific metadata associated with the `entrance `_. - :ivar access_point_name: Name of the access point in the dormakaba Ambiance access system. - """ + :ivar access_point_name: Name of the access point in the dormakaba Ambiance access system.""" access_point_name: Optional[str] @@ -215,8 +212,7 @@ def from_dict(cls, d: Any): class DormakabaCommunityMetadata(ResourceMapping): """dormakaba Community-specific metadata associated with the `entrance `_. - :ivar access_point_profile: Type of access point profile in the dormakaba Community access system. - """ + :ivar access_point_profile: Type of access point profile in the dormakaba Community access system.""" access_point_profile: Optional[str] @@ -234,8 +230,7 @@ class Errors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - """ + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" created_at: str error_code: str @@ -389,8 +384,7 @@ class Profiles(ResourceMapping): :ivar visionline_door_profile_id: Door profile ID in the Visionline access system. - :ivar visionline_door_profile_type: Door profile type in the Visionline access system. - """ + :ivar visionline_door_profile_type: Door profile type in the Visionline access system.""" visionline_door_profile_id: Optional[str] visionline_door_profile_type: Optional[str] @@ -398,12 +392,8 @@ class Profiles(ResourceMapping): @classmethod def from_dict(cls, d: Any): return cls( - visionline_door_profile_id=d.get( - "visionline_door_profile_id", None - ), - visionline_door_profile_type=d.get( - "visionline_door_profile_type", None - ), + visionline_door_profile_id=d.get("visionline_door_profile_id", None), + visionline_door_profile_type=d.get("visionline_door_profile_type", None), ) door_category: Optional[str] @@ -426,8 +416,7 @@ class Warnings(ResourceMapping): :ivar message: Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - """ + :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue.""" created_at: str message: str @@ -472,28 +461,10 @@ def from_dict(cls, d: Any): return cls( acs_entrance_id=d.get("acs_entrance_id", None), acs_system_id=d.get("acs_system_id", None), - akiles_metadata=( - cls.AkilesMetadata.from_dict(d.get("akiles_metadata")) - if d.get("akiles_metadata") is not None - else None - ), - assa_abloy_vostio_metadata=( - cls.AssaAbloyVostioMetadata.from_dict( - d.get("assa_abloy_vostio_metadata") - ) - if d.get("assa_abloy_vostio_metadata") is not None - else None - ), - avigilon_alta_metadata=( - cls.AvigilonAltaMetadata.from_dict(d.get("avigilon_alta_metadata")) - if d.get("avigilon_alta_metadata") is not None - else None - ), - brivo_metadata=( - cls.BrivoMetadata.from_dict(d.get("brivo_metadata")) - if d.get("brivo_metadata") is not None - else None - ), + akiles_metadata=cls.AkilesMetadata.from_dict(d.get("akiles_metadata")) if d.get("akiles_metadata") is not None else None, + assa_abloy_vostio_metadata=cls.AssaAbloyVostioMetadata.from_dict(d.get("assa_abloy_vostio_metadata")) if d.get("assa_abloy_vostio_metadata") is not None else None, + avigilon_alta_metadata=cls.AvigilonAltaMetadata.from_dict(d.get("avigilon_alta_metadata")) if d.get("avigilon_alta_metadata") is not None else None, + brivo_metadata=cls.BrivoMetadata.from_dict(d.get("brivo_metadata")) if d.get("brivo_metadata") is not None else None, can_belong_to_reservation=d.get("can_belong_to_reservation", None), can_unlock_with_card=d.get("can_unlock_with_card", None), can_unlock_with_cloud_key=d.get("can_unlock_with_cloud_key", None), @@ -502,47 +473,15 @@ def from_dict(cls, d: Any): connected_account_id=d.get("connected_account_id", None), created_at=d.get("created_at", None), display_name=d.get("display_name", None), - dormakaba_ambiance_metadata=( - cls.DormakabaAmbianceMetadata.from_dict( - d.get("dormakaba_ambiance_metadata") - ) - if d.get("dormakaba_ambiance_metadata") is not None - else None - ), - dormakaba_community_metadata=( - cls.DormakabaCommunityMetadata.from_dict( - d.get("dormakaba_community_metadata") - ) - if d.get("dormakaba_community_metadata") is not None - else None - ), + dormakaba_ambiance_metadata=cls.DormakabaAmbianceMetadata.from_dict(d.get("dormakaba_ambiance_metadata")) if d.get("dormakaba_ambiance_metadata") is not None else None, + dormakaba_community_metadata=cls.DormakabaCommunityMetadata.from_dict(d.get("dormakaba_community_metadata")) if d.get("dormakaba_community_metadata") is not None else None, errors=[cls.Errors.from_dict(i) for i in d.get("errors") or []], - hotek_metadata=( - cls.HotekMetadata.from_dict(d.get("hotek_metadata")) - if d.get("hotek_metadata") is not None - else None - ), + hotek_metadata=cls.HotekMetadata.from_dict(d.get("hotek_metadata")) if d.get("hotek_metadata") is not None else None, is_locked=d.get("is_locked", None), - latch_metadata=( - cls.LatchMetadata.from_dict(d.get("latch_metadata")) - if d.get("latch_metadata") is not None - else None - ), - salto_ks_metadata=( - cls.SaltoKsMetadata.from_dict(d.get("salto_ks_metadata")) - if d.get("salto_ks_metadata") is not None - else None - ), - salto_space_metadata=( - cls.SaltoSpaceMetadata.from_dict(d.get("salto_space_metadata")) - if d.get("salto_space_metadata") is not None - else None - ), + latch_metadata=cls.LatchMetadata.from_dict(d.get("latch_metadata")) if d.get("latch_metadata") is not None else None, + salto_ks_metadata=cls.SaltoKsMetadata.from_dict(d.get("salto_ks_metadata")) if d.get("salto_ks_metadata") is not None else None, + salto_space_metadata=cls.SaltoSpaceMetadata.from_dict(d.get("salto_space_metadata")) if d.get("salto_space_metadata") is not None else None, space_ids=d.get("space_ids", None), - visionline_metadata=( - cls.VisionlineMetadata.from_dict(d.get("visionline_metadata")) - if d.get("visionline_metadata") is not None - else None - ), + visionline_metadata=cls.VisionlineMetadata.from_dict(d.get("visionline_metadata")) if d.get("visionline_metadata") is not None else None, warnings=[cls.Warnings.from_dict(i) for i in d.get("warnings") or []], ) diff --git a/seam/resources/acs_system.py b/seam/resources/acs_system.py index 976a5070..e3e51f07 100644 --- a/seam/resources/acs_system.py +++ b/seam/resources/acs_system.py @@ -7,9 +7,9 @@ @dataclass class AcsSystem: """Represents an `access control system `_. - + Within an ``acs_system``, create ```acs_user``s `_ and ```acs_credential``s `_ to grant access to the ``acs_user``s. - + For details about the resources associated with an access control system, see the `access control systems namespace `_. :ivar acs_access_group_count: Number of access groups in the `access control system `_. @@ -50,8 +50,7 @@ class AcsSystem: :ivar warnings: Warnings associated with the `access control system `_. - :ivar workspace_id: ID of the workspace that contains the `access control system `_. - """ + :ivar workspace_id: ID of the workspace that contains the `access control system `_.""" @dataclass class Errors(ResourceMapping): @@ -63,8 +62,7 @@ class Errors(ResourceMapping): :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - :ivar is_bridge_error: Indicates whether the error is related to the `Seam Bridge `_. - """ + :ivar is_bridge_error: Indicates whether the error is related to the `Seam Bridge `_.""" created_at: str error_code: str @@ -84,8 +82,7 @@ def from_dict(cls, d: Any): class Location(ResourceMapping): """Location information for the `access control system `_. - :ivar time_zone: Time zone in which the `access control system `_ is located. - """ + :ivar time_zone: Time zone in which the `access control system `_ is located.""" time_zone: Optional[str] @@ -103,8 +100,7 @@ class VisionlineMetadata(ResourceMapping): :ivar mobile_access_uuid: Keyset loaded into a reader. Mobile keys and reader administration tools securely authenticate only with readers programmed with a matching keyset. - :ivar system_id: Unique ID assigned by the ASSA ABLOY licensing team that identifies each hotel in your credential manager. - """ + :ivar system_id: Unique ID assigned by the ASSA ABLOY licensing team that identifies each hotel in your credential manager.""" lan_address: Optional[str] mobile_access_uuid: Optional[str] @@ -141,9 +137,7 @@ def from_dict(cls, d: Any): created_at=d.get("created_at", None), message=d.get("message", None), warning_code=d.get("warning_code", None), - misconfigured_acs_entrance_ids=d.get( - "misconfigured_acs_entrance_ids", None - ), + misconfigured_acs_entrance_ids=d.get("misconfigured_acs_entrance_ids", None), ) acs_access_group_count: Optional[float] @@ -176,28 +170,18 @@ def from_dict(cls, d: Any): connected_account_id=d.get("connected_account_id", None), connected_account_ids=d.get("connected_account_ids", None), created_at=d.get("created_at", None), - default_credential_manager_acs_system_id=d.get( - "default_credential_manager_acs_system_id", None - ), + default_credential_manager_acs_system_id=d.get("default_credential_manager_acs_system_id", None), errors=[cls.Errors.from_dict(i) for i in d.get("errors") or []], external_type=d.get("external_type", None), external_type_display_name=d.get("external_type_display_name", None), image_alt_text=d.get("image_alt_text", None), image_url=d.get("image_url", None), is_credential_manager=d.get("is_credential_manager", None), - location=( - cls.Location.from_dict(d.get("location")) - if d.get("location") is not None - else None - ), + location=cls.Location.from_dict(d.get("location")) if d.get("location") is not None else None, name=d.get("name", None), system_type=d.get("system_type", None), system_type_display_name=d.get("system_type_display_name", None), - visionline_metadata=( - cls.VisionlineMetadata.from_dict(d.get("visionline_metadata")) - if d.get("visionline_metadata") is not None - else None - ), + visionline_metadata=cls.VisionlineMetadata.from_dict(d.get("visionline_metadata")) if d.get("visionline_metadata") is not None else None, warnings=[cls.Warnings.from_dict(i) for i in d.get("warnings") or []], workspace_id=d.get("workspace_id", None), ) diff --git a/seam/resources/acs_user.py b/seam/resources/acs_user.py index 3c794ab2..b3cefaaa 100644 --- a/seam/resources/acs_user.py +++ b/seam/resources/acs_user.py @@ -7,9 +7,9 @@ @dataclass class AcsUser: """Represents a `user `_ in an `access system `_. - + An access system user typically refers to an individual who requires access, like an employee or resident. Each user can possess multiple credentials that serve as their keys or identifiers for access. The type of credential can vary widely. For example, in the Salto system, a user can have a PIN code, a mobile app account, and a fob. In other platforms, it is not uncommon for a user to have more than one of the same credential type, such as multiple key cards. Additionally, these credentials can have a schedule or validity period. - + For details about how to configure users in your access system, see the corresponding `system integration guide `_. :ivar access_schedule: ``starts_at`` and ``ends_at`` timestamps for the `access system user's `_ access. @@ -60,8 +60,7 @@ class AcsUser: :ivar warnings: Warnings associated with the `access system user `_. - :ivar workspace_id: ID of the workspace that contains the `access system user `_. - """ + :ivar workspace_id: ID of the workspace that contains the `access system user `_.""" @dataclass class AccessSchedule(ResourceMapping): @@ -69,8 +68,7 @@ class AccessSchedule(ResourceMapping): :ivar ends_at: Date and time at which the user's access ends, in `ISO 8601 `_ format. - :ivar starts_at: Date and time at which the user's access starts, in `ISO 8601 `_ format. - """ + :ivar starts_at: Date and time at which the user's access starts, in `ISO 8601 `_ format.""" ends_at: Optional[str] starts_at: str @@ -88,10 +86,9 @@ class Errors(ResourceMapping): :ivar created_at: Date and time at which Seam created the error. - :ivar error_code: + :ivar error_code: - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - """ + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" created_at: str error_code: str @@ -113,18 +110,17 @@ class PendingMutations(ResourceMapping): :ivar message: Detailed description of the mutation. - :ivar mutation_code: + :ivar mutation_code: :ivar scheduled_at: Optional: When the user creation is scheduled to occur. - :ivar from_: + :ivar from_: - :ivar to: + :ivar to: :ivar acs_access_group_id: ID of the access group involved in the scheduled change. - :ivar variant: Whether the user is scheduled to be added to or removed from the access group. - """ + :ivar variant: Whether the user is scheduled to be added to or removed from the access group.""" @dataclass class From(ResourceMapping): @@ -140,7 +136,7 @@ class From(ResourceMapping): :ivar starts_at: Starting time for the access schedule. - :ivar is_suspended: + :ivar is_suspended: :ivar acs_access_group_id: Old access group ID. @@ -182,7 +178,7 @@ class To(ResourceMapping): :ivar starts_at: Starting time for the access schedule. - :ivar is_suspended: + :ivar is_suspended: :ivar acs_access_group_id: New access group ID. @@ -226,11 +222,7 @@ def from_dict(cls, d: Any): message=d.get("message", None), mutation_code=d.get("mutation_code", None), scheduled_at=d.get("scheduled_at", None), - from_=( - cls.From.from_dict(d.get("from")) - if d.get("from") is not None - else None - ), + from_=cls.From.from_dict(d.get("from")) if d.get("from") is not None else None, to=cls.To.from_dict(d.get("to")) if d.get("to") is not None else None, acs_access_group_id=d.get("acs_access_group_id", None), variant=d.get("variant", None), @@ -240,8 +232,7 @@ def from_dict(cls, d: Any): class SaltoKsMetadata(ResourceMapping): """Salto KS-specific metadata associated with the `access system user `_. - :ivar is_subscribed: Indicates whether the user holds an active subscription slot on the Salto KS site. Only subscribed users can unlock doors and count against the site's user-subscription limit. A user may not be subscribed because their access schedule has not started or has ended, the site has reached its subscription limit, or they were manually unsubscribed. This is distinct from ``is_suspended``, which reflects whether the user has been explicitly blocked. - """ + :ivar is_subscribed: Indicates whether the user holds an active subscription slot on the Salto KS site. Only subscribed users can unlock doors and count against the site's user-subscription limit. A user may not be subscribed because their access schedule has not started or has ended, the site has reached its subscription limit, or they were manually unsubscribed. This is distinct from ``is_suspended``, which reflects whether the user has been explicitly blocked.""" is_subscribed: Optional[bool] @@ -277,7 +268,7 @@ class Warnings(ResourceMapping): :ivar message: Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - :ivar warning_code:""" + :ivar warning_code: """ created_at: str message: str @@ -320,11 +311,7 @@ def from_dict(cls, d: Any): @classmethod def from_dict(cls, d: Any): return cls( - access_schedule=( - cls.AccessSchedule.from_dict(d.get("access_schedule")) - if d.get("access_schedule") is not None - else None - ), + access_schedule=cls.AccessSchedule.from_dict(d.get("access_schedule")) if d.get("access_schedule") is not None else None, acs_system_id=d.get("acs_system_id", None), acs_user_id=d.get("acs_user_id", None), connected_account_id=d.get("connected_account_id", None), @@ -339,21 +326,10 @@ def from_dict(cls, d: Any): hid_acs_system_id=d.get("hid_acs_system_id", None), is_managed=d.get("is_managed", None), is_suspended=d.get("is_suspended", None), - pending_mutations=[ - cls.PendingMutations.from_dict(i) - for i in d.get("pending_mutations") or [] - ], + pending_mutations=[cls.PendingMutations.from_dict(i) for i in d.get("pending_mutations") or []], phone_number=d.get("phone_number", None), - salto_ks_metadata=( - cls.SaltoKsMetadata.from_dict(d.get("salto_ks_metadata")) - if d.get("salto_ks_metadata") is not None - else None - ), - salto_space_metadata=( - cls.SaltoSpaceMetadata.from_dict(d.get("salto_space_metadata")) - if d.get("salto_space_metadata") is not None - else None - ), + salto_ks_metadata=cls.SaltoKsMetadata.from_dict(d.get("salto_ks_metadata")) if d.get("salto_ks_metadata") is not None else None, + salto_space_metadata=cls.SaltoSpaceMetadata.from_dict(d.get("salto_space_metadata")) if d.get("salto_space_metadata") is not None else None, user_identity_email_address=d.get("user_identity_email_address", None), user_identity_full_name=d.get("user_identity_full_name", None), user_identity_id=d.get("user_identity_id", None), diff --git a/seam/resources/action_attempt.py b/seam/resources/action_attempt.py index 0c0ff07e..d3f61968 100644 --- a/seam/resources/action_attempt.py +++ b/seam/resources/action_attempt.py @@ -10,13 +10,13 @@ class ActionAttempt: :ivar action_attempt_id: ID of the action attempt. - :ivar action_type: + :ivar action_type: :ivar error: Error associated with the action. - :ivar result: + :ivar result: - :ivar status:""" + :ivar status: """ @dataclass class Error(ResourceMapping): @@ -24,7 +24,7 @@ class Error(ResourceMapping): :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - :ivar type:""" + :ivar type: """ message: str type: str @@ -40,13 +40,13 @@ def from_dict(cls, d: Any): class Result(ResourceMapping): """ - :ivar was_confirmed_by_device: + :ivar was_confirmed_by_device: :ivar acs_credential_on_encoder: Snapshot of credential data read from the physical encoder. :ivar acs_credential_on_seam: Corresponding credential data as stored on Seam and the access system. - :ivar warnings: + :ivar warnings: :ivar access_method: Access method for the `credential `_. Supported values: ``code``, ``card``, ``mobile_key``, ``cloud_key``. @@ -64,23 +64,23 @@ class Result(ResourceMapping): :ivar card_number: Number of the card associated with the `credential `_. - :ivar code: + :ivar code: :ivar connected_account_id: ID of the `connected account `_ to which the `credential `_ belongs. - :ivar created_at: + :ivar created_at: - :ivar display_name: + :ivar display_name: :ivar ends_at: Date and time at which the `credential `_ validity ends, in `ISO 8601 `_ format. Must be a time in the future and after ``starts_at``. - :ivar errors: + :ivar errors: :ivar external_type: Brand-specific terminology for the `credential `_ type. Supported values: ``pti_card``, ``brivo_credential``, ``hid_credential``, ``visionline_card``. :ivar external_type_display_name: Display name that corresponds to the brand-specific terminology for the `credential `_ type. - :ivar is_issued: + :ivar is_issued: :ivar is_latest_desired_state_synced_with_provider: Indicates whether the latest state of the `credential `_ has been synced from Seam to the provider. @@ -90,7 +90,7 @@ class Result(ResourceMapping): :ivar is_one_time_use: Indicates whether the `credential `_ can only be used once. If ``true``, the code becomes invalid after the first use. - :ivar issued_at: + :ivar issued_at: :ivar latest_desired_state_synced_with_provider_at: Date and time at which the state of the `credential `_ was most recently synced from Seam to the provider. @@ -102,7 +102,7 @@ class Result(ResourceMapping): :ivar visionline_metadata: Visionline-specific metadata for the `credential `_. - :ivar workspace_id: + :ivar workspace_id: :ivar access_method_id: ID of the access method. @@ -124,9 +124,9 @@ class Result(ResourceMapping): :ivar pending_mutations: Pending mutations for the `access method `_. Indicates operations that are in progress. - :ivar access_code: + :ivar access_code: - :ivar noise_threshold:""" + :ivar noise_threshold: """ @dataclass class AcsCredentialOnEncoder(ResourceMapping): @@ -142,8 +142,7 @@ class AcsCredentialOnEncoder(ResourceMapping): :ivar starts_at: Date and time at which the `credential `_ becomes usable. - :ivar visionline_metadata: Visionline-specific metadata for the `credential `_. - """ + :ivar visionline_metadata: Visionline-specific metadata for the `credential `_.""" @dataclass class VisionlineMetadata(ResourceMapping): @@ -171,8 +170,7 @@ class VisionlineMetadata(ResourceMapping): :ivar overwritten: Indicates whether the card associated with the `credential `_ is overwritten. - :ivar pending_auto_update: Indicates whether the card associated with the `credential `_ is pending auto-update. - """ + :ivar pending_auto_update: Indicates whether the card associated with the `credential `_ is pending auto-update.""" cancelled: Optional[bool] card_format: Optional[str] @@ -219,11 +217,7 @@ def from_dict(cls, d: Any): ends_at=d.get("ends_at", None), is_issued=d.get("is_issued", None), starts_at=d.get("starts_at", None), - visionline_metadata=( - cls.VisionlineMetadata.from_dict(d.get("visionline_metadata")) - if d.get("visionline_metadata") is not None - else None - ), + visionline_metadata=cls.VisionlineMetadata.from_dict(d.get("visionline_metadata")) if d.get("visionline_metadata") is not None else None, ) @dataclass @@ -266,7 +260,7 @@ class AcsCredentialOnSeam(ResourceMapping): :ivar is_latest_desired_state_synced_with_provider: Indicates whether the latest state of the `credential `_ has been synced from Seam to the provider. - :ivar is_managed: + :ivar is_managed: :ivar is_multi_phone_sync_credential: Indicates whether the `credential `_ is a `multi-phone sync credential `_. @@ -286,8 +280,7 @@ class AcsCredentialOnSeam(ResourceMapping): :ivar warnings: Warnings associated with the `credential `_. - :ivar workspace_id: ID of the workspace that contains the `credential `_. - """ + :ivar workspace_id: ID of the workspace that contains the `credential `_.""" @dataclass class AkilesMetadata(ResourceMapping): @@ -317,8 +310,7 @@ class AssaAbloyVostioMetadata(ResourceMapping): :ivar key_issuing_request_id: Key issuing request ID in the Vostio access system. - :ivar override_guest_acs_entrance_ids: IDs of the guest entrances to override in the Vostio access system. - """ + :ivar override_guest_acs_entrance_ids: IDs of the guest entrances to override in the Vostio access system.""" auto_join: Optional[bool] door_names: Optional[List[str]] @@ -335,9 +327,7 @@ def from_dict(cls, d: Any): endpoint_id=d.get("endpoint_id", None), key_id=d.get("key_id", None), key_issuing_request_id=d.get("key_issuing_request_id", None), - override_guest_acs_entrance_ids=d.get( - "override_guest_acs_entrance_ids", None - ), + override_guest_acs_entrance_ids=d.get("override_guest_acs_entrance_ids", None), ) @dataclass @@ -346,9 +336,9 @@ class Errors(ResourceMapping): :ivar created_at: Date and time at which Seam created the error. - :ivar error_code: + :ivar error_code: - :ivar message:""" + :ivar message: """ created_at: str error_code: str @@ -380,8 +370,7 @@ class VisionlineMetadata(ResourceMapping): :ivar is_valid: Indicates whether the credential is valid. - :ivar joiner_acs_credential_ids: IDs of the credentials to which you want to join. - """ + :ivar joiner_acs_credential_ids: IDs of the credentials to which you want to join.""" auto_join: Optional[bool] card_function_type: Optional[str] @@ -402,9 +391,7 @@ def from_dict(cls, d: Any): credential_id=d.get("credential_id", None), guest_acs_entrance_ids=d.get("guest_acs_entrance_ids", None), is_valid=d.get("is_valid", None), - joiner_acs_credential_ids=d.get( - "joiner_acs_credential_ids", None - ), + joiner_acs_credential_ids=d.get("joiner_acs_credential_ids", None), ) @dataclass @@ -419,8 +406,7 @@ class Warnings(ResourceMapping): :ivar new_code: The PIN code that was assigned instead. - :ivar original_code: The originally requested PIN code that could not be used. - """ + :ivar original_code: The originally requested PIN code that could not be used.""" created_at: str message: str @@ -476,18 +462,8 @@ def from_dict(cls, d: Any): acs_credential_pool_id=d.get("acs_credential_pool_id", None), acs_system_id=d.get("acs_system_id", None), acs_user_id=d.get("acs_user_id", None), - akiles_metadata=( - cls.AkilesMetadata.from_dict(d.get("akiles_metadata")) - if d.get("akiles_metadata") is not None - else None - ), - assa_abloy_vostio_metadata=( - cls.AssaAbloyVostioMetadata.from_dict( - d.get("assa_abloy_vostio_metadata") - ) - if d.get("assa_abloy_vostio_metadata") is not None - else None - ), + akiles_metadata=cls.AkilesMetadata.from_dict(d.get("akiles_metadata")) if d.get("akiles_metadata") is not None else None, + assa_abloy_vostio_metadata=cls.AssaAbloyVostioMetadata.from_dict(d.get("assa_abloy_vostio_metadata")) if d.get("assa_abloy_vostio_metadata") is not None else None, card_number=d.get("card_number", None), code=d.get("code", None), connected_account_id=d.get("connected_account_id", None), @@ -496,33 +472,19 @@ def from_dict(cls, d: Any): ends_at=d.get("ends_at", None), errors=[cls.Errors.from_dict(i) for i in d.get("errors") or []], external_type=d.get("external_type", None), - external_type_display_name=d.get( - "external_type_display_name", None - ), + external_type_display_name=d.get("external_type_display_name", None), is_issued=d.get("is_issued", None), - is_latest_desired_state_synced_with_provider=d.get( - "is_latest_desired_state_synced_with_provider", None - ), + is_latest_desired_state_synced_with_provider=d.get("is_latest_desired_state_synced_with_provider", None), is_managed=d.get("is_managed", None), - is_multi_phone_sync_credential=d.get( - "is_multi_phone_sync_credential", None - ), + is_multi_phone_sync_credential=d.get("is_multi_phone_sync_credential", None), is_one_time_use=d.get("is_one_time_use", None), issued_at=d.get("issued_at", None), - latest_desired_state_synced_with_provider_at=d.get( - "latest_desired_state_synced_with_provider_at", None - ), + latest_desired_state_synced_with_provider_at=d.get("latest_desired_state_synced_with_provider_at", None), parent_acs_credential_id=d.get("parent_acs_credential_id", None), starts_at=d.get("starts_at", None), user_identity_id=d.get("user_identity_id", None), - visionline_metadata=( - cls.VisionlineMetadata.from_dict(d.get("visionline_metadata")) - if d.get("visionline_metadata") is not None - else None - ), - warnings=[ - cls.Warnings.from_dict(i) for i in d.get("warnings") or [] - ], + visionline_metadata=cls.VisionlineMetadata.from_dict(d.get("visionline_metadata")) if d.get("visionline_metadata") is not None else None, + warnings=[cls.Warnings.from_dict(i) for i in d.get("warnings") or []], workspace_id=d.get("workspace_id", None), ) @@ -530,7 +492,7 @@ def from_dict(cls, d: Any): class Warnings(ResourceMapping): """ - :ivar warning_code: + :ivar warning_code: :ivar warning_message: Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. @@ -542,8 +504,7 @@ class Warnings(ResourceMapping): :ivar original_code: The originally requested PIN code that could not be used. - :ivar original_access_method_id: ID of the original access method from which this backup access method was split, if applicable. - """ + :ivar original_access_method_id: ID of the original access method from which this backup access method was split, if applicable.""" warning_code: str warning_message: Optional[str] @@ -593,8 +554,7 @@ class AssaAbloyVostioMetadata(ResourceMapping): :ivar key_issuing_request_id: Key issuing request ID in the Vostio access system. - :ivar override_guest_acs_entrance_ids: IDs of the guest entrances to override in the Vostio access system. - """ + :ivar override_guest_acs_entrance_ids: IDs of the guest entrances to override in the Vostio access system.""" auto_join: Optional[bool] door_names: Optional[List[str]] @@ -611,9 +571,7 @@ def from_dict(cls, d: Any): endpoint_id=d.get("endpoint_id", None), key_id=d.get("key_id", None), key_issuing_request_id=d.get("key_issuing_request_id", None), - override_guest_acs_entrance_ids=d.get( - "override_guest_acs_entrance_ids", None - ), + override_guest_acs_entrance_ids=d.get("override_guest_acs_entrance_ids", None), ) @dataclass @@ -624,8 +582,7 @@ class Errors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - """ + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" created_at: str error_code: str @@ -657,8 +614,7 @@ class VisionlineMetadata(ResourceMapping): :ivar is_valid: Indicates whether the credential is valid. - :ivar joiner_acs_credential_ids: IDs of the credentials to which you want to join. - """ + :ivar joiner_acs_credential_ids: IDs of the credentials to which you want to join.""" auto_join: Optional[bool] card_function_type: Optional[str] @@ -742,18 +698,10 @@ def from_dict(cls, d: Any): def from_dict(cls, d: Any): return cls( created_at=d.get("created_at", None), - from_=( - cls.From.from_dict(d.get("from")) - if d.get("from") is not None - else None - ), + from_=cls.From.from_dict(d.get("from")) if d.get("from") is not None else None, message=d.get("message", None), mutation_code=d.get("mutation_code", None), - to=( - cls.To.from_dict(d.get("to")) - if d.get("to") is not None - else None - ), + to=cls.To.from_dict(d.get("to")) if d.get("to") is not None else None, ) was_confirmed_by_device: Optional[bool] @@ -805,36 +753,16 @@ def from_dict(cls, d: Any): def from_dict(cls, d: Any): return cls( was_confirmed_by_device=d.get("was_confirmed_by_device", None), - acs_credential_on_encoder=( - cls.AcsCredentialOnEncoder.from_dict( - d.get("acs_credential_on_encoder") - ) - if d.get("acs_credential_on_encoder") is not None - else None - ), - acs_credential_on_seam=( - cls.AcsCredentialOnSeam.from_dict(d.get("acs_credential_on_seam")) - if d.get("acs_credential_on_seam") is not None - else None - ), + acs_credential_on_encoder=cls.AcsCredentialOnEncoder.from_dict(d.get("acs_credential_on_encoder")) if d.get("acs_credential_on_encoder") is not None else None, + acs_credential_on_seam=cls.AcsCredentialOnSeam.from_dict(d.get("acs_credential_on_seam")) if d.get("acs_credential_on_seam") is not None else None, warnings=[cls.Warnings.from_dict(i) for i in d.get("warnings") or []], access_method=d.get("access_method", None), acs_credential_id=d.get("acs_credential_id", None), acs_credential_pool_id=d.get("acs_credential_pool_id", None), acs_system_id=d.get("acs_system_id", None), acs_user_id=d.get("acs_user_id", None), - akiles_metadata=( - cls.AkilesMetadata.from_dict(d.get("akiles_metadata")) - if d.get("akiles_metadata") is not None - else None - ), - assa_abloy_vostio_metadata=( - cls.AssaAbloyVostioMetadata.from_dict( - d.get("assa_abloy_vostio_metadata") - ) - if d.get("assa_abloy_vostio_metadata") is not None - else None - ), + akiles_metadata=cls.AkilesMetadata.from_dict(d.get("akiles_metadata")) if d.get("akiles_metadata") is not None else None, + assa_abloy_vostio_metadata=cls.AssaAbloyVostioMetadata.from_dict(d.get("assa_abloy_vostio_metadata")) if d.get("assa_abloy_vostio_metadata") is not None else None, card_number=d.get("card_number", None), code=d.get("code", None), connected_account_id=d.get("connected_account_id", None), @@ -845,26 +773,16 @@ def from_dict(cls, d: Any): external_type=d.get("external_type", None), external_type_display_name=d.get("external_type_display_name", None), is_issued=d.get("is_issued", None), - is_latest_desired_state_synced_with_provider=d.get( - "is_latest_desired_state_synced_with_provider", None - ), + is_latest_desired_state_synced_with_provider=d.get("is_latest_desired_state_synced_with_provider", None), is_managed=d.get("is_managed", None), - is_multi_phone_sync_credential=d.get( - "is_multi_phone_sync_credential", None - ), + is_multi_phone_sync_credential=d.get("is_multi_phone_sync_credential", None), is_one_time_use=d.get("is_one_time_use", None), issued_at=d.get("issued_at", None), - latest_desired_state_synced_with_provider_at=d.get( - "latest_desired_state_synced_with_provider_at", None - ), + latest_desired_state_synced_with_provider_at=d.get("latest_desired_state_synced_with_provider_at", None), parent_acs_credential_id=d.get("parent_acs_credential_id", None), starts_at=d.get("starts_at", None), user_identity_id=d.get("user_identity_id", None), - visionline_metadata=( - cls.VisionlineMetadata.from_dict(d.get("visionline_metadata")) - if d.get("visionline_metadata") is not None - else None - ), + visionline_metadata=cls.VisionlineMetadata.from_dict(d.get("visionline_metadata")) if d.get("visionline_metadata") is not None else None, workspace_id=d.get("workspace_id", None), access_method_id=d.get("access_method_id", None), client_session_token=d.get("client_session_token", None), @@ -875,10 +793,7 @@ def from_dict(cls, d: Any): is_ready_for_assignment=d.get("is_ready_for_assignment", None), is_ready_for_encoding=d.get("is_ready_for_encoding", None), mode=d.get("mode", None), - pending_mutations=[ - cls.PendingMutations.from_dict(i) - for i in d.get("pending_mutations") or [] - ], + pending_mutations=[cls.PendingMutations.from_dict(i) for i in d.get("pending_mutations") or []], access_code=DeepAttrDict(d.get("access_code", None)), noise_threshold=DeepAttrDict(d.get("noise_threshold", None)), ) @@ -894,15 +809,7 @@ 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")) 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, status=d.get("status", None), ) diff --git a/seam/resources/batch.py b/seam/resources/batch.py index 02ae27b1..aaa1b466 100644 --- a/seam/resources/batch.py +++ b/seam/resources/batch.py @@ -9,13 +9,13 @@ class Batch: """A batch of workspace resources. :ivar access_codes: Represents a smart lock `access code `_. - + An access code is a code used for a keypad or pinpad device. Unlike physical keys, which can easily be lost or duplicated, PIN codes can be customized, tracked, and altered on the fly. Using the Seam Access Code API, you can easily generate access codes on the hundreds of door lock models with which we integrate. - + Seam supports programming two types of access codes: `ongoing `_ and `time-bound `_. To differentiate between the two, refer to the ``type`` property of the access code. Ongoing codes display as ``ongoing``, whereas time-bound codes are labeled ``time_bound``. An ongoing access code is active, until it has been removed from the device. To specify an ongoing access code, leave both ``starts_at`` and ``ends_at`` empty. A time-bound access code will be programmed at the ``starts_at`` time and removed at the ``ends_at`` time. - + In addition, for certain devices, Seam also supports `offline access codes `_. Offline access (PIN) codes are designed for door locks that might not always maintain an internet connection. For this type of access code, the device manufacturer uses encryption keys (tokens) to create server-based registries of algorithmically-generated offline PIN codes. Because the tokens remain synchronized with the managed devices, the locks do not require an active internet connection—and you do not need to be near the locks—to create an offline access code. Then, owners or managers can share these offline codes with users through a variety of mechanisms, such as messaging applications. That is, lock users do not need to install a smartphone application to receive an offline access code. - + For granting a person access to a space, `Access Grants `_ are the default and recommended approach and work across both standalone smart locks and access systems. Use the lower-level Access Codes API directly only when you specifically need to manage individual PIN codes. :ivar access_grants: Represents an Access Grant. Access Grants enable you to grant a user identity access to spaces, entrances, and devices through one or more access methods, such as mobile keys, plastic cards, and PIN codes. You can create an Access Grant for an existing user identity, or you can create a new user identity *while* creating the new Access Grant. @@ -23,76 +23,76 @@ class Batch: :ivar access_methods: Represents an access method for an Access Grant. Access methods describe the modes of access, such as PIN codes, plastic cards, and mobile keys. For a mobile key, the access method also stores the URL for the associated Instant Key. :ivar acs_access_groups: Group that defines the entrances to which a set of users has access and, in some cases, the access schedule for these entrances and users. - + Some access control systems use `access group `_, which are sets of users, combined with sets of permissions. These permissions include both the set of areas or assets that the users can access and the schedule during which the users can access these areas or assets. Instead of assigning access rights individually to each access control system user, which can be time-consuming and error-prone, administrators can assign users to an access group, thereby ensuring that the users inherit all the permissions associated with the access group. Using access groups streamlines the process of managing large numbers of access control system users, especially in bigger organizations or complexes. - + To learn whether your access control system supports access groups, see the corresponding `system integration guide `_. :ivar acs_credentials: Means by which an `access control system user `_ gains access at an `entrance `_. The ``acs_credential`` object represents a `credential `_ that provides an ACS user access within an `access control system `_. - + An access control system generally uses digital means of access to authorize a user trying to get through a specific entrance. Examples of credentials include plastic key cards, mobile keys, biometric identifiers, and PIN codes. The electronic nature of these credentials, as well as the fact that access is centralized, enables both the rapid provisioning and rescinding of access and the ability to compile access audit logs. - + For each ``acs_credential``, you define the access method. You can also specify additional properties, such as a PIN code, depending on the credential type. - + For granting a person access to a space, `Access Grants `_ are the default and recommended approach. Use the lower-level ACS credential API directly only when you specifically need to manage individual credentials. :ivar acs_encoders: Represents a hardware device that encodes `credential `_ data onto physical cards within an `access control system `_. - + Some access control systems require credentials to be encoded onto plastic key cards using a card encoder. This process involves the following two key steps: - + 1. Credential creation Configure the access parameters for the credential. 2. Card encoding Write the credential data onto the card using a compatible card encoder. - + Separately, the Seam API also supports card scanning, which enables you to scan and read the encoded data on a card. You can use this action to confirm consistency with access control system records or diagnose discrepancies if needed. - + See `Working with Card Encoders and Scanners `_. - + To verify if your access control system requires a card encoder, see the corresponding `system integration guide `_. :ivar acs_entrances: Represents an `entrance `_ within an `access control system `_. - + In an access control system, an entrance is a secured door, gate, zone, or other method of entry. You can list details for all the ``acs_entrance`` resources in your workspace or get these details for a specific ``acs_entrance``. You can also list all entrances associated with a specific credential, and you can list all credentials associated with a specific entrance. :ivar acs_systems: Represents an `access control system `_. - + Within an ``acs_system``, create ```acs_user``s `_ and ```acs_credential``s `_ to grant access to the ``acs_user``s. - + For details about the resources associated with an access control system, see the `access control systems namespace `_. :ivar acs_users: Represents a `user `_ in an `access system `_. - + An access system user typically refers to an individual who requires access, like an employee or resident. Each user can possess multiple credentials that serve as their keys or identifiers for access. The type of credential can vary widely. For example, in the Salto system, a user can have a PIN code, a mobile app account, and a fob. In other platforms, it is not uncommon for a user to have more than one of the same credential type, such as multiple key cards. Additionally, these credentials can have a schedule or validity period. - + For details about how to configure users in your access system, see the corresponding `system integration guide `_. :ivar action_attempts: Represents an action attempt that enables you to keep track of the progress of your action that affects a physical device or system.actions against a device. Action attempts are useful because the physical world is intrinsically asynchronous. - + When you request for a device to perform an action, the Seam API immediately returns an action attempt object. In the background, the Seam API performs the action. - + See also `Action Attempts `_. :ivar client_sessions: Represents a `client session `_. If you want to restrict your users' access to their own devices, use client sessions. - + You create each client session with a custom ``user_identifier_key``. Normally, the ``user_identifier_key`` is a user ID that your application provides. - + When calling the Seam API from your backend using an API key, you can pass the ``user_identifier_key`` as a parameter to limit results to the associated client session. For example, ``/devices/list?user_identifier_key=123`` only returns devices associated with the client session created with the ``user_identifier_key`` ``123``. - + A client session has a token that you can use with the Seam JavaScript SDK to make requests from the client (browser) directly to the Seam API. The token restricts the user's access to only the devices that they own. - + See also `Get Started with React `_. :ivar connect_webviews: Represents a `Connect Webview `_. - + Connect Webviews are fully-embedded client-side components that you add to your app. Your users interact with your embedded Connect Webviews to link their IoT device or system accounts to Seam. That is, Connect Webviews walk your users through the process of logging in to their device or system accounts. Seam handles all the authentication steps, and—once your user has completed the authorization through your app—you can access and control their devices or systems using the Seam API. - + Connect Webviews perform credential validation, multifactor authentication (when applicable), and error handling for each brand that Seam supports. Further, Connect Webviews work across all modern browsers and platforms, including Chrome, Safari, and Firefox. - + To enable a user to connect their device or system account to Seam through your app, first create a ``connect_webview``. Once created, this ``connect_webview`` includes a URL that you can use to open an `iframe `_ or new window containing the Connect Webview for your user. - + When you create a Connect Webview, specify the desired provider category key in the ``provider_category`` parameter. Alternately, to specify a list of providers explicitly, use the ``accepted_providers`` parameter with a list of device provider keys. - + To list all providers within a category, use ``/devices/list_device_providers`` with the desired ``provider_category`` filter. To list all provider keys, use ``/devices/list_device_providers`` with no filters. :ivar connected_accounts: Represents a `connected account `_. A connected account is an external third-party account to which your user has authorized Seam to get access, for example, an August account with a list of door locks. @@ -100,11 +100,11 @@ class Batch: :ivar devices: Represents a `device `_ that has been connected to Seam. :ivar events: Represents an event. Events let you know when something interesting happens in your workspace. For example, when a lock is unlocked, Seam creates a ``lock.unlocked`` event. When a device's battery level is low, Seam creates a ``device.battery_low`` event. - + As with other API resources, you can retrieve an individual event or a list of events. Seam also provides a separate webhook system for sending the event objects directly to an endpoint on your sever. Manage webhooks through `Seam Console `_. You can also use the webhooks sandbox in Seam Console to see the different payloads for each event and test them against your own endpoints. :ivar instant_keys: Represents a Seam Instant Key. For issuing Bluetooth mobile keys, Instant Keys are the fastest way to share access. With a single API call, you can create a mobile key and send it through text or email or embed it in your own app. - + There’s no app to install, nor account to create. Your user just taps a link and gets a lightweight, native-feeling experience using iOS App Clip or Instant Apps on Android. Further, Instant Keys work offline, so even in areas with poor cellular or Wi-Fi, like elevator banks or concrete-walled hallways, the Instant Keys still work. :ivar noise_thresholds: Represents a `noise threshold `_ for a `noise sensor `_. Thresholds represent the limits of noise tolerated at a property, which can be customized for each hour of the day. Each device has its own default thresholds, but you can use the Seam API to modify them. @@ -116,23 +116,22 @@ class Batch: :ivar thermostat_schedules: Represents a `thermostat schedule `_ that activates a configured `climate preset `_ on a `thermostat `_ at a specified starting time and deactivates the climate preset at a specified ending time. :ivar unmanaged_access_codes: Represents an `unmanaged smart lock access code `_. - + An access code is a code used for a keypad or pinpad device. Unlike physical keys, which can easily be lost or duplicated, PIN codes can be customized, tracked, and altered on the fly. - + When you create an access code on a device in Seam, it is created as a managed access code. Access codes that exist on a device that were not created through Seam are considered unmanaged codes. We strictly limit the operations that can be performed on unmanaged codes. - + Prior to using Seam to manage your devices, you may have used another lock management system to manage the access codes on your devices. Where possible, we help you keep any existing access codes on devices and transition those codes to ones managed by your Seam workspace. - + Not all providers support unmanaged access codes. The following providers do not support unmanaged access codes: - + - `Kwikset `_ :ivar unmanaged_devices: Represents an `unmanaged device `_. An unmanaged device has a limited set of visible properties and a subset of supported events. You cannot control an unmanaged device. Any `access codes `_ on an unmanaged device are unmanaged. To control an unmanaged device with Seam, `convert it to a managed device `_. :ivar user_identities: Represents a `user identity `_ associated with an application user account. - :ivar workspaces: Represents a Seam `workspace `_. A workspace is a top-level entity that encompasses all other resources below it, such as devices, connected accounts, and Connect Webviews. Seam provides two types of workspaces. A `sandbox workspace `_ is a special type of workspace designed for testing code. Sandbox workspaces offer test device accounts and virtual devices that you can connect and control. This ability to work with virtual devices is quite handy because it removes the need to own physical devices from multiple brands. To connect real devices and systems to Seam, use a `production workspace `_. - """ + :ivar workspaces: Represents a Seam `workspace `_. A workspace is a top-level entity that encompasses all other resources below it, such as devices, connected accounts, and Connect Webviews. Seam provides two types of workspaces. A `sandbox workspace `_ is a special type of workspace designed for testing code. Sandbox workspaces offer test device accounts and virtual devices that you can connect and control. This ability to work with virtual devices is quite handy because it removes the need to own physical devices from multiple brands. To connect real devices and systems to Seam, use a `production workspace `_.""" access_codes: Optional[List[Dict[str, Any]]] access_grants: Optional[List[Dict[str, Any]]] diff --git a/seam/resources/client_session.py b/seam/resources/client_session.py index c4ec26b1..ff77ff6a 100644 --- a/seam/resources/client_session.py +++ b/seam/resources/client_session.py @@ -7,13 +7,13 @@ @dataclass class ClientSession: """Represents a `client session `_. If you want to restrict your users' access to their own devices, use client sessions. - + You create each client session with a custom ``user_identifier_key``. Normally, the ``user_identifier_key`` is a user ID that your application provides. - + When calling the Seam API from your backend using an API key, you can pass the ``user_identifier_key`` as a parameter to limit results to the associated client session. For example, ``/devices/list?user_identifier_key=123`` only returns devices associated with the client session created with the ``user_identifier_key`` ``123``. - + A client session has a token that you can use with the Seam JavaScript SDK to make requests from the client (browser) directly to the Seam API. The token restricts the user's access to only the devices that they own. - + See also `Get Started with React `_. :ivar client_session_id: ID of the client session. diff --git a/seam/resources/connect_webview.py b/seam/resources/connect_webview.py index 582894a2..344b8c0e 100644 --- a/seam/resources/connect_webview.py +++ b/seam/resources/connect_webview.py @@ -7,15 +7,15 @@ @dataclass class ConnectWebview: """Represents a `Connect Webview `_. - + Connect Webviews are fully-embedded client-side components that you add to your app. Your users interact with your embedded Connect Webviews to link their IoT device or system accounts to Seam. That is, Connect Webviews walk your users through the process of logging in to their device or system accounts. Seam handles all the authentication steps, and—once your user has completed the authorization through your app—you can access and control their devices or systems using the Seam API. - + Connect Webviews perform credential validation, multifactor authentication (when applicable), and error handling for each brand that Seam supports. Further, Connect Webviews work across all modern browsers and platforms, including Chrome, Safari, and Firefox. - + To enable a user to connect their device or system account to Seam through your app, first create a ``connect_webview``. Once created, this ``connect_webview`` includes a URL that you can use to open an `iframe `_ or new window containing the Connect Webview for your user. - + When you create a Connect Webview, specify the desired provider category key in the ``provider_category`` parameter. Alternately, to specify a list of providers explicitly, use the ``accepted_providers`` parameter with a list of device provider keys. - + To list all providers within a category, use ``/devices/list_device_providers`` with the desired ``provider_category`` filter. To list all provider keys, use ``/devices/list_device_providers`` with no filters. :ivar accepted_capabilities: High-level device capabilities that the Connect Webview can accept. When creating a Connect Webview, you can specify the types of devices that it can connect to Seam. If you do not set custom ``accepted_capabilities``, Seam uses a default set of ``accepted_capabilities`` for each provider. For example, if you create a Connect Webview that accepts SmartThing devices, without specifying ``accepted_capabilities``, Seam accepts only SmartThings locks. To connect SmartThings thermostats and locks to Seam, create a Connect Webview and include both ``thermostat`` and ``lock`` in the ``accepted_capabilities``. @@ -83,9 +83,7 @@ def from_dict(cls, d: Any): accepted_providers=d.get("accepted_providers", None), any_provider_allowed=d.get("any_provider_allowed", None), authorized_at=d.get("authorized_at", None), - automatically_manage_new_devices=d.get( - "automatically_manage_new_devices", None - ), + automatically_manage_new_devices=d.get("automatically_manage_new_devices", None), connect_webview_id=d.get("connect_webview_id", None), connected_account_id=d.get("connected_account_id", None), created_at=d.get("created_at", None), diff --git a/seam/resources/connected_account.py b/seam/resources/connected_account.py index c27e4e7f..af65c1e3 100644 --- a/seam/resources/connected_account.py +++ b/seam/resources/connected_account.py @@ -58,15 +58,13 @@ class Errors(ResourceMapping): :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - :ivar salto_ks_metadata: Salto KS metadata associated with the connected account that has an error. - """ + :ivar salto_ks_metadata: Salto KS metadata associated with the connected account that has an error.""" @dataclass class SaltoKsMetadata(ResourceMapping): """Salto KS metadata associated with the connected account that has an error. - :ivar sites: Salto sites associated with the connected account that has an error. - """ + :ivar sites: Salto sites associated with the connected account that has an error.""" @dataclass class Sites(ResourceMapping): @@ -78,8 +76,7 @@ class Sites(ResourceMapping): :ivar site_user_subscription_limit: Subscription limit of site users for a Salto site associated with the connected account that has an error. - :ivar subscribed_site_user_count: Count of subscribed site users for a Salto site associated with the connected account that has an error. - """ + :ivar subscribed_site_user_count: Count of subscribed site users for a Salto site associated with the connected account that has an error.""" site_id: Optional[str] site_name: Optional[str] @@ -91,12 +88,8 @@ def from_dict(cls, d: Any): return cls( site_id=d.get("site_id", None), site_name=d.get("site_name", None), - site_user_subscription_limit=d.get( - "site_user_subscription_limit", None - ), - subscribed_site_user_count=d.get( - "subscribed_site_user_count", None - ), + site_user_subscription_limit=d.get("site_user_subscription_limit", None), + subscribed_site_user_count=d.get("subscribed_site_user_count", None), ) sites: Optional[List[Sites]] @@ -122,11 +115,7 @@ def from_dict(cls, d: Any): is_bridge_error=d.get("is_bridge_error", None), is_connected_account_error=d.get("is_connected_account_error", None), message=d.get("message", None), - salto_ks_metadata=( - cls.SaltoKsMetadata.from_dict(d.get("salto_ks_metadata")) - if d.get("salto_ks_metadata") is not None - else None - ), + salto_ks_metadata=cls.SaltoKsMetadata.from_dict(d.get("salto_ks_metadata")) if d.get("salto_ks_metadata") is not None else None, ) @dataclass @@ -141,8 +130,7 @@ class UserIdentifier(ResourceMapping): :ivar phone: Phone number of the user identifier associated with the connected account. - :ivar username: Username of the user identifier associated with the connected account. - """ + :ivar username: Username of the user identifier associated with the connected account.""" api_url: Optional[str] email: Optional[str] @@ -170,15 +158,13 @@ class Warnings(ResourceMapping): :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - :ivar salto_ks_metadata: Salto KS metadata associated with the connected account that has a warning. - """ + :ivar salto_ks_metadata: Salto KS metadata associated with the connected account that has a warning.""" @dataclass class SaltoKsMetadata(ResourceMapping): """Salto KS metadata associated with the connected account that has a warning. - :ivar sites: Salto sites associated with the connected account that has a warning. - """ + :ivar sites: Salto sites associated with the connected account that has a warning.""" @dataclass class Sites(ResourceMapping): @@ -190,8 +176,7 @@ class Sites(ResourceMapping): :ivar site_user_subscription_limit: Subscription limit of site users for a Salto site associated with the connected account that has a warning. - :ivar subscribed_site_user_count: Count of subscribed site users for a Salto site associated with the connected account that has a warning. - """ + :ivar subscribed_site_user_count: Count of subscribed site users for a Salto site associated with the connected account that has a warning.""" site_id: Optional[str] site_name: Optional[str] @@ -203,12 +188,8 @@ def from_dict(cls, d: Any): return cls( site_id=d.get("site_id", None), site_name=d.get("site_name", None), - site_user_subscription_limit=d.get( - "site_user_subscription_limit", None - ), - subscribed_site_user_count=d.get( - "subscribed_site_user_count", None - ), + site_user_subscription_limit=d.get("site_user_subscription_limit", None), + subscribed_site_user_count=d.get("subscribed_site_user_count", None), ) sites: Optional[List[Sites]] @@ -230,11 +211,7 @@ def from_dict(cls, d: Any): created_at=d.get("created_at", None), message=d.get("message", None), warning_code=d.get("warning_code", None), - salto_ks_metadata=( - cls.SaltoKsMetadata.from_dict(d.get("salto_ks_metadata")) - if d.get("salto_ks_metadata") is not None - else None - ), + salto_ks_metadata=cls.SaltoKsMetadata.from_dict(d.get("salto_ks_metadata")) if d.get("salto_ks_metadata") is not None else None, ) accepted_capabilities: List[str] @@ -262,9 +239,7 @@ def from_dict(cls, d: Any): accepted_capabilities=d.get("accepted_capabilities", None), account_type=d.get("account_type", None), account_type_display_name=d.get("account_type_display_name", None), - automatically_manage_new_devices=d.get( - "automatically_manage_new_devices", None - ), + automatically_manage_new_devices=d.get("automatically_manage_new_devices", None), connected_account_id=d.get("connected_account_id", None), created_at=d.get("created_at", None), custom_metadata=DeepAttrDict(d.get("custom_metadata", None)), @@ -277,10 +252,6 @@ def from_dict(cls, d: Any): ical_url=d.get("ical_url", None), image_url=d.get("image_url", None), time_zone=d.get("time_zone", None), - user_identifier=( - cls.UserIdentifier.from_dict(d.get("user_identifier")) - if d.get("user_identifier") is not None - else None - ), + user_identifier=cls.UserIdentifier.from_dict(d.get("user_identifier")) if d.get("user_identifier") is not None else None, warnings=[cls.Warnings.from_dict(i) for i in d.get("warnings") or []], ) diff --git a/seam/resources/customer_portal.py b/seam/resources/customer_portal.py index d49625df..9ab2ddc1 100644 --- a/seam/resources/customer_portal.py +++ b/seam/resources/customer_portal.py @@ -7,9 +7,9 @@ @dataclass class CustomerPortal: """Represents a Customer Portal. Customer Portal is a hosted, customizable interface for managing device access. It enables you to embed secure, pre-authenticated access flows into your product—either by sharing a link with users or embedding a view in an iframe. - + With Customer Portal, you no longer need to build out frontend experiences for physical access, thermostats, and sensors. Instead, you can ship enterprise-grade access control experiences in a fraction of the time, while maintaining your product's branding and user experience. - + Seam hosts these flows, handling everything from account connection and device mapping to full-featured device control. :ivar created_at: Date and time at which the customer portal link was created. diff --git a/seam/resources/device.py b/seam/resources/device.py index 827983a7..290e8ae9 100644 --- a/seam/resources/device.py +++ b/seam/resources/device.py @@ -80,8 +80,7 @@ class Device: :ivar warnings: Array of warnings associated with the device. Each warning object within the array contains two fields: ``warning_code`` and ``message``. ``warning_code`` is a string that uniquely identifies the type of warning, enabling quick recognition and categorization of the issue. ``message`` provides a more detailed description of the warning, offering insights into the issue and potentially how to rectify it. - :ivar workspace_id: Unique identifier for the Seam workspace associated with the device. - """ + :ivar workspace_id: Unique identifier for the Seam workspace associated with the device.""" @dataclass class DeviceManufacturer(ResourceMapping): @@ -91,8 +90,7 @@ class DeviceManufacturer(ResourceMapping): :ivar image_url: Image URL for the manufacturer logo. - :ivar manufacturer: Manufacturer identifier, such as ``august``, ``yale``, ``salto``, and so on. - """ + :ivar manufacturer: Manufacturer identifier, such as ``august``, ``yale``, ``salto``, and so on.""" display_name: str image_url: Optional[str] @@ -116,8 +114,7 @@ class DeviceProvider(ResourceMapping): :ivar image_url: Image URL for the device provider. - :ivar provider_category: Provider category. Indicates the third-party provider type, such as ``stable``, for stable integrations, or ``internal``, for internal integrations. - """ + :ivar provider_category: Provider category. Indicates the third-party provider type, such as ``stable``, for stable integrations, or ``internal``, for internal integrations.""" device_provider_name: str display_name: str @@ -141,14 +138,13 @@ class Errors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar is_connected_account_error: + :ivar is_connected_account_error: - :ivar is_device_error: + :ivar is_device_error: :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - :ivar is_bridge_error: Indicates whether the error is related to `Seam Bridge `_. - """ + :ivar is_bridge_error: Indicates whether the error is related to `Seam Bridge `_.""" created_at: str error_code: str @@ -178,8 +174,7 @@ class Location(ResourceMapping): :ivar time_zone: Time zone of the device location. - :ivar timezone: Deprecated: Use ``time_zone`` instead. Time zone of the device location. - """ + :ivar timezone: Deprecated: Use ``time_zone`` instead. Time zone of the device location.""" location_name: Optional[str] room_name: Optional[str] @@ -407,8 +402,7 @@ class Properties(ResourceMapping): :ivar thermostat_daily_programs: Configured `daily programs `_ for the thermostat. - :ivar thermostat_weekly_program: Current `weekly program `_ for the thermostat. - """ + :ivar thermostat_weekly_program: Current `weekly program `_ for the thermostat.""" @dataclass class AccessoryKeypad(ResourceMapping): @@ -416,14 +410,13 @@ class AccessoryKeypad(ResourceMapping): :ivar battery: Keypad battery properties. - :ivar is_connected: Indicates if an accessory keypad is connected to the device. - """ + :ivar is_connected: Indicates if an accessory keypad is connected to the device.""" @dataclass class Battery(ResourceMapping): """Keypad battery properties. - :ivar level:""" + :ivar level: """ level: float @@ -439,11 +432,7 @@ def from_dict(cls, d: Any): @classmethod def from_dict(cls, d: Any): return cls( - battery=( - cls.Battery.from_dict(d.get("battery")) - if d.get("battery") is not None - else None - ), + battery=cls.Battery.from_dict(d.get("battery")) if d.get("battery") is not None else None, is_connected=d.get("is_connected", None), ) @@ -451,8 +440,7 @@ def from_dict(cls, d: Any): class Appearance(ResourceMapping): """Appearance-related properties, as reported by the device. - :ivar name: Name of the device as seen from the provider API and application, not settable through Seam. - """ + :ivar name: Name of the device as seen from the provider API and application, not settable through Seam.""" name: str @@ -468,8 +456,7 @@ class Battery(ResourceMapping): :ivar level: Battery charge level as a value between 0 and 1, inclusive. - :ivar status: Represents the current status of the battery charge level. Values are ``critical``, which indicates an extremely low level, suggesting imminent shutdown or an urgent need for charging; ``low``, which signifies that the battery is under the preferred threshold and should be charged soon; ``good``, which denotes a satisfactory charge level, adequate for normal use without the immediate need for recharging; and ``full``, which represents a battery that is fully charged, providing the maximum duration of usage. - """ + :ivar status: Represents the current status of the battery charge level. Values are ``critical``, which indicates an extremely low level, suggesting imminent shutdown or an urgent need for charging; ``low``, which signifies that the battery is under the preferred threshold and should be charged soon; ``good``, which denotes a satisfactory charge level, adequate for normal use without the immediate need for recharging; and ``full``, which represents a battery that is fully charged, providing the maximum duration of usage.""" level: float status: str @@ -497,8 +484,7 @@ class Model(ResourceMapping): :ivar offline_access_codes_supported: Deprecated: use device.can_program_offline_access_codes. - :ivar online_access_codes_supported: Deprecated: use device.can_program_online_access_codes. - """ + :ivar online_access_codes_supported: Deprecated: use device.can_program_online_access_codes.""" accessory_keypad_supported: Optional[bool] can_connect_accessory_keypad: Optional[bool] @@ -511,21 +497,13 @@ class Model(ResourceMapping): @classmethod def from_dict(cls, d: Any): return cls( - accessory_keypad_supported=d.get( - "accessory_keypad_supported", None - ), - can_connect_accessory_keypad=d.get( - "can_connect_accessory_keypad", None - ), + accessory_keypad_supported=d.get("accessory_keypad_supported", None), + can_connect_accessory_keypad=d.get("can_connect_accessory_keypad", None), display_name=d.get("display_name", None), has_built_in_keypad=d.get("has_built_in_keypad", None), manufacturer_display_name=d.get("manufacturer_display_name", None), - offline_access_codes_supported=d.get( - "offline_access_codes_supported", None - ), - online_access_codes_supported=d.get( - "online_access_codes_supported", None - ), + offline_access_codes_supported=d.get("offline_access_codes_supported", None), + online_access_codes_supported=d.get("online_access_codes_supported", None), ) @dataclass @@ -534,8 +512,7 @@ class AssaAbloyCredentialServiceMetadata(ResourceMapping): :ivar endpoints: Endpoints associated with the phone. - :ivar has_active_endpoint: Indicates whether the credential service has active endpoints associated with the phone. - """ + :ivar has_active_endpoint: Indicates whether the credential service has active endpoints associated with the phone.""" @dataclass class Endpoints(ResourceMapping): @@ -561,9 +538,7 @@ def from_dict(cls, d: Any): @classmethod def from_dict(cls, d: Any): return cls( - endpoints=[ - cls.Endpoints.from_dict(i) for i in d.get("endpoints") or [] - ], + endpoints=[cls.Endpoints.from_dict(i) for i in d.get("endpoints") or []], has_active_endpoint=d.get("has_active_endpoint", None), ) @@ -571,8 +546,7 @@ def from_dict(cls, d: Any): class SaltoSpaceCredentialServiceMetadata(ResourceMapping): """Salto Space credential service metadata for the phone. - :ivar has_active_phone: Indicates whether the credential service has an active associated phone. - """ + :ivar has_active_phone: Indicates whether the credential service has an active associated phone.""" has_active_phone: Optional[bool] @@ -840,12 +814,8 @@ def from_dict(cls, d: Any): return cls( check_in_time=d.get("check_in_time", None), check_out_time=d.get("check_out_time", None), - dormakaba_oracode_user_level_id=d.get( - "dormakaba_oracode_user_level_id", None - ), - dormakaba_oracode_user_level_prefix=d.get( - "dormakaba_oracode_user_level_prefix", None - ), + dormakaba_oracode_user_level_id=d.get("dormakaba_oracode_user_level_id", None), + dormakaba_oracode_user_level_prefix=d.get("dormakaba_oracode_user_level_prefix", None), is_24_hour=d.get("is_24_hour", None), is_biweekly_mode=d.get("is_biweekly_mode", None), is_master=d.get("is_master", None), @@ -871,10 +841,7 @@ def from_dict(cls, d: Any): door_is_wireless=d.get("door_is_wireless", None), door_name=d.get("door_name", None), iana_timezone=d.get("iana_timezone", None), - predefined_time_slots=[ - cls.PredefinedTimeSlots.from_dict(i) - for i in d.get("predefined_time_slots") or [] - ], + predefined_time_slots=[cls.PredefinedTimeSlots.from_dict(i) for i in d.get("predefined_time_slots") or []], site_id=d.get("site_id", None), site_name=d.get("site_name", None), ) @@ -905,8 +872,7 @@ class FourSuitesMetadata(ResourceMapping): :ivar device_name: Device name for a 4SUITES device. - :ivar reclose_delay_in_seconds: Reclose delay, in seconds, for a 4SUITES device. - """ + :ivar reclose_delay_in_seconds: Reclose delay, in seconds, for a 4SUITES device.""" device_id: Optional[float] device_name: Optional[str] @@ -944,8 +910,7 @@ class HoneywellResideoMetadata(ResourceMapping): :ivar device_name: Device name for a Honeywell Resideo device. - :ivar honeywell_resideo_device_id: Device ID for a Honeywell Resideo device. - """ + :ivar honeywell_resideo_device_id: Device ID for a Honeywell Resideo device.""" device_name: Optional[str] honeywell_resideo_device_id: Optional[str] @@ -954,9 +919,7 @@ class HoneywellResideoMetadata(ResourceMapping): def from_dict(cls, d: Any): return cls( device_name=d.get("device_name", None), - honeywell_resideo_device_id=d.get( - "honeywell_resideo_device_id", None - ), + honeywell_resideo_device_id=d.get("honeywell_resideo_device_id", None), ) @dataclass @@ -1011,9 +974,7 @@ def from_dict(cls, d: Any): bridge_name=d.get("bridge_name", None), device_id=d.get("device_id", None), device_name=d.get("device_name", None), - is_accessory_keypad_linked_to_bridge=d.get( - "is_accessory_keypad_linked_to_bridge", None - ), + is_accessory_keypad_linked_to_bridge=d.get("is_accessory_keypad_linked_to_bridge", None), keypad_id=d.get("keypad_id", None), ) @@ -1153,8 +1114,7 @@ class KorelockMetadata(ResourceMapping): :ivar serial_number: Serial number for a Korelock device. - :ivar wifi_signal_strength: WiFi signal strength (0-1) for a Korelock device. - """ + :ivar wifi_signal_strength: WiFi signal strength (0-1) for a Korelock device.""" device_id: Optional[str] device_name: Optional[str] @@ -1250,8 +1210,7 @@ class AccelerometerZ(ResourceMapping): :ivar time: Time of latest accelerometer Z-axis reading for a Minut device. - :ivar value: Value of latest accelerometer Z-axis reading for a Minut device. - """ + :ivar value: Value of latest accelerometer Z-axis reading for a Minut device.""" time: Optional[str] value: Optional[float] @@ -1323,8 +1282,7 @@ class Temperature(ResourceMapping): :ivar time: Time of latest temperature reading for a Minut device. - :ivar value: Value of latest temperature reading for a Minut device. - """ + :ivar value: Value of latest temperature reading for a Minut device.""" time: Optional[str] value: Optional[float] @@ -1345,31 +1303,11 @@ def from_dict(cls, d: Any): @classmethod def from_dict(cls, d: Any): return cls( - accelerometer_z=( - cls.AccelerometerZ.from_dict(d.get("accelerometer_z")) - if d.get("accelerometer_z") is not None - else None - ), - humidity=( - cls.Humidity.from_dict(d.get("humidity")) - if d.get("humidity") is not None - else None - ), - pressure=( - cls.Pressure.from_dict(d.get("pressure")) - if d.get("pressure") is not None - else None - ), - sound=( - cls.Sound.from_dict(d.get("sound")) - if d.get("sound") is not None - else None - ), - temperature=( - cls.Temperature.from_dict(d.get("temperature")) - if d.get("temperature") is not None - else None - ), + accelerometer_z=cls.AccelerometerZ.from_dict(d.get("accelerometer_z")) if d.get("accelerometer_z") is not None else None, + humidity=cls.Humidity.from_dict(d.get("humidity")) if d.get("humidity") is not None else None, + pressure=cls.Pressure.from_dict(d.get("pressure")) if d.get("pressure") is not None else None, + sound=cls.Sound.from_dict(d.get("sound")) if d.get("sound") is not None else None, + temperature=cls.Temperature.from_dict(d.get("temperature")) if d.get("temperature") is not None else None, ) device_id: Optional[str] @@ -1381,11 +1319,7 @@ def from_dict(cls, d: Any): return cls( device_id=d.get("device_id", None), device_name=d.get("device_name", None), - latest_sensor_values=( - cls.LatestSensorValues.from_dict(d.get("latest_sensor_values")) - if d.get("latest_sensor_values") is not None - else None - ), + latest_sensor_values=cls.LatestSensorValues.from_dict(d.get("latest_sensor_values")) if d.get("latest_sensor_values") is not None else None, ) @dataclass @@ -1402,8 +1336,7 @@ class NestMetadata(ResourceMapping): :ivar nest_structure_id: ID of the Google Nest structure containing the device. - :ivar structure_name: Name of the Google Nest structure containing the device. The device owner sets this value. - """ + :ivar structure_name: Name of the Google Nest structure containing the device. The device owner sets this value.""" device_custom_name: Optional[str] device_name: Optional[str] @@ -1435,8 +1368,7 @@ class NoiseawareMetadata(ResourceMapping): :ivar noise_level_decibel: Noise level, in decibels, for a NoiseAware device. - :ivar noise_level_nrs: Noise level, expressed as a Noise Risk Score (NRS), for a NoiseAware device. - """ + :ivar noise_level_nrs: Noise level, expressed as a Noise Risk Score (NRS), for a NoiseAware device.""" device_id: Optional[str] device_model: Optional[str] @@ -1466,8 +1398,7 @@ class NukiMetadata(ResourceMapping): :ivar keypad_battery_critical: Indicates whether the keypad battery is in a critical state for a Nuki device. - :ivar keypad_paired: Indicates whether the keypad is paired for a Nuki device. - """ + :ivar keypad_paired: Indicates whether the keypad is paired for a Nuki device.""" device_id: Optional[str] device_name: Optional[str] @@ -1501,8 +1432,7 @@ class OmnitecMetadata(ResourceMapping): :ivar time_zone: IANA time zone for the Omnitec device, used to schedule time-bound access codes at the correct local time (accounting for DST). - :ivar timezone_raw_offset_ms: Static UTC offset of the Omnitec lock in milliseconds. Does not account for DST. - """ + :ivar timezone_raw_offset_ms: Static UTC offset of the Omnitec lock in milliseconds. Does not account for DST.""" has_gateway: Optional[bool] lock_alias: Optional[str] @@ -1562,8 +1492,7 @@ class SaltoKsMetadata(ResourceMapping): :ivar site_id: Site ID for the Salto KS site to which the device belongs. - :ivar site_name: Site name for the Salto KS site to which the device belongs. - """ + :ivar site_name: Site name for the Salto KS site to which the device belongs.""" battery_level: Optional[str] customer_reference: Optional[str] @@ -1580,9 +1509,7 @@ def from_dict(cls, d: Any): return cls( battery_level=d.get("battery_level", None), customer_reference=d.get("customer_reference", None), - has_custom_pin_subscription=d.get( - "has_custom_pin_subscription", None - ), + has_custom_pin_subscription=d.get("has_custom_pin_subscription", None), lock_id=d.get("lock_id", None), lock_type=d.get("lock_type", None), locked_state=d.get("locked_state", None), @@ -1609,8 +1536,7 @@ class SaltoMetadata(ResourceMapping): :ivar site_id: Site ID for the Salto KS site to which the device belongs. - :ivar site_name: Site name for the Salto KS site to which the device belongs. - """ + :ivar site_name: Site name for the Salto KS site to which the device belongs.""" battery_level: Optional[str] customer_reference: Optional[str] @@ -1703,12 +1629,8 @@ def from_dict(cls, d: Any): return cls( device_id=d.get("device_id", None), device_name=d.get("device_name", None), - dual_setpoints_not_supported=d.get( - "dual_setpoints_not_supported", None - ), - enforced_setpoint_range_celsius=d.get( - "enforced_setpoint_range_celsius", None - ), + dual_setpoints_not_supported=d.get("dual_setpoints_not_supported", None), + enforced_setpoint_range_celsius=d.get("enforced_setpoint_range_celsius", None), product_type=d.get("product_type", None), ) @@ -1842,9 +1764,7 @@ class Features(ResourceMapping): def from_dict(cls, d: Any): return cls( auto_lock_time_config=d.get("auto_lock_time_config", None), - incomplete_keyboard_passcode=d.get( - "incomplete_keyboard_passcode", None - ), + incomplete_keyboard_passcode=d.get("incomplete_keyboard_passcode", None), lock_command=d.get("lock_command", None), passcode=d.get("passcode", None), passcode_management=d.get("passcode_management", None), @@ -1858,8 +1778,7 @@ class WirelessKeypads(ResourceMapping): :ivar wireless_keypad_id: ID for a wireless keypad for a TTLock device. - :ivar wireless_keypad_name: Name for a wireless keypad for a TTLock device. - """ + :ivar wireless_keypad_name: Name for a wireless keypad for a TTLock device.""" wireless_keypad_id: Optional[float] wireless_keypad_name: Optional[str] @@ -1883,19 +1802,12 @@ def from_dict(cls, d: Any): def from_dict(cls, d: Any): return cls( feature_value=d.get("feature_value", None), - features=( - cls.Features.from_dict(d.get("features")) - if d.get("features") is not None - else None - ), + features=cls.Features.from_dict(d.get("features")) if d.get("features") is not None else None, has_gateway=d.get("has_gateway", None), lock_alias=d.get("lock_alias", None), lock_id=d.get("lock_id", None), timezone_raw_offset_ms=d.get("timezone_raw_offset_ms", None), - wireless_keypads=[ - cls.WirelessKeypads.from_dict(i) - for i in d.get("wireless_keypads") or [] - ], + wireless_keypads=[cls.WirelessKeypads.from_dict(i) for i in d.get("wireless_keypads") or []], ) @dataclass @@ -2028,7 +1940,7 @@ def from_dict(cls, d: Any): class CodeConstraints(ResourceMapping): """Constraints on access codes for the device. Seam represents each constraint as an object with a ``constraint_type`` property. Depending on the constraint type, there may also be additional properties. Note that some constraints are manufacturer- or device-specific. - :ivar constraint_type: + :ivar constraint_type: :ivar max_length: Maximum name length constraint for access codes. @@ -2078,8 +1990,7 @@ class OfflineTimeFrameOptions(ResourceMapping): :ivar time_pairs: Fixed start/end time pairings the caller chooses from. Mutually exclusive with ``matching_start_end_time``. - :ivar time_zone: IANA time zone for interpreting ``time_pairs`` and the date recurrence rules. Present only when the option fixes times or dates. - """ + :ivar time_zone: IANA time zone for interpreting ``time_pairs`` and the date recurrence rules. Present only when the option fixes times or dates.""" @dataclass class TimePairs(ResourceMapping): @@ -2089,8 +2000,7 @@ class TimePairs(ResourceMapping): :ivar end_time: End time of day as a 24-hour ``HH:MM`` value, interpreted in the option's ``time_zone``. An ``end_time`` earlier on the clock than ``start_time`` means the end falls on a later date. - :ivar start_time: Start time of day as a 24-hour ``HH:MM`` value, interpreted in the option's ``time_zone``. - """ + :ivar start_time: Start time of day as a 24-hour ``HH:MM`` value, interpreted in the option's ``time_zone``.""" display_name: str end_time: str @@ -2121,12 +2031,8 @@ def from_dict(cls, d: Any): matching_start_end_time=d.get("matching_start_end_time", None), max_duration=d.get("max_duration", None), min_duration=d.get("min_duration", None), - start_date_recurrence_rule=d.get( - "start_date_recurrence_rule", None - ), - time_pairs=[ - cls.TimePairs.from_dict(i) for i in d.get("time_pairs") or [] - ], + start_date_recurrence_rule=d.get("start_date_recurrence_rule", None), + time_pairs=[cls.TimePairs.from_dict(i) for i in d.get("time_pairs") or []], time_zone=d.get("time_zone", None), ) @@ -2148,8 +2054,7 @@ class OnlineTimeFrameOptions(ResourceMapping): :ivar time_pairs: Fixed start/end time pairings the caller chooses from. Mutually exclusive with ``matching_start_end_time``. - :ivar time_zone: IANA time zone for interpreting ``time_pairs`` and the date recurrence rules. Present only when the option fixes times or dates. - """ + :ivar time_zone: IANA time zone for interpreting ``time_pairs`` and the date recurrence rules. Present only when the option fixes times or dates.""" @dataclass class TimePairs(ResourceMapping): @@ -2159,8 +2064,7 @@ class TimePairs(ResourceMapping): :ivar end_time: End time of day as a 24-hour ``HH:MM`` value, interpreted in the option's ``time_zone``. An ``end_time`` earlier on the clock than ``start_time`` means the end falls on a later date. - :ivar start_time: Start time of day as a 24-hour ``HH:MM`` value, interpreted in the option's ``time_zone``. - """ + :ivar start_time: Start time of day as a 24-hour ``HH:MM`` value, interpreted in the option's ``time_zone``.""" display_name: str end_time: str @@ -2191,12 +2095,8 @@ def from_dict(cls, d: Any): matching_start_end_time=d.get("matching_start_end_time", None), max_duration=d.get("max_duration", None), min_duration=d.get("min_duration", None), - start_date_recurrence_rule=d.get( - "start_date_recurrence_rule", None - ), - time_pairs=[ - cls.TimePairs.from_dict(i) for i in d.get("time_pairs") or [] - ], + start_date_recurrence_rule=d.get("start_date_recurrence_rule", None), + time_pairs=[cls.TimePairs.from_dict(i) for i in d.get("time_pairs") or []], time_zone=d.get("time_zone", None), ) @@ -2224,8 +2124,7 @@ class ActiveThermostatSchedule(ResourceMapping): :ivar thermostat_schedule_id: ID of the `thermostat schedule `_. - :ivar workspace_id: ID of the workspace that contains the thermostat schedule. - """ + :ivar workspace_id: ID of the workspace that contains the thermostat schedule.""" @dataclass class Errors(ResourceMapping): @@ -2235,8 +2134,7 @@ class Errors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - """ + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" created_at: str error_code: str @@ -2271,9 +2169,7 @@ def from_dict(cls, d: Any): ends_at=d.get("ends_at", None), errors=[cls.Errors.from_dict(i) for i in d.get("errors") or []], is_override_allowed=d.get("is_override_allowed", None), - max_override_period_minutes=d.get( - "max_override_period_minutes", None - ), + max_override_period_minutes=d.get("max_override_period_minutes", None), name=d.get("name", None), starts_at=d.get("starts_at", None), thermostat_schedule_id=d.get("thermostat_schedule_id", None), @@ -2312,8 +2208,7 @@ class AvailableClimatePresets(ResourceMapping): :ivar manual_override_allowed: Deprecated: Use 'thermostat_schedule.is_override_allowed' Indicates whether a person at the thermostat can change the thermostat's settings. See `Specifying Manual Override Permissions `_. - :ivar name: User-friendly name to identify the `climate preset `_. - """ + :ivar name: User-friendly name to identify the `climate preset `_.""" @dataclass class EcobeeMetadata(ResourceMapping): @@ -2323,8 +2218,7 @@ class EcobeeMetadata(ResourceMapping): :ivar is_optimized: Indicates if the climate preset is optimized by Ecobee. - :ivar owner: Indicates whether the climate preset is owned by the user or the system. - """ + :ivar owner: Indicates whether the climate preset is owned by the user or the system.""" climate_ref: Optional[str] is_optimized: Optional[bool] @@ -2359,26 +2253,16 @@ def from_dict(cls, d: Any): return cls( can_delete=d.get("can_delete", None), can_edit=d.get("can_edit", None), - can_use_with_thermostat_daily_programs=d.get( - "can_use_with_thermostat_daily_programs", None - ), + can_use_with_thermostat_daily_programs=d.get("can_use_with_thermostat_daily_programs", None), climate_preset_key=d.get("climate_preset_key", None), climate_preset_mode=d.get("climate_preset_mode", None), cooling_set_point_celsius=d.get("cooling_set_point_celsius", None), - cooling_set_point_fahrenheit=d.get( - "cooling_set_point_fahrenheit", None - ), + cooling_set_point_fahrenheit=d.get("cooling_set_point_fahrenheit", None), display_name=d.get("display_name", None), - ecobee_metadata=( - cls.EcobeeMetadata.from_dict(d.get("ecobee_metadata")) - if d.get("ecobee_metadata") is not None - else None - ), + ecobee_metadata=cls.EcobeeMetadata.from_dict(d.get("ecobee_metadata")) if d.get("ecobee_metadata") is not None else None, fan_mode_setting=d.get("fan_mode_setting", None), heating_set_point_celsius=d.get("heating_set_point_celsius", None), - heating_set_point_fahrenheit=d.get( - "heating_set_point_fahrenheit", None - ), + heating_set_point_fahrenheit=d.get("heating_set_point_fahrenheit", None), hvac_mode_setting=d.get("hvac_mode_setting", None), manual_override_allowed=d.get("manual_override_allowed", None), name=d.get("name", None), @@ -2416,8 +2300,7 @@ class CurrentClimateSetting(ResourceMapping): :ivar manual_override_allowed: Deprecated: Use 'thermostat_schedule.is_override_allowed' Indicates whether a person at the thermostat can change the thermostat's settings. See `Specifying Manual Override Permissions `_. - :ivar name: User-friendly name to identify the `climate preset `_. - """ + :ivar name: User-friendly name to identify the `climate preset `_.""" @dataclass class EcobeeMetadata(ResourceMapping): @@ -2427,8 +2310,7 @@ class EcobeeMetadata(ResourceMapping): :ivar is_optimized: Indicates if the climate preset is optimized by Ecobee. - :ivar owner: Indicates whether the climate preset is owned by the user or the system. - """ + :ivar owner: Indicates whether the climate preset is owned by the user or the system.""" climate_ref: Optional[str] is_optimized: Optional[bool] @@ -2463,26 +2345,16 @@ def from_dict(cls, d: Any): return cls( can_delete=d.get("can_delete", None), can_edit=d.get("can_edit", None), - can_use_with_thermostat_daily_programs=d.get( - "can_use_with_thermostat_daily_programs", None - ), + can_use_with_thermostat_daily_programs=d.get("can_use_with_thermostat_daily_programs", None), climate_preset_key=d.get("climate_preset_key", None), climate_preset_mode=d.get("climate_preset_mode", None), cooling_set_point_celsius=d.get("cooling_set_point_celsius", None), - cooling_set_point_fahrenheit=d.get( - "cooling_set_point_fahrenheit", None - ), + cooling_set_point_fahrenheit=d.get("cooling_set_point_fahrenheit", None), display_name=d.get("display_name", None), - ecobee_metadata=( - cls.EcobeeMetadata.from_dict(d.get("ecobee_metadata")) - if d.get("ecobee_metadata") is not None - else None - ), + ecobee_metadata=cls.EcobeeMetadata.from_dict(d.get("ecobee_metadata")) if d.get("ecobee_metadata") is not None else None, fan_mode_setting=d.get("fan_mode_setting", None), heating_set_point_celsius=d.get("heating_set_point_celsius", None), - heating_set_point_fahrenheit=d.get( - "heating_set_point_fahrenheit", None - ), + heating_set_point_fahrenheit=d.get("heating_set_point_fahrenheit", None), hvac_mode_setting=d.get("hvac_mode_setting", None), manual_override_allowed=d.get("manual_override_allowed", None), name=d.get("name", None), @@ -2520,8 +2392,7 @@ class DefaultClimateSetting(ResourceMapping): :ivar manual_override_allowed: Deprecated: Use 'thermostat_schedule.is_override_allowed' Indicates whether a person at the thermostat can change the thermostat's settings. See `Specifying Manual Override Permissions `_. - :ivar name: User-friendly name to identify the `climate preset `_. - """ + :ivar name: User-friendly name to identify the `climate preset `_.""" @dataclass class EcobeeMetadata(ResourceMapping): @@ -2531,8 +2402,7 @@ class EcobeeMetadata(ResourceMapping): :ivar is_optimized: Indicates if the climate preset is optimized by Ecobee. - :ivar owner: Indicates whether the climate preset is owned by the user or the system. - """ + :ivar owner: Indicates whether the climate preset is owned by the user or the system.""" climate_ref: Optional[str] is_optimized: Optional[bool] @@ -2567,26 +2437,16 @@ def from_dict(cls, d: Any): return cls( can_delete=d.get("can_delete", None), can_edit=d.get("can_edit", None), - can_use_with_thermostat_daily_programs=d.get( - "can_use_with_thermostat_daily_programs", None - ), + can_use_with_thermostat_daily_programs=d.get("can_use_with_thermostat_daily_programs", None), climate_preset_key=d.get("climate_preset_key", None), climate_preset_mode=d.get("climate_preset_mode", None), cooling_set_point_celsius=d.get("cooling_set_point_celsius", None), - cooling_set_point_fahrenheit=d.get( - "cooling_set_point_fahrenheit", None - ), + cooling_set_point_fahrenheit=d.get("cooling_set_point_fahrenheit", None), display_name=d.get("display_name", None), - ecobee_metadata=( - cls.EcobeeMetadata.from_dict(d.get("ecobee_metadata")) - if d.get("ecobee_metadata") is not None - else None - ), + ecobee_metadata=cls.EcobeeMetadata.from_dict(d.get("ecobee_metadata")) if d.get("ecobee_metadata") is not None else None, fan_mode_setting=d.get("fan_mode_setting", None), heating_set_point_celsius=d.get("heating_set_point_celsius", None), - heating_set_point_fahrenheit=d.get( - "heating_set_point_fahrenheit", None - ), + heating_set_point_fahrenheit=d.get("heating_set_point_fahrenheit", None), hvac_mode_setting=d.get("hvac_mode_setting", None), manual_override_allowed=d.get("manual_override_allowed", None), name=d.get("name", None), @@ -2602,8 +2462,7 @@ class TemperatureThreshold(ResourceMapping): :ivar upper_limit_celsius: Upper limit in °C within the current `temperature threshold `_ set for the thermostat. - :ivar upper_limit_fahrenheit: Upper limit in °F within the current `temperature threshold `_ set for the thermostat. - """ + :ivar upper_limit_fahrenheit: Upper limit in °F within the current `temperature threshold `_ set for the thermostat.""" lower_limit_celsius: Optional[float] lower_limit_fahrenheit: Optional[float] @@ -2633,8 +2492,7 @@ class ThermostatDailyPrograms(ResourceMapping): :ivar thermostat_daily_program_id: ID of the thermostat daily program. - :ivar workspace_id: ID of the workspace that contains the thermostat daily program. - """ + :ivar workspace_id: ID of the workspace that contains the thermostat daily program.""" @dataclass class Periods(ResourceMapping): @@ -2642,8 +2500,7 @@ class Periods(ResourceMapping): :ivar climate_preset_key: Key of the `climate preset `_ to activate at the ``starts_at_time``. - :ivar starts_at_time: Time at which the thermostat daily program period starts, in `ISO 8601 `_ format. - """ + :ivar starts_at_time: Time at which the thermostat daily program period starts, in `ISO 8601 `_ format.""" climate_preset_key: str starts_at_time: str @@ -2669,9 +2526,7 @@ def from_dict(cls, d: Any): device_id=d.get("device_id", None), name=d.get("name", None), periods=[cls.Periods.from_dict(i) for i in d.get("periods") or []], - thermostat_daily_program_id=d.get( - "thermostat_daily_program_id", None - ), + thermostat_daily_program_id=d.get("thermostat_daily_program_id", None), workspace_id=d.get("workspace_id", None), ) @@ -2693,8 +2548,7 @@ class ThermostatWeeklyProgram(ResourceMapping): :ivar tuesday_program_id: ID of the thermostat daily program to run on Tuesdays. - :ivar wednesday_program_id: ID of the thermostat daily program to run on Wednesdays. - """ + :ivar wednesday_program_id: ID of the thermostat daily program to run on Wednesdays.""" created_at: str friday_program_id: Optional[str] @@ -2736,12 +2590,8 @@ def from_dict(cls, d: Any): serial_number: Optional[str] supports_accessory_keypad: Optional[bool] supports_offline_access_codes: Optional[bool] - assa_abloy_credential_service_metadata: Optional[ - AssaAbloyCredentialServiceMetadata - ] - salto_space_credential_service_metadata: Optional[ - SaltoSpaceCredentialServiceMetadata - ] + assa_abloy_credential_service_metadata: Optional[AssaAbloyCredentialServiceMetadata] + salto_space_credential_service_metadata: Optional[SaltoSpaceCredentialServiceMetadata] akiles_metadata: Optional[AkilesMetadata] aqara_metadata: Optional[AqaraMetadata] assa_abloy_vostio_metadata: Optional[AssaAbloyVostioMetadata] @@ -2831,392 +2681,111 @@ def from_dict(cls, d: Any): @classmethod def from_dict(cls, d: Any): return cls( - accessory_keypad=( - cls.AccessoryKeypad.from_dict(d.get("accessory_keypad")) - if d.get("accessory_keypad") is not None - else None - ), - appearance=( - cls.Appearance.from_dict(d.get("appearance")) - if d.get("appearance") is not None - else None - ), - battery=( - cls.Battery.from_dict(d.get("battery")) - if d.get("battery") is not None - else None - ), + accessory_keypad=cls.AccessoryKeypad.from_dict(d.get("accessory_keypad")) if d.get("accessory_keypad") is not None else None, + appearance=cls.Appearance.from_dict(d.get("appearance")) if d.get("appearance") is not None else None, + battery=cls.Battery.from_dict(d.get("battery")) if d.get("battery") is not None else None, battery_level=d.get("battery_level", None), - currently_triggering_noise_threshold_ids=d.get( - "currently_triggering_noise_threshold_ids", None - ), + currently_triggering_noise_threshold_ids=d.get("currently_triggering_noise_threshold_ids", None), has_direct_power=d.get("has_direct_power", None), image_alt_text=d.get("image_alt_text", None), image_url=d.get("image_url", None), manufacturer=d.get("manufacturer", None), - model=( - cls.Model.from_dict(d.get("model")) - if d.get("model") is not None - else None - ), + model=cls.Model.from_dict(d.get("model")) if d.get("model") is not None else None, name=d.get("name", None), noise_level_decibels=d.get("noise_level_decibels", None), - offline_access_codes_enabled=d.get( - "offline_access_codes_enabled", None - ), + offline_access_codes_enabled=d.get("offline_access_codes_enabled", None), online=d.get("online", None), online_access_codes_enabled=d.get("online_access_codes_enabled", None), serial_number=d.get("serial_number", None), supports_accessory_keypad=d.get("supports_accessory_keypad", None), - supports_offline_access_codes=d.get( - "supports_offline_access_codes", None - ), - assa_abloy_credential_service_metadata=( - cls.AssaAbloyCredentialServiceMetadata.from_dict( - d.get("assa_abloy_credential_service_metadata") - ) - if d.get("assa_abloy_credential_service_metadata") is not None - else None - ), - salto_space_credential_service_metadata=( - cls.SaltoSpaceCredentialServiceMetadata.from_dict( - d.get("salto_space_credential_service_metadata") - ) - if d.get("salto_space_credential_service_metadata") is not None - else None - ), - akiles_metadata=( - cls.AkilesMetadata.from_dict(d.get("akiles_metadata")) - if d.get("akiles_metadata") is not None - else None - ), - aqara_metadata=( - cls.AqaraMetadata.from_dict(d.get("aqara_metadata")) - if d.get("aqara_metadata") is not None - else None - ), - assa_abloy_vostio_metadata=( - cls.AssaAbloyVostioMetadata.from_dict( - d.get("assa_abloy_vostio_metadata") - ) - if d.get("assa_abloy_vostio_metadata") is not None - else None - ), - august_metadata=( - cls.AugustMetadata.from_dict(d.get("august_metadata")) - if d.get("august_metadata") is not None - else None - ), - avigilon_alta_metadata=( - cls.AvigilonAltaMetadata.from_dict(d.get("avigilon_alta_metadata")) - if d.get("avigilon_alta_metadata") is not None - else None - ), - brivo_metadata=( - cls.BrivoMetadata.from_dict(d.get("brivo_metadata")) - if d.get("brivo_metadata") is not None - else None - ), - controlbyweb_metadata=( - cls.ControlbywebMetadata.from_dict(d.get("controlbyweb_metadata")) - if d.get("controlbyweb_metadata") is not None - else None - ), - dormakaba_oracode_metadata=( - cls.DormakabaOracodeMetadata.from_dict( - d.get("dormakaba_oracode_metadata") - ) - if d.get("dormakaba_oracode_metadata") is not None - else None - ), - ecobee_metadata=( - cls.EcobeeMetadata.from_dict(d.get("ecobee_metadata")) - if d.get("ecobee_metadata") is not None - else None - ), - four_suites_metadata=( - cls.FourSuitesMetadata.from_dict(d.get("four_suites_metadata")) - if d.get("four_suites_metadata") is not None - else None - ), - genie_metadata=( - cls.GenieMetadata.from_dict(d.get("genie_metadata")) - if d.get("genie_metadata") is not None - else None - ), - honeywell_resideo_metadata=( - cls.HoneywellResideoMetadata.from_dict( - d.get("honeywell_resideo_metadata") - ) - if d.get("honeywell_resideo_metadata") is not None - else None - ), - igloo_metadata=( - cls.IglooMetadata.from_dict(d.get("igloo_metadata")) - if d.get("igloo_metadata") is not None - else None - ), - igloohome_metadata=( - cls.IgloohomeMetadata.from_dict(d.get("igloohome_metadata")) - if d.get("igloohome_metadata") is not None - else None - ), - keynest_metadata=( - cls.KeynestMetadata.from_dict(d.get("keynest_metadata")) - if d.get("keynest_metadata") is not None - else None - ), - kisi_metadata=( - cls.KisiMetadata.from_dict(d.get("kisi_metadata")) - if d.get("kisi_metadata") is not None - else None - ), - korelock_metadata=( - cls.KorelockMetadata.from_dict(d.get("korelock_metadata")) - if d.get("korelock_metadata") is not None - else None - ), - kwikset_metadata=( - cls.KwiksetMetadata.from_dict(d.get("kwikset_metadata")) - if d.get("kwikset_metadata") is not None - else None - ), - lockly_metadata=( - cls.LocklyMetadata.from_dict(d.get("lockly_metadata")) - if d.get("lockly_metadata") is not None - else None - ), - minut_metadata=( - cls.MinutMetadata.from_dict(d.get("minut_metadata")) - if d.get("minut_metadata") is not None - else None - ), - nest_metadata=( - cls.NestMetadata.from_dict(d.get("nest_metadata")) - if d.get("nest_metadata") is not None - else None - ), - noiseaware_metadata=( - cls.NoiseawareMetadata.from_dict(d.get("noiseaware_metadata")) - if d.get("noiseaware_metadata") is not None - else None - ), - nuki_metadata=( - cls.NukiMetadata.from_dict(d.get("nuki_metadata")) - if d.get("nuki_metadata") is not None - else None - ), - omnitec_metadata=( - cls.OmnitecMetadata.from_dict(d.get("omnitec_metadata")) - if d.get("omnitec_metadata") is not None - else None - ), - ring_metadata=( - cls.RingMetadata.from_dict(d.get("ring_metadata")) - if d.get("ring_metadata") is not None - else None - ), - salto_ks_metadata=( - cls.SaltoKsMetadata.from_dict(d.get("salto_ks_metadata")) - if d.get("salto_ks_metadata") is not None - else None - ), - salto_metadata=( - cls.SaltoMetadata.from_dict(d.get("salto_metadata")) - if d.get("salto_metadata") is not None - else None - ), - schlage_metadata=( - cls.SchlageMetadata.from_dict(d.get("schlage_metadata")) - if d.get("schlage_metadata") is not None - else None - ), - seam_bridge_metadata=( - cls.SeamBridgeMetadata.from_dict(d.get("seam_bridge_metadata")) - if d.get("seam_bridge_metadata") is not None - else None - ), - sensi_metadata=( - cls.SensiMetadata.from_dict(d.get("sensi_metadata")) - if d.get("sensi_metadata") is not None - else None - ), - smartthings_metadata=( - cls.SmartthingsMetadata.from_dict(d.get("smartthings_metadata")) - if d.get("smartthings_metadata") is not None - else None - ), - tado_metadata=( - cls.TadoMetadata.from_dict(d.get("tado_metadata")) - if d.get("tado_metadata") is not None - else None - ), - tedee_metadata=( - cls.TedeeMetadata.from_dict(d.get("tedee_metadata")) - if d.get("tedee_metadata") is not None - else None - ), - ttlock_metadata=( - cls.TtlockMetadata.from_dict(d.get("ttlock_metadata")) - if d.get("ttlock_metadata") is not None - else None - ), - two_n_metadata=( - cls.TwoNMetadata.from_dict(d.get("two_n_metadata")) - if d.get("two_n_metadata") is not None - else None - ), - ultraloq_metadata=( - cls.UltraloqMetadata.from_dict(d.get("ultraloq_metadata")) - if d.get("ultraloq_metadata") is not None - else None - ), - visionline_metadata=( - cls.VisionlineMetadata.from_dict(d.get("visionline_metadata")) - if d.get("visionline_metadata") is not None - else None - ), - wyze_metadata=( - cls.WyzeMetadata.from_dict(d.get("wyze_metadata")) - if d.get("wyze_metadata") is not None - else None - ), - yacan_metadata=( - cls.YacanMetadata.from_dict(d.get("yacan_metadata")) - if d.get("yacan_metadata") is not None - else None - ), + supports_offline_access_codes=d.get("supports_offline_access_codes", None), + assa_abloy_credential_service_metadata=cls.AssaAbloyCredentialServiceMetadata.from_dict(d.get("assa_abloy_credential_service_metadata")) if d.get("assa_abloy_credential_service_metadata") is not None else None, + salto_space_credential_service_metadata=cls.SaltoSpaceCredentialServiceMetadata.from_dict(d.get("salto_space_credential_service_metadata")) if d.get("salto_space_credential_service_metadata") is not None else None, + akiles_metadata=cls.AkilesMetadata.from_dict(d.get("akiles_metadata")) if d.get("akiles_metadata") is not None else None, + aqara_metadata=cls.AqaraMetadata.from_dict(d.get("aqara_metadata")) if d.get("aqara_metadata") is not None else None, + assa_abloy_vostio_metadata=cls.AssaAbloyVostioMetadata.from_dict(d.get("assa_abloy_vostio_metadata")) if d.get("assa_abloy_vostio_metadata") is not None else None, + august_metadata=cls.AugustMetadata.from_dict(d.get("august_metadata")) if d.get("august_metadata") is not None else None, + avigilon_alta_metadata=cls.AvigilonAltaMetadata.from_dict(d.get("avigilon_alta_metadata")) if d.get("avigilon_alta_metadata") is not None else None, + brivo_metadata=cls.BrivoMetadata.from_dict(d.get("brivo_metadata")) if d.get("brivo_metadata") is not None else None, + controlbyweb_metadata=cls.ControlbywebMetadata.from_dict(d.get("controlbyweb_metadata")) if d.get("controlbyweb_metadata") is not None else None, + dormakaba_oracode_metadata=cls.DormakabaOracodeMetadata.from_dict(d.get("dormakaba_oracode_metadata")) if d.get("dormakaba_oracode_metadata") is not None else None, + ecobee_metadata=cls.EcobeeMetadata.from_dict(d.get("ecobee_metadata")) if d.get("ecobee_metadata") is not None else None, + four_suites_metadata=cls.FourSuitesMetadata.from_dict(d.get("four_suites_metadata")) if d.get("four_suites_metadata") is not None else None, + genie_metadata=cls.GenieMetadata.from_dict(d.get("genie_metadata")) if d.get("genie_metadata") is not None else None, + honeywell_resideo_metadata=cls.HoneywellResideoMetadata.from_dict(d.get("honeywell_resideo_metadata")) if d.get("honeywell_resideo_metadata") is not None else None, + igloo_metadata=cls.IglooMetadata.from_dict(d.get("igloo_metadata")) if d.get("igloo_metadata") is not None else None, + igloohome_metadata=cls.IgloohomeMetadata.from_dict(d.get("igloohome_metadata")) if d.get("igloohome_metadata") is not None else None, + keynest_metadata=cls.KeynestMetadata.from_dict(d.get("keynest_metadata")) if d.get("keynest_metadata") is not None else None, + kisi_metadata=cls.KisiMetadata.from_dict(d.get("kisi_metadata")) if d.get("kisi_metadata") is not None else None, + korelock_metadata=cls.KorelockMetadata.from_dict(d.get("korelock_metadata")) if d.get("korelock_metadata") is not None else None, + kwikset_metadata=cls.KwiksetMetadata.from_dict(d.get("kwikset_metadata")) if d.get("kwikset_metadata") is not None else None, + lockly_metadata=cls.LocklyMetadata.from_dict(d.get("lockly_metadata")) if d.get("lockly_metadata") is not None else None, + minut_metadata=cls.MinutMetadata.from_dict(d.get("minut_metadata")) if d.get("minut_metadata") is not None else None, + nest_metadata=cls.NestMetadata.from_dict(d.get("nest_metadata")) if d.get("nest_metadata") is not None else None, + noiseaware_metadata=cls.NoiseawareMetadata.from_dict(d.get("noiseaware_metadata")) if d.get("noiseaware_metadata") is not None else None, + nuki_metadata=cls.NukiMetadata.from_dict(d.get("nuki_metadata")) if d.get("nuki_metadata") is not None else None, + omnitec_metadata=cls.OmnitecMetadata.from_dict(d.get("omnitec_metadata")) if d.get("omnitec_metadata") is not None else None, + ring_metadata=cls.RingMetadata.from_dict(d.get("ring_metadata")) if d.get("ring_metadata") is not None else None, + salto_ks_metadata=cls.SaltoKsMetadata.from_dict(d.get("salto_ks_metadata")) if d.get("salto_ks_metadata") is not None else None, + salto_metadata=cls.SaltoMetadata.from_dict(d.get("salto_metadata")) if d.get("salto_metadata") is not None else None, + schlage_metadata=cls.SchlageMetadata.from_dict(d.get("schlage_metadata")) if d.get("schlage_metadata") is not None else None, + seam_bridge_metadata=cls.SeamBridgeMetadata.from_dict(d.get("seam_bridge_metadata")) if d.get("seam_bridge_metadata") is not None else None, + sensi_metadata=cls.SensiMetadata.from_dict(d.get("sensi_metadata")) if d.get("sensi_metadata") is not None else None, + smartthings_metadata=cls.SmartthingsMetadata.from_dict(d.get("smartthings_metadata")) if d.get("smartthings_metadata") is not None else None, + tado_metadata=cls.TadoMetadata.from_dict(d.get("tado_metadata")) if d.get("tado_metadata") is not None else None, + tedee_metadata=cls.TedeeMetadata.from_dict(d.get("tedee_metadata")) if d.get("tedee_metadata") is not None else None, + ttlock_metadata=cls.TtlockMetadata.from_dict(d.get("ttlock_metadata")) if d.get("ttlock_metadata") is not None else None, + two_n_metadata=cls.TwoNMetadata.from_dict(d.get("two_n_metadata")) if d.get("two_n_metadata") is not None else None, + ultraloq_metadata=cls.UltraloqMetadata.from_dict(d.get("ultraloq_metadata")) if d.get("ultraloq_metadata") is not None else None, + visionline_metadata=cls.VisionlineMetadata.from_dict(d.get("visionline_metadata")) if d.get("visionline_metadata") is not None else None, + wyze_metadata=cls.WyzeMetadata.from_dict(d.get("wyze_metadata")) if d.get("wyze_metadata") is not None else None, + yacan_metadata=cls.YacanMetadata.from_dict(d.get("yacan_metadata")) if d.get("yacan_metadata") is not None else None, auto_lock_delay_seconds=d.get("auto_lock_delay_seconds", None), auto_lock_enabled=d.get("auto_lock_enabled", None), - backup_access_code_pool_enabled=d.get( - "backup_access_code_pool_enabled", None - ), - code_constraints=[ - cls.CodeConstraints.from_dict(i) - for i in d.get("code_constraints") or [] - ], + backup_access_code_pool_enabled=d.get("backup_access_code_pool_enabled", None), + code_constraints=[cls.CodeConstraints.from_dict(i) for i in d.get("code_constraints") or []], door_open=d.get("door_open", None), has_native_entry_events=d.get("has_native_entry_events", None), - keypad_battery=( - cls.KeypadBattery.from_dict(d.get("keypad_battery")) - if d.get("keypad_battery") is not None - else None - ), + keypad_battery=cls.KeypadBattery.from_dict(d.get("keypad_battery")) if d.get("keypad_battery") is not None else None, locked=d.get("locked", None), max_active_codes_supported=d.get("max_active_codes_supported", None), - offline_time_frame_options=[ - cls.OfflineTimeFrameOptions.from_dict(i) - for i in d.get("offline_time_frame_options") or [] - ], - online_time_frame_options=[ - cls.OnlineTimeFrameOptions.from_dict(i) - for i in d.get("online_time_frame_options") or [] - ], + offline_time_frame_options=[cls.OfflineTimeFrameOptions.from_dict(i) for i in d.get("offline_time_frame_options") or []], + online_time_frame_options=[cls.OnlineTimeFrameOptions.from_dict(i) for i in d.get("online_time_frame_options") or []], supported_code_lengths=d.get("supported_code_lengths", None), - supports_backup_access_code_pool=d.get( - "supports_backup_access_code_pool", None - ), - active_thermostat_schedule=( - cls.ActiveThermostatSchedule.from_dict( - d.get("active_thermostat_schedule") - ) - if d.get("active_thermostat_schedule") is not None - else None - ), - active_thermostat_schedule_id=d.get( - "active_thermostat_schedule_id", None - ), - available_climate_preset_modes=d.get( - "available_climate_preset_modes", None - ), - available_climate_presets=[ - cls.AvailableClimatePresets.from_dict(i) - for i in d.get("available_climate_presets") or [] - ], + supports_backup_access_code_pool=d.get("supports_backup_access_code_pool", None), + active_thermostat_schedule=cls.ActiveThermostatSchedule.from_dict(d.get("active_thermostat_schedule")) if d.get("active_thermostat_schedule") is not None else None, + active_thermostat_schedule_id=d.get("active_thermostat_schedule_id", None), + available_climate_preset_modes=d.get("available_climate_preset_modes", None), + available_climate_presets=[cls.AvailableClimatePresets.from_dict(i) for i in d.get("available_climate_presets") or []], available_fan_mode_settings=d.get("available_fan_mode_settings", None), - available_hvac_mode_settings=d.get( - "available_hvac_mode_settings", None - ), - current_climate_setting=( - cls.CurrentClimateSetting.from_dict( - d.get("current_climate_setting") - ) - if d.get("current_climate_setting") is not None - else None - ), - default_climate_setting=( - cls.DefaultClimateSetting.from_dict( - d.get("default_climate_setting") - ) - if d.get("default_climate_setting") is not None - else None - ), + available_hvac_mode_settings=d.get("available_hvac_mode_settings", None), + current_climate_setting=cls.CurrentClimateSetting.from_dict(d.get("current_climate_setting")) if d.get("current_climate_setting") is not None else None, + default_climate_setting=cls.DefaultClimateSetting.from_dict(d.get("default_climate_setting")) if d.get("default_climate_setting") is not None else None, fallback_climate_preset_key=d.get("fallback_climate_preset_key", None), fan_mode_setting=d.get("fan_mode_setting", None), is_cooling=d.get("is_cooling", None), is_fan_running=d.get("is_fan_running", None), is_heating=d.get("is_heating", None), - is_temporary_manual_override_active=d.get( - "is_temporary_manual_override_active", None - ), - max_cooling_set_point_celsius=d.get( - "max_cooling_set_point_celsius", None - ), - max_cooling_set_point_fahrenheit=d.get( - "max_cooling_set_point_fahrenheit", None - ), - max_heating_set_point_celsius=d.get( - "max_heating_set_point_celsius", None - ), - max_heating_set_point_fahrenheit=d.get( - "max_heating_set_point_fahrenheit", None - ), - max_thermostat_daily_program_periods_per_day=d.get( - "max_thermostat_daily_program_periods_per_day", None - ), - max_unique_climate_presets_per_thermostat_weekly_program=d.get( - "max_unique_climate_presets_per_thermostat_weekly_program", None - ), - min_cooling_set_point_celsius=d.get( - "min_cooling_set_point_celsius", None - ), - min_cooling_set_point_fahrenheit=d.get( - "min_cooling_set_point_fahrenheit", None - ), - min_heating_cooling_delta_celsius=d.get( - "min_heating_cooling_delta_celsius", None - ), - min_heating_cooling_delta_fahrenheit=d.get( - "min_heating_cooling_delta_fahrenheit", None - ), - min_heating_set_point_celsius=d.get( - "min_heating_set_point_celsius", None - ), - min_heating_set_point_fahrenheit=d.get( - "min_heating_set_point_fahrenheit", None - ), + is_temporary_manual_override_active=d.get("is_temporary_manual_override_active", None), + max_cooling_set_point_celsius=d.get("max_cooling_set_point_celsius", None), + max_cooling_set_point_fahrenheit=d.get("max_cooling_set_point_fahrenheit", None), + max_heating_set_point_celsius=d.get("max_heating_set_point_celsius", None), + max_heating_set_point_fahrenheit=d.get("max_heating_set_point_fahrenheit", None), + max_thermostat_daily_program_periods_per_day=d.get("max_thermostat_daily_program_periods_per_day", None), + max_unique_climate_presets_per_thermostat_weekly_program=d.get("max_unique_climate_presets_per_thermostat_weekly_program", None), + min_cooling_set_point_celsius=d.get("min_cooling_set_point_celsius", None), + min_cooling_set_point_fahrenheit=d.get("min_cooling_set_point_fahrenheit", None), + min_heating_cooling_delta_celsius=d.get("min_heating_cooling_delta_celsius", None), + min_heating_cooling_delta_fahrenheit=d.get("min_heating_cooling_delta_fahrenheit", None), + min_heating_set_point_celsius=d.get("min_heating_set_point_celsius", None), + min_heating_set_point_fahrenheit=d.get("min_heating_set_point_fahrenheit", None), relative_humidity=d.get("relative_humidity", None), temperature_celsius=d.get("temperature_celsius", None), temperature_fahrenheit=d.get("temperature_fahrenheit", None), - temperature_threshold=( - cls.TemperatureThreshold.from_dict(d.get("temperature_threshold")) - if d.get("temperature_threshold") is not None - else None - ), - thermostat_daily_program_period_precision_minutes=d.get( - "thermostat_daily_program_period_precision_minutes", None - ), - thermostat_daily_programs=[ - cls.ThermostatDailyPrograms.from_dict(i) - for i in d.get("thermostat_daily_programs") or [] - ], - thermostat_weekly_program=( - cls.ThermostatWeeklyProgram.from_dict( - d.get("thermostat_weekly_program") - ) - if d.get("thermostat_weekly_program") is not None - else None - ), + temperature_threshold=cls.TemperatureThreshold.from_dict(d.get("temperature_threshold")) if d.get("temperature_threshold") is not None else None, + thermostat_daily_program_period_precision_minutes=d.get("thermostat_daily_program_period_precision_minutes", None), + thermostat_daily_programs=[cls.ThermostatDailyPrograms.from_dict(i) for i in d.get("thermostat_daily_programs") or []], + thermostat_weekly_program=cls.ThermostatWeeklyProgram.from_dict(d.get("thermostat_weekly_program")) if d.get("thermostat_weekly_program") is not None else None, ) @dataclass @@ -3231,8 +2800,7 @@ class Warnings(ResourceMapping): :ivar active_access_code_count: Number of active access codes on the device when the warning was set. - :ivar max_active_access_code_count: Maximum number of active access codes supported by the device. - """ + :ivar max_active_access_code_count: Maximum number of active access codes supported by the device.""" created_at: str message: str @@ -3247,9 +2815,7 @@ def from_dict(cls, d: Any): message=d.get("message", None), warning_code=d.get("warning_code", None), active_access_code_count=d.get("active_access_code_count", None), - max_active_access_code_count=d.get( - "max_active_access_code_count", None - ), + max_active_access_code_count=d.get("max_active_access_code_count", None), ) can_configure_auto_lock: Optional[bool] @@ -3297,33 +2863,19 @@ def from_dict(cls, d: Any): can_hvac_cool=d.get("can_hvac_cool", None), can_hvac_heat=d.get("can_hvac_heat", None), can_hvac_heat_cool=d.get("can_hvac_heat_cool", None), - can_program_offline_access_codes=d.get( - "can_program_offline_access_codes", None - ), - can_program_online_access_codes=d.get( - "can_program_online_access_codes", None - ), - can_program_thermostat_programs_as_different_each_day=d.get( - "can_program_thermostat_programs_as_different_each_day", None - ), - can_program_thermostat_programs_as_same_each_day=d.get( - "can_program_thermostat_programs_as_same_each_day", None - ), - can_program_thermostat_programs_as_weekday_weekend=d.get( - "can_program_thermostat_programs_as_weekday_weekend", None - ), + can_program_offline_access_codes=d.get("can_program_offline_access_codes", None), + can_program_online_access_codes=d.get("can_program_online_access_codes", None), + can_program_thermostat_programs_as_different_each_day=d.get("can_program_thermostat_programs_as_different_each_day", None), + can_program_thermostat_programs_as_same_each_day=d.get("can_program_thermostat_programs_as_same_each_day", None), + can_program_thermostat_programs_as_weekday_weekend=d.get("can_program_thermostat_programs_as_weekday_weekend", None), can_remotely_lock=d.get("can_remotely_lock", None), can_remotely_unlock=d.get("can_remotely_unlock", None), can_run_thermostat_programs=d.get("can_run_thermostat_programs", None), can_simulate_connection=d.get("can_simulate_connection", None), can_simulate_disconnection=d.get("can_simulate_disconnection", None), can_simulate_hub_connection=d.get("can_simulate_hub_connection", None), - can_simulate_hub_disconnection=d.get( - "can_simulate_hub_disconnection", None - ), - can_simulate_paid_subscription=d.get( - "can_simulate_paid_subscription", None - ), + can_simulate_hub_disconnection=d.get("can_simulate_hub_disconnection", None), + can_simulate_paid_subscription=d.get("can_simulate_paid_subscription", None), can_simulate_removal=d.get("can_simulate_removal", None), can_turn_off_hvac=d.get("can_turn_off_hvac", None), can_unlock_with_code=d.get("can_unlock_with_code", None), @@ -3332,31 +2884,15 @@ def from_dict(cls, d: Any): created_at=d.get("created_at", None), custom_metadata=DeepAttrDict(d.get("custom_metadata", None)), device_id=d.get("device_id", None), - device_manufacturer=( - cls.DeviceManufacturer.from_dict(d.get("device_manufacturer")) - if d.get("device_manufacturer") is not None - else None - ), - device_provider=( - cls.DeviceProvider.from_dict(d.get("device_provider")) - if d.get("device_provider") is not None - else None - ), + device_manufacturer=cls.DeviceManufacturer.from_dict(d.get("device_manufacturer")) if d.get("device_manufacturer") is not None else None, + device_provider=cls.DeviceProvider.from_dict(d.get("device_provider")) if d.get("device_provider") is not None else None, device_type=d.get("device_type", None), display_name=d.get("display_name", None), errors=[cls.Errors.from_dict(i) for i in d.get("errors") or []], is_managed=d.get("is_managed", None), - location=( - cls.Location.from_dict(d.get("location")) - if d.get("location") is not None - else None - ), + location=cls.Location.from_dict(d.get("location")) if d.get("location") is not None else None, nickname=d.get("nickname", None), - properties=( - cls.Properties.from_dict(d.get("properties")) - if d.get("properties") is not None - else None - ), + properties=cls.Properties.from_dict(d.get("properties")) if d.get("properties") is not None else None, space_ids=d.get("space_ids", None), warnings=[cls.Warnings.from_dict(i) for i in d.get("warnings") or []], workspace_id=d.get("workspace_id", None), diff --git a/seam/resources/device_provider.py b/seam/resources/device_provider.py index f58fb9d9..c663d4d4 100644 --- a/seam/resources/device_provider.py +++ b/seam/resources/device_provider.py @@ -54,8 +54,7 @@ class DeviceProvider: :ivar image_url: Image URL for the device provider. - :ivar provider_categories: List of provider categories to which the device provider belongs, such as ``stable``, ``consumer_smartlocks``, ``thermostats``, and so on. - """ + :ivar provider_categories: List of provider categories to which the device provider belongs, such as ``stable``, ``consumer_smartlocks``, ``thermostats``, and so on.""" can_configure_auto_lock: Optional[bool] can_hvac_cool: Optional[bool] @@ -89,33 +88,19 @@ def from_dict(cls, d: Any): can_hvac_cool=d.get("can_hvac_cool", None), can_hvac_heat=d.get("can_hvac_heat", None), can_hvac_heat_cool=d.get("can_hvac_heat_cool", None), - can_program_offline_access_codes=d.get( - "can_program_offline_access_codes", None - ), - can_program_online_access_codes=d.get( - "can_program_online_access_codes", None - ), - can_program_thermostat_programs_as_different_each_day=d.get( - "can_program_thermostat_programs_as_different_each_day", None - ), - can_program_thermostat_programs_as_same_each_day=d.get( - "can_program_thermostat_programs_as_same_each_day", None - ), - can_program_thermostat_programs_as_weekday_weekend=d.get( - "can_program_thermostat_programs_as_weekday_weekend", None - ), + can_program_offline_access_codes=d.get("can_program_offline_access_codes", None), + can_program_online_access_codes=d.get("can_program_online_access_codes", None), + can_program_thermostat_programs_as_different_each_day=d.get("can_program_thermostat_programs_as_different_each_day", None), + can_program_thermostat_programs_as_same_each_day=d.get("can_program_thermostat_programs_as_same_each_day", None), + can_program_thermostat_programs_as_weekday_weekend=d.get("can_program_thermostat_programs_as_weekday_weekend", None), can_remotely_lock=d.get("can_remotely_lock", None), can_remotely_unlock=d.get("can_remotely_unlock", None), can_run_thermostat_programs=d.get("can_run_thermostat_programs", None), can_simulate_connection=d.get("can_simulate_connection", None), can_simulate_disconnection=d.get("can_simulate_disconnection", None), can_simulate_hub_connection=d.get("can_simulate_hub_connection", None), - can_simulate_hub_disconnection=d.get( - "can_simulate_hub_disconnection", None - ), - can_simulate_paid_subscription=d.get( - "can_simulate_paid_subscription", None - ), + can_simulate_hub_disconnection=d.get("can_simulate_hub_disconnection", None), + can_simulate_paid_subscription=d.get("can_simulate_paid_subscription", None), can_simulate_removal=d.get("can_simulate_removal", None), can_turn_off_hvac=d.get("can_turn_off_hvac", None), can_unlock_with_code=d.get("can_unlock_with_code", None), diff --git a/seam/resources/instant_key.py b/seam/resources/instant_key.py index 775e8a11..42dcbb18 100644 --- a/seam/resources/instant_key.py +++ b/seam/resources/instant_key.py @@ -7,7 +7,7 @@ @dataclass class InstantKey: """Represents a Seam Instant Key. For issuing Bluetooth mobile keys, Instant Keys are the fastest way to share access. With a single API call, you can create a mobile key and send it through text or email or embed it in your own app. - + There’s no app to install, nor account to create. Your user just taps a link and gets a lightweight, native-feeling experience using iOS App Clip or Instant Apps on Android. Further, Instant Keys work offline, so even in areas with poor cellular or Wi-Fi, like elevator banks or concrete-walled hallways, the Instant Keys still work. :ivar client_session_id: ID of the client session associated with the Instant Key. @@ -65,11 +65,7 @@ def from_dict(cls, d: Any): return cls( client_session_id=d.get("client_session_id", None), created_at=d.get("created_at", None), - customization=( - cls.Customization.from_dict(d.get("customization")) - if d.get("customization") is not None - else None - ), + customization=cls.Customization.from_dict(d.get("customization")) if d.get("customization") is not None else None, customization_profile_id=d.get("customization_profile_id", None), expires_at=d.get("expires_at", None), instant_key_id=d.get("instant_key_id", None), diff --git a/seam/resources/noise_threshold.py b/seam/resources/noise_threshold.py index 9918d3c3..74f82470 100644 --- a/seam/resources/noise_threshold.py +++ b/seam/resources/noise_threshold.py @@ -20,8 +20,7 @@ class NoiseThreshold: :ivar noise_threshold_nrs: Noise level in Noiseaware Noise Risk Score (NRS) for the noise threshold. This parameter is only relevant for `Noiseaware sensors `_. - :ivar starts_daily_at: Time at which the noise threshold should become active daily. - """ + :ivar starts_daily_at: Time at which the noise threshold should become active daily.""" device_id: str ends_daily_at: str diff --git a/seam/resources/phone.py b/seam/resources/phone.py index 74b1963b..fa42284b 100644 --- a/seam/resources/phone.py +++ b/seam/resources/phone.py @@ -56,8 +56,7 @@ class Properties(ResourceMapping): :ivar assa_abloy_credential_service_metadata: ASSA ABLOY Credential Service metadata for the phone. - :ivar salto_space_credential_service_metadata: Salto Space credential service metadata for the phone. - """ + :ivar salto_space_credential_service_metadata: Salto Space credential service metadata for the phone.""" @dataclass class AssaAbloyCredentialServiceMetadata(ResourceMapping): @@ -65,8 +64,7 @@ class AssaAbloyCredentialServiceMetadata(ResourceMapping): :ivar endpoints: Endpoints associated with the phone. - :ivar has_active_endpoint: Indicates whether the credential service has active endpoints associated with the phone. - """ + :ivar has_active_endpoint: Indicates whether the credential service has active endpoints associated with the phone.""" @dataclass class Endpoints(ResourceMapping): @@ -92,9 +90,7 @@ def from_dict(cls, d: Any): @classmethod def from_dict(cls, d: Any): return cls( - endpoints=[ - cls.Endpoints.from_dict(i) for i in d.get("endpoints") or [] - ], + endpoints=[cls.Endpoints.from_dict(i) for i in d.get("endpoints") or []], has_active_endpoint=d.get("has_active_endpoint", None), ) @@ -102,8 +98,7 @@ def from_dict(cls, d: Any): class SaltoSpaceCredentialServiceMetadata(ResourceMapping): """Salto Space credential service metadata for the phone. - :ivar has_active_phone: Indicates whether the credential service has an active associated phone. - """ + :ivar has_active_phone: Indicates whether the credential service has an active associated phone.""" has_active_phone: Optional[bool] @@ -113,30 +108,14 @@ def from_dict(cls, d: Any): has_active_phone=d.get("has_active_phone", None), ) - assa_abloy_credential_service_metadata: Optional[ - AssaAbloyCredentialServiceMetadata - ] - salto_space_credential_service_metadata: Optional[ - SaltoSpaceCredentialServiceMetadata - ] + assa_abloy_credential_service_metadata: Optional[AssaAbloyCredentialServiceMetadata] + salto_space_credential_service_metadata: Optional[SaltoSpaceCredentialServiceMetadata] @classmethod def from_dict(cls, d: Any): return cls( - assa_abloy_credential_service_metadata=( - cls.AssaAbloyCredentialServiceMetadata.from_dict( - d.get("assa_abloy_credential_service_metadata") - ) - if d.get("assa_abloy_credential_service_metadata") is not None - else None - ), - salto_space_credential_service_metadata=( - cls.SaltoSpaceCredentialServiceMetadata.from_dict( - d.get("salto_space_credential_service_metadata") - ) - if d.get("salto_space_credential_service_metadata") is not None - else None - ), + assa_abloy_credential_service_metadata=cls.AssaAbloyCredentialServiceMetadata.from_dict(d.get("assa_abloy_credential_service_metadata")) if d.get("assa_abloy_credential_service_metadata") is not None else None, + salto_space_credential_service_metadata=cls.SaltoSpaceCredentialServiceMetadata.from_dict(d.get("salto_space_credential_service_metadata")) if d.get("salto_space_credential_service_metadata") is not None else None, ) @dataclass @@ -182,11 +161,7 @@ def from_dict(cls, d: Any): display_name=d.get("display_name", None), errors=[cls.Errors.from_dict(i) for i in d.get("errors") or []], nickname=d.get("nickname", None), - properties=( - cls.Properties.from_dict(d.get("properties")) - if d.get("properties") is not None - else None - ), + properties=cls.Properties.from_dict(d.get("properties")) if d.get("properties") is not None else None, warnings=[cls.Warnings.from_dict(i) for i in d.get("warnings") or []], workspace_id=d.get("workspace_id", None), ) diff --git a/seam/resources/seam_event.py b/seam/resources/seam_event.py index 323115b6..79917a02 100644 --- a/seam/resources/seam_event.py +++ b/seam/resources/seam_event.py @@ -8,17 +8,17 @@ class SeamEvent: """ - :ivar access_code_id: + :ivar access_code_id: - :ivar connected_account_custom_metadata: + :ivar connected_account_custom_metadata: - :ivar connected_account_id: + :ivar connected_account_id: :ivar created_at: Date and time at which the event was created. - :ivar device_custom_metadata: + :ivar device_custom_metadata: - :ivar device_id: + :ivar device_id: :ivar event_description: Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. @@ -36,13 +36,13 @@ class SeamEvent: :ivar description: Human-readable description of the change and its source. - :ivar from_: + :ivar from_: - :ivar to: + :ivar to: :ivar requested_mutations: Array of mutations requested on the access code, each containing the mutation type and from/to values. - :ivar code: + :ivar code: :ivar access_code_errors: Errors associated with the access code. @@ -60,7 +60,7 @@ class SeamEvent: :ivar access_grant_id: ID of the affected Access Grant. - :ivar acs_entrance_id: + :ivar acs_entrance_id: :ivar access_grant_key: Key of the affected Access Grant (if present). @@ -96,11 +96,11 @@ class SeamEvent: :ivar client_session_id: ID of the affected client session. - :ivar connect_webview_id: + :ivar connect_webview_id: - :ivar customer_key: + :ivar customer_key: - :ivar action_attempt_id: + :ivar action_attempt_id: :ivar action_type: Type of the action. @@ -112,7 +112,7 @@ class SeamEvent: :ivar battery_status: Battery status of the affected device, calculated from the numeric ``battery_level`` value. - :ivar device_name: + :ivar device_name: :ivar minut_metadata: Metadata from Minut. @@ -128,11 +128,11 @@ class SeamEvent: :ivar access_code_is_managed: Whether the access code is managed by Seam (true) or unmanaged (false). Only present when access_code_id is set. - :ivar is_via_bluetooth: + :ivar is_via_bluetooth: - :ivar is_via_nfc: + :ivar is_via_nfc: - :ivar method: + :ivar method: :ivar reason: Why access was denied, when the provider reports a determinable cause. Omitted when unknown. @@ -172,15 +172,15 @@ class SeamEvent: :ivar activation_reason: The reason the camera was activated. - :ivar image_url: + :ivar image_url: :ivar motion_sub_type: Sub-type of motion detected, if available. - :ivar video_url: + :ivar video_url: - :ivar acs_entrance_ids: + :ivar acs_entrance_ids: - :ivar device_ids: + :ivar device_ids: :ivar space_id: ID of the affected space. @@ -268,8 +268,7 @@ class RequestedMutations(ResourceMapping): :ivar mutation_code: Code identifying the type of mutation requested, such as ``updating_name``, ``updating_code``, ``updating_time_frame``, or ``deleting``. - :ivar to: New property values after the requested change. Keys depend on the mutation type. Absent for non-property mutations like ``deleting``. - """ + :ivar to: New property values after the requested change. Keys depend on the mutation type. Absent for non-property mutations like ``deleting``.""" from_: Optional[Dict[str, Any]] mutation_code: str @@ -291,8 +290,7 @@ class AccessCodeErrors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - """ + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" created_at: str error_code: str @@ -314,8 +312,7 @@ class AccessCodeWarnings(ResourceMapping): :ivar message: Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - """ + :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue.""" created_at: str message: str @@ -337,8 +334,7 @@ class ConnectedAccountErrors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - """ + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" created_at: str error_code: str @@ -360,8 +356,7 @@ class ConnectedAccountWarnings(ResourceMapping): :ivar message: Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - """ + :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue.""" created_at: str message: str @@ -383,8 +378,7 @@ class DeviceErrors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - """ + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" created_at: str error_code: str @@ -406,8 +400,7 @@ class DeviceWarnings(ResourceMapping): :ivar message: Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - """ + :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue.""" created_at: str message: str @@ -429,8 +422,7 @@ class AcsSystemErrors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - """ + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" created_at: str error_code: str @@ -452,8 +444,7 @@ class AcsSystemWarnings(ResourceMapping): :ivar message: Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - """ + :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue.""" created_at: str message: str @@ -473,8 +464,7 @@ class Reason(ResourceMapping): :ivar message: Human-readable explanation of why access was denied. - :ivar reason_code: Normalized reason a lock denied access. Provider-agnostic; not all providers report every value. - """ + :ivar reason_code: Normalized reason a lock denied access. Provider-agnostic; not all providers report every value.""" message: str reason_code: str @@ -580,9 +570,7 @@ def from_dict(cls, d: Any): def from_dict(cls, d: Any): return cls( access_code_id=d.get("access_code_id", None), - connected_account_custom_metadata=DeepAttrDict( - d.get("connected_account_custom_metadata", None) - ), + connected_account_custom_metadata=DeepAttrDict(d.get("connected_account_custom_metadata", None)), connected_account_id=d.get("connected_account_id", None), created_at=d.get("created_at", None), device_custom_metadata=DeepAttrDict(d.get("device_custom_metadata", None)), @@ -593,42 +581,18 @@ def from_dict(cls, d: Any): occurred_at=d.get("occurred_at", None), workspace_id=d.get("workspace_id", None), change_reason=d.get("change_reason", None), - changed_properties=[ - cls.ChangedProperties.from_dict(i) - for i in d.get("changed_properties") or [] - ], + changed_properties=[cls.ChangedProperties.from_dict(i) for i in d.get("changed_properties") or []], description=d.get("description", None), - from_=( - cls.From.from_dict(d.get("from")) if d.get("from") is not None else None - ), + from_=cls.From.from_dict(d.get("from")) if d.get("from") is not None else None, to=cls.To.from_dict(d.get("to")) if d.get("to") is not None else None, - requested_mutations=[ - cls.RequestedMutations.from_dict(i) - for i in d.get("requested_mutations") or [] - ], + requested_mutations=[cls.RequestedMutations.from_dict(i) for i in d.get("requested_mutations") or []], code=d.get("code", None), - access_code_errors=[ - cls.AccessCodeErrors.from_dict(i) - for i in d.get("access_code_errors") or [] - ], - access_code_warnings=[ - cls.AccessCodeWarnings.from_dict(i) - for i in d.get("access_code_warnings") or [] - ], - connected_account_errors=[ - cls.ConnectedAccountErrors.from_dict(i) - for i in d.get("connected_account_errors") or [] - ], - connected_account_warnings=[ - cls.ConnectedAccountWarnings.from_dict(i) - for i in d.get("connected_account_warnings") or [] - ], - device_errors=[ - cls.DeviceErrors.from_dict(i) for i in d.get("device_errors") or [] - ], - device_warnings=[ - cls.DeviceWarnings.from_dict(i) for i in d.get("device_warnings") or [] - ], + access_code_errors=[cls.AccessCodeErrors.from_dict(i) for i in d.get("access_code_errors") or []], + access_code_warnings=[cls.AccessCodeWarnings.from_dict(i) for i in d.get("access_code_warnings") or []], + connected_account_errors=[cls.ConnectedAccountErrors.from_dict(i) for i in d.get("connected_account_errors") or []], + connected_account_warnings=[cls.ConnectedAccountWarnings.from_dict(i) for i in d.get("connected_account_warnings") or []], + device_errors=[cls.DeviceErrors.from_dict(i) for i in d.get("device_errors") or []], + device_warnings=[cls.DeviceWarnings.from_dict(i) for i in d.get("device_warnings") or []], backup_access_code_id=d.get("backup_access_code_id", None), access_grant_id=d.get("access_grant_id", None), acs_entrance_id=d.get("acs_entrance_id", None), @@ -642,14 +606,8 @@ def from_dict(cls, d: Any): access_method_id=d.get("access_method_id", None), is_backup_code=d.get("is_backup_code", None), acs_system_id=d.get("acs_system_id", None), - acs_system_errors=[ - cls.AcsSystemErrors.from_dict(i) - for i in d.get("acs_system_errors") or [] - ], - acs_system_warnings=[ - cls.AcsSystemWarnings.from_dict(i) - for i in d.get("acs_system_warnings") or [] - ], + acs_system_errors=[cls.AcsSystemErrors.from_dict(i) for i in d.get("acs_system_errors") or []], + acs_system_warnings=[cls.AcsSystemWarnings.from_dict(i) for i in d.get("acs_system_warnings") or []], acs_credential_id=d.get("acs_credential_id", None), acs_user_id=d.get("acs_user_id", None), acs_encoder_id=d.get("acs_encoder_id", None), @@ -674,11 +632,7 @@ def from_dict(cls, d: Any): is_via_bluetooth=d.get("is_via_bluetooth", None), is_via_nfc=d.get("is_via_nfc", None), method=d.get("method", None), - reason=( - cls.Reason.from_dict(d.get("reason")) - if d.get("reason") is not None - else None - ), + reason=cls.Reason.from_dict(d.get("reason")) if d.get("reason") is not None else None, climate_preset_key=d.get("climate_preset_key", None), is_fallback_climate_preset=d.get("is_fallback_climate_preset", None), thermostat_schedule_id=d.get("thermostat_schedule_id", None), @@ -695,9 +649,7 @@ def from_dict(cls, d: Any): upper_limit_celsius=d.get("upper_limit_celsius", None), upper_limit_fahrenheit=d.get("upper_limit_fahrenheit", None), desired_temperature_celsius=d.get("desired_temperature_celsius", None), - desired_temperature_fahrenheit=d.get( - "desired_temperature_fahrenheit", None - ), + desired_temperature_fahrenheit=d.get("desired_temperature_fahrenheit", None), activation_reason=d.get("activation_reason", None), image_url=d.get("image_url", None), motion_sub_type=d.get("motion_sub_type", None), diff --git a/seam/resources/space.py b/seam/resources/space.py index 65e68978..f2634480 100644 --- a/seam/resources/space.py +++ b/seam/resources/space.py @@ -91,19 +91,11 @@ def from_dict(cls, d: Any): return cls( acs_entrance_count=d.get("acs_entrance_count", None), created_at=d.get("created_at", None), - customer_data=( - cls.CustomerData.from_dict(d.get("customer_data")) - if d.get("customer_data") is not None - else None - ), + customer_data=cls.CustomerData.from_dict(d.get("customer_data")) if d.get("customer_data") is not None else None, customer_key=d.get("customer_key", None), device_count=d.get("device_count", None), display_name=d.get("display_name", None), - geolocation=( - cls.Geolocation.from_dict(d.get("geolocation")) - if d.get("geolocation") is not None - else None - ), + geolocation=cls.Geolocation.from_dict(d.get("geolocation")) if d.get("geolocation") is not None else None, name=d.get("name", None), space_id=d.get("space_id", None), space_key=d.get("space_key", None), diff --git a/seam/resources/thermostat_daily_program.py b/seam/resources/thermostat_daily_program.py index 0c1c262d..6ad49642 100644 --- a/seam/resources/thermostat_daily_program.py +++ b/seam/resources/thermostat_daily_program.py @@ -18,8 +18,7 @@ class ThermostatDailyProgram: :ivar thermostat_daily_program_id: ID of the thermostat daily program. - :ivar workspace_id: ID of the workspace that contains the thermostat daily program. - """ + :ivar workspace_id: ID of the workspace that contains the thermostat daily program.""" @dataclass class Periods(ResourceMapping): @@ -27,8 +26,7 @@ class Periods(ResourceMapping): :ivar climate_preset_key: Key of the `climate preset `_ to activate at the ``starts_at_time``. - :ivar starts_at_time: Time at which the thermostat daily program period starts, in `ISO 8601 `_ format. - """ + :ivar starts_at_time: Time at which the thermostat daily program period starts, in `ISO 8601 `_ format.""" climate_preset_key: str starts_at_time: str diff --git a/seam/resources/thermostat_schedule.py b/seam/resources/thermostat_schedule.py index 2c022801..60c96e91 100644 --- a/seam/resources/thermostat_schedule.py +++ b/seam/resources/thermostat_schedule.py @@ -38,8 +38,7 @@ class Errors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - """ + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" created_at: str error_code: str diff --git a/seam/resources/unmanaged_access_code.py b/seam/resources/unmanaged_access_code.py index 8c1d00ed..4311dd3a 100644 --- a/seam/resources/unmanaged_access_code.py +++ b/seam/resources/unmanaged_access_code.py @@ -7,15 +7,15 @@ @dataclass class UnmanagedAccessCode: """Represents an `unmanaged smart lock access code `_. - + An access code is a code used for a keypad or pinpad device. Unlike physical keys, which can easily be lost or duplicated, PIN codes can be customized, tracked, and altered on the fly. - + When you create an access code on a device in Seam, it is created as a managed access code. Access codes that exist on a device that were not created through Seam are considered unmanaged codes. We strictly limit the operations that can be performed on unmanaged codes. - + Prior to using Seam to manage your devices, you may have used another lock management system to manage the access codes on your devices. Where possible, we help you keep any existing access codes on devices and transition those codes to ones managed by your Seam workspace. - + Not all providers support unmanaged access codes. The following providers do not support unmanaged access codes: - + - `Kwikset `_ :ivar access_code_id: Unique identifier for the access code. @@ -48,8 +48,7 @@ class UnmanagedAccessCode: :ivar warnings: Warnings associated with the `access code `_. - :ivar workspace_id: Unique identifier for the Seam workspace associated with the access code. - """ + :ivar workspace_id: Unique identifier for the Seam workspace associated with the access code.""" @dataclass class DormakabaOracodeMetadata(ResourceMapping): @@ -69,8 +68,7 @@ class DormakabaOracodeMetadata(ResourceMapping): :ivar user_level_id: Dormakaba Oracode user level ID associated with this access code. - :ivar user_level_name: Dormakaba Oracode user level name associated with this access code. - """ + :ivar user_level_name: Dormakaba Oracode user level name associated with this access code.""" is_cancellable: Optional[bool] is_early_checkin_able: Optional[bool] @@ -114,12 +112,11 @@ class Errors(ResourceMapping): :ivar modified_fields: List of fields that were changed externally, with their previous and new values. - :ivar is_connected_account_error: + :ivar is_connected_account_error: - :ivar is_device_error: + :ivar is_device_error: - :ivar is_bridge_error: Indicates whether the error is related to `Seam Bridge `_. - """ + :ivar is_bridge_error: Indicates whether the error is related to `Seam Bridge `_.""" @dataclass class ModifiedFields(ResourceMapping): @@ -165,10 +162,7 @@ def from_dict(cls, d: Any): managed_access_code_id=d.get("managed_access_code_id", None), unmanaged_access_code_id=d.get("unmanaged_access_code_id", None), change_type=d.get("change_type", None), - modified_fields=[ - cls.ModifiedFields.from_dict(i) - for i in d.get("modified_fields") or [] - ], + modified_fields=[cls.ModifiedFields.from_dict(i) for i in d.get("modified_fields") or []], is_connected_account_error=d.get("is_connected_account_error", None), is_device_error=d.get("is_device_error", None), is_bridge_error=d.get("is_bridge_error", None), @@ -186,8 +180,7 @@ class Warnings(ResourceMapping): :ivar change_type: Indicates the type of external modification. ``modified`` means the code's PIN or schedule was changed. ``removed`` means the code was deleted from the device. - :ivar modified_fields: List of fields that were changed externally, with their previous and new values. - """ + :ivar modified_fields: List of fields that were changed externally, with their previous and new values.""" @dataclass class ModifiedFields(ResourceMapping): @@ -224,10 +217,7 @@ def from_dict(cls, d: Any): message=d.get("message", None), warning_code=d.get("warning_code", None), change_type=d.get("change_type", None), - modified_fields=[ - cls.ModifiedFields.from_dict(i) - for i in d.get("modified_fields") or [] - ], + modified_fields=[cls.ModifiedFields.from_dict(i) for i in d.get("modified_fields") or []], ) access_code_id: str @@ -252,19 +242,11 @@ def from_dict(cls, d: Any): return cls( access_code_id=d.get("access_code_id", None), cannot_be_managed=d.get("cannot_be_managed", None), - cannot_delete_unmanaged_access_code=d.get( - "cannot_delete_unmanaged_access_code", None - ), + cannot_delete_unmanaged_access_code=d.get("cannot_delete_unmanaged_access_code", None), code=d.get("code", None), created_at=d.get("created_at", None), device_id=d.get("device_id", None), - dormakaba_oracode_metadata=( - cls.DormakabaOracodeMetadata.from_dict( - d.get("dormakaba_oracode_metadata") - ) - if d.get("dormakaba_oracode_metadata") is not None - else None - ), + dormakaba_oracode_metadata=cls.DormakabaOracodeMetadata.from_dict(d.get("dormakaba_oracode_metadata")) if d.get("dormakaba_oracode_metadata") is not None else None, ends_at=d.get("ends_at", None), errors=[cls.Errors.from_dict(i) for i in d.get("errors") or []], is_managed=d.get("is_managed", None), diff --git a/seam/resources/unmanaged_access_grant.py b/seam/resources/unmanaged_access_grant.py index 71e7a68e..eeff17c3 100644 --- a/seam/resources/unmanaged_access_grant.py +++ b/seam/resources/unmanaged_access_grant.py @@ -50,8 +50,7 @@ class Errors(ResourceMapping): :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - :ivar missing_device_ids: IDs of the devices that did not receive an access code at grant creation. Use these to identify which specific devices failed when the message reports a partial failure. - """ + :ivar missing_device_ids: IDs of the devices that did not receive an access code at grant creation. Use these to identify which specific devices failed when the message reports a partial failure.""" created_at: str error_code: str @@ -73,13 +72,13 @@ class PendingMutations(ResourceMapping): :ivar created_at: Date and time at which the mutation was created. - :ivar from_: + :ivar from_: :ivar message: Detailed description of the mutation. - :ivar mutation_code: + :ivar mutation_code: - :ivar to: + :ivar to: :ivar access_method_ids: IDs of the access methods being updated.""" @@ -142,11 +141,7 @@ def from_dict(cls, d: Any): def from_dict(cls, d: Any): return cls( created_at=d.get("created_at", None), - from_=( - cls.From.from_dict(d.get("from")) - if d.get("from") is not None - else None - ), + from_=cls.From.from_dict(d.get("from")) if d.get("from") is not None else None, message=d.get("message", None), mutation_code=d.get("mutation_code", None), to=cls.To.from_dict(d.get("to")) if d.get("to") is not None else None, @@ -167,8 +162,7 @@ class RequestedAccessMethods(ResourceMapping): :ivar instant_key_max_use_count: Maximum number of times the instant key can be used. Only applicable when mode is 'mobile_key'. Defaults to 1 if not specified. - :ivar mode: Access method mode. Supported values: ``code``, ``card``, ``mobile_key``, ``cloud_key``. - """ + :ivar mode: Access method mode. Supported values: ``code``, ``card``, ``mobile_key``, ``cloud_key``.""" code: Optional[str] created_access_method_ids: List[str] @@ -202,14 +196,13 @@ class Warnings(ResourceMapping): :ivar access_method_ids: IDs of the access methods being updated. - :ivar device_id: + :ivar device_id: :ivar new_code: The new PIN code that was assigned instead. :ivar original_code: The originally requested PIN code that was unavailable. - :ivar reason: Specific reason why the grant's times are not programmable on the device. - """ + :ivar reason: Specific reason why the grant's times are not programmable on the device.""" @dataclass class FailedDevices(ResourceMapping): @@ -249,10 +242,7 @@ def from_dict(cls, d: Any): created_at=d.get("created_at", None), message=d.get("message", None), warning_code=d.get("warning_code", None), - failed_devices=[ - cls.FailedDevices.from_dict(i) - for i in d.get("failed_devices") or [] - ], + failed_devices=[cls.FailedDevices.from_dict(i) for i in d.get("failed_devices") or []], access_method_ids=d.get("access_method_ids", None), device_id=d.get("device_id", None), new_code=d.get("new_code", None), @@ -288,14 +278,8 @@ def from_dict(cls, d: Any): errors=[cls.Errors.from_dict(i) for i in d.get("errors") or []], location_ids=d.get("location_ids", None), name=d.get("name", None), - pending_mutations=[ - cls.PendingMutations.from_dict(i) - for i in d.get("pending_mutations") or [] - ], - requested_access_methods=[ - cls.RequestedAccessMethods.from_dict(i) - for i in d.get("requested_access_methods") or [] - ], + pending_mutations=[cls.PendingMutations.from_dict(i) for i in d.get("pending_mutations") or []], + requested_access_methods=[cls.RequestedAccessMethods.from_dict(i) for i in d.get("requested_access_methods") or []], reservation_key=d.get("reservation_key", None), space_ids=d.get("space_ids", None), starts_at=d.get("starts_at", None), diff --git a/seam/resources/unmanaged_access_method.py b/seam/resources/unmanaged_access_method.py index c30e9a4e..b0300659 100644 --- a/seam/resources/unmanaged_access_method.py +++ b/seam/resources/unmanaged_access_method.py @@ -46,8 +46,7 @@ class Errors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - """ + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" created_at: str error_code: str @@ -67,19 +66,19 @@ class PendingMutations(ResourceMapping): :ivar created_at: Date and time at which the mutation was created. - :ivar from_: + :ivar from_: :ivar message: Detailed description of the mutation. - :ivar mutation_code: + :ivar mutation_code: - :ivar to:""" + :ivar to: """ @dataclass class From(ResourceMapping): """ - :ivar device_ids: + :ivar device_ids: :ivar ends_at: Previous end time for access. @@ -101,7 +100,7 @@ def from_dict(cls, d: Any): class To(ResourceMapping): """ - :ivar device_ids: + :ivar device_ids: :ivar ends_at: New end time for access. @@ -129,11 +128,7 @@ def from_dict(cls, d: Any): def from_dict(cls, d: Any): return cls( created_at=d.get("created_at", None), - from_=( - cls.From.from_dict(d.get("from")) - if d.get("from") is not None - else None - ), + from_=cls.From.from_dict(d.get("from")) if d.get("from") is not None else None, message=d.get("message", None), mutation_code=d.get("mutation_code", None), to=cls.To.from_dict(d.get("to")) if d.get("to") is not None else None, @@ -149,8 +144,7 @@ class Warnings(ResourceMapping): :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - :ivar original_access_method_id: ID of the original access method from which this backup access method was split, if applicable. - """ + :ivar original_access_method_id: ID of the original access method from which this backup access method was split, if applicable.""" created_at: str message: str @@ -197,10 +191,7 @@ def from_dict(cls, d: Any): is_ready_for_encoding=d.get("is_ready_for_encoding", None), issued_at=d.get("issued_at", None), mode=d.get("mode", None), - pending_mutations=[ - cls.PendingMutations.from_dict(i) - for i in d.get("pending_mutations") or [] - ], + pending_mutations=[cls.PendingMutations.from_dict(i) for i in d.get("pending_mutations") or []], warnings=[cls.Warnings.from_dict(i) for i in d.get("warnings") or []], workspace_id=d.get("workspace_id", None), ) diff --git a/seam/resources/unmanaged_device.py b/seam/resources/unmanaged_device.py index 15ba2dcc..e1432f3d 100644 --- a/seam/resources/unmanaged_device.py +++ b/seam/resources/unmanaged_device.py @@ -70,8 +70,7 @@ class UnmanagedDevice: :ivar warnings: Array of warnings associated with the device. Each warning object within the array contains two fields: ``warning_code`` and ``message``. ``warning_code`` is a string that uniquely identifies the type of warning, enabling quick recognition and categorization of the issue. ``message`` provides a more detailed description of the warning, offering insights into the issue and potentially how to rectify it. - :ivar workspace_id: Unique identifier for the Seam workspace associated with the device. - """ + :ivar workspace_id: Unique identifier for the Seam workspace associated with the device.""" @dataclass class Errors(ResourceMapping): @@ -81,14 +80,13 @@ class Errors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar is_connected_account_error: + :ivar is_connected_account_error: - :ivar is_device_error: + :ivar is_device_error: :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - :ivar is_bridge_error: Indicates whether the error is related to `Seam Bridge `_. - """ + :ivar is_bridge_error: Indicates whether the error is related to `Seam Bridge `_.""" created_at: str error_code: str @@ -118,8 +116,7 @@ class Location(ResourceMapping): :ivar time_zone: Time zone of the device location. - :ivar timezone: Deprecated: Use ``time_zone`` instead. Time zone of the device location. - """ + :ivar timezone: Deprecated: Use ``time_zone`` instead. Time zone of the device location.""" location_name: Optional[str] room_name: Optional[str] @@ -159,8 +156,7 @@ class Properties(ResourceMapping): :ivar online: Indicates whether the device is online. - :ivar online_access_codes_enabled: Deprecated: use device.can_program_online_access_codes Indicates whether it is currently possible to use online access codes for the device. - """ + :ivar online_access_codes_enabled: Deprecated: use device.can_program_online_access_codes Indicates whether it is currently possible to use online access codes for the device.""" @dataclass class AccessoryKeypad(ResourceMapping): @@ -168,14 +164,13 @@ class AccessoryKeypad(ResourceMapping): :ivar battery: Keypad battery properties. - :ivar is_connected: Indicates if an accessory keypad is connected to the device. - """ + :ivar is_connected: Indicates if an accessory keypad is connected to the device.""" @dataclass class Battery(ResourceMapping): """Keypad battery properties. - :ivar level:""" + :ivar level: """ level: float @@ -191,11 +186,7 @@ def from_dict(cls, d: Any): @classmethod def from_dict(cls, d: Any): return cls( - battery=( - cls.Battery.from_dict(d.get("battery")) - if d.get("battery") is not None - else None - ), + battery=cls.Battery.from_dict(d.get("battery")) if d.get("battery") is not None else None, is_connected=d.get("is_connected", None), ) @@ -205,8 +196,7 @@ class Battery(ResourceMapping): :ivar level: Battery charge level as a value between 0 and 1, inclusive. - :ivar status: Represents the current status of the battery charge level. Values are ``critical``, which indicates an extremely low level, suggesting imminent shutdown or an urgent need for charging; ``low``, which signifies that the battery is under the preferred threshold and should be charged soon; ``good``, which denotes a satisfactory charge level, adequate for normal use without the immediate need for recharging; and ``full``, which represents a battery that is fully charged, providing the maximum duration of usage. - """ + :ivar status: Represents the current status of the battery charge level. Values are ``critical``, which indicates an extremely low level, suggesting imminent shutdown or an urgent need for charging; ``low``, which signifies that the battery is under the preferred threshold and should be charged soon; ``good``, which denotes a satisfactory charge level, adequate for normal use without the immediate need for recharging; and ``full``, which represents a battery that is fully charged, providing the maximum duration of usage.""" level: float status: str @@ -234,8 +224,7 @@ class Model(ResourceMapping): :ivar offline_access_codes_supported: Deprecated: use device.can_program_offline_access_codes. - :ivar online_access_codes_supported: Deprecated: use device.can_program_online_access_codes. - """ + :ivar online_access_codes_supported: Deprecated: use device.can_program_online_access_codes.""" accessory_keypad_supported: Optional[bool] can_connect_accessory_keypad: Optional[bool] @@ -248,21 +237,13 @@ class Model(ResourceMapping): @classmethod def from_dict(cls, d: Any): return cls( - accessory_keypad_supported=d.get( - "accessory_keypad_supported", None - ), - can_connect_accessory_keypad=d.get( - "can_connect_accessory_keypad", None - ), + accessory_keypad_supported=d.get("accessory_keypad_supported", None), + can_connect_accessory_keypad=d.get("can_connect_accessory_keypad", None), display_name=d.get("display_name", None), has_built_in_keypad=d.get("has_built_in_keypad", None), manufacturer_display_name=d.get("manufacturer_display_name", None), - offline_access_codes_supported=d.get( - "offline_access_codes_supported", None - ), - online_access_codes_supported=d.get( - "online_access_codes_supported", None - ), + offline_access_codes_supported=d.get("offline_access_codes_supported", None), + online_access_codes_supported=d.get("online_access_codes_supported", None), ) accessory_keypad: Optional[AccessoryKeypad] @@ -280,29 +261,15 @@ def from_dict(cls, d: Any): @classmethod def from_dict(cls, d: Any): return cls( - accessory_keypad=( - cls.AccessoryKeypad.from_dict(d.get("accessory_keypad")) - if d.get("accessory_keypad") is not None - else None - ), - battery=( - cls.Battery.from_dict(d.get("battery")) - if d.get("battery") is not None - else None - ), + accessory_keypad=cls.AccessoryKeypad.from_dict(d.get("accessory_keypad")) if d.get("accessory_keypad") is not None else None, + battery=cls.Battery.from_dict(d.get("battery")) if d.get("battery") is not None else None, battery_level=d.get("battery_level", None), image_alt_text=d.get("image_alt_text", None), image_url=d.get("image_url", None), manufacturer=d.get("manufacturer", None), - model=( - cls.Model.from_dict(d.get("model")) - if d.get("model") is not None - else None - ), + model=cls.Model.from_dict(d.get("model")) if d.get("model") is not None else None, name=d.get("name", None), - offline_access_codes_enabled=d.get( - "offline_access_codes_enabled", None - ), + offline_access_codes_enabled=d.get("offline_access_codes_enabled", None), online=d.get("online", None), online_access_codes_enabled=d.get("online_access_codes_enabled", None), ) @@ -319,8 +286,7 @@ class Warnings(ResourceMapping): :ivar active_access_code_count: Number of active access codes on the device when the warning was set. - :ivar max_active_access_code_count: Maximum number of active access codes supported by the device. - """ + :ivar max_active_access_code_count: Maximum number of active access codes supported by the device.""" created_at: str message: str @@ -335,9 +301,7 @@ def from_dict(cls, d: Any): message=d.get("message", None), warning_code=d.get("warning_code", None), active_access_code_count=d.get("active_access_code_count", None), - max_active_access_code_count=d.get( - "max_active_access_code_count", None - ), + max_active_access_code_count=d.get("max_active_access_code_count", None), ) can_configure_auto_lock: Optional[bool] @@ -380,33 +344,19 @@ def from_dict(cls, d: Any): can_hvac_cool=d.get("can_hvac_cool", None), can_hvac_heat=d.get("can_hvac_heat", None), can_hvac_heat_cool=d.get("can_hvac_heat_cool", None), - can_program_offline_access_codes=d.get( - "can_program_offline_access_codes", None - ), - can_program_online_access_codes=d.get( - "can_program_online_access_codes", None - ), - can_program_thermostat_programs_as_different_each_day=d.get( - "can_program_thermostat_programs_as_different_each_day", None - ), - can_program_thermostat_programs_as_same_each_day=d.get( - "can_program_thermostat_programs_as_same_each_day", None - ), - can_program_thermostat_programs_as_weekday_weekend=d.get( - "can_program_thermostat_programs_as_weekday_weekend", None - ), + can_program_offline_access_codes=d.get("can_program_offline_access_codes", None), + can_program_online_access_codes=d.get("can_program_online_access_codes", None), + can_program_thermostat_programs_as_different_each_day=d.get("can_program_thermostat_programs_as_different_each_day", None), + can_program_thermostat_programs_as_same_each_day=d.get("can_program_thermostat_programs_as_same_each_day", None), + can_program_thermostat_programs_as_weekday_weekend=d.get("can_program_thermostat_programs_as_weekday_weekend", None), can_remotely_lock=d.get("can_remotely_lock", None), can_remotely_unlock=d.get("can_remotely_unlock", None), can_run_thermostat_programs=d.get("can_run_thermostat_programs", None), can_simulate_connection=d.get("can_simulate_connection", None), can_simulate_disconnection=d.get("can_simulate_disconnection", None), can_simulate_hub_connection=d.get("can_simulate_hub_connection", None), - can_simulate_hub_disconnection=d.get( - "can_simulate_hub_disconnection", None - ), - can_simulate_paid_subscription=d.get( - "can_simulate_paid_subscription", None - ), + can_simulate_hub_disconnection=d.get("can_simulate_hub_disconnection", None), + can_simulate_paid_subscription=d.get("can_simulate_paid_subscription", None), can_simulate_removal=d.get("can_simulate_removal", None), can_turn_off_hvac=d.get("can_turn_off_hvac", None), can_unlock_with_code=d.get("can_unlock_with_code", None), @@ -418,16 +368,8 @@ def from_dict(cls, d: Any): device_type=d.get("device_type", None), errors=[cls.Errors.from_dict(i) for i in d.get("errors") or []], is_managed=d.get("is_managed", None), - location=( - cls.Location.from_dict(d.get("location")) - if d.get("location") is not None - else None - ), - properties=( - cls.Properties.from_dict(d.get("properties")) - if d.get("properties") is not None - else None - ), + location=cls.Location.from_dict(d.get("location")) if d.get("location") is not None else None, + properties=cls.Properties.from_dict(d.get("properties")) if d.get("properties") is not None else None, warnings=[cls.Warnings.from_dict(i) for i in d.get("warnings") or []], workspace_id=d.get("workspace_id", None), ) diff --git a/seam/resources/unmanaged_user_identity.py b/seam/resources/unmanaged_user_identity.py index 6905f5af..2df3a66c 100644 --- a/seam/resources/unmanaged_user_identity.py +++ b/seam/resources/unmanaged_user_identity.py @@ -40,8 +40,7 @@ class Errors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - """ + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" acs_system_id: str acs_user_id: str @@ -67,8 +66,7 @@ class Warnings(ResourceMapping): :ivar message: Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - """ + :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue.""" created_at: str message: str diff --git a/seam/resources/user_identity.py b/seam/resources/user_identity.py index 141a82a3..9b1166b0 100644 --- a/seam/resources/user_identity.py +++ b/seam/resources/user_identity.py @@ -42,8 +42,7 @@ class Errors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - """ + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" acs_system_id: str acs_user_id: str @@ -69,8 +68,7 @@ class Warnings(ResourceMapping): :ivar message: Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - """ + :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue.""" created_at: str message: str diff --git a/seam/resources/workspace.py b/seam/resources/workspace.py index bbc0e01e..4d7c5836 100644 --- a/seam/resources/workspace.py +++ b/seam/resources/workspace.py @@ -12,7 +12,7 @@ class Workspace: :ivar connect_partner_name: Deprecated: Use ``company_name`` instead. - :ivar connect_webview_customization: + :ivar connect_webview_customization: :ivar is_publishable_key_auth_enabled: Indicates whether publishable key authentication is enabled for this workspace. @@ -40,8 +40,7 @@ class ConnectWebviewCustomization(ResourceMapping): :ivar primary_button_text_color: Primary button text color for `Connect Webviews `_ in the workspace. See also `Customize the Look and Feel of Your Connect Webviews `_. - :ivar success_message: Success message for `Connect Webviews `_ in the workspace. See also `Customize the Look and Feel of Your Connect Webviews `_. - """ + :ivar success_message: Success message for `Connect Webviews `_ in the workspace. See also `Customize the Look and Feel of Your Connect Webviews `_.""" inviter_logo_url: Optional[str] logo_shape: Optional[str] @@ -75,16 +74,8 @@ def from_dict(cls, d: Any): return cls( company_name=d.get("company_name", None), connect_partner_name=d.get("connect_partner_name", None), - connect_webview_customization=( - cls.ConnectWebviewCustomization.from_dict( - d.get("connect_webview_customization") - ) - if d.get("connect_webview_customization") is not None - else None - ), - is_publishable_key_auth_enabled=d.get( - "is_publishable_key_auth_enabled", None - ), + connect_webview_customization=cls.ConnectWebviewCustomization.from_dict(d.get("connect_webview_customization")) if d.get("connect_webview_customization") is not None else None, + is_publishable_key_auth_enabled=d.get("is_publishable_key_auth_enabled", None), is_sandbox=d.get("is_sandbox", None), is_suspended=d.get("is_suspended", None), name=d.get("name", None), diff --git a/seam/routes/access_codes.py b/seam/routes/access_codes.py index 82231033..972fe15c 100644 --- a/seam/routes/access_codes.py +++ b/seam/routes/access_codes.py @@ -2,7 +2,8 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import AccessCode +from ..null import Null +from ..resources import (AccessCode) from .access_codes_simulate import AbstractAccessCodesSimulate, AccessCodesSimulate from .access_codes_unmanaged import AbstractAccessCodesUnmanaged, AccessCodesUnmanaged @@ -20,33 +21,14 @@ def unmanaged(self) -> AbstractAccessCodesUnmanaged: raise NotImplementedError() @abc.abstractmethod - def create( - self, - *, - device_id: str, - allow_external_modification: Optional[bool] = None, - attempt_for_offline_device: Optional[bool] = None, - code: Optional[str] = None, - common_code_key: Optional[str] = None, - ends_at: Optional[str] = None, - is_external_modification_allowed: Optional[bool] = None, - is_offline_access_code: Optional[bool] = None, - is_one_time_use: Optional[bool] = None, - max_time_rounding: Optional[str] = None, - name: Optional[str] = None, - prefer_native_scheduling: Optional[bool] = None, - preferred_code_length: Optional[float] = None, - starts_at: Optional[str] = None, - use_backup_access_code_pool: Optional[bool] = None, - use_offline_access_code: Optional[bool] = None, - ) -> AccessCode: + def create(self, *, device_id: str, allow_external_modification: Optional[bool] = None, attempt_for_offline_device: Optional[bool] = None, code: Optional[str] = None, common_code_key: Optional[str] = None, ends_at: Optional[str] = None, is_external_modification_allowed: Optional[bool] = None, is_offline_access_code: Optional[bool] = None, is_one_time_use: Optional[bool] = None, max_time_rounding: Optional[str] = None, name: Optional[str] = None, prefer_native_scheduling: Optional[bool] = None, preferred_code_length: Optional[float] = None, starts_at: Optional[str] = None, use_backup_access_code_pool: Optional[bool] = None, use_offline_access_code: Optional[bool] = None) -> AccessCode: """Creates a new `access code `_. For granting access, we recommend `Access Grants `_ instead: they work across both standalone smart locks and access control systems and manage the underlying codes for you. Use this low-level endpoint only when you need direct control over a code on a single device, such as setting a custom PIN value. :param device_id: ID of the device for which you want to create the new access code. :param allow_external_modification: Indicates whether `external modification `_ of the code is allowed. Default: ``false``. - :param attempt_for_offline_device: + :param attempt_for_offline_device: :param code: Code to be used for access. @@ -63,11 +45,11 @@ def create( :param max_time_rounding: Maximum rounding adjustment. To create a daily-bound `offline access code `_ for devices that support this feature, set this parameter to ``1d``. :param name: Name of the new access code. Enables administrators and users to identify the access code easily, especially when there are numerous access codes. - + Note that the name provided on Seam is used to identify the code on Seam and is not necessarily the name that will appear in the lock provider's app or on the device. This is because lock providers may have constraints on names, such as length, uniqueness, or characters that can be used. In addition, some lock providers may break down names into components such as ``first_name`` and ``last_name``. - + To provide a consistent experience, Seam identifies the code on Seam by its name but may modify the name that appears on the lock provider's app or on the device. For example, Seam may add additional characters or truncate the name to meet provider constraints. - + To help your users identify codes set by Seam, Seam provides the name exactly as it appears on the lock provider's app or on the device as a separate property called ``appearance``. This is an object with a ``name`` property and, optionally, ``first_name`` and ``last_name`` properties (for providers that break down a name into components). :param prefer_native_scheduling: Indicates whether `native scheduling `_ should be used for time-bound codes when supported by the provider. Default: ``true``. @@ -86,39 +68,24 @@ def create( raise NotImplementedError() @abc.abstractmethod - def create_multiple( - self, - *, - device_ids: List[str], - allow_external_modification: Optional[bool] = None, - attempt_for_offline_device: Optional[bool] = None, - behavior_when_code_cannot_be_shared: Optional[str] = None, - code: Optional[str] = None, - ends_at: Optional[str] = None, - is_external_modification_allowed: Optional[bool] = None, - name: Optional[str] = None, - prefer_native_scheduling: Optional[bool] = None, - preferred_code_length: Optional[float] = None, - starts_at: Optional[str] = None, - use_backup_access_code_pool: Optional[bool] = None, - ) -> List[AccessCode]: + def create_multiple(self, *, device_ids: List[str], allow_external_modification: Optional[bool] = None, attempt_for_offline_device: Optional[bool] = None, behavior_when_code_cannot_be_shared: Optional[str] = None, code: Optional[str] = None, ends_at: Optional[str] = None, is_external_modification_allowed: Optional[bool] = None, name: Optional[str] = None, prefer_native_scheduling: Optional[bool] = None, preferred_code_length: Optional[float] = None, starts_at: Optional[str] = None, use_backup_access_code_pool: Optional[bool] = None) -> List[AccessCode]: """Creates new `access codes `_ that share a common code across multiple devices. - + Users with more than one door lock in a property may want to create groups of linked access codes, all of which have the same code (PIN). For example, a short-term rental host may want to provide guests the same PIN for both a front door lock and a back door lock. - + If you specify a custom code, Seam assigns this custom code to each of the resulting access codes. However, in this case, Seam does not link these access codes together with a ``common_code_key``. That is, ``common_code_key`` remains null for these access codes. - + If you want to change these access codes that are not linked by a ``common_code_key``, you cannot use ``/access_codes/update_multiple``. However, you can update each of these access codes individually, using ``/access_codes/update``. - + See also `Creating and Updating Multiple Linked Access Codes `_. - + For granting a person access to a space, `Access Grants `_ are the default and recommended approach and work across both standalone smart locks and access systems. Use the lower-level Access Codes API directly only when you specifically need to manage individual PIN codes. :param device_ids: IDs of the devices for which you want to create the new access codes. :param allow_external_modification: Indicates whether `external modification `_ of the code is allowed. Default: ``false``. - :param attempt_for_offline_device: + :param attempt_for_offline_device: :param behavior_when_code_cannot_be_shared: Desired behavior if any device cannot share a code. If ``throw`` (default), no access codes will be created if any device cannot share a code. If ``create_random_code``, a random code will be created on devices that cannot share a code. @@ -129,11 +96,11 @@ def create_multiple( :param is_external_modification_allowed: Indicates whether `external modification `_ of the code is allowed. Default: ``false``. :param name: Name of the new access code. Enables administrators and users to identify the access code easily, especially when there are numerous access codes. - + Note that the name provided on Seam is used to identify the code on Seam and is not necessarily the name that will appear in the lock provider's app or on the device. This is because lock providers may have constraints on names, such as length, uniqueness, or characters that can be used. In addition, some lock providers may break down names into components such as ``first_name`` and ``last_name``. - + To provide a consistent experience, Seam identifies the code on Seam by its name but may modify the name that appears on the lock provider's app or on the device. For example, Seam may add additional characters or truncate the name to meet provider constraints. - + To help your users identify codes set by Seam, Seam provides the name exactly as it appears on the lock provider's app or on the device as a separate property called ``appearance``. This is an object with a ``name`` property and, optionally, ``first_name`` and ``last_name`` properties (for providers that break down a name into components). :param prefer_native_scheduling: Indicates whether `native scheduling `_ should be used for time-bound codes when supported by the provider. Default: ``true``. @@ -172,15 +139,9 @@ def generate_code(self, *, device_id: str) -> AccessCode: raise NotImplementedError() @abc.abstractmethod - def get( - self, - *, - access_code_id: Optional[str] = None, - code: Optional[str] = None, - device_id: Optional[str] = None, - ) -> AccessCode: + def get(self, *, access_code_id: Optional[str] = None, code: Optional[str] = None, device_id: Optional[str] = None) -> AccessCode: """Returns a specified `access code `_. - + You must specify either ``access_code_id`` or both ``device_id`` and ``code``. :param access_code_id: ID of the access code that you want to get. You must specify either ``access_code_id`` or both ``device_id`` and ``code``. @@ -195,22 +156,9 @@ def get( raise NotImplementedError() @abc.abstractmethod - def list( - self, - *, - access_code_ids: Optional[List[str]] = None, - access_grant_id: Optional[str] = None, - access_grant_key: Optional[str] = None, - access_method_id: Optional[str] = None, - customer_key: Optional[str] = None, - device_id: Optional[str] = None, - limit: Optional[float] = None, - page_cursor: Optional[str] = None, - search: Optional[str] = None, - user_identifier_key: Optional[str] = None, - ) -> List[AccessCode]: + def list(self, *, access_code_ids: Optional[List[str]] = None, access_grant_id: Optional[str] = None, access_grant_key: Optional[str] = None, access_method_id: Optional[str] = None, customer_key: Optional[str] = None, device_id: Optional[str] = None, limit: Optional[float] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, user_identifier_key: Optional[str] = None) -> List[AccessCode]: """Returns a list of all `access codes `_. - + Specify ``device_id``, ``access_code_ids``, ``access_method_id``, ``access_grant_id``, or ``access_grant_key``. :param access_code_ids: IDs of the access codes that you want to retrieve. Specify ``device_id``, ``access_code_ids``, ``access_method_id``, ``access_grant_id``, or ``access_grant_key``. @@ -241,13 +189,13 @@ def list( @abc.abstractmethod def pull_backup_access_code(self, *, access_code_id: str) -> AccessCode: """Retrieves a backup access code for an `access code `_. See also `Managing Backup Access Codes `_. - + A backup access code pool is a collection of pre-programmed access codes stored on a device, ready for use. These codes are programmed in addition to the regular access codes on Seam, serving as a safety net for any issues with the primary codes. If there's ever a complication with a primary access code—be it due to intermittent connectivity, manual removal from a device, or provider outages—a backup code can be retrieved. Its end time can then be adjusted to align with the original code, facilitating seamless and uninterrupted access. - + You can pull a backup access code from the pool at any time. These backup codes are guaranteed to work immediately and automatically programmed to be removed from the device after the access code ends. - + You can only pull backup access codes for time-bound access codes. - + Before pulling a backup access code, make sure that the device's ``properties.supports_backup_access_code_pool`` is ``true``. Then, to activate the backup pool, set ``use_backup_access_code_pool`` to ``true`` when creating an access code. :param access_code_id: ID of the access code for which you want to pull a backup access code. @@ -258,16 +206,9 @@ def pull_backup_access_code(self, *, access_code_id: str) -> AccessCode: raise NotImplementedError() @abc.abstractmethod - def report_device_constraints( - self, - *, - device_id: str, - max_code_length: Optional[int] = None, - min_code_length: Optional[int] = None, - supported_code_lengths: Optional[List[float]] = None, - ) -> None: + def report_device_constraints(self, *, device_id: str, max_code_length: Optional[int] = None, min_code_length: Optional[int] = None, supported_code_lengths: Optional[List[float]] = None) -> None: """Enables you to report access code-related constraints for a device. Currently, supports reporting supported code length constraints for SmartThings devices. - + Specify either ``supported_code_lengths`` or ``min_code_length``/``max_code_length``. :param device_id: ID of the device for which you want to report constraints. @@ -282,30 +223,16 @@ def report_device_constraints( raise NotImplementedError() @abc.abstractmethod - def update( - self, - *, - access_code_id: str, - allow_external_modification: Optional[bool] = None, - attempt_for_offline_device: Optional[bool] = None, - code: Optional[str] = None, - device_id: Optional[str] = None, - ends_at: Optional[str] = None, - is_external_modification_allowed: Optional[bool] = None, - is_managed: Optional[bool] = None, - name: Optional[str] = None, - starts_at: Optional[str] = None, - type: Optional[str] = None, - ) -> None: + def update(self, *, access_code_id: str, allow_external_modification: Optional[bool] = None, attempt_for_offline_device: Optional[bool] = None, code: Optional[str] = None, device_id: Optional[str] = None, ends_at: Optional[str] = None, is_external_modification_allowed: Optional[bool] = None, is_managed: Optional[bool] = None, name: Optional[str] = None, starts_at: Optional[str] = None, type: Optional[str] = None) -> None: """Updates a specified active or upcoming `access code `_. - + See also `Modifying Access Codes `_. :param access_code_id: ID of the access code that you want to update. :param allow_external_modification: Indicates whether `external modification `_ of the code is allowed. Default: ``false``. - :param attempt_for_offline_device: + :param attempt_for_offline_device: :param code: Code to be used for access. @@ -318,11 +245,11 @@ def update( :param is_managed: Indicates whether the access code is managed through Seam. Note that to convert an unmanaged access code into a managed access code, use ``/access_codes/unmanaged/convert_to_managed``. :param name: Name of the new access code. Enables administrators and users to identify the access code easily, especially when there are numerous access codes. - + Note that the name provided on Seam is used to identify the code on Seam and is not necessarily the name that will appear in the lock provider's app or on the device. This is because lock providers may have constraints on names, such as length, uniqueness, or characters that can be used. In addition, some lock providers may break down names into components such as ``first_name`` and ``last_name``. - + To provide a consistent experience, Seam identifies the code on Seam by its name but may modify the name that appears on the lock provider's app or on the device. For example, Seam may add additional characters or truncate the name to meet provider constraints. - + To help your users identify codes set by Seam, Seam provides the name exactly as it appears on the lock provider's app or on the device as a separate property called ``appearance``. This is an object with a ``name`` property and, optionally, ``first_name`` and ``last_name`` properties (for providers that break down a name into components). :param starts_at: Date and time at which the validity of the new access code starts, in `ISO 8601 `_ format. @@ -333,18 +260,11 @@ def update( raise NotImplementedError() @abc.abstractmethod - def update_multiple( - self, - *, - common_code_key: str, - ends_at: Optional[str] = None, - name: Optional[str] = None, - starts_at: Optional[str] = None, - ) -> None: + def update_multiple(self, *, common_code_key: str, ends_at: Optional[str] = None, name: Optional[str] = None, starts_at: Optional[str] = None) -> None: """Updates `access codes `_ that share a common code across multiple devices. - + Specify the ``common_code_key`` to identify the set of access codes that you want to update. - + See also `Update Linked Access Codes `_. :param common_code_key: Key that links the group of access codes, assigned on creation by ``/access_codes/create_multiple``. @@ -352,11 +272,11 @@ def update_multiple( :param ends_at: Date and time at which the validity of the new access code ends, in `ISO 8601 `_ format. Must be a time in the future and after ``starts_at``. :param name: Name of the new access code. Enables administrators and users to identify the access code easily, especially when there are numerous access codes. - + Note that the name provided on Seam is used to identify the code on Seam and is not necessarily the name that will appear in the lock provider's app or on the device. This is because lock providers may have constraints on names, such as length, uniqueness, or characters that can be used. In addition, some lock providers may break down names into components such as ``first_name`` and ``last_name``. - + To provide a consistent experience, Seam identifies the code on Seam by its name but may modify the name that appears on the lock provider's app or on the device. For example, Seam may add additional characters or truncate the name to meet provider constraints. - + To help your users identify codes set by Seam, Seam provides the name exactly as it appears on the lock provider's app or on the device as a separate property called ``appearance``. This is an object with a ``name`` property and, optionally, ``first_name`` and ``last_name`` properties (for providers that break down a name into components). :param starts_at: Date and time at which the validity of the new access code starts, in `ISO 8601 `_ format. @@ -380,36 +300,15 @@ def simulate(self) -> AccessCodesSimulate: def unmanaged(self) -> AccessCodesUnmanaged: return self._unmanaged - @route_metadata( - path="/access_codes/create", has_required_parameters=True, has_pagination=False - ) - def create( - self, - *, - device_id: str, - allow_external_modification: Optional[bool] = None, - attempt_for_offline_device: Optional[bool] = None, - code: Optional[str] = None, - common_code_key: Optional[str] = None, - ends_at: Optional[str] = None, - is_external_modification_allowed: Optional[bool] = None, - is_offline_access_code: Optional[bool] = None, - is_one_time_use: Optional[bool] = None, - max_time_rounding: Optional[str] = None, - name: Optional[str] = None, - prefer_native_scheduling: Optional[bool] = None, - preferred_code_length: Optional[float] = None, - starts_at: Optional[str] = None, - use_backup_access_code_pool: Optional[bool] = None, - use_offline_access_code: Optional[bool] = None, - ) -> AccessCode: + @route_metadata(path="/access_codes/create", has_required_parameters=True, has_pagination=False) + def create(self, *, device_id: str, allow_external_modification: Optional[bool] = None, attempt_for_offline_device: Optional[bool] = None, code: Optional[str] = None, common_code_key: Optional[str] = None, ends_at: Optional[str] = None, is_external_modification_allowed: Optional[bool] = None, is_offline_access_code: Optional[bool] = None, is_one_time_use: Optional[bool] = None, max_time_rounding: Optional[str] = None, name: Optional[str] = None, prefer_native_scheduling: Optional[bool] = None, preferred_code_length: Optional[float] = None, starts_at: Optional[str] = None, use_backup_access_code_pool: Optional[bool] = None, use_offline_access_code: Optional[bool] = None) -> AccessCode: """Creates a new `access code `_. For granting access, we recommend `Access Grants `_ instead: they work across both standalone smart locks and access control systems and manage the underlying codes for you. Use this low-level endpoint only when you need direct control over a code on a single device, such as setting a custom PIN value. :param device_id: ID of the device for which you want to create the new access code. :param allow_external_modification: Indicates whether `external modification `_ of the code is allowed. Default: ``false``. - :param attempt_for_offline_device: + :param attempt_for_offline_device: :param code: Code to be used for access. @@ -426,11 +325,11 @@ def create( :param max_time_rounding: Maximum rounding adjustment. To create a daily-bound `offline access code `_ for devices that support this feature, set this parameter to ``1d``. :param name: Name of the new access code. Enables administrators and users to identify the access code easily, especially when there are numerous access codes. - + Note that the name provided on Seam is used to identify the code on Seam and is not necessarily the name that will appear in the lock provider's app or on the device. This is because lock providers may have constraints on names, such as length, uniqueness, or characters that can be used. In addition, some lock providers may break down names into components such as ``first_name`` and ``last_name``. - + To provide a consistent experience, Seam identifies the code on Seam by its name but may modify the name that appears on the lock provider's app or on the device. For example, Seam may add additional characters or truncate the name to meet provider constraints. - + To help your users identify codes set by Seam, Seam provides the name exactly as it appears on the lock provider's app or on the device as a separate property called ``appearance``. This is an object with a ``name`` property and, optionally, ``first_name`` and ``last_name`` properties (for providers that break down a name into components). :param prefer_native_scheduling: Indicates whether `native scheduling `_ should be used for time-bound codes when supported by the provider. Default: ``true``. @@ -461,9 +360,7 @@ def create( if ends_at is not None: json_payload["ends_at"] = ends_at if is_external_modification_allowed is not None: - json_payload["is_external_modification_allowed"] = ( - is_external_modification_allowed - ) + json_payload["is_external_modification_allowed"] = is_external_modification_allowed if is_offline_access_code is not None: json_payload["is_offline_access_code"] = is_offline_access_code if is_one_time_use is not None: @@ -484,52 +381,31 @@ def create( json_payload["use_offline_access_code"] = use_offline_access_code if not json_payload: - raise ValueError( - "At least one parameter is required for /access_codes/create" - ) + raise ValueError("At least one parameter is required for /access_codes/create") res = self.client.post("/access_codes/create", json=json_payload) return AccessCode.from_dict(res["access_code"]) - @route_metadata( - path="/access_codes/create_multiple", - has_required_parameters=True, - has_pagination=False, - ) - def create_multiple( - self, - *, - device_ids: List[str], - allow_external_modification: Optional[bool] = None, - attempt_for_offline_device: Optional[bool] = None, - behavior_when_code_cannot_be_shared: Optional[str] = None, - code: Optional[str] = None, - ends_at: Optional[str] = None, - is_external_modification_allowed: Optional[bool] = None, - name: Optional[str] = None, - prefer_native_scheduling: Optional[bool] = None, - preferred_code_length: Optional[float] = None, - starts_at: Optional[str] = None, - use_backup_access_code_pool: Optional[bool] = None, - ) -> List[AccessCode]: + @route_metadata(path="/access_codes/create_multiple", has_required_parameters=True, has_pagination=False) + def create_multiple(self, *, device_ids: List[str], allow_external_modification: Optional[bool] = None, attempt_for_offline_device: Optional[bool] = None, behavior_when_code_cannot_be_shared: Optional[str] = None, code: Optional[str] = None, ends_at: Optional[str] = None, is_external_modification_allowed: Optional[bool] = None, name: Optional[str] = None, prefer_native_scheduling: Optional[bool] = None, preferred_code_length: Optional[float] = None, starts_at: Optional[str] = None, use_backup_access_code_pool: Optional[bool] = None) -> List[AccessCode]: """Creates new `access codes `_ that share a common code across multiple devices. - + Users with more than one door lock in a property may want to create groups of linked access codes, all of which have the same code (PIN). For example, a short-term rental host may want to provide guests the same PIN for both a front door lock and a back door lock. - + If you specify a custom code, Seam assigns this custom code to each of the resulting access codes. However, in this case, Seam does not link these access codes together with a ``common_code_key``. That is, ``common_code_key`` remains null for these access codes. - + If you want to change these access codes that are not linked by a ``common_code_key``, you cannot use ``/access_codes/update_multiple``. However, you can update each of these access codes individually, using ``/access_codes/update``. - + See also `Creating and Updating Multiple Linked Access Codes `_. - + For granting a person access to a space, `Access Grants `_ are the default and recommended approach and work across both standalone smart locks and access systems. Use the lower-level Access Codes API directly only when you specifically need to manage individual PIN codes. :param device_ids: IDs of the devices for which you want to create the new access codes. :param allow_external_modification: Indicates whether `external modification `_ of the code is allowed. Default: ``false``. - :param attempt_for_offline_device: + :param attempt_for_offline_device: :param behavior_when_code_cannot_be_shared: Desired behavior if any device cannot share a code. If ``throw`` (default), no access codes will be created if any device cannot share a code. If ``create_random_code``, a random code will be created on devices that cannot share a code. @@ -540,11 +416,11 @@ def create_multiple( :param is_external_modification_allowed: Indicates whether `external modification `_ of the code is allowed. Default: ``false``. :param name: Name of the new access code. Enables administrators and users to identify the access code easily, especially when there are numerous access codes. - + Note that the name provided on Seam is used to identify the code on Seam and is not necessarily the name that will appear in the lock provider's app or on the device. This is because lock providers may have constraints on names, such as length, uniqueness, or characters that can be used. In addition, some lock providers may break down names into components such as ``first_name`` and ``last_name``. - + To provide a consistent experience, Seam identifies the code on Seam by its name but may modify the name that appears on the lock provider's app or on the device. For example, Seam may add additional characters or truncate the name to meet provider constraints. - + To help your users identify codes set by Seam, Seam provides the name exactly as it appears on the lock provider's app or on the device as a separate property called ``appearance``. This is an object with a ``name`` property and, optionally, ``first_name`` and ``last_name`` properties (for providers that break down a name into components). :param prefer_native_scheduling: Indicates whether `native scheduling `_ should be used for time-bound codes when supported by the provider. Default: ``true``. @@ -567,17 +443,13 @@ def create_multiple( if attempt_for_offline_device is not None: json_payload["attempt_for_offline_device"] = attempt_for_offline_device if behavior_when_code_cannot_be_shared is not None: - json_payload["behavior_when_code_cannot_be_shared"] = ( - behavior_when_code_cannot_be_shared - ) + json_payload["behavior_when_code_cannot_be_shared"] = behavior_when_code_cannot_be_shared if code is not None: json_payload["code"] = code if ends_at is not None: json_payload["ends_at"] = ends_at if is_external_modification_allowed is not None: - json_payload["is_external_modification_allowed"] = ( - is_external_modification_allowed - ) + json_payload["is_external_modification_allowed"] = is_external_modification_allowed if name is not None: json_payload["name"] = name if prefer_native_scheduling is not None: @@ -590,17 +462,13 @@ def create_multiple( json_payload["use_backup_access_code_pool"] = use_backup_access_code_pool if not json_payload: - raise ValueError( - "At least one parameter is required for /access_codes/create_multiple" - ) + raise ValueError("At least one parameter is required for /access_codes/create_multiple") res = self.client.put("/access_codes/create_multiple", json=json_payload) return [AccessCode.from_dict(item) for item in res["access_codes"]] - @route_metadata( - path="/access_codes/delete", has_required_parameters=True, has_pagination=False - ) + @route_metadata(path="/access_codes/delete", has_required_parameters=True, has_pagination=False) def delete(self, *, access_code_id: str, device_id: Optional[str] = None) -> None: """Deletes an `access code `_. @@ -617,19 +485,13 @@ def delete(self, *, access_code_id: str, device_id: Optional[str] = None) -> Non params["device_id"] = device_id if not params: - raise ValueError( - "At least one parameter is required for /access_codes/delete" - ) + raise ValueError("At least one parameter is required for /access_codes/delete") self.client.delete("/access_codes/delete", params=params) return None - @route_metadata( - path="/access_codes/generate_code", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/access_codes/generate_code", has_required_parameters=True, has_pagination=False) def generate_code(self, *, device_id: str) -> AccessCode: """Generates a code for an `access code `_, given a device ID. @@ -644,26 +506,16 @@ def generate_code(self, *, device_id: str) -> AccessCode: params["device_id"] = device_id if not params: - raise ValueError( - "At least one parameter is required for /access_codes/generate_code" - ) + raise ValueError("At least one parameter is required for /access_codes/generate_code") res = self.client.get("/access_codes/generate_code", params=params) return AccessCode.from_dict(res["generated_code"]) - @route_metadata( - path="/access_codes/get", has_required_parameters=True, has_pagination=False - ) - def get( - self, - *, - access_code_id: Optional[str] = None, - code: Optional[str] = None, - device_id: Optional[str] = None, - ) -> AccessCode: + @route_metadata(path="/access_codes/get", has_required_parameters=True, has_pagination=False) + def get(self, *, access_code_id: Optional[str] = None, code: Optional[str] = None, device_id: Optional[str] = None) -> AccessCode: """Returns a specified `access code `_. - + You must specify either ``access_code_id`` or both ``device_id`` and ``code``. :param access_code_id: ID of the access code that you want to get. You must specify either ``access_code_id`` or both ``device_id`` and ``code``. @@ -691,25 +543,10 @@ def get( return AccessCode.from_dict(res["access_code"]) - @route_metadata( - path="/access_codes/list", has_required_parameters=True, has_pagination=True - ) - def list( - self, - *, - access_code_ids: Optional[List[str]] = None, - access_grant_id: Optional[str] = None, - access_grant_key: Optional[str] = None, - access_method_id: Optional[str] = None, - customer_key: Optional[str] = None, - device_id: Optional[str] = None, - limit: Optional[float] = None, - page_cursor: Optional[str] = None, - search: Optional[str] = None, - user_identifier_key: Optional[str] = None, - ) -> List[AccessCode]: + @route_metadata(path="/access_codes/list", has_required_parameters=True, has_pagination=True) + def list(self, *, access_code_ids: Optional[List[str]] = None, access_grant_id: Optional[str] = None, access_grant_key: Optional[str] = None, access_method_id: Optional[str] = None, customer_key: Optional[str] = None, device_id: Optional[str] = None, limit: Optional[float] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, user_identifier_key: Optional[str] = None) -> List[AccessCode]: """Returns a list of all `access codes `_. - + Specify ``device_id``, ``access_code_ids``, ``access_method_id``, ``access_grant_id``, or ``access_grant_key``. :param access_code_ids: IDs of the access codes that you want to retrieve. Specify ``device_id``, ``access_code_ids``, ``access_method_id``, ``access_grant_id``, or ``access_grant_key``. @@ -759,28 +596,22 @@ def list( json_payload["user_identifier_key"] = user_identifier_key if not json_payload: - raise ValueError( - "At least one parameter is required for /access_codes/list" - ) + raise ValueError("At least one parameter is required for /access_codes/list") res = self.client.post("/access_codes/list", json=json_payload) return [AccessCode.from_dict(item) for item in res["access_codes"]] - @route_metadata( - path="/access_codes/pull_backup_access_code", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/access_codes/pull_backup_access_code", has_required_parameters=True, has_pagination=False) def pull_backup_access_code(self, *, access_code_id: str) -> AccessCode: """Retrieves a backup access code for an `access code `_. See also `Managing Backup Access Codes `_. - + A backup access code pool is a collection of pre-programmed access codes stored on a device, ready for use. These codes are programmed in addition to the regular access codes on Seam, serving as a safety net for any issues with the primary codes. If there's ever a complication with a primary access code—be it due to intermittent connectivity, manual removal from a device, or provider outages—a backup code can be retrieved. Its end time can then be adjusted to align with the original code, facilitating seamless and uninterrupted access. - + You can pull a backup access code from the pool at any time. These backup codes are guaranteed to work immediately and automatically programmed to be removed from the device after the access code ends. - + You can only pull backup access codes for time-bound access codes. - + Before pulling a backup access code, make sure that the device's ``properties.supports_backup_access_code_pool`` is ``true``. Then, to activate the backup pool, set ``use_backup_access_code_pool`` to ``true`` when creating an access code. :param access_code_id: ID of the access code for which you want to pull a backup access code. @@ -794,31 +625,16 @@ def pull_backup_access_code(self, *, access_code_id: str) -> AccessCode: json_payload["access_code_id"] = access_code_id if not json_payload: - raise ValueError( - "At least one parameter is required for /access_codes/pull_backup_access_code" - ) + raise ValueError("At least one parameter is required for /access_codes/pull_backup_access_code") - res = self.client.post( - "/access_codes/pull_backup_access_code", json=json_payload - ) + res = self.client.post("/access_codes/pull_backup_access_code", json=json_payload) return AccessCode.from_dict(res["access_code"]) - @route_metadata( - path="/access_codes/report_device_constraints", - has_required_parameters=True, - has_pagination=False, - ) - def report_device_constraints( - self, - *, - device_id: str, - max_code_length: Optional[int] = None, - min_code_length: Optional[int] = None, - supported_code_lengths: Optional[List[float]] = None, - ) -> None: + @route_metadata(path="/access_codes/report_device_constraints", has_required_parameters=True, has_pagination=False) + def report_device_constraints(self, *, device_id: str, max_code_length: Optional[int] = None, min_code_length: Optional[int] = None, supported_code_lengths: Optional[List[float]] = None) -> None: """Enables you to report access code-related constraints for a device. Currently, supports reporting supported code length constraints for SmartThings devices. - + Specify either ``supported_code_lengths`` or ``min_code_length``/``max_code_length``. :param device_id: ID of the device for which you want to report constraints. @@ -842,41 +658,23 @@ def report_device_constraints( json_payload["supported_code_lengths"] = supported_code_lengths if not json_payload: - raise ValueError( - "At least one parameter is required for /access_codes/report_device_constraints" - ) + raise ValueError("At least one parameter is required for /access_codes/report_device_constraints") self.client.post("/access_codes/report_device_constraints", json=json_payload) return None - @route_metadata( - path="/access_codes/update", has_required_parameters=True, has_pagination=False - ) - def update( - self, - *, - access_code_id: str, - allow_external_modification: Optional[bool] = None, - attempt_for_offline_device: Optional[bool] = None, - code: Optional[str] = None, - device_id: Optional[str] = None, - ends_at: Optional[str] = None, - is_external_modification_allowed: Optional[bool] = None, - is_managed: Optional[bool] = None, - name: Optional[str] = None, - starts_at: Optional[str] = None, - type: Optional[str] = None, - ) -> None: + @route_metadata(path="/access_codes/update", has_required_parameters=True, has_pagination=False) + def update(self, *, access_code_id: str, allow_external_modification: Optional[bool] = None, attempt_for_offline_device: Optional[bool] = None, code: Optional[str] = None, device_id: Optional[str] = None, ends_at: Optional[str] = None, is_external_modification_allowed: Optional[bool] = None, is_managed: Optional[bool] = None, name: Optional[str] = None, starts_at: Optional[str] = None, type: Optional[str] = None) -> None: """Updates a specified active or upcoming `access code `_. - + See also `Modifying Access Codes `_. :param access_code_id: ID of the access code that you want to update. :param allow_external_modification: Indicates whether `external modification `_ of the code is allowed. Default: ``false``. - :param attempt_for_offline_device: + :param attempt_for_offline_device: :param code: Code to be used for access. @@ -889,11 +687,11 @@ def update( :param is_managed: Indicates whether the access code is managed through Seam. Note that to convert an unmanaged access code into a managed access code, use ``/access_codes/unmanaged/convert_to_managed``. :param name: Name of the new access code. Enables administrators and users to identify the access code easily, especially when there are numerous access codes. - + Note that the name provided on Seam is used to identify the code on Seam and is not necessarily the name that will appear in the lock provider's app or on the device. This is because lock providers may have constraints on names, such as length, uniqueness, or characters that can be used. In addition, some lock providers may break down names into components such as ``first_name`` and ``last_name``. - + To provide a consistent experience, Seam identifies the code on Seam by its name but may modify the name that appears on the lock provider's app or on the device. For example, Seam may add additional characters or truncate the name to meet provider constraints. - + To help your users identify codes set by Seam, Seam provides the name exactly as it appears on the lock provider's app or on the device as a separate property called ``appearance``. This is an object with a ``name`` property and, optionally, ``first_name`` and ``last_name`` properties (for providers that break down a name into components). :param starts_at: Date and time at which the validity of the new access code starts, in `ISO 8601 `_ format. @@ -916,9 +714,7 @@ def update( if ends_at is not None: json_payload["ends_at"] = ends_at if is_external_modification_allowed is not None: - json_payload["is_external_modification_allowed"] = ( - is_external_modification_allowed - ) + json_payload["is_external_modification_allowed"] = is_external_modification_allowed if is_managed is not None: json_payload["is_managed"] = is_managed if name is not None: @@ -929,31 +725,18 @@ def update( json_payload["type"] = type if not json_payload: - raise ValueError( - "At least one parameter is required for /access_codes/update" - ) + raise ValueError("At least one parameter is required for /access_codes/update") self.client.put("/access_codes/update", json=json_payload) return None - @route_metadata( - path="/access_codes/update_multiple", - has_required_parameters=True, - has_pagination=False, - ) - def update_multiple( - self, - *, - common_code_key: str, - ends_at: Optional[str] = None, - name: Optional[str] = None, - starts_at: Optional[str] = None, - ) -> None: + @route_metadata(path="/access_codes/update_multiple", has_required_parameters=True, has_pagination=False) + def update_multiple(self, *, common_code_key: str, ends_at: Optional[str] = None, name: Optional[str] = None, starts_at: Optional[str] = None) -> None: """Updates `access codes `_ that share a common code across multiple devices. - + Specify the ``common_code_key`` to identify the set of access codes that you want to update. - + See also `Update Linked Access Codes `_. :param common_code_key: Key that links the group of access codes, assigned on creation by ``/access_codes/create_multiple``. @@ -961,11 +744,11 @@ def update_multiple( :param ends_at: Date and time at which the validity of the new access code ends, in `ISO 8601 `_ format. Must be a time in the future and after ``starts_at``. :param name: Name of the new access code. Enables administrators and users to identify the access code easily, especially when there are numerous access codes. - + Note that the name provided on Seam is used to identify the code on Seam and is not necessarily the name that will appear in the lock provider's app or on the device. This is because lock providers may have constraints on names, such as length, uniqueness, or characters that can be used. In addition, some lock providers may break down names into components such as ``first_name`` and ``last_name``. - + To provide a consistent experience, Seam identifies the code on Seam by its name but may modify the name that appears on the lock provider's app or on the device. For example, Seam may add additional characters or truncate the name to meet provider constraints. - + To help your users identify codes set by Seam, Seam provides the name exactly as it appears on the lock provider's app or on the device as a separate property called ``appearance``. This is an object with a ``name`` property and, optionally, ``first_name`` and ``last_name`` properties (for providers that break down a name into components). :param starts_at: Date and time at which the validity of the new access code starts, in `ISO 8601 `_ format. @@ -983,9 +766,7 @@ def update_multiple( json_payload["starts_at"] = starts_at if not json_payload: - raise ValueError( - "At least one parameter is required for /access_codes/update_multiple" - ) + raise ValueError("At least one parameter is required for /access_codes/update_multiple") self.client.patch("/access_codes/update_multiple", json=json_payload) diff --git a/seam/routes/access_codes_simulate.py b/seam/routes/access_codes_simulate.py index 4c4c756c..cd0592f9 100644 --- a/seam/routes/access_codes_simulate.py +++ b/seam/routes/access_codes_simulate.py @@ -2,15 +2,14 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import UnmanagedAccessCode +from ..null import Null +from ..resources import (UnmanagedAccessCode) class AbstractAccessCodesSimulate(abc.ABC): @abc.abstractmethod - def create_unmanaged_access_code( - self, *, code: str, device_id: str, name: str - ) -> UnmanagedAccessCode: + def create_unmanaged_access_code(self, *, code: str, device_id: str, name: str) -> UnmanagedAccessCode: """Simulates the creation of an `unmanaged access code `_ in a `sandbox workspace `_. :param code: Code of the simulated unmanaged access code. @@ -30,14 +29,8 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/access_codes/simulate/create_unmanaged_access_code", - has_required_parameters=True, - has_pagination=False, - ) - def create_unmanaged_access_code( - self, *, code: str, device_id: str, name: str - ) -> UnmanagedAccessCode: + @route_metadata(path="/access_codes/simulate/create_unmanaged_access_code", has_required_parameters=True, has_pagination=False) + def create_unmanaged_access_code(self, *, code: str, device_id: str, name: str) -> UnmanagedAccessCode: """Simulates the creation of an `unmanaged access code `_ in a `sandbox workspace `_. :param code: Code of the simulated unmanaged access code. @@ -59,12 +52,8 @@ def create_unmanaged_access_code( json_payload["name"] = name if not json_payload: - raise ValueError( - "At least one parameter is required for /access_codes/simulate/create_unmanaged_access_code" - ) + raise ValueError("At least one parameter is required for /access_codes/simulate/create_unmanaged_access_code") - res = self.client.post( - "/access_codes/simulate/create_unmanaged_access_code", json=json_payload - ) + res = self.client.post("/access_codes/simulate/create_unmanaged_access_code", json=json_payload) return UnmanagedAccessCode.from_dict(res["access_code"]) diff --git a/seam/routes/access_codes_unmanaged.py b/seam/routes/access_codes_unmanaged.py index d8f35bc6..7f834113 100644 --- a/seam/routes/access_codes_unmanaged.py +++ b/seam/routes/access_codes_unmanaged.py @@ -2,24 +2,18 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import UnmanagedAccessCode +from ..null import Null +from ..resources import (UnmanagedAccessCode) class AbstractAccessCodesUnmanaged(abc.ABC): @abc.abstractmethod - def convert_to_managed( - self, - *, - access_code_id: str, - allow_external_modification: Optional[bool] = None, - force: Optional[bool] = None, - is_external_modification_allowed: Optional[bool] = None, - ) -> None: + def convert_to_managed(self, *, access_code_id: str, allow_external_modification: Optional[bool] = None, force: Optional[bool] = None, is_external_modification_allowed: Optional[bool] = None) -> None: """Converts an `unmanaged access code `_ to an `access code managed through Seam `_. - + An unmanaged access code has a limited set of operations that you can perform on it. Once you convert an unmanaged access code to a managed access code, the full set of access code operations and lifecycle events becomes available for it. - + Note that not all device providers support converting an unmanaged access code to a managed access code. :param access_code_id: ID of the unmanaged access code that you want to convert to a managed access code. @@ -43,15 +37,9 @@ def delete(self, *, access_code_id: str) -> None: raise NotImplementedError() @abc.abstractmethod - def get( - self, - *, - access_code_id: Optional[str] = None, - code: Optional[str] = None, - device_id: Optional[str] = None, - ) -> UnmanagedAccessCode: + def get(self, *, access_code_id: Optional[str] = None, code: Optional[str] = None, device_id: Optional[str] = None) -> UnmanagedAccessCode: """Returns a specified `unmanaged access code `_. - + You must specify either ``access_code_id`` or both ``device_id`` and ``code``. :param access_code_id: ID of the unmanaged access code that you want to get. You must specify either ``access_code_id`` or both ``device_id`` and ``code``. @@ -66,15 +54,7 @@ def get( raise NotImplementedError() @abc.abstractmethod - def list( - self, - *, - device_id: str, - limit: Optional[float] = None, - page_cursor: Optional[str] = None, - search: Optional[str] = None, - user_identifier_key: Optional[str] = None, - ) -> List[UnmanagedAccessCode]: + def list(self, *, device_id: str, limit: Optional[float] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, user_identifier_key: Optional[str] = None) -> List[UnmanagedAccessCode]: """Returns a list of all `unmanaged access codes `_. :param device_id: ID of the device for which you want to list unmanaged access codes. @@ -93,20 +73,12 @@ def list( raise NotImplementedError() @abc.abstractmethod - def update( - self, - *, - access_code_id: str, - is_managed: bool, - allow_external_modification: Optional[bool] = None, - force: Optional[bool] = None, - is_external_modification_allowed: Optional[bool] = None, - ) -> None: + def update(self, *, access_code_id: str, is_managed: bool, allow_external_modification: Optional[bool] = None, force: Optional[bool] = None, is_external_modification_allowed: Optional[bool] = None) -> None: """Updates a specified `unmanaged access code `_. :param access_code_id: ID of the unmanaged access code that you want to update. - :param is_managed: + :param is_managed: :param allow_external_modification: Indicates whether `external modification `_ of the code is allowed. @@ -123,23 +95,12 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/access_codes/unmanaged/convert_to_managed", - has_required_parameters=True, - has_pagination=False, - ) - def convert_to_managed( - self, - *, - access_code_id: str, - allow_external_modification: Optional[bool] = None, - force: Optional[bool] = None, - is_external_modification_allowed: Optional[bool] = None, - ) -> None: + @route_metadata(path="/access_codes/unmanaged/convert_to_managed", has_required_parameters=True, has_pagination=False) + def convert_to_managed(self, *, access_code_id: str, allow_external_modification: Optional[bool] = None, force: Optional[bool] = None, is_external_modification_allowed: Optional[bool] = None) -> None: """Converts an `unmanaged access code `_ to an `access code managed through Seam `_. - + An unmanaged access code has a limited set of operations that you can perform on it. Once you convert an unmanaged access code to a managed access code, the full set of access code operations and lifecycle events becomes available for it. - + Note that not all device providers support converting an unmanaged access code to a managed access code. :param access_code_id: ID of the unmanaged access code that you want to convert to a managed access code. @@ -160,26 +121,16 @@ def convert_to_managed( if force is not None: json_payload["force"] = force if is_external_modification_allowed is not None: - json_payload["is_external_modification_allowed"] = ( - is_external_modification_allowed - ) + json_payload["is_external_modification_allowed"] = is_external_modification_allowed if not json_payload: - raise ValueError( - "At least one parameter is required for /access_codes/unmanaged/convert_to_managed" - ) + raise ValueError("At least one parameter is required for /access_codes/unmanaged/convert_to_managed") - self.client.patch( - "/access_codes/unmanaged/convert_to_managed", json=json_payload - ) + self.client.patch("/access_codes/unmanaged/convert_to_managed", json=json_payload) return None - @route_metadata( - path="/access_codes/unmanaged/delete", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/access_codes/unmanaged/delete", has_required_parameters=True, has_pagination=False) def delete(self, *, access_code_id: str) -> None: """Deletes an `unmanaged access code `_. @@ -192,28 +143,16 @@ def delete(self, *, access_code_id: str) -> None: params["access_code_id"] = access_code_id if not params: - raise ValueError( - "At least one parameter is required for /access_codes/unmanaged/delete" - ) + raise ValueError("At least one parameter is required for /access_codes/unmanaged/delete") self.client.delete("/access_codes/unmanaged/delete", params=params) return None - @route_metadata( - path="/access_codes/unmanaged/get", - has_required_parameters=True, - has_pagination=False, - ) - def get( - self, - *, - access_code_id: Optional[str] = None, - code: Optional[str] = None, - device_id: Optional[str] = None, - ) -> UnmanagedAccessCode: + @route_metadata(path="/access_codes/unmanaged/get", has_required_parameters=True, has_pagination=False) + def get(self, *, access_code_id: Optional[str] = None, code: Optional[str] = None, device_id: Optional[str] = None) -> UnmanagedAccessCode: """Returns a specified `unmanaged access code `_. - + You must specify either ``access_code_id`` or both ``device_id`` and ``code``. :param access_code_id: ID of the unmanaged access code that you want to get. You must specify either ``access_code_id`` or both ``device_id`` and ``code``. @@ -235,28 +174,14 @@ def get( params["device_id"] = device_id if not params: - raise ValueError( - "At least one parameter is required for /access_codes/unmanaged/get" - ) + raise ValueError("At least one parameter is required for /access_codes/unmanaged/get") res = self.client.get("/access_codes/unmanaged/get", params=params) return UnmanagedAccessCode.from_dict(res["access_code"]) - @route_metadata( - path="/access_codes/unmanaged/list", - has_required_parameters=True, - has_pagination=True, - ) - def list( - self, - *, - device_id: str, - limit: Optional[float] = None, - page_cursor: Optional[str] = None, - search: Optional[str] = None, - user_identifier_key: Optional[str] = None, - ) -> List[UnmanagedAccessCode]: + @route_metadata(path="/access_codes/unmanaged/list", has_required_parameters=True, has_pagination=True) + def list(self, *, device_id: str, limit: Optional[float] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, user_identifier_key: Optional[str] = None) -> List[UnmanagedAccessCode]: """Returns a list of all `unmanaged access codes `_. :param device_id: ID of the device for which you want to list unmanaged access codes. @@ -286,33 +211,19 @@ def list( params["user_identifier_key"] = user_identifier_key if not params: - raise ValueError( - "At least one parameter is required for /access_codes/unmanaged/list" - ) + raise ValueError("At least one parameter is required for /access_codes/unmanaged/list") res = self.client.get("/access_codes/unmanaged/list", params=params) return [UnmanagedAccessCode.from_dict(item) for item in res["access_codes"]] - @route_metadata( - path="/access_codes/unmanaged/update", - has_required_parameters=True, - has_pagination=False, - ) - def update( - self, - *, - access_code_id: str, - is_managed: bool, - allow_external_modification: Optional[bool] = None, - force: Optional[bool] = None, - is_external_modification_allowed: Optional[bool] = None, - ) -> None: + @route_metadata(path="/access_codes/unmanaged/update", has_required_parameters=True, has_pagination=False) + def update(self, *, access_code_id: str, is_managed: bool, allow_external_modification: Optional[bool] = None, force: Optional[bool] = None, is_external_modification_allowed: Optional[bool] = None) -> None: """Updates a specified `unmanaged access code `_. :param access_code_id: ID of the unmanaged access code that you want to update. - :param is_managed: + :param is_managed: :param allow_external_modification: Indicates whether `external modification `_ of the code is allowed. @@ -332,14 +243,10 @@ def update( if force is not None: json_payload["force"] = force if is_external_modification_allowed is not None: - json_payload["is_external_modification_allowed"] = ( - is_external_modification_allowed - ) + json_payload["is_external_modification_allowed"] = is_external_modification_allowed if not json_payload: - raise ValueError( - "At least one parameter is required for /access_codes/unmanaged/update" - ) + raise ValueError("At least one parameter is required for /access_codes/unmanaged/update") self.client.patch("/access_codes/unmanaged/update", json=json_payload) diff --git a/seam/routes/access_grants.py b/seam/routes/access_grants.py index dfad2ccd..23627942 100644 --- a/seam/routes/access_grants.py +++ b/seam/routes/access_grants.py @@ -2,11 +2,9 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import AccessGrant, Batch -from .access_grants_unmanaged import ( - AbstractAccessGrantsUnmanaged, - AccessGrantsUnmanaged, -) +from ..null import Null +from ..resources import (AccessGrant,Batch) +from .access_grants_unmanaged import AbstractAccessGrantsUnmanaged, AccessGrantsUnmanaged class AbstractAccessGrants(abc.ABC): @@ -17,28 +15,10 @@ def unmanaged(self) -> AbstractAccessGrantsUnmanaged: raise NotImplementedError() @abc.abstractmethod - def create( - self, - *, - requested_access_methods: List[Dict[str, Any]], - user_identity_id: Optional[str] = None, - user_identity: Optional[Dict[str, Any]] = None, - access_grant_key: Optional[str] = None, - acs_entrance_ids: Optional[List[str]] = None, - customization_profile_id: Optional[str] = None, - device_ids: Optional[List[str]] = None, - ends_at: Optional[str] = None, - location: Optional[Dict[str, Any]] = None, - location_ids: Optional[List[str]] = None, - name: Optional[str] = None, - reservation_key: Optional[str] = None, - space_ids: Optional[List[str]] = None, - space_keys: Optional[List[str]] = None, - starts_at: Optional[str] = None, - ) -> AccessGrant: + def create(self, *, requested_access_methods: List[Dict[str, Any]], user_identity_id: Optional[str] = None, user_identity: Optional[Dict[str, Any]] = None, access_grant_key: Optional[str] = None, acs_entrance_ids: Optional[List[str]] = None, customization_profile_id: Optional[str] = None, device_ids: Optional[List[str]] = None, ends_at: Optional[Union[str, Null]] = None, location: Optional[Dict[str, Any]] = None, location_ids: Optional[List[str]] = None, name: Optional[Union[str, Null]] = None, reservation_key: Optional[str] = None, space_ids: Optional[List[str]] = None, space_keys: Optional[List[str]] = None, starts_at: Optional[str] = None) -> AccessGrant: """Creates a new `Access Grant `_. Access Grants are the default and recommended way to grant a user access to any physical space, irrespective of the locking hardware. They work with both standalone smart locks (using ``device_ids``) and access control systems (using ``acs_entrance_ids`` or ``space_ids``), and can issue PIN codes, key cards, and mobile keys through a single request. - :param requested_access_methods: + :param requested_access_methods: :param user_identity_id: ID of user identity for whom access is being granted. @@ -83,12 +63,7 @@ def delete(self, *, access_grant_id: str) -> None: raise NotImplementedError() @abc.abstractmethod - def get( - self, - *, - access_grant_id: Optional[str] = None, - access_grant_key: Optional[str] = None, - ) -> AccessGrant: + def get(self, *, access_grant_id: Optional[str] = None, access_grant_key: Optional[str] = None) -> AccessGrant: """Get an Access Grant. :param access_grant_id: ID of Access Grant to get. @@ -101,23 +76,16 @@ def get( raise NotImplementedError() @abc.abstractmethod - def get_related( - self, - *, - access_grant_ids: Optional[List[str]] = None, - access_grant_keys: Optional[List[str]] = None, - exclude: Optional[List[str]] = None, - include: Optional[List[str]] = None, - ) -> Batch: + def get_related(self, *, access_grant_ids: Optional[List[str]] = None, access_grant_keys: Optional[List[str]] = None, exclude: Optional[List[str]] = None, include: Optional[List[str]] = None) -> Batch: """Gets all related resources for one or more Access Grants. :param access_grant_ids: IDs of the access grants that you want to get along with their related resources. :param access_grant_keys: Keys of the access grants that you want to get along with their related resources. - :param exclude: + :param exclude: - :param include: + :param include: :returns: OK @@ -125,23 +93,7 @@ def get_related( raise NotImplementedError() @abc.abstractmethod - def list( - self, - *, - access_code_id: Optional[str] = None, - access_grant_ids: Optional[List[str]] = None, - access_grant_key: Optional[str] = None, - acs_entrance_id: Optional[str] = None, - acs_system_id: Optional[str] = None, - customer_key: Optional[str] = None, - device_id: Optional[str] = None, - limit: Optional[float] = None, - location_id: Optional[str] = None, - page_cursor: Optional[str] = None, - reservation_key: Optional[str] = None, - space_id: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> List[AccessGrant]: + def list(self, *, access_code_id: Optional[str] = None, access_grant_ids: Optional[List[str]] = None, access_grant_key: Optional[Union[str, Null]] = None, acs_entrance_id: Optional[str] = None, acs_system_id: Optional[str] = None, customer_key: Optional[str] = None, device_id: Optional[str] = None, limit: Optional[float] = None, location_id: Optional[str] = None, page_cursor: Optional[Union[str, Null]] = None, reservation_key: Optional[str] = None, space_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> List[AccessGrant]: """Gets an Access Grant. :param access_code_id: ID of the access code by which you want to filter the list of Access Grants. @@ -174,9 +126,7 @@ def list( raise NotImplementedError() @abc.abstractmethod - def request_access_methods( - self, *, access_grant_id: str, requested_access_methods: List[Dict[str, Any]] - ) -> AccessGrant: + def request_access_methods(self, *, access_grant_id: str, requested_access_methods: List[Dict[str, Any]]) -> AccessGrant: """Adds additional requested access methods to an existing Access Grant. :param access_grant_id: ID of the Access Grant to add access methods to. @@ -189,15 +139,7 @@ def request_access_methods( raise NotImplementedError() @abc.abstractmethod - def update( - self, - *, - access_grant_id: Optional[str] = None, - access_grant_key: Optional[str] = None, - ends_at: Optional[str] = None, - name: Optional[str] = None, - starts_at: Optional[str] = None, - ) -> None: + def update(self, *, access_grant_id: Optional[str] = None, access_grant_key: Optional[str] = None, ends_at: Optional[Union[str, Null]] = None, name: Optional[Union[str, Null]] = None, starts_at: Optional[str] = None) -> None: """Updates an existing Access Grant's time window. :param access_grant_id: ID of the Access Grant to update. Provide either ``access_grant_id`` or ``access_grant_key``. @@ -224,31 +166,11 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): def unmanaged(self) -> AccessGrantsUnmanaged: return self._unmanaged - @route_metadata( - path="/access_grants/create", has_required_parameters=True, has_pagination=False - ) - def create( - self, - *, - requested_access_methods: List[Dict[str, Any]], - user_identity_id: Optional[str] = None, - user_identity: Optional[Dict[str, Any]] = None, - access_grant_key: Optional[str] = None, - acs_entrance_ids: Optional[List[str]] = None, - customization_profile_id: Optional[str] = None, - device_ids: Optional[List[str]] = None, - ends_at: Optional[str] = None, - location: Optional[Dict[str, Any]] = None, - location_ids: Optional[List[str]] = None, - name: Optional[str] = None, - reservation_key: Optional[str] = None, - space_ids: Optional[List[str]] = None, - space_keys: Optional[List[str]] = None, - starts_at: Optional[str] = None, - ) -> AccessGrant: + @route_metadata(path="/access_grants/create", has_required_parameters=True, has_pagination=False) + def create(self, *, requested_access_methods: List[Dict[str, Any]], user_identity_id: Optional[str] = None, user_identity: Optional[Dict[str, Any]] = None, access_grant_key: Optional[str] = None, acs_entrance_ids: Optional[List[str]] = None, customization_profile_id: Optional[str] = None, device_ids: Optional[List[str]] = None, ends_at: Optional[Union[str, Null]] = None, location: Optional[Dict[str, Any]] = None, location_ids: Optional[List[str]] = None, name: Optional[Union[str, Null]] = None, reservation_key: Optional[str] = None, space_ids: Optional[List[str]] = None, space_keys: Optional[List[str]] = None, starts_at: Optional[str] = None) -> AccessGrant: """Creates a new `Access Grant `_. Access Grants are the default and recommended way to grant a user access to any physical space, irrespective of the locking hardware. They work with both standalone smart locks (using ``device_ids``) and access control systems (using ``acs_entrance_ids`` or ``space_ids``), and can issue PIN codes, key cards, and mobile keys through a single request. - :param requested_access_methods: + :param requested_access_methods: :param user_identity_id: ID of user identity for whom access is being granted. @@ -315,17 +237,13 @@ def create( json_payload["starts_at"] = starts_at if not json_payload: - raise ValueError( - "At least one parameter is required for /access_grants/create" - ) + raise ValueError("At least one parameter is required for /access_grants/create") res = self.client.post("/access_grants/create", json=json_payload) return AccessGrant.from_dict(res["access_grant"]) - @route_metadata( - path="/access_grants/delete", has_required_parameters=True, has_pagination=False - ) + @route_metadata(path="/access_grants/delete", has_required_parameters=True, has_pagination=False) def delete(self, *, access_grant_id: str) -> None: """Delete an Access Grant. @@ -338,23 +256,14 @@ def delete(self, *, access_grant_id: str) -> None: params["access_grant_id"] = access_grant_id if not params: - raise ValueError( - "At least one parameter is required for /access_grants/delete" - ) + raise ValueError("At least one parameter is required for /access_grants/delete") self.client.delete("/access_grants/delete", params=params) return None - @route_metadata( - path="/access_grants/get", has_required_parameters=True, has_pagination=False - ) - def get( - self, - *, - access_grant_id: Optional[str] = None, - access_grant_key: Optional[str] = None, - ) -> AccessGrant: + @route_metadata(path="/access_grants/get", has_required_parameters=True, has_pagination=False) + def get(self, *, access_grant_id: Optional[str] = None, access_grant_key: Optional[str] = None) -> AccessGrant: """Get an Access Grant. :param access_grant_id: ID of Access Grant to get. @@ -372,36 +281,23 @@ def get( params["access_grant_key"] = access_grant_key if not params: - raise ValueError( - "At least one parameter is required for /access_grants/get" - ) + raise ValueError("At least one parameter is required for /access_grants/get") res = self.client.get("/access_grants/get", params=params) return AccessGrant.from_dict(res["access_grant"]) - @route_metadata( - path="/access_grants/get_related", - has_required_parameters=True, - has_pagination=False, - ) - def get_related( - self, - *, - access_grant_ids: Optional[List[str]] = None, - access_grant_keys: Optional[List[str]] = None, - exclude: Optional[List[str]] = None, - include: Optional[List[str]] = None, - ) -> Batch: + @route_metadata(path="/access_grants/get_related", has_required_parameters=True, has_pagination=False) + def get_related(self, *, access_grant_ids: Optional[List[str]] = None, access_grant_keys: Optional[List[str]] = None, exclude: Optional[List[str]] = None, include: Optional[List[str]] = None) -> Batch: """Gets all related resources for one or more Access Grants. :param access_grant_ids: IDs of the access grants that you want to get along with their related resources. :param access_grant_keys: Keys of the access grants that you want to get along with their related resources. - :param exclude: + :param exclude: - :param include: + :param include: :returns: OK @@ -418,34 +314,14 @@ def get_related( json_payload["include"] = include if not json_payload: - raise ValueError( - "At least one parameter is required for /access_grants/get_related" - ) + raise ValueError("At least one parameter is required for /access_grants/get_related") res = self.client.post("/access_grants/get_related", json=json_payload) return Batch.from_dict(res["batch"]) - @route_metadata( - path="/access_grants/list", has_required_parameters=False, has_pagination=True - ) - def list( - self, - *, - access_code_id: Optional[str] = None, - access_grant_ids: Optional[List[str]] = None, - access_grant_key: Optional[str] = None, - acs_entrance_id: Optional[str] = None, - acs_system_id: Optional[str] = None, - customer_key: Optional[str] = None, - device_id: Optional[str] = None, - limit: Optional[float] = None, - location_id: Optional[str] = None, - page_cursor: Optional[str] = None, - reservation_key: Optional[str] = None, - space_id: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> List[AccessGrant]: + @route_metadata(path="/access_grants/list", has_required_parameters=False, has_pagination=True) + def list(self, *, access_code_id: Optional[str] = None, access_grant_ids: Optional[List[str]] = None, access_grant_key: Optional[Union[str, Null]] = None, acs_entrance_id: Optional[str] = None, acs_system_id: Optional[str] = None, customer_key: Optional[str] = None, device_id: Optional[str] = None, limit: Optional[float] = None, location_id: Optional[str] = None, page_cursor: Optional[Union[str, Null]] = None, reservation_key: Optional[str] = None, space_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> List[AccessGrant]: """Gets an Access Grant. :param access_code_id: ID of the access code by which you want to filter the list of Access Grants. @@ -508,14 +384,8 @@ def list( return [AccessGrant.from_dict(item) for item in res["access_grants"]] - @route_metadata( - path="/access_grants/request_access_methods", - has_required_parameters=True, - has_pagination=False, - ) - def request_access_methods( - self, *, access_grant_id: str, requested_access_methods: List[Dict[str, Any]] - ) -> AccessGrant: + @route_metadata(path="/access_grants/request_access_methods", has_required_parameters=True, has_pagination=False) + def request_access_methods(self, *, access_grant_id: str, requested_access_methods: List[Dict[str, Any]]) -> AccessGrant: """Adds additional requested access methods to an existing Access Grant. :param access_grant_id: ID of the Access Grant to add access methods to. @@ -533,28 +403,14 @@ def request_access_methods( json_payload["requested_access_methods"] = requested_access_methods if not json_payload: - raise ValueError( - "At least one parameter is required for /access_grants/request_access_methods" - ) + raise ValueError("At least one parameter is required for /access_grants/request_access_methods") - res = self.client.post( - "/access_grants/request_access_methods", json=json_payload - ) + res = self.client.post("/access_grants/request_access_methods", json=json_payload) return AccessGrant.from_dict(res["access_grant"]) - @route_metadata( - path="/access_grants/update", has_required_parameters=True, has_pagination=False - ) - def update( - self, - *, - access_grant_id: Optional[str] = None, - access_grant_key: Optional[str] = None, - ends_at: Optional[str] = None, - name: Optional[str] = None, - starts_at: Optional[str] = None, - ) -> None: + @route_metadata(path="/access_grants/update", has_required_parameters=True, has_pagination=False) + def update(self, *, access_grant_id: Optional[str] = None, access_grant_key: Optional[str] = None, ends_at: Optional[Union[str, Null]] = None, name: Optional[Union[str, Null]] = None, starts_at: Optional[str] = None) -> None: """Updates an existing Access Grant's time window. :param access_grant_id: ID of the Access Grant to update. Provide either ``access_grant_id`` or ``access_grant_key``. @@ -582,9 +438,7 @@ def update( json_payload["starts_at"] = starts_at if not json_payload: - raise ValueError( - "At least one parameter is required for /access_grants/update" - ) + raise ValueError("At least one parameter is required for /access_grants/update") self.client.patch("/access_grants/update", json=json_payload) diff --git a/seam/routes/access_grants_unmanaged.py b/seam/routes/access_grants_unmanaged.py index 78eb8918..2c1b4401 100644 --- a/seam/routes/access_grants_unmanaged.py +++ b/seam/routes/access_grants_unmanaged.py @@ -2,7 +2,8 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import UnmanagedAccessGrant +from ..null import Null +from ..resources import (UnmanagedAccessGrant) class AbstractAccessGrantsUnmanaged(abc.ABC): @@ -19,16 +20,7 @@ def get(self, *, access_grant_id: str) -> UnmanagedAccessGrant: raise NotImplementedError() @abc.abstractmethod - def list( - self, - *, - acs_entrance_id: Optional[str] = None, - acs_system_id: Optional[str] = None, - limit: Optional[float] = None, - page_cursor: Optional[str] = None, - reservation_key: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> List[UnmanagedAccessGrant]: + def list(self, *, acs_entrance_id: Optional[str] = None, acs_system_id: Optional[str] = None, limit: Optional[float] = None, page_cursor: Optional[Union[str, Null]] = None, reservation_key: Optional[str] = None, user_identity_id: Optional[str] = None) -> List[UnmanagedAccessGrant]: """Gets unmanaged Access Grants (where is_managed = false). :param acs_entrance_id: ID of the entrance by which you want to filter the list of unmanaged Access Grants. @@ -47,17 +39,11 @@ def list( raise NotImplementedError() @abc.abstractmethod - def update( - self, - *, - access_grant_id: str, - is_managed: bool, - access_grant_key: Optional[str] = None, - ) -> None: + def update(self, *, access_grant_id: str, is_managed: bool, access_grant_key: Optional[str] = None) -> None: """Updates an unmanaged Access Grant to make it managed. - + This endpoint can only be used to convert unmanaged access grants to managed ones by setting ``is_managed`` to ``true``. It cannot be used to convert managed access grants back to unmanaged. - + When converting an unmanaged access grant to managed, all associated access methods will also be converted to managed. :param access_grant_id: ID of the unmanaged Access Grant to update. @@ -75,11 +61,7 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/access_grants/unmanaged/get", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/access_grants/unmanaged/get", has_required_parameters=True, has_pagination=False) def get(self, *, access_grant_id: str) -> UnmanagedAccessGrant: """Get an unmanaged Access Grant (where is_managed = false). @@ -94,29 +76,14 @@ def get(self, *, access_grant_id: str) -> UnmanagedAccessGrant: params["access_grant_id"] = access_grant_id if not params: - raise ValueError( - "At least one parameter is required for /access_grants/unmanaged/get" - ) + raise ValueError("At least one parameter is required for /access_grants/unmanaged/get") res = self.client.get("/access_grants/unmanaged/get", params=params) return UnmanagedAccessGrant.from_dict(res["access_grant"]) - @route_metadata( - path="/access_grants/unmanaged/list", - has_required_parameters=False, - has_pagination=True, - ) - def list( - self, - *, - acs_entrance_id: Optional[str] = None, - acs_system_id: Optional[str] = None, - limit: Optional[float] = None, - page_cursor: Optional[str] = None, - reservation_key: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> List[UnmanagedAccessGrant]: + @route_metadata(path="/access_grants/unmanaged/list", has_required_parameters=False, has_pagination=True) + def list(self, *, acs_entrance_id: Optional[str] = None, acs_system_id: Optional[str] = None, limit: Optional[float] = None, page_cursor: Optional[Union[str, Null]] = None, reservation_key: Optional[str] = None, user_identity_id: Optional[str] = None) -> List[UnmanagedAccessGrant]: """Gets unmanaged Access Grants (where is_managed = false). :param acs_entrance_id: ID of the entrance by which you want to filter the list of unmanaged Access Grants. @@ -151,22 +118,12 @@ def list( return [UnmanagedAccessGrant.from_dict(item) for item in res["access_grants"]] - @route_metadata( - path="/access_grants/unmanaged/update", - has_required_parameters=True, - has_pagination=False, - ) - def update( - self, - *, - access_grant_id: str, - is_managed: bool, - access_grant_key: Optional[str] = None, - ) -> None: + @route_metadata(path="/access_grants/unmanaged/update", has_required_parameters=True, has_pagination=False) + def update(self, *, access_grant_id: str, is_managed: bool, access_grant_key: Optional[str] = None) -> None: """Updates an unmanaged Access Grant to make it managed. - + This endpoint can only be used to convert unmanaged access grants to managed ones by setting ``is_managed`` to ``true``. It cannot be used to convert managed access grants back to unmanaged. - + When converting an unmanaged access grant to managed, all associated access methods will also be converted to managed. :param access_grant_id: ID of the unmanaged Access Grant to update. @@ -186,9 +143,7 @@ def update( json_payload["access_grant_key"] = access_grant_key if not json_payload: - raise ValueError( - "At least one parameter is required for /access_grants/unmanaged/update" - ) + raise ValueError("At least one parameter is required for /access_grants/unmanaged/update") self.client.patch("/access_grants/unmanaged/update", json=json_payload) diff --git a/seam/routes/access_methods.py b/seam/routes/access_methods.py index 8c9ca9ee..83d0cf49 100644 --- a/seam/routes/access_methods.py +++ b/seam/routes/access_methods.py @@ -2,11 +2,9 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import ActionAttempt, AccessMethod, Batch -from .access_methods_unmanaged import ( - AbstractAccessMethodsUnmanaged, - AccessMethodsUnmanaged, -) +from ..null import Null +from ..resources import (ActionAttempt,AccessMethod,Batch) +from .access_methods_unmanaged import AbstractAccessMethodsUnmanaged, AccessMethodsUnmanaged from ..modules.action_attempts import resolve_action_attempt @@ -18,13 +16,7 @@ def unmanaged(self) -> AbstractAccessMethodsUnmanaged: raise NotImplementedError() @abc.abstractmethod - def assign_card( - self, - *, - access_method_id: str, - card_number: str, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + def assign_card(self, *, access_method_id: str, card_number: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Assigns a pre-registered card credential, identified by ``card_number``, to a card-mode access method. Use this endpoint for access systems that use pre-registered cards, where a physical card must be associated with an access method before it can be used for access. Assigning a card credential also triggers issuance of the access method. :param access_method_id: ID of the ``access_method`` to assign the credential to. @@ -39,13 +31,7 @@ def assign_card( raise NotImplementedError() @abc.abstractmethod - def delete( - self, - *, - access_method_id: Optional[str] = None, - access_grant_id: Optional[str] = None, - reservation_key: Optional[str] = None, - ) -> None: + def delete(self, *, access_method_id: Optional[str] = None, access_grant_id: Optional[str] = None, reservation_key: Optional[str] = None) -> None: """Deletes an access method. :param access_method_id: ID of access method to delete. @@ -58,13 +44,7 @@ def delete( raise NotImplementedError() @abc.abstractmethod - def encode( - self, - *, - access_method_id: str, - acs_encoder_id: str, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + def encode(self, *, access_method_id: str, acs_encoder_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Encodes an existing access method onto a plastic card placed on the specified `encoder `_. :param access_method_id: ID of the ``access_method`` to encode onto a card. @@ -90,20 +70,14 @@ def get(self, *, access_method_id: str) -> AccessMethod: raise NotImplementedError() @abc.abstractmethod - def get_related( - self, - *, - access_method_ids: List[str], - exclude: Optional[List[str]] = None, - include: Optional[List[str]] = None, - ) -> Batch: + def get_related(self, *, access_method_ids: List[str], exclude: Optional[List[str]] = None, include: Optional[List[str]] = None) -> Batch: """Gets all related resources for one or more Access Methods. :param access_method_ids: IDs of the access methods that you want to get along with their related resources. - :param exclude: + :param exclude: - :param include: + :param include: :returns: OK @@ -111,18 +85,7 @@ def get_related( raise NotImplementedError() @abc.abstractmethod - def list( - self, - *, - access_code_id: Optional[str] = None, - access_grant_id: Optional[str] = None, - access_grant_key: Optional[str] = None, - acs_entrance_id: Optional[str] = None, - device_id: Optional[str] = None, - limit: Optional[int] = None, - page_cursor: Optional[str] = None, - space_id: Optional[str] = None, - ) -> List[AccessMethod]: + def list(self, *, access_code_id: Optional[str] = None, access_grant_id: Optional[str] = None, access_grant_key: Optional[str] = None, acs_entrance_id: Optional[str] = None, device_id: Optional[str] = None, limit: Optional[int] = None, page_cursor: Optional[Union[str, Null]] = None, space_id: Optional[str] = None) -> List[AccessMethod]: """Lists all access methods, usually filtered by Access Grant. :param access_code_id: ID of the access code by which to filter the returned access methods. Must be combined with ``access_grant_id``, ``access_grant_key``, or ``acs_entrance_id``. @@ -147,13 +110,7 @@ def list( raise NotImplementedError() @abc.abstractmethod - def unlock_door( - self, - *, - access_method_id: str, - acs_entrance_id: str, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + def unlock_door(self, *, access_method_id: str, acs_entrance_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Remotely unlocks a specified `entrance `_ using the cloud key credential associated with an access method. Returns an action attempt that tracks the progress of the unlock operation. :param access_method_id: ID of the cloud_key ``access_method`` to use for the unlock operation. @@ -178,18 +135,8 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): def unmanaged(self) -> AccessMethodsUnmanaged: return self._unmanaged - @route_metadata( - path="/access_methods/assign_card", - has_required_parameters=True, - has_pagination=False, - ) - def assign_card( - self, - *, - access_method_id: str, - card_number: str, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + @route_metadata(path="/access_methods/assign_card", has_required_parameters=True, has_pagination=False) + def assign_card(self, *, access_method_id: str, card_number: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Assigns a pre-registered card credential, identified by ``card_number``, to a card-mode access method. Use this endpoint for access systems that use pre-registered cards, where a physical card must be associated with an access method before it can be used for access. Assigning a card credential also triggers issuance of the access method. :param access_method_id: ID of the ``access_method`` to assign the credential to. @@ -209,9 +156,7 @@ def assign_card( json_payload["card_number"] = card_number if not json_payload: - raise ValueError( - "At least one parameter is required for /access_methods/assign_card" - ) + raise ValueError("At least one parameter is required for /access_methods/assign_card") res = self.client.post("/access_methods/assign_card", json=json_payload) @@ -224,21 +169,11 @@ def assign_card( return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt, + wait_for_action_attempt=wait_for_action_attempt ) - @route_metadata( - path="/access_methods/delete", - has_required_parameters=True, - has_pagination=False, - ) - def delete( - self, - *, - access_method_id: Optional[str] = None, - access_grant_id: Optional[str] = None, - reservation_key: Optional[str] = None, - ) -> None: + @route_metadata(path="/access_methods/delete", has_required_parameters=True, has_pagination=False) + def delete(self, *, access_method_id: Optional[str] = None, access_grant_id: Optional[str] = None, reservation_key: Optional[str] = None) -> None: """Deletes an access method. :param access_method_id: ID of access method to delete. @@ -258,26 +193,14 @@ def delete( params["reservation_key"] = reservation_key if not params: - raise ValueError( - "At least one parameter is required for /access_methods/delete" - ) + raise ValueError("At least one parameter is required for /access_methods/delete") self.client.delete("/access_methods/delete", params=params) return None - @route_metadata( - path="/access_methods/encode", - has_required_parameters=True, - has_pagination=False, - ) - def encode( - self, - *, - access_method_id: str, - acs_encoder_id: str, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + @route_metadata(path="/access_methods/encode", has_required_parameters=True, has_pagination=False) + def encode(self, *, access_method_id: str, acs_encoder_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Encodes an existing access method onto a plastic card placed on the specified `encoder `_. :param access_method_id: ID of the ``access_method`` to encode onto a card. @@ -297,9 +220,7 @@ def encode( json_payload["acs_encoder_id"] = acs_encoder_id if not json_payload: - raise ValueError( - "At least one parameter is required for /access_methods/encode" - ) + raise ValueError("At least one parameter is required for /access_methods/encode") res = self.client.post("/access_methods/encode", json=json_payload) @@ -312,12 +233,10 @@ def encode( return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt, + wait_for_action_attempt=wait_for_action_attempt ) - @route_metadata( - path="/access_methods/get", has_required_parameters=True, has_pagination=False - ) + @route_metadata(path="/access_methods/get", has_required_parameters=True, has_pagination=False) def get(self, *, access_method_id: str) -> AccessMethod: """Gets an access method. @@ -332,33 +251,21 @@ def get(self, *, access_method_id: str) -> AccessMethod: params["access_method_id"] = access_method_id if not params: - raise ValueError( - "At least one parameter is required for /access_methods/get" - ) + raise ValueError("At least one parameter is required for /access_methods/get") res = self.client.get("/access_methods/get", params=params) return AccessMethod.from_dict(res["access_method"]) - @route_metadata( - path="/access_methods/get_related", - has_required_parameters=True, - has_pagination=False, - ) - def get_related( - self, - *, - access_method_ids: List[str], - exclude: Optional[List[str]] = None, - include: Optional[List[str]] = None, - ) -> Batch: + @route_metadata(path="/access_methods/get_related", has_required_parameters=True, has_pagination=False) + def get_related(self, *, access_method_ids: List[str], exclude: Optional[List[str]] = None, include: Optional[List[str]] = None) -> Batch: """Gets all related resources for one or more Access Methods. :param access_method_ids: IDs of the access methods that you want to get along with their related resources. - :param exclude: + :param exclude: - :param include: + :param include: :returns: OK @@ -373,29 +280,14 @@ def get_related( json_payload["include"] = include if not json_payload: - raise ValueError( - "At least one parameter is required for /access_methods/get_related" - ) + raise ValueError("At least one parameter is required for /access_methods/get_related") res = self.client.post("/access_methods/get_related", json=json_payload) return Batch.from_dict(res["batch"]) - @route_metadata( - path="/access_methods/list", has_required_parameters=True, has_pagination=True - ) - def list( - self, - *, - access_code_id: Optional[str] = None, - access_grant_id: Optional[str] = None, - access_grant_key: Optional[str] = None, - acs_entrance_id: Optional[str] = None, - device_id: Optional[str] = None, - limit: Optional[int] = None, - page_cursor: Optional[str] = None, - space_id: Optional[str] = None, - ) -> List[AccessMethod]: + @route_metadata(path="/access_methods/list", has_required_parameters=True, has_pagination=True) + def list(self, *, access_code_id: Optional[str] = None, access_grant_id: Optional[str] = None, access_grant_key: Optional[str] = None, acs_entrance_id: Optional[str] = None, device_id: Optional[str] = None, limit: Optional[int] = None, page_cursor: Optional[Union[str, Null]] = None, space_id: Optional[str] = None) -> List[AccessMethod]: """Lists all access methods, usually filtered by Access Grant. :param access_code_id: ID of the access code by which to filter the returned access methods. Must be combined with ``access_grant_id``, ``access_grant_key``, or ``acs_entrance_id``. @@ -437,26 +329,14 @@ def list( params["space_id"] = space_id if not params: - raise ValueError( - "At least one parameter is required for /access_methods/list" - ) + raise ValueError("At least one parameter is required for /access_methods/list") res = self.client.get("/access_methods/list", params=params) return [AccessMethod.from_dict(item) for item in res["access_methods"]] - @route_metadata( - path="/access_methods/unlock_door", - has_required_parameters=True, - has_pagination=False, - ) - def unlock_door( - self, - *, - access_method_id: str, - acs_entrance_id: str, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + @route_metadata(path="/access_methods/unlock_door", has_required_parameters=True, has_pagination=False) + def unlock_door(self, *, access_method_id: str, acs_entrance_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Remotely unlocks a specified `entrance `_ using the cloud key credential associated with an access method. Returns an action attempt that tracks the progress of the unlock operation. :param access_method_id: ID of the cloud_key ``access_method`` to use for the unlock operation. @@ -476,9 +356,7 @@ def unlock_door( json_payload["acs_entrance_id"] = acs_entrance_id if not json_payload: - raise ValueError( - "At least one parameter is required for /access_methods/unlock_door" - ) + raise ValueError("At least one parameter is required for /access_methods/unlock_door") res = self.client.post("/access_methods/unlock_door", json=json_payload) @@ -491,5 +369,5 @@ def unlock_door( return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt, + wait_for_action_attempt=wait_for_action_attempt ) diff --git a/seam/routes/access_methods_unmanaged.py b/seam/routes/access_methods_unmanaged.py index fd7cd14a..c2779354 100644 --- a/seam/routes/access_methods_unmanaged.py +++ b/seam/routes/access_methods_unmanaged.py @@ -2,7 +2,8 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import UnmanagedAccessMethod +from ..null import Null +from ..resources import (UnmanagedAccessMethod) class AbstractAccessMethodsUnmanaged(abc.ABC): @@ -19,14 +20,7 @@ def get(self, *, access_method_id: str) -> UnmanagedAccessMethod: raise NotImplementedError() @abc.abstractmethod - def list( - self, - *, - access_grant_id: str, - acs_entrance_id: Optional[str] = None, - device_id: Optional[str] = None, - space_id: Optional[str] = None, - ) -> List[UnmanagedAccessMethod]: + def list(self, *, access_grant_id: str, acs_entrance_id: Optional[str] = None, device_id: Optional[str] = None, space_id: Optional[str] = None) -> List[UnmanagedAccessMethod]: """Lists all unmanaged access methods (where is_managed = false), usually filtered by Access Grant. :param access_grant_id: ID of Access Grant to list unmanaged access methods for. @@ -48,11 +42,7 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/access_methods/unmanaged/get", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/access_methods/unmanaged/get", has_required_parameters=True, has_pagination=False) def get(self, *, access_method_id: str) -> UnmanagedAccessMethod: """Gets an unmanaged access method (where is_managed = false). @@ -67,27 +57,14 @@ def get(self, *, access_method_id: str) -> UnmanagedAccessMethod: params["access_method_id"] = access_method_id if not params: - raise ValueError( - "At least one parameter is required for /access_methods/unmanaged/get" - ) + raise ValueError("At least one parameter is required for /access_methods/unmanaged/get") res = self.client.get("/access_methods/unmanaged/get", params=params) return UnmanagedAccessMethod.from_dict(res["access_method"]) - @route_metadata( - path="/access_methods/unmanaged/list", - has_required_parameters=True, - has_pagination=False, - ) - def list( - self, - *, - access_grant_id: str, - acs_entrance_id: Optional[str] = None, - device_id: Optional[str] = None, - space_id: Optional[str] = None, - ) -> List[UnmanagedAccessMethod]: + @route_metadata(path="/access_methods/unmanaged/list", has_required_parameters=True, has_pagination=False) + def list(self, *, access_grant_id: str, acs_entrance_id: Optional[str] = None, device_id: Optional[str] = None, space_id: Optional[str] = None) -> List[UnmanagedAccessMethod]: """Lists all unmanaged access methods (where is_managed = false), usually filtered by Access Grant. :param access_grant_id: ID of Access Grant to list unmanaged access methods for. @@ -113,9 +90,7 @@ def list( params["space_id"] = space_id if not params: - raise ValueError( - "At least one parameter is required for /access_methods/unmanaged/list" - ) + raise ValueError("At least one parameter is required for /access_methods/unmanaged/list") res = self.client.get("/access_methods/unmanaged/list", params=params) diff --git a/seam/routes/acs.py b/seam/routes/acs.py index 125f3c31..c9cefdc6 100644 --- a/seam/routes/acs.py +++ b/seam/routes/acs.py @@ -2,6 +2,7 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata +from ..null import Null from .acs_access_groups import AbstractAcsAccessGroups, AcsAccessGroups from .acs_credentials import AbstractAcsCredentials, AcsCredentials from .acs_encoders import AbstractAcsEncoders, AcsEncoders diff --git a/seam/routes/acs_access_groups.py b/seam/routes/acs_access_groups.py index 6b84487e..58de3651 100644 --- a/seam/routes/acs_access_groups.py +++ b/seam/routes/acs_access_groups.py @@ -2,19 +2,14 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import AcsAccessGroup, AcsEntrance, AcsUser +from ..null import Null +from ..resources import (AcsAccessGroup,AcsEntrance,AcsUser) class AbstractAcsAccessGroups(abc.ABC): @abc.abstractmethod - def add_user( - self, - *, - acs_access_group_id: str, - acs_user_id: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> None: + def add_user(self, *, acs_access_group_id: str, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: """Adds a specified `access system user `_ to a specified `access group `_. :param acs_access_group_id: ID of the access group to which you want to add an access system user. @@ -47,14 +42,7 @@ def get(self, *, acs_access_group_id: str) -> AcsAccessGroup: raise NotImplementedError() @abc.abstractmethod - def list( - self, - *, - acs_system_id: Optional[str] = None, - acs_user_id: Optional[str] = None, - search: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> List[AcsAccessGroup]: + def list(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, search: Optional[str] = None, user_identity_id: Optional[str] = None) -> List[AcsAccessGroup]: """Returns a list of all `access groups `_. :param acs_system_id: ID of the access system for which you want to retrieve all access groups. @@ -69,9 +57,7 @@ def list( raise NotImplementedError() @abc.abstractmethod - def list_accessible_entrances( - self, *, acs_access_group_id: str - ) -> List[AcsEntrance]: + def list_accessible_entrances(self, *, acs_access_group_id: str) -> List[AcsEntrance]: """Returns a list of all accessible entrances for a specified `access group `_. :param acs_access_group_id: ID of the access group for which you want to retrieve all accessible entrances. @@ -93,13 +79,7 @@ def list_users(self, *, acs_access_group_id: str) -> List[AcsUser]: raise NotImplementedError() @abc.abstractmethod - def remove_user( - self, - *, - acs_access_group_id: str, - acs_user_id: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> None: + def remove_user(self, *, acs_access_group_id: str, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: """Removes a specified `access system user `_ from a specified `access group `_. :param acs_access_group_id: ID of the access group from which you want to remove an access system user. @@ -117,18 +97,8 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/acs/access_groups/add_user", - has_required_parameters=True, - has_pagination=False, - ) - def add_user( - self, - *, - acs_access_group_id: str, - acs_user_id: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> None: + @route_metadata(path="/acs/access_groups/add_user", has_required_parameters=True, has_pagination=False) + def add_user(self, *, acs_access_group_id: str, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: """Adds a specified `access system user `_ to a specified `access group `_. :param acs_access_group_id: ID of the access group to which you want to add an access system user. @@ -148,19 +118,13 @@ def add_user( json_payload["user_identity_id"] = user_identity_id if not json_payload: - raise ValueError( - "At least one parameter is required for /acs/access_groups/add_user" - ) + raise ValueError("At least one parameter is required for /acs/access_groups/add_user") self.client.put("/acs/access_groups/add_user", json=json_payload) return None - @route_metadata( - path="/acs/access_groups/delete", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/acs/access_groups/delete", has_required_parameters=True, has_pagination=False) def delete(self, *, acs_access_group_id: str) -> None: """Deletes a specified `access group `_. @@ -173,19 +137,13 @@ def delete(self, *, acs_access_group_id: str) -> None: params["acs_access_group_id"] = acs_access_group_id if not params: - raise ValueError( - "At least one parameter is required for /acs/access_groups/delete" - ) + raise ValueError("At least one parameter is required for /acs/access_groups/delete") self.client.delete("/acs/access_groups/delete", params=params) return None - @route_metadata( - path="/acs/access_groups/get", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/acs/access_groups/get", has_required_parameters=True, has_pagination=False) def get(self, *, acs_access_group_id: str) -> AcsAccessGroup: """Returns a specified `access group `_. @@ -200,27 +158,14 @@ def get(self, *, acs_access_group_id: str) -> AcsAccessGroup: params["acs_access_group_id"] = acs_access_group_id if not params: - raise ValueError( - "At least one parameter is required for /acs/access_groups/get" - ) + raise ValueError("At least one parameter is required for /acs/access_groups/get") res = self.client.get("/acs/access_groups/get", params=params) return AcsAccessGroup.from_dict(res["acs_access_group"]) - @route_metadata( - path="/acs/access_groups/list", - has_required_parameters=False, - has_pagination=False, - ) - def list( - self, - *, - acs_system_id: Optional[str] = None, - acs_user_id: Optional[str] = None, - search: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> List[AcsAccessGroup]: + @route_metadata(path="/acs/access_groups/list", has_required_parameters=False, has_pagination=False) + def list(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, search: Optional[str] = None, user_identity_id: Optional[str] = None) -> List[AcsAccessGroup]: """Returns a list of all `access groups `_. :param acs_system_id: ID of the access system for which you want to retrieve all access groups. @@ -247,14 +192,8 @@ def list( return [AcsAccessGroup.from_dict(item) for item in res["acs_access_groups"]] - @route_metadata( - path="/acs/access_groups/list_accessible_entrances", - has_required_parameters=True, - has_pagination=False, - ) - def list_accessible_entrances( - self, *, acs_access_group_id: str - ) -> List[AcsEntrance]: + @route_metadata(path="/acs/access_groups/list_accessible_entrances", has_required_parameters=True, has_pagination=False) + def list_accessible_entrances(self, *, acs_access_group_id: str) -> List[AcsEntrance]: """Returns a list of all accessible entrances for a specified `access group `_. :param acs_access_group_id: ID of the access group for which you want to retrieve all accessible entrances. @@ -268,21 +207,13 @@ def list_accessible_entrances( params["acs_access_group_id"] = acs_access_group_id if not params: - raise ValueError( - "At least one parameter is required for /acs/access_groups/list_accessible_entrances" - ) + raise ValueError("At least one parameter is required for /acs/access_groups/list_accessible_entrances") - res = self.client.get( - "/acs/access_groups/list_accessible_entrances", params=params - ) + res = self.client.get("/acs/access_groups/list_accessible_entrances", params=params) return [AcsEntrance.from_dict(item) for item in res["acs_entrances"]] - @route_metadata( - path="/acs/access_groups/list_users", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/acs/access_groups/list_users", has_required_parameters=True, has_pagination=False) def list_users(self, *, acs_access_group_id: str) -> List[AcsUser]: """Returns a list of all `access system users `_ in an `access group `_. @@ -297,26 +228,14 @@ def list_users(self, *, acs_access_group_id: str) -> List[AcsUser]: params["acs_access_group_id"] = acs_access_group_id if not params: - raise ValueError( - "At least one parameter is required for /acs/access_groups/list_users" - ) + raise ValueError("At least one parameter is required for /acs/access_groups/list_users") res = self.client.get("/acs/access_groups/list_users", params=params) return [AcsUser.from_dict(item) for item in res["acs_users"]] - @route_metadata( - path="/acs/access_groups/remove_user", - has_required_parameters=True, - has_pagination=False, - ) - def remove_user( - self, - *, - acs_access_group_id: str, - acs_user_id: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> None: + @route_metadata(path="/acs/access_groups/remove_user", has_required_parameters=True, has_pagination=False) + def remove_user(self, *, acs_access_group_id: str, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: """Removes a specified `access system user `_ from a specified `access group `_. :param acs_access_group_id: ID of the access group from which you want to remove an access system user. @@ -336,9 +255,7 @@ def remove_user( params["user_identity_id"] = user_identity_id if not params: - raise ValueError( - "At least one parameter is required for /acs/access_groups/remove_user" - ) + raise ValueError("At least one parameter is required for /acs/access_groups/remove_user") self.client.delete("/acs/access_groups/remove_user", params=params) diff --git a/seam/routes/acs_credentials.py b/seam/routes/acs_credentials.py index 68a56846..c18e4783 100644 --- a/seam/routes/acs_credentials.py +++ b/seam/routes/acs_credentials.py @@ -2,19 +2,14 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import AcsCredential, AcsEntrance +from ..null import Null +from ..resources import (AcsCredential,AcsEntrance) class AbstractAcsCredentials(abc.ABC): @abc.abstractmethod - def assign( - self, - *, - acs_credential_id: str, - acs_user_id: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> None: + def assign(self, *, acs_credential_id: str, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: """Assigns a specified `credential `_ to a specified `access system user `_. :param acs_credential_id: ID of the credential that you want to assign to an access system user. @@ -27,23 +22,7 @@ def assign( raise NotImplementedError() @abc.abstractmethod - def create( - self, - *, - access_method: str, - acs_system_id: Optional[str] = None, - acs_user_id: Optional[str] = None, - allowed_acs_entrance_ids: Optional[List[str]] = None, - assa_abloy_vostio_metadata: Optional[Dict[str, Any]] = None, - code: Optional[str] = None, - credential_manager_acs_system_id: Optional[str] = None, - ends_at: Optional[str] = None, - is_multi_phone_sync_credential: Optional[bool] = None, - salto_space_metadata: Optional[Dict[str, Any]] = None, - starts_at: Optional[str] = None, - user_identity_id: Optional[str] = None, - visionline_metadata: Optional[Dict[str, Any]] = None, - ) -> AcsCredential: + def create(self, *, access_method: str, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, allowed_acs_entrance_ids: Optional[List[str]] = None, assa_abloy_vostio_metadata: Optional[Dict[str, Any]] = None, code: Optional[str] = None, credential_manager_acs_system_id: Optional[str] = None, ends_at: Optional[str] = None, is_multi_phone_sync_credential: Optional[bool] = None, salto_space_metadata: Optional[Dict[str, Any]] = None, starts_at: Optional[str] = None, user_identity_id: Optional[str] = None, visionline_metadata: Optional[Dict[str, Any]] = None) -> AcsCredential: """Creates a new `credential `_ for a specified `ACS user `_. For granting access, we recommend `Access Grants `_ instead: they create and manage the underlying credentials for you, across access systems and standalone smart locks alike. Use this low-level endpoint only when you need direct control over an individual ACS credential. :param access_method: Access method for the new credential. Supported values: ``code``, ``card``, ``mobile_key``, ``cloud_key``. @@ -98,18 +77,7 @@ def get(self, *, acs_credential_id: str) -> AcsCredential: raise NotImplementedError() @abc.abstractmethod - def list( - self, - *, - acs_user_id: Optional[str] = None, - acs_system_id: Optional[str] = None, - user_identity_id: Optional[str] = None, - created_before: Optional[str] = None, - is_multi_phone_sync_credential: Optional[bool] = None, - limit: Optional[float] = None, - page_cursor: Optional[str] = None, - search: Optional[str] = None, - ) -> List[AcsCredential]: + def list(self, *, acs_user_id: Optional[str] = None, acs_system_id: Optional[str] = None, user_identity_id: Optional[str] = None, created_before: Optional[str] = None, is_multi_phone_sync_credential: Optional[bool] = None, limit: Optional[float] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None) -> List[AcsCredential]: """Returns a list of all `credentials `_. :param acs_user_id: ID of the access system user for which you want to retrieve all credentials. @@ -143,13 +111,7 @@ def list_accessible_entrances(self, *, acs_credential_id: str) -> List[AcsEntran raise NotImplementedError() @abc.abstractmethod - def unassign( - self, - *, - acs_credential_id: str, - acs_user_id: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> None: + def unassign(self, *, acs_credential_id: str, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: """Unassigns a specified `credential `_ from a specified `access system user `_. :param acs_credential_id: ID of the credential that you want to unassign from an access system user. @@ -162,13 +124,7 @@ def unassign( raise NotImplementedError() @abc.abstractmethod - def update( - self, - *, - acs_credential_id: str, - code: Optional[str] = None, - ends_at: Optional[str] = None, - ) -> None: + def update(self, *, acs_credential_id: str, code: Optional[str] = None, ends_at: Optional[str] = None) -> None: """Updates the code and ends at date and time for a specified `credential `_. :param acs_credential_id: ID of the credential that you want to update. @@ -186,18 +142,8 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/acs/credentials/assign", - has_required_parameters=True, - has_pagination=False, - ) - def assign( - self, - *, - acs_credential_id: str, - acs_user_id: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> None: + @route_metadata(path="/acs/credentials/assign", has_required_parameters=True, has_pagination=False) + def assign(self, *, acs_credential_id: str, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: """Assigns a specified `credential `_ to a specified `access system user `_. :param acs_credential_id: ID of the credential that you want to assign to an access system user. @@ -217,36 +163,14 @@ def assign( json_payload["user_identity_id"] = user_identity_id if not json_payload: - raise ValueError( - "At least one parameter is required for /acs/credentials/assign" - ) + raise ValueError("At least one parameter is required for /acs/credentials/assign") self.client.patch("/acs/credentials/assign", json=json_payload) return None - @route_metadata( - path="/acs/credentials/create", - has_required_parameters=True, - has_pagination=False, - ) - def create( - self, - *, - access_method: str, - acs_system_id: Optional[str] = None, - acs_user_id: Optional[str] = None, - allowed_acs_entrance_ids: Optional[List[str]] = None, - assa_abloy_vostio_metadata: Optional[Dict[str, Any]] = None, - code: Optional[str] = None, - credential_manager_acs_system_id: Optional[str] = None, - ends_at: Optional[str] = None, - is_multi_phone_sync_credential: Optional[bool] = None, - salto_space_metadata: Optional[Dict[str, Any]] = None, - starts_at: Optional[str] = None, - user_identity_id: Optional[str] = None, - visionline_metadata: Optional[Dict[str, Any]] = None, - ) -> AcsCredential: + @route_metadata(path="/acs/credentials/create", has_required_parameters=True, has_pagination=False) + def create(self, *, access_method: str, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, allowed_acs_entrance_ids: Optional[List[str]] = None, assa_abloy_vostio_metadata: Optional[Dict[str, Any]] = None, code: Optional[str] = None, credential_manager_acs_system_id: Optional[str] = None, ends_at: Optional[str] = None, is_multi_phone_sync_credential: Optional[bool] = None, salto_space_metadata: Optional[Dict[str, Any]] = None, starts_at: Optional[str] = None, user_identity_id: Optional[str] = None, visionline_metadata: Optional[Dict[str, Any]] = None) -> AcsCredential: """Creates a new `credential `_ for a specified `ACS user `_. For granting access, we recommend `Access Grants `_ instead: they create and manage the underlying credentials for you, across access systems and standalone smart locks alike. Use this low-level endpoint only when you need direct control over an individual ACS credential. :param access_method: Access method for the new credential. Supported values: ``code``, ``card``, ``mobile_key``, ``cloud_key``. @@ -293,15 +217,11 @@ def create( if code is not None: json_payload["code"] = code if credential_manager_acs_system_id is not None: - json_payload["credential_manager_acs_system_id"] = ( - credential_manager_acs_system_id - ) + json_payload["credential_manager_acs_system_id"] = credential_manager_acs_system_id if ends_at is not None: json_payload["ends_at"] = ends_at if is_multi_phone_sync_credential is not None: - json_payload["is_multi_phone_sync_credential"] = ( - is_multi_phone_sync_credential - ) + json_payload["is_multi_phone_sync_credential"] = is_multi_phone_sync_credential if salto_space_metadata is not None: json_payload["salto_space_metadata"] = salto_space_metadata if starts_at is not None: @@ -312,19 +232,13 @@ def create( json_payload["visionline_metadata"] = visionline_metadata if not json_payload: - raise ValueError( - "At least one parameter is required for /acs/credentials/create" - ) + raise ValueError("At least one parameter is required for /acs/credentials/create") res = self.client.post("/acs/credentials/create", json=json_payload) return AcsCredential.from_dict(res["acs_credential"]) - @route_metadata( - path="/acs/credentials/delete", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/acs/credentials/delete", has_required_parameters=True, has_pagination=False) def delete(self, *, acs_credential_id: str) -> None: """Deletes a specified `credential `_. @@ -337,17 +251,13 @@ def delete(self, *, acs_credential_id: str) -> None: params["acs_credential_id"] = acs_credential_id if not params: - raise ValueError( - "At least one parameter is required for /acs/credentials/delete" - ) + raise ValueError("At least one parameter is required for /acs/credentials/delete") self.client.delete("/acs/credentials/delete", params=params) return None - @route_metadata( - path="/acs/credentials/get", has_required_parameters=True, has_pagination=False - ) + @route_metadata(path="/acs/credentials/get", has_required_parameters=True, has_pagination=False) def get(self, *, acs_credential_id: str) -> AcsCredential: """Returns a specified `credential `_. @@ -362,29 +272,14 @@ def get(self, *, acs_credential_id: str) -> AcsCredential: params["acs_credential_id"] = acs_credential_id if not params: - raise ValueError( - "At least one parameter is required for /acs/credentials/get" - ) + raise ValueError("At least one parameter is required for /acs/credentials/get") res = self.client.get("/acs/credentials/get", params=params) return AcsCredential.from_dict(res["acs_credential"]) - @route_metadata( - path="/acs/credentials/list", has_required_parameters=False, has_pagination=True - ) - def list( - self, - *, - acs_user_id: Optional[str] = None, - acs_system_id: Optional[str] = None, - user_identity_id: Optional[str] = None, - created_before: Optional[str] = None, - is_multi_phone_sync_credential: Optional[bool] = None, - limit: Optional[float] = None, - page_cursor: Optional[str] = None, - search: Optional[str] = None, - ) -> List[AcsCredential]: + @route_metadata(path="/acs/credentials/list", has_required_parameters=False, has_pagination=True) + def list(self, *, acs_user_id: Optional[str] = None, acs_system_id: Optional[str] = None, user_identity_id: Optional[str] = None, created_before: Optional[str] = None, is_multi_phone_sync_credential: Optional[bool] = None, limit: Optional[float] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None) -> List[AcsCredential]: """Returns a list of all `credentials `_. :param acs_user_id: ID of the access system user for which you want to retrieve all credentials. @@ -427,11 +322,7 @@ def list( return [AcsCredential.from_dict(item) for item in res["acs_credentials"]] - @route_metadata( - path="/acs/credentials/list_accessible_entrances", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/acs/credentials/list_accessible_entrances", has_required_parameters=True, has_pagination=False) def list_accessible_entrances(self, *, acs_credential_id: str) -> List[AcsEntrance]: """Returns a list of all `entrances `_ to which a `credential `_ grants access. @@ -446,28 +337,14 @@ def list_accessible_entrances(self, *, acs_credential_id: str) -> List[AcsEntran params["acs_credential_id"] = acs_credential_id if not params: - raise ValueError( - "At least one parameter is required for /acs/credentials/list_accessible_entrances" - ) + raise ValueError("At least one parameter is required for /acs/credentials/list_accessible_entrances") - res = self.client.get( - "/acs/credentials/list_accessible_entrances", params=params - ) + res = self.client.get("/acs/credentials/list_accessible_entrances", params=params) return [AcsEntrance.from_dict(item) for item in res["acs_entrances"]] - @route_metadata( - path="/acs/credentials/unassign", - has_required_parameters=True, - has_pagination=False, - ) - def unassign( - self, - *, - acs_credential_id: str, - acs_user_id: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> None: + @route_metadata(path="/acs/credentials/unassign", has_required_parameters=True, has_pagination=False) + def unassign(self, *, acs_credential_id: str, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: """Unassigns a specified `credential `_ from a specified `access system user `_. :param acs_credential_id: ID of the credential that you want to unassign from an access system user. @@ -487,26 +364,14 @@ def unassign( json_payload["user_identity_id"] = user_identity_id if not json_payload: - raise ValueError( - "At least one parameter is required for /acs/credentials/unassign" - ) + raise ValueError("At least one parameter is required for /acs/credentials/unassign") self.client.patch("/acs/credentials/unassign", json=json_payload) return None - @route_metadata( - path="/acs/credentials/update", - has_required_parameters=True, - has_pagination=False, - ) - def update( - self, - *, - acs_credential_id: str, - code: Optional[str] = None, - ends_at: Optional[str] = None, - ) -> None: + @route_metadata(path="/acs/credentials/update", has_required_parameters=True, has_pagination=False) + def update(self, *, acs_credential_id: str, code: Optional[str] = None, ends_at: Optional[str] = None) -> None: """Updates the code and ends at date and time for a specified `credential `_. :param acs_credential_id: ID of the credential that you want to update. @@ -526,9 +391,7 @@ def update( json_payload["ends_at"] = ends_at if not json_payload: - raise ValueError( - "At least one parameter is required for /acs/credentials/update" - ) + raise ValueError("At least one parameter is required for /acs/credentials/update") self.client.patch("/acs/credentials/update", json=json_payload) diff --git a/seam/routes/acs_encoders.py b/seam/routes/acs_encoders.py index 194008aa..db148a98 100644 --- a/seam/routes/acs_encoders.py +++ b/seam/routes/acs_encoders.py @@ -2,7 +2,8 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import ActionAttempt, AcsEncoder +from ..null import Null +from ..resources import (ActionAttempt,AcsEncoder) from .acs_encoders_simulate import AbstractAcsEncodersSimulate, AcsEncodersSimulate from ..modules.action_attempts import resolve_action_attempt @@ -15,14 +16,7 @@ def simulate(self) -> AbstractAcsEncodersSimulate: raise NotImplementedError() @abc.abstractmethod - def encode_credential( - self, - *, - acs_encoder_id: str, - access_method_id: Optional[str] = None, - acs_credential_id: Optional[str] = None, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + def encode_credential(self, *, acs_encoder_id: str, access_method_id: Optional[str] = None, acs_credential_id: Optional[str] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Encodes an existing `credential `_ onto a plastic card placed on the specified `encoder `_. Either provide an ``acs_credential_id`` or an ``access_method_id`` :param acs_encoder_id: ID of the ``acs_encoder`` to use to encode the ``acs_credential``. @@ -50,15 +44,7 @@ def get(self, *, acs_encoder_id: str) -> AcsEncoder: raise NotImplementedError() @abc.abstractmethod - def list( - self, - *, - acs_system_id: Optional[str] = None, - acs_system_ids: Optional[List[str]] = None, - acs_encoder_ids: Optional[List[str]] = None, - limit: Optional[float] = None, - page_cursor: Optional[str] = None, - ) -> List[AcsEncoder]: + def list(self, *, acs_system_id: Optional[str] = None, acs_system_ids: Optional[List[str]] = None, acs_encoder_ids: Optional[List[str]] = None, limit: Optional[float] = None, page_cursor: Optional[Union[str, Null]] = None) -> List[AcsEncoder]: """Returns a list of all `encoders `_. :param acs_system_id: ID of the access system for which you want to retrieve all encoders. @@ -75,13 +61,7 @@ def list( raise NotImplementedError() @abc.abstractmethod - def scan_credential( - self, - *, - acs_encoder_id: str, - salto_ks_metadata: Optional[Dict[str, Any]] = None, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + def scan_credential(self, *, acs_encoder_id: str, salto_ks_metadata: Optional[Dict[str, Any]] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Scans an encoded `acs_credential `_ from a plastic card placed on the specified `encoder `_. :param acs_encoder_id: ID of the encoder to use for the scan. @@ -96,15 +76,7 @@ def scan_credential( raise NotImplementedError() @abc.abstractmethod - def scan_to_assign_credential( - self, - *, - acs_encoder_id: str, - acs_user_id: Optional[str] = None, - salto_ks_metadata: Optional[Dict[str, Any]] = None, - user_identity_id: Optional[str] = None, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + def scan_to_assign_credential(self, *, acs_encoder_id: str, acs_user_id: Optional[str] = None, salto_ks_metadata: Optional[Dict[str, Any]] = None, user_identity_id: Optional[str] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Scans a physical card placed on the specified `encoder `_ and assigns the scanned credential to an ACS user. Provide either an ``acs_user_id`` or a ``user_identity_id``. :param acs_encoder_id: ID of the ``acs_encoder`` to use to scan the credential. @@ -133,19 +105,8 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): def simulate(self) -> AcsEncodersSimulate: return self._simulate - @route_metadata( - path="/acs/encoders/encode_credential", - has_required_parameters=True, - has_pagination=False, - ) - def encode_credential( - self, - *, - acs_encoder_id: str, - access_method_id: Optional[str] = None, - acs_credential_id: Optional[str] = None, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + @route_metadata(path="/acs/encoders/encode_credential", has_required_parameters=True, has_pagination=False) + def encode_credential(self, *, acs_encoder_id: str, access_method_id: Optional[str] = None, acs_credential_id: Optional[str] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Encodes an existing `credential `_ onto a plastic card placed on the specified `encoder `_. Either provide an ``acs_credential_id`` or an ``access_method_id`` :param acs_encoder_id: ID of the ``acs_encoder`` to use to encode the ``acs_credential``. @@ -169,9 +130,7 @@ def encode_credential( json_payload["acs_credential_id"] = acs_credential_id if not json_payload: - raise ValueError( - "At least one parameter is required for /acs/encoders/encode_credential" - ) + raise ValueError("At least one parameter is required for /acs/encoders/encode_credential") res = self.client.post("/acs/encoders/encode_credential", json=json_payload) @@ -184,12 +143,10 @@ def encode_credential( return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt, + wait_for_action_attempt=wait_for_action_attempt ) - @route_metadata( - path="/acs/encoders/get", has_required_parameters=True, has_pagination=False - ) + @route_metadata(path="/acs/encoders/get", has_required_parameters=True, has_pagination=False) def get(self, *, acs_encoder_id: str) -> AcsEncoder: """Returns a specified `encoder `_. @@ -210,18 +167,8 @@ def get(self, *, acs_encoder_id: str) -> AcsEncoder: return AcsEncoder.from_dict(res["acs_encoder"]) - @route_metadata( - path="/acs/encoders/list", has_required_parameters=False, has_pagination=True - ) - def list( - self, - *, - acs_system_id: Optional[str] = None, - acs_system_ids: Optional[List[str]] = None, - acs_encoder_ids: Optional[List[str]] = None, - limit: Optional[float] = None, - page_cursor: Optional[str] = None, - ) -> List[AcsEncoder]: + @route_metadata(path="/acs/encoders/list", has_required_parameters=False, has_pagination=True) + def list(self, *, acs_system_id: Optional[str] = None, acs_system_ids: Optional[List[str]] = None, acs_encoder_ids: Optional[List[str]] = None, limit: Optional[float] = None, page_cursor: Optional[Union[str, Null]] = None) -> List[AcsEncoder]: """Returns a list of all `encoders `_. :param acs_system_id: ID of the access system for which you want to retrieve all encoders. @@ -252,18 +199,8 @@ def list( return [AcsEncoder.from_dict(item) for item in res["acs_encoders"]] - @route_metadata( - path="/acs/encoders/scan_credential", - has_required_parameters=True, - has_pagination=False, - ) - def scan_credential( - self, - *, - acs_encoder_id: str, - salto_ks_metadata: Optional[Dict[str, Any]] = None, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + @route_metadata(path="/acs/encoders/scan_credential", has_required_parameters=True, has_pagination=False) + def scan_credential(self, *, acs_encoder_id: str, salto_ks_metadata: Optional[Dict[str, Any]] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Scans an encoded `acs_credential `_ from a plastic card placed on the specified `encoder `_. :param acs_encoder_id: ID of the encoder to use for the scan. @@ -283,9 +220,7 @@ def scan_credential( json_payload["salto_ks_metadata"] = salto_ks_metadata if not json_payload: - raise ValueError( - "At least one parameter is required for /acs/encoders/scan_credential" - ) + raise ValueError("At least one parameter is required for /acs/encoders/scan_credential") res = self.client.post("/acs/encoders/scan_credential", json=json_payload) @@ -298,23 +233,11 @@ def scan_credential( return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt, + wait_for_action_attempt=wait_for_action_attempt ) - @route_metadata( - path="/acs/encoders/scan_to_assign_credential", - has_required_parameters=True, - has_pagination=False, - ) - def scan_to_assign_credential( - self, - *, - acs_encoder_id: str, - acs_user_id: Optional[str] = None, - salto_ks_metadata: Optional[Dict[str, Any]] = None, - user_identity_id: Optional[str] = None, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + @route_metadata(path="/acs/encoders/scan_to_assign_credential", has_required_parameters=True, has_pagination=False) + def scan_to_assign_credential(self, *, acs_encoder_id: str, acs_user_id: Optional[str] = None, salto_ks_metadata: Optional[Dict[str, Any]] = None, user_identity_id: Optional[str] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Scans a physical card placed on the specified `encoder `_ and assigns the scanned credential to an ACS user. Provide either an ``acs_user_id`` or a ``user_identity_id``. :param acs_encoder_id: ID of the ``acs_encoder`` to use to scan the credential. @@ -342,13 +265,9 @@ def scan_to_assign_credential( json_payload["user_identity_id"] = user_identity_id if not json_payload: - raise ValueError( - "At least one parameter is required for /acs/encoders/scan_to_assign_credential" - ) + raise ValueError("At least one parameter is required for /acs/encoders/scan_to_assign_credential") - res = self.client.post( - "/acs/encoders/scan_to_assign_credential", json=json_payload - ) + res = self.client.post("/acs/encoders/scan_to_assign_credential", json=json_payload) wait_for_action_attempt = ( self.defaults.get("wait_for_action_attempt") @@ -359,5 +278,5 @@ def scan_to_assign_credential( return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt, + wait_for_action_attempt=wait_for_action_attempt ) diff --git a/seam/routes/acs_encoders_simulate.py b/seam/routes/acs_encoders_simulate.py index ac0d1793..62b0d6ee 100644 --- a/seam/routes/acs_encoders_simulate.py +++ b/seam/routes/acs_encoders_simulate.py @@ -2,18 +2,13 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata +from ..null import Null class AbstractAcsEncodersSimulate(abc.ABC): @abc.abstractmethod - def next_credential_encode_will_fail( - self, - *, - acs_encoder_id: str, - error_code: Optional[str] = None, - acs_credential_id: Optional[str] = None, - ) -> None: + def next_credential_encode_will_fail(self, *, acs_encoder_id: str, error_code: Optional[str] = None, acs_credential_id: Optional[str] = None) -> None: """Simulates that the next attempt to encode a `credential `_ using the specified `encoder `_ will fail. You can only perform this action within a `sandbox workspace `_. :param acs_encoder_id: ID of the ``acs_encoder`` that will be used in the next request to encode the ``acs_credential``. @@ -26,9 +21,7 @@ def next_credential_encode_will_fail( raise NotImplementedError() @abc.abstractmethod - def next_credential_encode_will_succeed( - self, *, acs_encoder_id: str, scenario: Optional[str] = None - ) -> None: + def next_credential_encode_will_succeed(self, *, acs_encoder_id: str, scenario: Optional[str] = None) -> None: """Simulates that the next attempt to encode a `credential `_ using the specified `encoder `_ will succeed. You can only perform this action within a `sandbox workspace `_. :param acs_encoder_id: ID of the ``acs_encoder`` that will be used in the next request to encode the ``acs_credential``. @@ -39,32 +32,20 @@ def next_credential_encode_will_succeed( raise NotImplementedError() @abc.abstractmethod - def next_credential_scan_will_fail( - self, - *, - acs_encoder_id: str, - error_code: Optional[str] = None, - acs_credential_id_on_seam: Optional[str] = None, - ) -> None: + def next_credential_scan_will_fail(self, *, acs_encoder_id: str, error_code: Optional[str] = None, acs_credential_id_on_seam: Optional[str] = None) -> None: """Simulates that the next attempt to scan a `credential `_ using the specified `encoder `_ will fail. You can only perform this action within a `sandbox workspace `_. :param acs_encoder_id: ID of the ``acs_encoder`` that will fail to scan the ``acs_credential`` in the next request. - :param error_code: + :param error_code: - :param acs_credential_id_on_seam: + :param acs_credential_id_on_seam: :raises ValueError: At least one parameter must be provided.""" raise NotImplementedError() @abc.abstractmethod - def next_credential_scan_will_succeed( - self, - *, - acs_encoder_id: str, - acs_credential_id_on_seam: Optional[str] = None, - scenario: Optional[str] = None, - ) -> None: + def next_credential_scan_will_succeed(self, *, acs_encoder_id: str, acs_credential_id_on_seam: Optional[str] = None, scenario: Optional[str] = None) -> None: """Simulates that the next attempt to scan a `credential `_ using the specified `encoder `_ will succeed. You can only perform this action within a `sandbox workspace `_. :param acs_encoder_id: ID of the ``acs_encoder`` that will be used in the next request to scan the ``acs_credential``. @@ -82,18 +63,8 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/acs/encoders/simulate/next_credential_encode_will_fail", - has_required_parameters=True, - has_pagination=False, - ) - def next_credential_encode_will_fail( - self, - *, - acs_encoder_id: str, - error_code: Optional[str] = None, - acs_credential_id: Optional[str] = None, - ) -> None: + @route_metadata(path="/acs/encoders/simulate/next_credential_encode_will_fail", has_required_parameters=True, has_pagination=False) + def next_credential_encode_will_fail(self, *, acs_encoder_id: str, error_code: Optional[str] = None, acs_credential_id: Optional[str] = None) -> None: """Simulates that the next attempt to encode a `credential `_ using the specified `encoder `_ will fail. You can only perform this action within a `sandbox workspace `_. :param acs_encoder_id: ID of the ``acs_encoder`` that will be used in the next request to encode the ``acs_credential``. @@ -113,24 +84,14 @@ def next_credential_encode_will_fail( json_payload["acs_credential_id"] = acs_credential_id if not json_payload: - raise ValueError( - "At least one parameter is required for /acs/encoders/simulate/next_credential_encode_will_fail" - ) + raise ValueError("At least one parameter is required for /acs/encoders/simulate/next_credential_encode_will_fail") - self.client.post( - "/acs/encoders/simulate/next_credential_encode_will_fail", json=json_payload - ) + self.client.post("/acs/encoders/simulate/next_credential_encode_will_fail", json=json_payload) return None - @route_metadata( - path="/acs/encoders/simulate/next_credential_encode_will_succeed", - has_required_parameters=True, - has_pagination=False, - ) - def next_credential_encode_will_succeed( - self, *, acs_encoder_id: str, scenario: Optional[str] = None - ) -> None: + @route_metadata(path="/acs/encoders/simulate/next_credential_encode_will_succeed", has_required_parameters=True, has_pagination=False) + def next_credential_encode_will_succeed(self, *, acs_encoder_id: str, scenario: Optional[str] = None) -> None: """Simulates that the next attempt to encode a `credential `_ using the specified `encoder `_ will succeed. You can only perform this action within a `sandbox workspace `_. :param acs_encoder_id: ID of the ``acs_encoder`` that will be used in the next request to encode the ``acs_credential``. @@ -146,36 +107,21 @@ def next_credential_encode_will_succeed( json_payload["scenario"] = scenario if not json_payload: - raise ValueError( - "At least one parameter is required for /acs/encoders/simulate/next_credential_encode_will_succeed" - ) + raise ValueError("At least one parameter is required for /acs/encoders/simulate/next_credential_encode_will_succeed") - self.client.post( - "/acs/encoders/simulate/next_credential_encode_will_succeed", - json=json_payload, - ) + self.client.post("/acs/encoders/simulate/next_credential_encode_will_succeed", json=json_payload) return None - @route_metadata( - path="/acs/encoders/simulate/next_credential_scan_will_fail", - has_required_parameters=True, - has_pagination=False, - ) - def next_credential_scan_will_fail( - self, - *, - acs_encoder_id: str, - error_code: Optional[str] = None, - acs_credential_id_on_seam: Optional[str] = None, - ) -> None: + @route_metadata(path="/acs/encoders/simulate/next_credential_scan_will_fail", has_required_parameters=True, has_pagination=False) + def next_credential_scan_will_fail(self, *, acs_encoder_id: str, error_code: Optional[str] = None, acs_credential_id_on_seam: Optional[str] = None) -> None: """Simulates that the next attempt to scan a `credential `_ using the specified `encoder `_ will fail. You can only perform this action within a `sandbox workspace `_. :param acs_encoder_id: ID of the ``acs_encoder`` that will fail to scan the ``acs_credential`` in the next request. - :param error_code: + :param error_code: - :param acs_credential_id_on_seam: + :param acs_credential_id_on_seam: :raises ValueError: At least one parameter must be provided.""" json_payload: Dict[str, Any] = {} @@ -188,28 +134,14 @@ def next_credential_scan_will_fail( json_payload["acs_credential_id_on_seam"] = acs_credential_id_on_seam if not json_payload: - raise ValueError( - "At least one parameter is required for /acs/encoders/simulate/next_credential_scan_will_fail" - ) + raise ValueError("At least one parameter is required for /acs/encoders/simulate/next_credential_scan_will_fail") - self.client.post( - "/acs/encoders/simulate/next_credential_scan_will_fail", json=json_payload - ) + self.client.post("/acs/encoders/simulate/next_credential_scan_will_fail", json=json_payload) return None - @route_metadata( - path="/acs/encoders/simulate/next_credential_scan_will_succeed", - has_required_parameters=True, - has_pagination=False, - ) - def next_credential_scan_will_succeed( - self, - *, - acs_encoder_id: str, - acs_credential_id_on_seam: Optional[str] = None, - scenario: Optional[str] = None, - ) -> None: + @route_metadata(path="/acs/encoders/simulate/next_credential_scan_will_succeed", has_required_parameters=True, has_pagination=False) + def next_credential_scan_will_succeed(self, *, acs_encoder_id: str, acs_credential_id_on_seam: Optional[str] = None, scenario: Optional[str] = None) -> None: """Simulates that the next attempt to scan a `credential `_ using the specified `encoder `_ will succeed. You can only perform this action within a `sandbox workspace `_. :param acs_encoder_id: ID of the ``acs_encoder`` that will be used in the next request to scan the ``acs_credential``. @@ -229,13 +161,8 @@ def next_credential_scan_will_succeed( json_payload["scenario"] = scenario if not json_payload: - raise ValueError( - "At least one parameter is required for /acs/encoders/simulate/next_credential_scan_will_succeed" - ) - - self.client.post( - "/acs/encoders/simulate/next_credential_scan_will_succeed", - json=json_payload, - ) + raise ValueError("At least one parameter is required for /acs/encoders/simulate/next_credential_scan_will_succeed") + + self.client.post("/acs/encoders/simulate/next_credential_scan_will_succeed", json=json_payload) return None diff --git a/seam/routes/acs_entrances.py b/seam/routes/acs_entrances.py index 353929ca..043a91c4 100644 --- a/seam/routes/acs_entrances.py +++ b/seam/routes/acs_entrances.py @@ -2,7 +2,8 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import AcsEntrance, AcsCredential, ActionAttempt +from ..null import Null +from ..resources import (AcsEntrance,AcsCredential,ActionAttempt) from ..modules.action_attempts import resolve_action_attempt @@ -20,13 +21,7 @@ def get(self, *, acs_entrance_id: str) -> AcsEntrance: raise NotImplementedError() @abc.abstractmethod - def grant_access( - self, - *, - acs_entrance_id: str, - acs_user_id: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> None: + def grant_access(self, *, acs_entrance_id: str, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: """Grants a specified `access system user `_ access to a specified `access system entrance `_. :param acs_entrance_id: ID of the entrance to which you want to grant an access system user access. @@ -39,21 +34,7 @@ def grant_access( raise NotImplementedError() @abc.abstractmethod - def list( - self, - *, - access_method_id: Optional[str] = None, - acs_credential_id: Optional[str] = None, - acs_entrance_ids: Optional[List[str]] = None, - acs_system_id: Optional[str] = None, - connected_account_id: Optional[str] = None, - customer_key: Optional[str] = None, - limit: Optional[int] = None, - location_id: Optional[str] = None, - page_cursor: Optional[str] = None, - search: Optional[str] = None, - space_id: Optional[str] = None, - ) -> List[AcsEntrance]: + def list(self, *, access_method_id: Optional[str] = None, acs_credential_id: Optional[str] = None, acs_entrance_ids: Optional[List[str]] = None, acs_system_id: Optional[str] = None, connected_account_id: Optional[str] = None, customer_key: Optional[str] = None, limit: Optional[int] = None, location_id: Optional[Union[str, Null]] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, space_id: Optional[str] = None) -> List[AcsEntrance]: """Returns a list of all `access system entrances `_. :param access_method_id: ID of the access method for which you want to retrieve all entrances to which it grants access. @@ -82,9 +63,7 @@ def list( raise NotImplementedError() @abc.abstractmethod - def list_credentials_with_access( - self, *, acs_entrance_id: str, include_if: Optional[List[str]] = None - ) -> List[AcsCredential]: + def list_credentials_with_access(self, *, acs_entrance_id: str, include_if: Optional[List[str]] = None) -> List[AcsCredential]: """Returns a list of all `credentials `_ with access to a specified `entrance `_. :param acs_entrance_id: ID of the entrance for which you want to list all credentials that grant access. @@ -97,13 +76,7 @@ def list_credentials_with_access( raise NotImplementedError() @abc.abstractmethod - def unlock( - self, - *, - acs_credential_id: str, - acs_entrance_id: str, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + def unlock(self, *, acs_credential_id: str, acs_entrance_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Remotely unlocks a specified `entrance `_ using a cloud_key credential. Returns an action attempt that tracks the progress of the unlock operation. :param acs_credential_id: ID of the cloud_key credential to use for the unlock operation. @@ -123,9 +96,7 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/acs/entrances/get", has_required_parameters=True, has_pagination=False - ) + @route_metadata(path="/acs/entrances/get", has_required_parameters=True, has_pagination=False) def get(self, *, acs_entrance_id: str) -> AcsEntrance: """Returns a specified `access system entrance `_. @@ -140,26 +111,14 @@ def get(self, *, acs_entrance_id: str) -> AcsEntrance: params["acs_entrance_id"] = acs_entrance_id if not params: - raise ValueError( - "At least one parameter is required for /acs/entrances/get" - ) + raise ValueError("At least one parameter is required for /acs/entrances/get") res = self.client.get("/acs/entrances/get", params=params) return AcsEntrance.from_dict(res["acs_entrance"]) - @route_metadata( - path="/acs/entrances/grant_access", - has_required_parameters=True, - has_pagination=False, - ) - def grant_access( - self, - *, - acs_entrance_id: str, - acs_user_id: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> None: + @route_metadata(path="/acs/entrances/grant_access", has_required_parameters=True, has_pagination=False) + def grant_access(self, *, acs_entrance_id: str, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: """Grants a specified `access system user `_ access to a specified `access system entrance `_. :param acs_entrance_id: ID of the entrance to which you want to grant an access system user access. @@ -179,32 +138,14 @@ def grant_access( json_payload["user_identity_id"] = user_identity_id if not json_payload: - raise ValueError( - "At least one parameter is required for /acs/entrances/grant_access" - ) + raise ValueError("At least one parameter is required for /acs/entrances/grant_access") self.client.post("/acs/entrances/grant_access", json=json_payload) return None - @route_metadata( - path="/acs/entrances/list", has_required_parameters=False, has_pagination=True - ) - def list( - self, - *, - access_method_id: Optional[str] = None, - acs_credential_id: Optional[str] = None, - acs_entrance_ids: Optional[List[str]] = None, - acs_system_id: Optional[str] = None, - connected_account_id: Optional[str] = None, - customer_key: Optional[str] = None, - limit: Optional[int] = None, - location_id: Optional[str] = None, - page_cursor: Optional[str] = None, - search: Optional[str] = None, - space_id: Optional[str] = None, - ) -> List[AcsEntrance]: + @route_metadata(path="/acs/entrances/list", has_required_parameters=False, has_pagination=True) + def list(self, *, access_method_id: Optional[str] = None, acs_credential_id: Optional[str] = None, acs_entrance_ids: Optional[List[str]] = None, acs_system_id: Optional[str] = None, connected_account_id: Optional[str] = None, customer_key: Optional[str] = None, limit: Optional[int] = None, location_id: Optional[Union[str, Null]] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, space_id: Optional[str] = None) -> List[AcsEntrance]: """Returns a list of all `access system entrances `_. :param access_method_id: ID of the access method for which you want to retrieve all entrances to which it grants access. @@ -259,14 +200,8 @@ def list( return [AcsEntrance.from_dict(item) for item in res["acs_entrances"]] - @route_metadata( - path="/acs/entrances/list_credentials_with_access", - has_required_parameters=True, - has_pagination=False, - ) - def list_credentials_with_access( - self, *, acs_entrance_id: str, include_if: Optional[List[str]] = None - ) -> List[AcsCredential]: + @route_metadata(path="/acs/entrances/list_credentials_with_access", has_required_parameters=True, has_pagination=False) + def list_credentials_with_access(self, *, acs_entrance_id: str, include_if: Optional[List[str]] = None) -> List[AcsCredential]: """Returns a list of all `credentials `_ with access to a specified `entrance `_. :param acs_entrance_id: ID of the entrance for which you want to list all credentials that grant access. @@ -284,26 +219,14 @@ def list_credentials_with_access( json_payload["include_if"] = include_if if not json_payload: - raise ValueError( - "At least one parameter is required for /acs/entrances/list_credentials_with_access" - ) + raise ValueError("At least one parameter is required for /acs/entrances/list_credentials_with_access") - res = self.client.post( - "/acs/entrances/list_credentials_with_access", json=json_payload - ) + res = self.client.post("/acs/entrances/list_credentials_with_access", json=json_payload) return [AcsCredential.from_dict(item) for item in res["acs_credentials"]] - @route_metadata( - path="/acs/entrances/unlock", has_required_parameters=True, has_pagination=False - ) - def unlock( - self, - *, - acs_credential_id: str, - acs_entrance_id: str, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + @route_metadata(path="/acs/entrances/unlock", has_required_parameters=True, has_pagination=False) + def unlock(self, *, acs_credential_id: str, acs_entrance_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Remotely unlocks a specified `entrance `_ using a cloud_key credential. Returns an action attempt that tracks the progress of the unlock operation. :param acs_credential_id: ID of the cloud_key credential to use for the unlock operation. @@ -323,9 +246,7 @@ def unlock( json_payload["acs_entrance_id"] = acs_entrance_id if not json_payload: - raise ValueError( - "At least one parameter is required for /acs/entrances/unlock" - ) + raise ValueError("At least one parameter is required for /acs/entrances/unlock") res = self.client.post("/acs/entrances/unlock", json=json_payload) @@ -338,5 +259,5 @@ def unlock( return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt, + wait_for_action_attempt=wait_for_action_attempt ) diff --git a/seam/routes/acs_systems.py b/seam/routes/acs_systems.py index 7eb41612..a1a6cea4 100644 --- a/seam/routes/acs_systems.py +++ b/seam/routes/acs_systems.py @@ -2,7 +2,8 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import AcsSystem +from ..null import Null +from ..resources import (AcsSystem) class AbstractAcsSystems(abc.ABC): @@ -19,15 +20,9 @@ def get(self, *, acs_system_id: str) -> AcsSystem: raise NotImplementedError() @abc.abstractmethod - def list( - self, - *, - connected_account_id: Optional[str] = None, - customer_key: Optional[str] = None, - search: Optional[str] = None, - ) -> List[AcsSystem]: + def list(self, *, connected_account_id: Optional[str] = None, customer_key: Optional[str] = None, search: Optional[str] = None) -> List[AcsSystem]: """Returns a list of all `access systems `_. - + To filter the list of returned access systems by a specific connected account ID, include the ``connected_account_id`` in the request body. If you omit the ``connected_account_id`` parameter, the response includes all access systems connected to your workspace. :param connected_account_id: ID of the connected account by which you want to filter the list of access systems. @@ -40,11 +35,9 @@ def list( raise NotImplementedError() @abc.abstractmethod - def list_compatible_credential_manager_acs_systems( - self, *, acs_system_id: str - ) -> List[AcsSystem]: + def list_compatible_credential_manager_acs_systems(self, *, acs_system_id: str) -> List[AcsSystem]: """Returns a list of all credential manager systems that are compatible with a specified `access system `_. - + Specify the access system for which you want to retrieve all compatible credential manager systems by including the corresponding ``acs_system_id`` in the request body. :param acs_system_id: ID of the access system for which you want to retrieve all compatible credential manager systems. @@ -55,13 +48,7 @@ def list_compatible_credential_manager_acs_systems( raise NotImplementedError() @abc.abstractmethod - def report_devices( - self, - *, - acs_system_id: str, - acs_encoders: Optional[List[Dict[str, Any]]] = None, - acs_entrances: Optional[List[Dict[str, Any]]] = None, - ) -> None: + def report_devices(self, *, acs_system_id: str, acs_encoders: Optional[List[Dict[str, Any]]] = None, acs_entrances: Optional[List[Dict[str, Any]]] = None) -> None: """Reports ACS system device status including encoders and entrances. :param acs_system_id: ID of the ACS system to report resources for @@ -79,9 +66,7 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/acs/systems/get", has_required_parameters=True, has_pagination=False - ) + @route_metadata(path="/acs/systems/get", has_required_parameters=True, has_pagination=False) def get(self, *, acs_system_id: str) -> AcsSystem: """Returns a specified `access system `_. @@ -102,18 +87,10 @@ def get(self, *, acs_system_id: str) -> AcsSystem: return AcsSystem.from_dict(res["acs_system"]) - @route_metadata( - path="/acs/systems/list", has_required_parameters=False, has_pagination=False - ) - def list( - self, - *, - connected_account_id: Optional[str] = None, - customer_key: Optional[str] = None, - search: Optional[str] = None, - ) -> List[AcsSystem]: + @route_metadata(path="/acs/systems/list", has_required_parameters=False, has_pagination=False) + def list(self, *, connected_account_id: Optional[str] = None, customer_key: Optional[str] = None, search: Optional[str] = None) -> List[AcsSystem]: """Returns a list of all `access systems `_. - + To filter the list of returned access systems by a specific connected account ID, include the ``connected_account_id`` in the request body. If you omit the ``connected_account_id`` parameter, the response includes all access systems connected to your workspace. :param connected_account_id: ID of the connected account by which you want to filter the list of access systems. @@ -136,16 +113,10 @@ def list( return [AcsSystem.from_dict(item) for item in res["acs_systems"]] - @route_metadata( - path="/acs/systems/list_compatible_credential_manager_acs_systems", - has_required_parameters=True, - has_pagination=False, - ) - def list_compatible_credential_manager_acs_systems( - self, *, acs_system_id: str - ) -> List[AcsSystem]: + @route_metadata(path="/acs/systems/list_compatible_credential_manager_acs_systems", has_required_parameters=True, has_pagination=False) + def list_compatible_credential_manager_acs_systems(self, *, acs_system_id: str) -> List[AcsSystem]: """Returns a list of all credential manager systems that are compatible with a specified `access system `_. - + Specify the access system for which you want to retrieve all compatible credential manager systems by including the corresponding ``acs_system_id`` in the request body. :param acs_system_id: ID of the access system for which you want to retrieve all compatible credential manager systems. @@ -159,28 +130,14 @@ def list_compatible_credential_manager_acs_systems( params["acs_system_id"] = acs_system_id if not params: - raise ValueError( - "At least one parameter is required for /acs/systems/list_compatible_credential_manager_acs_systems" - ) + raise ValueError("At least one parameter is required for /acs/systems/list_compatible_credential_manager_acs_systems") - res = self.client.get( - "/acs/systems/list_compatible_credential_manager_acs_systems", params=params - ) + res = self.client.get("/acs/systems/list_compatible_credential_manager_acs_systems", params=params) return [AcsSystem.from_dict(item) for item in res["acs_systems"]] - @route_metadata( - path="/acs/systems/report_devices", - has_required_parameters=True, - has_pagination=False, - ) - def report_devices( - self, - *, - acs_system_id: str, - acs_encoders: Optional[List[Dict[str, Any]]] = None, - acs_entrances: Optional[List[Dict[str, Any]]] = None, - ) -> None: + @route_metadata(path="/acs/systems/report_devices", has_required_parameters=True, has_pagination=False) + def report_devices(self, *, acs_system_id: str, acs_encoders: Optional[List[Dict[str, Any]]] = None, acs_entrances: Optional[List[Dict[str, Any]]] = None) -> None: """Reports ACS system device status including encoders and entrances. :param acs_system_id: ID of the ACS system to report resources for @@ -200,9 +157,7 @@ def report_devices( json_payload["acs_entrances"] = acs_entrances if not json_payload: - raise ValueError( - "At least one parameter is required for /acs/systems/report_devices" - ) + raise ValueError("At least one parameter is required for /acs/systems/report_devices") self.client.post("/acs/systems/report_devices", json=json_payload) diff --git a/seam/routes/acs_users.py b/seam/routes/acs_users.py index 6e905b5e..f918c800 100644 --- a/seam/routes/acs_users.py +++ b/seam/routes/acs_users.py @@ -2,15 +2,14 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import AcsUser, AcsEntrance +from ..null import Null +from ..resources import (AcsUser,AcsEntrance) class AbstractAcsUsers(abc.ABC): @abc.abstractmethod - def add_to_access_group( - self, *, acs_access_group_id: str, acs_user_id: str - ) -> None: + def add_to_access_group(self, *, acs_access_group_id: str, acs_user_id: str) -> None: """Adds a specified `access system user `_ to a specified `access group `_. :param acs_access_group_id: ID of the access group to which you want to add an access system user. @@ -21,18 +20,7 @@ def add_to_access_group( raise NotImplementedError() @abc.abstractmethod - def create( - self, - *, - acs_system_id: str, - full_name: str, - access_schedule: Optional[Dict[str, Any]] = None, - acs_access_group_ids: Optional[List[str]] = None, - email: Optional[str] = None, - email_address: Optional[str] = None, - phone_number: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> AcsUser: + def create(self, *, acs_system_id: str, full_name: str, access_schedule: Optional[Dict[str, Any]] = None, acs_access_group_ids: Optional[List[str]] = None, email: Optional[str] = None, email_address: Optional[str] = None, phone_number: Optional[str] = None, user_identity_id: Optional[str] = None) -> AcsUser: """Creates a new `access system user `_. :param acs_system_id: ID of the access system to which you want to add the new access system user. @@ -57,13 +45,7 @@ def create( raise NotImplementedError() @abc.abstractmethod - def delete( - self, - *, - acs_system_id: Optional[str] = None, - acs_user_id: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> None: + def delete(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: """Deletes a specified `access system user `_ and invalidates the access system user's `credentials `_. :param acs_system_id: ID of the access system that you want to delete. You must provide acs_system_id with user_identity_id. @@ -76,13 +58,7 @@ def delete( raise NotImplementedError() @abc.abstractmethod - def get( - self, - *, - acs_user_id: Optional[str] = None, - acs_system_id: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> AcsUser: + def get(self, *, acs_user_id: Optional[str] = None, acs_system_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> AcsUser: """Returns a specified `access system user `_. :param acs_user_id: ID of the access system user that you want to get. You can only provide acs_user_id or user_identity_id. @@ -97,18 +73,7 @@ def get( raise NotImplementedError() @abc.abstractmethod - def list( - self, - *, - acs_system_id: Optional[str] = None, - created_before: Optional[str] = None, - limit: Optional[int] = None, - page_cursor: Optional[str] = None, - search: Optional[str] = None, - user_identity_email_address: Optional[str] = None, - user_identity_id: Optional[str] = None, - user_identity_phone_number: Optional[str] = None, - ) -> List[AcsUser]: + def list(self, *, acs_system_id: Optional[str] = None, created_before: Optional[str] = None, limit: Optional[int] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, user_identity_email_address: Optional[str] = None, user_identity_id: Optional[str] = None, user_identity_phone_number: Optional[str] = None) -> List[AcsUser]: """Returns a list of all `access system users `_. :param acs_system_id: ID of the ``acs_system`` for which you want to retrieve all access system users. @@ -131,13 +96,7 @@ def list( raise NotImplementedError() @abc.abstractmethod - def list_accessible_entrances( - self, - *, - acs_system_id: Optional[str] = None, - acs_user_id: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> List[AcsEntrance]: + def list_accessible_entrances(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> List[AcsEntrance]: """Lists the `entrances `_ to which a specified `access system user `_ has access. :param acs_system_id: ID of the access system for which you want to list accessible entrances. You can only provide acs_system_id with user_identity_id. @@ -152,13 +111,7 @@ def list_accessible_entrances( raise NotImplementedError() @abc.abstractmethod - def remove_from_access_group( - self, - *, - acs_access_group_id: str, - acs_user_id: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> None: + def remove_from_access_group(self, *, acs_access_group_id: str, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: """Removes a specified `access system user `_ from a specified `access group `_. :param acs_access_group_id: ID of the access group from which you want to remove an access system user. @@ -171,13 +124,7 @@ def remove_from_access_group( raise NotImplementedError() @abc.abstractmethod - def revoke_access_to_all_entrances( - self, - *, - acs_system_id: Optional[str] = None, - acs_user_id: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> None: + def revoke_access_to_all_entrances(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: """Revokes access to all `entrances `_ for a specified `access system user `_. :param acs_system_id: ID of the access system for which you want to revoke access. You can only provide acs_system_id with user_identity_id. @@ -190,13 +137,7 @@ def revoke_access_to_all_entrances( raise NotImplementedError() @abc.abstractmethod - def suspend( - self, - *, - acs_system_id: Optional[str] = None, - acs_user_id: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> None: + def suspend(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: """`Suspends `_ a specified `access system user `_. Suspending an access system user revokes their access temporarily. To restore an access system user's access, you can `unsuspend `_ them. :param acs_system_id: ID of the access system that you want to suspend. You can only provide acs_user_id or the combination of acs_system_id and user_identity_id. @@ -209,13 +150,7 @@ def suspend( raise NotImplementedError() @abc.abstractmethod - def unsuspend( - self, - *, - acs_system_id: Optional[str] = None, - acs_user_id: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> None: + def unsuspend(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: """`Unsuspends `_ a specified suspended `access system user `_. While `suspending an access system user `_ revokes their access temporarily, unsuspending the access system user restores their access. :param acs_system_id: ID of the access system of the user that you want to unsuspend. You can only provide acs_system_id with user_identity_id. @@ -228,19 +163,7 @@ def unsuspend( raise NotImplementedError() @abc.abstractmethod - def update( - self, - *, - access_schedule: Optional[Dict[str, Any]] = None, - acs_system_id: Optional[str] = None, - acs_user_id: Optional[str] = None, - email: Optional[str] = None, - email_address: Optional[str] = None, - full_name: Optional[str] = None, - hid_acs_system_id: Optional[str] = None, - phone_number: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> None: + def update(self, *, access_schedule: Optional[Union[Dict[str, Any], Null]] = None, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, email: Optional[str] = None, email_address: Optional[str] = None, full_name: Optional[str] = None, hid_acs_system_id: Optional[str] = None, phone_number: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: """Updates the properties of a specified `access system user `_. :param access_schedule: ``starts_at`` and ``ends_at`` timestamps for the access system user's access. If you specify an ``access_schedule``, you may include both ``starts_at`` and ``ends_at``. If you omit ``starts_at``, it defaults to the current time. ``ends_at`` is optional and must be a time in the future and after ``starts_at``. @@ -270,14 +193,8 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/acs/users/add_to_access_group", - has_required_parameters=True, - has_pagination=False, - ) - def add_to_access_group( - self, *, acs_access_group_id: str, acs_user_id: str - ) -> None: + @route_metadata(path="/acs/users/add_to_access_group", has_required_parameters=True, has_pagination=False) + def add_to_access_group(self, *, acs_access_group_id: str, acs_user_id: str) -> None: """Adds a specified `access system user `_ to a specified `access group `_. :param acs_access_group_id: ID of the access group to which you want to add an access system user. @@ -293,29 +210,14 @@ def add_to_access_group( json_payload["acs_user_id"] = acs_user_id if not json_payload: - raise ValueError( - "At least one parameter is required for /acs/users/add_to_access_group" - ) + raise ValueError("At least one parameter is required for /acs/users/add_to_access_group") self.client.put("/acs/users/add_to_access_group", json=json_payload) return None - @route_metadata( - path="/acs/users/create", has_required_parameters=True, has_pagination=False - ) - def create( - self, - *, - acs_system_id: str, - full_name: str, - access_schedule: Optional[Dict[str, Any]] = None, - acs_access_group_ids: Optional[List[str]] = None, - email: Optional[str] = None, - email_address: Optional[str] = None, - phone_number: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> AcsUser: + @route_metadata(path="/acs/users/create", has_required_parameters=True, has_pagination=False) + def create(self, *, acs_system_id: str, full_name: str, access_schedule: Optional[Dict[str, Any]] = None, acs_access_group_ids: Optional[List[str]] = None, email: Optional[str] = None, email_address: Optional[str] = None, phone_number: Optional[str] = None, user_identity_id: Optional[str] = None) -> AcsUser: """Creates a new `access system user `_. :param acs_system_id: ID of the access system to which you want to add the new access system user. @@ -363,16 +265,8 @@ def create( return AcsUser.from_dict(res["acs_user"]) - @route_metadata( - path="/acs/users/delete", has_required_parameters=True, has_pagination=False - ) - def delete( - self, - *, - acs_system_id: Optional[str] = None, - acs_user_id: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> None: + @route_metadata(path="/acs/users/delete", has_required_parameters=True, has_pagination=False) + def delete(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: """Deletes a specified `access system user `_ and invalidates the access system user's `credentials `_. :param acs_system_id: ID of the access system that you want to delete. You must provide acs_system_id with user_identity_id. @@ -398,16 +292,8 @@ def delete( return None - @route_metadata( - path="/acs/users/get", has_required_parameters=True, has_pagination=False - ) - def get( - self, - *, - acs_user_id: Optional[str] = None, - acs_system_id: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> AcsUser: + @route_metadata(path="/acs/users/get", has_required_parameters=True, has_pagination=False) + def get(self, *, acs_user_id: Optional[str] = None, acs_system_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> AcsUser: """Returns a specified `access system user `_. :param acs_user_id: ID of the access system user that you want to get. You can only provide acs_user_id or user_identity_id. @@ -435,21 +321,8 @@ def get( return AcsUser.from_dict(res["acs_user"]) - @route_metadata( - path="/acs/users/list", has_required_parameters=False, has_pagination=True - ) - def list( - self, - *, - acs_system_id: Optional[str] = None, - created_before: Optional[str] = None, - limit: Optional[int] = None, - page_cursor: Optional[str] = None, - search: Optional[str] = None, - user_identity_email_address: Optional[str] = None, - user_identity_id: Optional[str] = None, - user_identity_phone_number: Optional[str] = None, - ) -> List[AcsUser]: + @route_metadata(path="/acs/users/list", has_required_parameters=False, has_pagination=True) + def list(self, *, acs_system_id: Optional[str] = None, created_before: Optional[str] = None, limit: Optional[int] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, user_identity_email_address: Optional[str] = None, user_identity_id: Optional[str] = None, user_identity_phone_number: Optional[str] = None) -> List[AcsUser]: """Returns a list of all `access system users `_. :param acs_system_id: ID of the ``acs_system`` for which you want to retrieve all access system users. @@ -492,18 +365,8 @@ def list( return [AcsUser.from_dict(item) for item in res["acs_users"]] - @route_metadata( - path="/acs/users/list_accessible_entrances", - has_required_parameters=True, - has_pagination=False, - ) - def list_accessible_entrances( - self, - *, - acs_system_id: Optional[str] = None, - acs_user_id: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> List[AcsEntrance]: + @route_metadata(path="/acs/users/list_accessible_entrances", has_required_parameters=True, has_pagination=False) + def list_accessible_entrances(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> List[AcsEntrance]: """Lists the `entrances `_ to which a specified `access system user `_ has access. :param acs_system_id: ID of the access system for which you want to list accessible entrances. You can only provide acs_system_id with user_identity_id. @@ -525,26 +388,14 @@ def list_accessible_entrances( params["user_identity_id"] = user_identity_id if not params: - raise ValueError( - "At least one parameter is required for /acs/users/list_accessible_entrances" - ) + raise ValueError("At least one parameter is required for /acs/users/list_accessible_entrances") res = self.client.get("/acs/users/list_accessible_entrances", params=params) return [AcsEntrance.from_dict(item) for item in res["acs_entrances"]] - @route_metadata( - path="/acs/users/remove_from_access_group", - has_required_parameters=True, - has_pagination=False, - ) - def remove_from_access_group( - self, - *, - acs_access_group_id: str, - acs_user_id: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> None: + @route_metadata(path="/acs/users/remove_from_access_group", has_required_parameters=True, has_pagination=False) + def remove_from_access_group(self, *, acs_access_group_id: str, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: """Removes a specified `access system user `_ from a specified `access group `_. :param acs_access_group_id: ID of the access group from which you want to remove an access system user. @@ -564,26 +415,14 @@ def remove_from_access_group( params["user_identity_id"] = user_identity_id if not params: - raise ValueError( - "At least one parameter is required for /acs/users/remove_from_access_group" - ) + raise ValueError("At least one parameter is required for /acs/users/remove_from_access_group") self.client.delete("/acs/users/remove_from_access_group", params=params) return None - @route_metadata( - path="/acs/users/revoke_access_to_all_entrances", - has_required_parameters=True, - has_pagination=False, - ) - def revoke_access_to_all_entrances( - self, - *, - acs_system_id: Optional[str] = None, - acs_user_id: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> None: + @route_metadata(path="/acs/users/revoke_access_to_all_entrances", has_required_parameters=True, has_pagination=False) + def revoke_access_to_all_entrances(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: """Revokes access to all `entrances `_ for a specified `access system user `_. :param acs_system_id: ID of the access system for which you want to revoke access. You can only provide acs_system_id with user_identity_id. @@ -603,24 +442,14 @@ def revoke_access_to_all_entrances( json_payload["user_identity_id"] = user_identity_id if not json_payload: - raise ValueError( - "At least one parameter is required for /acs/users/revoke_access_to_all_entrances" - ) + raise ValueError("At least one parameter is required for /acs/users/revoke_access_to_all_entrances") self.client.post("/acs/users/revoke_access_to_all_entrances", json=json_payload) return None - @route_metadata( - path="/acs/users/suspend", has_required_parameters=True, has_pagination=False - ) - def suspend( - self, - *, - acs_system_id: Optional[str] = None, - acs_user_id: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> None: + @route_metadata(path="/acs/users/suspend", has_required_parameters=True, has_pagination=False) + def suspend(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: """`Suspends `_ a specified `access system user `_. Suspending an access system user revokes their access temporarily. To restore an access system user's access, you can `unsuspend `_ them. :param acs_system_id: ID of the access system that you want to suspend. You can only provide acs_user_id or the combination of acs_system_id and user_identity_id. @@ -640,24 +469,14 @@ def suspend( json_payload["user_identity_id"] = user_identity_id if not json_payload: - raise ValueError( - "At least one parameter is required for /acs/users/suspend" - ) + raise ValueError("At least one parameter is required for /acs/users/suspend") self.client.post("/acs/users/suspend", json=json_payload) return None - @route_metadata( - path="/acs/users/unsuspend", has_required_parameters=True, has_pagination=False - ) - def unsuspend( - self, - *, - acs_system_id: Optional[str] = None, - acs_user_id: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> None: + @route_metadata(path="/acs/users/unsuspend", has_required_parameters=True, has_pagination=False) + def unsuspend(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: """`Unsuspends `_ a specified suspended `access system user `_. While `suspending an access system user `_ revokes their access temporarily, unsuspending the access system user restores their access. :param acs_system_id: ID of the access system of the user that you want to unsuspend. You can only provide acs_system_id with user_identity_id. @@ -677,30 +496,14 @@ def unsuspend( json_payload["user_identity_id"] = user_identity_id if not json_payload: - raise ValueError( - "At least one parameter is required for /acs/users/unsuspend" - ) + raise ValueError("At least one parameter is required for /acs/users/unsuspend") self.client.post("/acs/users/unsuspend", json=json_payload) return None - @route_metadata( - path="/acs/users/update", has_required_parameters=True, has_pagination=False - ) - def update( - self, - *, - access_schedule: Optional[Dict[str, Any]] = None, - acs_system_id: Optional[str] = None, - acs_user_id: Optional[str] = None, - email: Optional[str] = None, - email_address: Optional[str] = None, - full_name: Optional[str] = None, - hid_acs_system_id: Optional[str] = None, - phone_number: Optional[str] = None, - user_identity_id: Optional[str] = None, - ) -> None: + @route_metadata(path="/acs/users/update", has_required_parameters=True, has_pagination=False) + def update(self, *, access_schedule: Optional[Union[Dict[str, Any], Null]] = None, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, email: Optional[str] = None, email_address: Optional[str] = None, full_name: Optional[str] = None, hid_acs_system_id: Optional[str] = None, phone_number: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: """Updates the properties of a specified `access system user `_. :param access_schedule: ``starts_at`` and ``ends_at`` timestamps for the access system user's access. If you specify an ``access_schedule``, you may include both ``starts_at`` and ``ends_at``. If you omit ``starts_at``, it defaults to the current time. ``ends_at`` is optional and must be a time in the future and after ``starts_at``. diff --git a/seam/routes/action_attempts.py b/seam/routes/action_attempts.py index 977ad01b..f01698ee 100644 --- a/seam/routes/action_attempts.py +++ b/seam/routes/action_attempts.py @@ -2,19 +2,15 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import ActionAttempt +from ..null import Null +from ..resources import (ActionAttempt) from ..modules.action_attempts import resolve_action_attempt class AbstractActionAttempts(abc.ABC): @abc.abstractmethod - def get( - self, - *, - action_attempt_id: str, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + def get(self, *, action_attempt_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Returns a specified `action attempt `_. :param action_attempt_id: ID of the action attempt that you want to get. @@ -27,14 +23,7 @@ def get( raise NotImplementedError() @abc.abstractmethod - def list( - self, - *, - action_attempt_ids: Optional[List[str]] = None, - device_id: Optional[str] = None, - limit: Optional[int] = None, - page_cursor: Optional[str] = None, - ) -> List[ActionAttempt]: + def list(self, *, action_attempt_ids: Optional[List[str]] = None, device_id: Optional[str] = None, limit: Optional[int] = None, page_cursor: Optional[Union[str, Null]] = None) -> List[ActionAttempt]: """Returns a list of the `action attempts `_ that you specify as an array of ``action_attempt_id``s. :param action_attempt_ids: IDs of the action attempts that you want to retrieve. @@ -54,15 +43,8 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/action_attempts/get", has_required_parameters=True, has_pagination=False - ) - def get( - self, - *, - action_attempt_id: str, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + @route_metadata(path="/action_attempts/get", has_required_parameters=True, has_pagination=False) + def get(self, *, action_attempt_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Returns a specified `action attempt `_. :param action_attempt_id: ID of the action attempt that you want to get. @@ -78,9 +60,7 @@ def get( params["action_attempt_id"] = action_attempt_id if not params: - raise ValueError( - "At least one parameter is required for /action_attempts/get" - ) + raise ValueError("At least one parameter is required for /action_attempts/get") res = self.client.get("/action_attempts/get", params=params) @@ -93,20 +73,11 @@ def get( return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt, + wait_for_action_attempt=wait_for_action_attempt ) - @route_metadata( - path="/action_attempts/list", has_required_parameters=False, has_pagination=True - ) - def list( - self, - *, - action_attempt_ids: Optional[List[str]] = None, - device_id: Optional[str] = None, - limit: Optional[int] = None, - page_cursor: Optional[str] = None, - ) -> List[ActionAttempt]: + @route_metadata(path="/action_attempts/list", has_required_parameters=False, has_pagination=True) + def list(self, *, action_attempt_ids: Optional[List[str]] = None, device_id: Optional[str] = None, limit: Optional[int] = None, page_cursor: Optional[Union[str, Null]] = None) -> List[ActionAttempt]: """Returns a list of the `action attempts `_ that you specify as an array of ``action_attempt_id``s. :param action_attempt_ids: IDs of the action attempts that you want to retrieve. diff --git a/seam/routes/client_sessions.py b/seam/routes/client_sessions.py index 62da3156..d34ef84d 100644 --- a/seam/routes/client_sessions.py +++ b/seam/routes/client_sessions.py @@ -2,24 +2,14 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import ClientSession +from ..null import Null +from ..resources import (ClientSession) class AbstractClientSessions(abc.ABC): @abc.abstractmethod - def create( - self, - *, - connect_webview_ids: Optional[List[str]] = None, - connected_account_ids: Optional[List[str]] = None, - customer_id: Optional[str] = None, - customer_key: Optional[str] = None, - expires_at: Optional[str] = None, - user_identifier_key: Optional[str] = None, - user_identity_id: Optional[str] = None, - user_identity_ids: Optional[List[str]] = None, - ) -> ClientSession: + def create(self, *, connect_webview_ids: Optional[List[str]] = None, connected_account_ids: Optional[List[str]] = None, customer_id: Optional[str] = None, customer_key: Optional[str] = None, expires_at: Optional[str] = None, user_identifier_key: Optional[str] = None, user_identity_id: Optional[str] = None, user_identity_ids: Optional[List[str]] = None) -> ClientSession: """Creates a new `client session `_. :param connect_webview_ids: IDs of the `Connect Webviews `_ for which you want to create a client session. @@ -51,12 +41,7 @@ def delete(self, *, client_session_id: str) -> None: raise NotImplementedError() @abc.abstractmethod - def get( - self, - *, - client_session_id: Optional[str] = None, - user_identifier_key: Optional[str] = None, - ) -> ClientSession: + def get(self, *, client_session_id: Optional[str] = None, user_identifier_key: Optional[str] = None) -> ClientSession: """Returns a specified `client session `_. :param client_session_id: ID of the client session that you want to get. @@ -67,16 +52,7 @@ def get( raise NotImplementedError() @abc.abstractmethod - def get_or_create( - self, - *, - connect_webview_ids: Optional[List[str]] = None, - connected_account_ids: Optional[List[str]] = None, - expires_at: Optional[str] = None, - user_identifier_key: Optional[str] = None, - user_identity_id: Optional[str] = None, - user_identity_ids: Optional[List[str]] = None, - ) -> ClientSession: + def get_or_create(self, *, connect_webview_ids: Optional[List[str]] = None, connected_account_ids: Optional[List[str]] = None, expires_at: Optional[str] = None, user_identifier_key: Optional[str] = None, user_identity_id: Optional[str] = None, user_identity_ids: Optional[List[str]] = None) -> ClientSession: """Returns a `client session `_ with specific characteristics or creates a new client session with these characteristics if it does not yet exist. :param connect_webview_ids: IDs of the `Connect Webviews `_ that you want to associate with the client session (or that are already associated with the existing client session). @@ -95,16 +71,7 @@ def get_or_create( raise NotImplementedError() @abc.abstractmethod - def grant_access( - self, - *, - client_session_id: Optional[str] = None, - connect_webview_ids: Optional[List[str]] = None, - connected_account_ids: Optional[List[str]] = None, - user_identifier_key: Optional[str] = None, - user_identity_id: Optional[str] = None, - user_identity_ids: Optional[List[str]] = None, - ) -> None: + def grant_access(self, *, client_session_id: Optional[str] = None, connect_webview_ids: Optional[List[str]] = None, connected_account_ids: Optional[List[str]] = None, user_identifier_key: Optional[str] = None, user_identity_id: Optional[str] = None, user_identity_ids: Optional[List[str]] = None) -> None: """Grants a `client session `_ access to one or more resources, such as `Connect Webviews `_, `user identities `_, and so on. :param client_session_id: ID of the client session to which you want to grant access to resources. @@ -123,15 +90,7 @@ def grant_access( raise NotImplementedError() @abc.abstractmethod - def list( - self, - *, - client_session_id: Optional[str] = None, - connect_webview_id: Optional[str] = None, - user_identifier_key: Optional[str] = None, - user_identity_id: Optional[str] = None, - without_user_identifier_key: Optional[bool] = None, - ) -> List[ClientSession]: + def list(self, *, client_session_id: Optional[str] = None, connect_webview_id: Optional[str] = None, user_identifier_key: Optional[str] = None, user_identity_id: Optional[str] = None, without_user_identifier_key: Optional[bool] = None) -> List[ClientSession]: """Returns a list of all `client sessions `_. :param client_session_id: ID of the client session that you want to retrieve. @@ -150,7 +109,7 @@ def list( @abc.abstractmethod def revoke(self, *, client_session_id: str) -> None: """Revokes a `client session `_. - + Note that `deleting a client session `_ is a separate action. :param client_session_id: ID of the client session that you want to revoke. @@ -164,23 +123,8 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/client_sessions/create", - has_required_parameters=False, - has_pagination=False, - ) - def create( - self, - *, - connect_webview_ids: Optional[List[str]] = None, - connected_account_ids: Optional[List[str]] = None, - customer_id: Optional[str] = None, - customer_key: Optional[str] = None, - expires_at: Optional[str] = None, - user_identifier_key: Optional[str] = None, - user_identity_id: Optional[str] = None, - user_identity_ids: Optional[List[str]] = None, - ) -> ClientSession: + @route_metadata(path="/client_sessions/create", has_required_parameters=False, has_pagination=False) + def create(self, *, connect_webview_ids: Optional[List[str]] = None, connected_account_ids: Optional[List[str]] = None, customer_id: Optional[str] = None, customer_key: Optional[str] = None, expires_at: Optional[str] = None, user_identifier_key: Optional[str] = None, user_identity_id: Optional[str] = None, user_identity_ids: Optional[List[str]] = None) -> ClientSession: """Creates a new `client session `_. :param connect_webview_ids: IDs of the `Connect Webviews `_ for which you want to create a client session. @@ -223,11 +167,7 @@ def create( return ClientSession.from_dict(res["client_session"]) - @route_metadata( - path="/client_sessions/delete", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/client_sessions/delete", has_required_parameters=True, has_pagination=False) def delete(self, *, client_session_id: str) -> None: """Deletes a `client session `_. @@ -240,23 +180,14 @@ def delete(self, *, client_session_id: str) -> None: params["client_session_id"] = client_session_id if not params: - raise ValueError( - "At least one parameter is required for /client_sessions/delete" - ) + raise ValueError("At least one parameter is required for /client_sessions/delete") self.client.delete("/client_sessions/delete", params=params) return None - @route_metadata( - path="/client_sessions/get", has_required_parameters=False, has_pagination=False - ) - def get( - self, - *, - client_session_id: Optional[str] = None, - user_identifier_key: Optional[str] = None, - ) -> ClientSession: + @route_metadata(path="/client_sessions/get", has_required_parameters=False, has_pagination=False) + def get(self, *, client_session_id: Optional[str] = None, user_identifier_key: Optional[str] = None) -> ClientSession: """Returns a specified `client session `_. :param client_session_id: ID of the client session that you want to get. @@ -275,21 +206,8 @@ def get( return ClientSession.from_dict(res["client_session"]) - @route_metadata( - path="/client_sessions/get_or_create", - has_required_parameters=False, - has_pagination=False, - ) - def get_or_create( - self, - *, - connect_webview_ids: Optional[List[str]] = None, - connected_account_ids: Optional[List[str]] = None, - expires_at: Optional[str] = None, - user_identifier_key: Optional[str] = None, - user_identity_id: Optional[str] = None, - user_identity_ids: Optional[List[str]] = None, - ) -> ClientSession: + @route_metadata(path="/client_sessions/get_or_create", has_required_parameters=False, has_pagination=False) + def get_or_create(self, *, connect_webview_ids: Optional[List[str]] = None, connected_account_ids: Optional[List[str]] = None, expires_at: Optional[str] = None, user_identifier_key: Optional[str] = None, user_identity_id: Optional[str] = None, user_identity_ids: Optional[List[str]] = None) -> ClientSession: """Returns a `client session `_ with specific characteristics or creates a new client session with these characteristics if it does not yet exist. :param connect_webview_ids: IDs of the `Connect Webviews `_ that you want to associate with the client session (or that are already associated with the existing client session). @@ -324,21 +242,8 @@ def get_or_create( return ClientSession.from_dict(res["client_session"]) - @route_metadata( - path="/client_sessions/grant_access", - has_required_parameters=True, - has_pagination=False, - ) - def grant_access( - self, - *, - client_session_id: Optional[str] = None, - connect_webview_ids: Optional[List[str]] = None, - connected_account_ids: Optional[List[str]] = None, - user_identifier_key: Optional[str] = None, - user_identity_id: Optional[str] = None, - user_identity_ids: Optional[List[str]] = None, - ) -> None: + @route_metadata(path="/client_sessions/grant_access", has_required_parameters=True, has_pagination=False) + def grant_access(self, *, client_session_id: Optional[str] = None, connect_webview_ids: Optional[List[str]] = None, connected_account_ids: Optional[List[str]] = None, user_identifier_key: Optional[str] = None, user_identity_id: Optional[str] = None, user_identity_ids: Optional[List[str]] = None) -> None: """Grants a `client session `_ access to one or more resources, such as `Connect Webviews `_, `user identities `_, and so on. :param client_session_id: ID of the client session to which you want to grant access to resources. @@ -370,28 +275,14 @@ def grant_access( json_payload["user_identity_ids"] = user_identity_ids if not json_payload: - raise ValueError( - "At least one parameter is required for /client_sessions/grant_access" - ) + raise ValueError("At least one parameter is required for /client_sessions/grant_access") self.client.patch("/client_sessions/grant_access", json=json_payload) return None - @route_metadata( - path="/client_sessions/list", - has_required_parameters=False, - has_pagination=False, - ) - def list( - self, - *, - client_session_id: Optional[str] = None, - connect_webview_id: Optional[str] = None, - user_identifier_key: Optional[str] = None, - user_identity_id: Optional[str] = None, - without_user_identifier_key: Optional[bool] = None, - ) -> List[ClientSession]: + @route_metadata(path="/client_sessions/list", has_required_parameters=False, has_pagination=False) + def list(self, *, client_session_id: Optional[str] = None, connect_webview_id: Optional[str] = None, user_identifier_key: Optional[str] = None, user_identity_id: Optional[str] = None, without_user_identifier_key: Optional[bool] = None) -> List[ClientSession]: """Returns a list of all `client sessions `_. :param client_session_id: ID of the client session that you want to retrieve. @@ -422,14 +313,10 @@ def list( return [ClientSession.from_dict(item) for item in res["client_sessions"]] - @route_metadata( - path="/client_sessions/revoke", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/client_sessions/revoke", has_required_parameters=True, has_pagination=False) def revoke(self, *, client_session_id: str) -> None: """Revokes a `client session `_. - + Note that `deleting a client session `_ is a separate action. :param client_session_id: ID of the client session that you want to revoke. @@ -441,9 +328,7 @@ def revoke(self, *, client_session_id: str) -> None: json_payload["client_session_id"] = client_session_id if not json_payload: - raise ValueError( - "At least one parameter is required for /client_sessions/revoke" - ) + raise ValueError("At least one parameter is required for /client_sessions/revoke") self.client.post("/client_sessions/revoke", json=json_payload) diff --git a/seam/routes/connect_webviews.py b/seam/routes/connect_webviews.py index 2310a432..37b3ef22 100644 --- a/seam/routes/connect_webviews.py +++ b/seam/routes/connect_webviews.py @@ -2,32 +2,20 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import ConnectWebview +from ..null import Null +from ..resources import (ConnectWebview) class AbstractConnectWebviews(abc.ABC): @abc.abstractmethod - def create( - self, - *, - accepted_capabilities: Optional[List[str]] = None, - accepted_providers: Optional[List[str]] = None, - automatically_manage_new_devices: Optional[bool] = None, - custom_metadata: Optional[Dict[str, Any]] = None, - custom_redirect_failure_url: Optional[str] = None, - custom_redirect_url: Optional[str] = None, - customer_key: Optional[str] = None, - excluded_providers: Optional[List[str]] = None, - provider_category: Optional[str] = None, - wait_for_device_creation: Optional[bool] = None, - ) -> ConnectWebview: + def create(self, *, accepted_capabilities: Optional[List[str]] = None, accepted_providers: Optional[List[str]] = None, automatically_manage_new_devices: Optional[bool] = None, custom_metadata: Optional[Dict[str, Any]] = None, custom_redirect_failure_url: Optional[str] = None, custom_redirect_url: Optional[str] = None, customer_key: Optional[str] = None, excluded_providers: Optional[List[str]] = None, provider_category: Optional[str] = None, wait_for_device_creation: Optional[bool] = None) -> ConnectWebview: """Creates a new `Connect Webview `_. - + To enable a user to connect their devices or systems to Seam, they must sign in to their device or system account. To enable a user to sign in, you create a ``connect_webview``. After creating the Connect Webview, you receive a URL that you can use to display the visual component of this Connect Webview for your user. You can open an iframe or new window to display the Connect Webview. - + You should make a new ``connect_webview`` for each unique login request. Each ``connect_webview`` tracks the user that signed in with it. You receive an error if you reuse a Connect Webview for the same user twice or if you use the same Connect Webview for multiple users. - + See also: `Connect Webview Process `_. :param accepted_capabilities: List of accepted device capabilities that restrict the types of devices that can be connected through the Connect Webview. If not provided, defaults will be determined based on the accepted providers. @@ -56,7 +44,7 @@ def create( @abc.abstractmethod def delete(self, *, connect_webview_id: str) -> None: """Deletes a `Connect Webview `_. - + You do not need to delete a Connect Webview once a user completes it. Instead, you can simply ignore completed Connect Webviews. :param connect_webview_id: ID of the Connect Webview that you want to delete. @@ -67,7 +55,7 @@ def delete(self, *, connect_webview_id: str) -> None: @abc.abstractmethod def get(self, *, connect_webview_id: str) -> ConnectWebview: """Returns a specified `Connect Webview `_. - + Unless you're using a ``custom_redirect_url``, you should poll a newly-created ``connect_webview`` to find out if the user has signed in or to get details about what devices they've connected. :param connect_webview_id: ID of the Connect Webview that you want to get. @@ -78,16 +66,7 @@ def get(self, *, connect_webview_id: str) -> ConnectWebview: raise NotImplementedError() @abc.abstractmethod - def list( - self, - *, - custom_metadata_has: Optional[Dict[str, Any]] = None, - customer_key: Optional[str] = None, - limit: Optional[float] = None, - page_cursor: Optional[str] = None, - search: Optional[str] = None, - user_identifier_key: Optional[str] = None, - ) -> List[ConnectWebview]: + def list(self, *, custom_metadata_has: Optional[Dict[str, Any]] = None, customer_key: Optional[str] = None, limit: Optional[float] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, user_identifier_key: Optional[str] = None) -> List[ConnectWebview]: """Returns a list of all `Connect Webviews `_. :param custom_metadata_has: Custom metadata pairs by which you want to `filter Connect Webviews `_. Returns Connect Webviews with ``custom_metadata`` that contains all of the provided key:value pairs. @@ -111,31 +90,14 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/connect_webviews/create", - has_required_parameters=False, - has_pagination=False, - ) - def create( - self, - *, - accepted_capabilities: Optional[List[str]] = None, - accepted_providers: Optional[List[str]] = None, - automatically_manage_new_devices: Optional[bool] = None, - custom_metadata: Optional[Dict[str, Any]] = None, - custom_redirect_failure_url: Optional[str] = None, - custom_redirect_url: Optional[str] = None, - customer_key: Optional[str] = None, - excluded_providers: Optional[List[str]] = None, - provider_category: Optional[str] = None, - wait_for_device_creation: Optional[bool] = None, - ) -> ConnectWebview: + @route_metadata(path="/connect_webviews/create", has_required_parameters=False, has_pagination=False) + def create(self, *, accepted_capabilities: Optional[List[str]] = None, accepted_providers: Optional[List[str]] = None, automatically_manage_new_devices: Optional[bool] = None, custom_metadata: Optional[Dict[str, Any]] = None, custom_redirect_failure_url: Optional[str] = None, custom_redirect_url: Optional[str] = None, customer_key: Optional[str] = None, excluded_providers: Optional[List[str]] = None, provider_category: Optional[str] = None, wait_for_device_creation: Optional[bool] = None) -> ConnectWebview: """Creates a new `Connect Webview `_. - + To enable a user to connect their devices or systems to Seam, they must sign in to their device or system account. To enable a user to sign in, you create a ``connect_webview``. After creating the Connect Webview, you receive a URL that you can use to display the visual component of this Connect Webview for your user. You can open an iframe or new window to display the Connect Webview. - + You should make a new ``connect_webview`` for each unique login request. Each ``connect_webview`` tracks the user that signed in with it. You receive an error if you reuse a Connect Webview for the same user twice or if you use the same Connect Webview for multiple users. - + See also: `Connect Webview Process `_. :param accepted_capabilities: List of accepted device capabilities that restrict the types of devices that can be connected through the Connect Webview. If not provided, defaults will be determined based on the accepted providers. @@ -166,9 +128,7 @@ def create( if accepted_providers is not None: json_payload["accepted_providers"] = accepted_providers if automatically_manage_new_devices is not None: - json_payload["automatically_manage_new_devices"] = ( - automatically_manage_new_devices - ) + json_payload["automatically_manage_new_devices"] = automatically_manage_new_devices if custom_metadata is not None: json_payload["custom_metadata"] = custom_metadata if custom_redirect_failure_url is not None: @@ -188,14 +148,10 @@ def create( return ConnectWebview.from_dict(res["connect_webview"]) - @route_metadata( - path="/connect_webviews/delete", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/connect_webviews/delete", has_required_parameters=True, has_pagination=False) def delete(self, *, connect_webview_id: str) -> None: """Deletes a `Connect Webview `_. - + You do not need to delete a Connect Webview once a user completes it. Instead, you can simply ignore completed Connect Webviews. :param connect_webview_id: ID of the Connect Webview that you want to delete. @@ -207,20 +163,16 @@ def delete(self, *, connect_webview_id: str) -> None: params["connect_webview_id"] = connect_webview_id if not params: - raise ValueError( - "At least one parameter is required for /connect_webviews/delete" - ) + raise ValueError("At least one parameter is required for /connect_webviews/delete") self.client.delete("/connect_webviews/delete", params=params) return None - @route_metadata( - path="/connect_webviews/get", has_required_parameters=True, has_pagination=False - ) + @route_metadata(path="/connect_webviews/get", has_required_parameters=True, has_pagination=False) def get(self, *, connect_webview_id: str) -> ConnectWebview: """Returns a specified `Connect Webview `_. - + Unless you're using a ``custom_redirect_url``, you should poll a newly-created ``connect_webview`` to find out if the user has signed in or to get details about what devices they've connected. :param connect_webview_id: ID of the Connect Webview that you want to get. @@ -234,29 +186,14 @@ def get(self, *, connect_webview_id: str) -> ConnectWebview: params["connect_webview_id"] = connect_webview_id if not params: - raise ValueError( - "At least one parameter is required for /connect_webviews/get" - ) + raise ValueError("At least one parameter is required for /connect_webviews/get") res = self.client.get("/connect_webviews/get", params=params) return ConnectWebview.from_dict(res["connect_webview"]) - @route_metadata( - path="/connect_webviews/list", - has_required_parameters=False, - has_pagination=True, - ) - def list( - self, - *, - custom_metadata_has: Optional[Dict[str, Any]] = None, - customer_key: Optional[str] = None, - limit: Optional[float] = None, - page_cursor: Optional[str] = None, - search: Optional[str] = None, - user_identifier_key: Optional[str] = None, - ) -> List[ConnectWebview]: + @route_metadata(path="/connect_webviews/list", has_required_parameters=False, has_pagination=True) + def list(self, *, custom_metadata_has: Optional[Dict[str, Any]] = None, customer_key: Optional[str] = None, limit: Optional[float] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, user_identifier_key: Optional[str] = None) -> List[ConnectWebview]: """Returns a list of all `Connect Webviews `_. :param custom_metadata_has: Custom metadata pairs by which you want to `filter Connect Webviews `_. Returns Connect Webviews with ``custom_metadata`` that contains all of the provided key:value pairs. diff --git a/seam/routes/connected_accounts.py b/seam/routes/connected_accounts.py index 1d8a6a6f..9657fa91 100644 --- a/seam/routes/connected_accounts.py +++ b/seam/routes/connected_accounts.py @@ -2,11 +2,9 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import ConnectedAccount -from .connected_accounts_simulate import ( - AbstractConnectedAccountsSimulate, - ConnectedAccountsSimulate, -) +from ..null import Null +from ..resources import (ConnectedAccount) +from .connected_accounts_simulate import AbstractConnectedAccountsSimulate, ConnectedAccountsSimulate class AbstractConnectedAccounts(abc.ABC): @@ -19,9 +17,9 @@ def simulate(self) -> AbstractConnectedAccountsSimulate: @abc.abstractmethod def delete(self, *, connected_account_id: str) -> None: """Deletes a specified `connected account `_. - + Deleting a connected account triggers a ``connected_account.deleted`` event and removes the connected account and all data associated with the connected account from Seam, including devices, events, access codes, and so on. For every deleted resource, Seam sends a corresponding deleted event, but the resource is not deleted from the provider. - + For example, if you delete a connected account with a device that has an access code, Seam sends a ``connected_account.deleted`` event, a ``device.deleted`` event, and an ``access_code.deleted`` event, but Seam does not remove the access code from the device. :param connected_account_id: ID of the connected account that you want to delete. @@ -30,9 +28,7 @@ def delete(self, *, connected_account_id: str) -> None: raise NotImplementedError() @abc.abstractmethod - def get( - self, *, connected_account_id: Optional[str] = None, email: Optional[str] = None - ) -> ConnectedAccount: + def get(self, *, connected_account_id: Optional[str] = None, email: Optional[str] = None) -> ConnectedAccount: """Returns a specified `connected account `_. :param connected_account_id: ID of the connected account that you want to get. @@ -45,17 +41,7 @@ def get( raise NotImplementedError() @abc.abstractmethod - def list( - self, - *, - custom_metadata_has: Optional[Dict[str, Any]] = None, - customer_key: Optional[str] = None, - limit: Optional[int] = None, - page_cursor: Optional[str] = None, - search: Optional[str] = None, - space_id: Optional[str] = None, - user_identifier_key: Optional[str] = None, - ) -> List[ConnectedAccount]: + def list(self, *, custom_metadata_has: Optional[Dict[str, Any]] = None, customer_key: Optional[str] = None, limit: Optional[int] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, space_id: Optional[str] = None, user_identifier_key: Optional[str] = None) -> List[ConnectedAccount]: """Returns a list of all `connected accounts `_. :param custom_metadata_has: Custom metadata pairs by which you want to filter connected accounts. Returns connected accounts with ``custom_metadata`` that contains all of the provided key:value pairs. @@ -85,16 +71,7 @@ def sync(self, *, connected_account_id: str) -> None: raise NotImplementedError() @abc.abstractmethod - def update( - self, - *, - connected_account_id: str, - accepted_capabilities: Optional[List[str]] = None, - automatically_manage_new_devices: Optional[bool] = None, - custom_metadata: Optional[Dict[str, Any]] = None, - customer_key: Optional[str] = None, - display_name: Optional[str] = None, - ) -> None: + def update(self, *, connected_account_id: str, accepted_capabilities: Optional[List[str]] = None, automatically_manage_new_devices: Optional[bool] = None, custom_metadata: Optional[Dict[str, Any]] = None, customer_key: Optional[str] = None, display_name: Optional[str] = None) -> None: """Updates a `connected account `_. :param connected_account_id: ID of the connected account that you want to update. @@ -123,16 +100,12 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): def simulate(self) -> ConnectedAccountsSimulate: return self._simulate - @route_metadata( - path="/connected_accounts/delete", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/connected_accounts/delete", has_required_parameters=True, has_pagination=False) def delete(self, *, connected_account_id: str) -> None: """Deletes a specified `connected account `_. - + Deleting a connected account triggers a ``connected_account.deleted`` event and removes the connected account and all data associated with the connected account from Seam, including devices, events, access codes, and so on. For every deleted resource, Seam sends a corresponding deleted event, but the resource is not deleted from the provider. - + For example, if you delete a connected account with a device that has an access code, Seam sends a ``connected_account.deleted`` event, a ``device.deleted`` event, and an ``access_code.deleted`` event, but Seam does not remove the access code from the device. :param connected_account_id: ID of the connected account that you want to delete. @@ -144,22 +117,14 @@ def delete(self, *, connected_account_id: str) -> None: params["connected_account_id"] = connected_account_id if not params: - raise ValueError( - "At least one parameter is required for /connected_accounts/delete" - ) + raise ValueError("At least one parameter is required for /connected_accounts/delete") self.client.delete("/connected_accounts/delete", params=params) return None - @route_metadata( - path="/connected_accounts/get", - has_required_parameters=True, - has_pagination=False, - ) - def get( - self, *, connected_account_id: Optional[str] = None, email: Optional[str] = None - ) -> ConnectedAccount: + @route_metadata(path="/connected_accounts/get", has_required_parameters=True, has_pagination=False) + def get(self, *, connected_account_id: Optional[str] = None, email: Optional[str] = None) -> ConnectedAccount: """Returns a specified `connected account `_. :param connected_account_id: ID of the connected account that you want to get. @@ -177,30 +142,14 @@ def get( params["email"] = email if not params: - raise ValueError( - "At least one parameter is required for /connected_accounts/get" - ) + raise ValueError("At least one parameter is required for /connected_accounts/get") res = self.client.get("/connected_accounts/get", params=params) return ConnectedAccount.from_dict(res["connected_account"]) - @route_metadata( - path="/connected_accounts/list", - has_required_parameters=False, - has_pagination=True, - ) - def list( - self, - *, - custom_metadata_has: Optional[Dict[str, Any]] = None, - customer_key: Optional[str] = None, - limit: Optional[int] = None, - page_cursor: Optional[str] = None, - search: Optional[str] = None, - space_id: Optional[str] = None, - user_identifier_key: Optional[str] = None, - ) -> List[ConnectedAccount]: + @route_metadata(path="/connected_accounts/list", has_required_parameters=False, has_pagination=True) + def list(self, *, custom_metadata_has: Optional[Dict[str, Any]] = None, customer_key: Optional[str] = None, limit: Optional[int] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, space_id: Optional[str] = None, user_identifier_key: Optional[str] = None) -> List[ConnectedAccount]: """Returns a list of all `connected accounts `_. :param custom_metadata_has: Custom metadata pairs by which you want to filter connected accounts. Returns connected accounts with ``custom_metadata`` that contains all of the provided key:value pairs. @@ -239,11 +188,7 @@ def list( return [ConnectedAccount.from_dict(item) for item in res["connected_accounts"]] - @route_metadata( - path="/connected_accounts/sync", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/connected_accounts/sync", has_required_parameters=True, has_pagination=False) def sync(self, *, connected_account_id: str) -> None: """Request a `connected account `_ sync attempt for the specified ``connected_account_id``. @@ -256,29 +201,14 @@ def sync(self, *, connected_account_id: str) -> None: json_payload["connected_account_id"] = connected_account_id if not json_payload: - raise ValueError( - "At least one parameter is required for /connected_accounts/sync" - ) + raise ValueError("At least one parameter is required for /connected_accounts/sync") self.client.post("/connected_accounts/sync", json=json_payload) return None - @route_metadata( - path="/connected_accounts/update", - has_required_parameters=True, - has_pagination=False, - ) - def update( - self, - *, - connected_account_id: str, - accepted_capabilities: Optional[List[str]] = None, - automatically_manage_new_devices: Optional[bool] = None, - custom_metadata: Optional[Dict[str, Any]] = None, - customer_key: Optional[str] = None, - display_name: Optional[str] = None, - ) -> None: + @route_metadata(path="/connected_accounts/update", has_required_parameters=True, has_pagination=False) + def update(self, *, connected_account_id: str, accepted_capabilities: Optional[List[str]] = None, automatically_manage_new_devices: Optional[bool] = None, custom_metadata: Optional[Dict[str, Any]] = None, customer_key: Optional[str] = None, display_name: Optional[str] = None) -> None: """Updates a `connected account `_. :param connected_account_id: ID of the connected account that you want to update. @@ -301,9 +231,7 @@ def update( if accepted_capabilities is not None: json_payload["accepted_capabilities"] = accepted_capabilities if automatically_manage_new_devices is not None: - json_payload["automatically_manage_new_devices"] = ( - automatically_manage_new_devices - ) + json_payload["automatically_manage_new_devices"] = automatically_manage_new_devices if custom_metadata is not None: json_payload["custom_metadata"] = custom_metadata if customer_key is not None: @@ -312,9 +240,7 @@ def update( json_payload["display_name"] = display_name if not json_payload: - raise ValueError( - "At least one parameter is required for /connected_accounts/update" - ) + raise ValueError("At least one parameter is required for /connected_accounts/update") self.client.patch("/connected_accounts/update", json=json_payload) diff --git a/seam/routes/connected_accounts_simulate.py b/seam/routes/connected_accounts_simulate.py index 76df8f62..19a69fb5 100644 --- a/seam/routes/connected_accounts_simulate.py +++ b/seam/routes/connected_accounts_simulate.py @@ -2,6 +2,7 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata +from ..null import Null class AbstractConnectedAccountsSimulate(abc.ABC): @@ -21,11 +22,7 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/connected_accounts/simulate/disconnect", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/connected_accounts/simulate/disconnect", has_required_parameters=True, has_pagination=False) def disconnect(self, *, connected_account_id: str) -> None: """Simulates a connected account becoming disconnected from Seam. Only applicable for `sandbox workspaces `_. @@ -38,9 +35,7 @@ def disconnect(self, *, connected_account_id: str) -> None: json_payload["connected_account_id"] = connected_account_id if not json_payload: - raise ValueError( - "At least one parameter is required for /connected_accounts/simulate/disconnect" - ) + raise ValueError("At least one parameter is required for /connected_accounts/simulate/disconnect") self.client.post("/connected_accounts/simulate/disconnect", json=json_payload) diff --git a/seam/routes/customers.py b/seam/routes/customers.py index 494848c5..2f06537c 100644 --- a/seam/routes/customers.py +++ b/seam/routes/customers.py @@ -2,27 +2,14 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import CustomerPortal +from ..null import Null +from ..resources import (CustomerPortal) class AbstractCustomers(abc.ABC): @abc.abstractmethod - def create_portal( - self, - *, - customer_resources_filters: Optional[List[Dict[str, Any]]] = None, - customization_profile_id: Optional[str] = None, - deep_link: Optional[Dict[str, Any]] = None, - exclude_locale_picker: Optional[bool] = None, - features: Optional[Dict[str, Any]] = None, - is_embedded: Optional[bool] = None, - landing_page: Optional[Dict[str, Any]] = None, - locale: Optional[str] = None, - navigation_mode: Optional[str] = None, - read_only: Optional[bool] = None, - customer_data: Optional[Dict[str, Any]] = None, - ) -> CustomerPortal: + def create_portal(self, *, customer_resources_filters: Optional[List[Dict[str, Any]]] = None, customization_profile_id: Optional[str] = None, deep_link: Optional[Dict[str, Any]] = None, exclude_locale_picker: Optional[bool] = None, features: Optional[Dict[str, Any]] = None, is_embedded: Optional[bool] = None, landing_page: Optional[Dict[str, Any]] = None, locale: Optional[str] = None, navigation_mode: Optional[str] = None, read_only: Optional[bool] = None, customer_data: Optional[Dict[str, Any]] = None) -> CustomerPortal: """Creates a new customer portal magic link with configurable features. :param customer_resources_filters: Filter configuration for resources based on their custom_metadata. Each filter specifies a field, operation, and value to match against resource custom_metadata. @@ -33,7 +20,7 @@ def create_portal( :param exclude_locale_picker: Whether to exclude the option to select a locale within the portal UI. - :param features: + :param features: :param is_embedded: Whether the portal is embedded in another application. @@ -45,35 +32,13 @@ def create_portal( :param read_only: Whether the portal is read-only. When true, the customer can browse the portal but cannot perform any mutating action; write requests made with the portal's client session are rejected. - :param customer_data: + :param customer_data: :returns: OK""" raise NotImplementedError() @abc.abstractmethod - def delete_data( - self, - *, - access_grant_keys: Optional[List[str]] = None, - booking_keys: Optional[List[str]] = None, - building_keys: Optional[List[str]] = None, - common_area_keys: Optional[List[str]] = None, - customer_keys: Optional[List[str]] = None, - facility_keys: Optional[List[str]] = None, - guest_keys: Optional[List[str]] = None, - listing_keys: Optional[List[str]] = None, - property_keys: Optional[List[str]] = None, - property_listing_keys: Optional[List[str]] = None, - reservation_keys: Optional[List[str]] = None, - resident_keys: Optional[List[str]] = None, - room_keys: Optional[List[str]] = None, - space_keys: Optional[List[str]] = None, - staff_member_keys: Optional[List[str]] = None, - tenant_keys: Optional[List[str]] = None, - unit_keys: Optional[List[str]] = None, - user_identity_keys: Optional[List[str]] = None, - user_keys: Optional[List[str]] = None, - ) -> None: + def delete_data(self, *, access_grant_keys: Optional[List[str]] = None, booking_keys: Optional[List[str]] = None, building_keys: Optional[List[str]] = None, common_area_keys: Optional[List[str]] = None, customer_keys: Optional[List[str]] = None, facility_keys: Optional[List[str]] = None, guest_keys: Optional[List[str]] = None, listing_keys: Optional[List[str]] = None, property_keys: Optional[List[str]] = None, property_listing_keys: Optional[List[str]] = None, reservation_keys: Optional[List[str]] = None, resident_keys: Optional[List[str]] = None, room_keys: Optional[List[str]] = None, space_keys: Optional[List[str]] = None, staff_member_keys: Optional[List[str]] = None, tenant_keys: Optional[List[str]] = None, unit_keys: Optional[List[str]] = None, user_identity_keys: Optional[List[str]] = None, user_keys: Optional[List[str]] = None) -> None: """Deletes customer data including resources like spaces, properties, rooms, users, etc. This will delete the partner resources and any related Seam resources (user identities, access grants, spaces). @@ -117,30 +82,7 @@ def delete_data( raise NotImplementedError() @abc.abstractmethod - def push_data( - self, - *, - customer_key: str, - access_grants: Optional[List[Dict[str, Any]]] = None, - bookings: Optional[List[Dict[str, Any]]] = None, - buildings: Optional[List[Dict[str, Any]]] = None, - common_areas: Optional[List[Dict[str, Any]]] = None, - facilities: Optional[List[Dict[str, Any]]] = None, - guests: Optional[List[Dict[str, Any]]] = None, - listings: Optional[List[Dict[str, Any]]] = None, - properties: Optional[List[Dict[str, Any]]] = None, - property_listings: Optional[List[Dict[str, Any]]] = None, - reservations: Optional[List[Dict[str, Any]]] = None, - residents: Optional[List[Dict[str, Any]]] = None, - rooms: Optional[List[Dict[str, Any]]] = None, - sites: Optional[List[Dict[str, Any]]] = None, - spaces: Optional[List[Dict[str, Any]]] = None, - staff_members: Optional[List[Dict[str, Any]]] = None, - tenants: Optional[List[Dict[str, Any]]] = None, - units: Optional[List[Dict[str, Any]]] = None, - user_identities: Optional[List[Dict[str, Any]]] = None, - users: Optional[List[Dict[str, Any]]] = None, - ) -> None: + def push_data(self, *, customer_key: str, access_grants: Optional[List[Dict[str, Any]]] = None, bookings: Optional[List[Dict[str, Any]]] = None, buildings: Optional[List[Dict[str, Any]]] = None, common_areas: Optional[List[Dict[str, Any]]] = None, facilities: Optional[List[Dict[str, Any]]] = None, guests: Optional[List[Dict[str, Any]]] = None, listings: Optional[List[Dict[str, Any]]] = None, properties: Optional[List[Dict[str, Any]]] = None, property_listings: Optional[List[Dict[str, Any]]] = None, reservations: Optional[List[Dict[str, Any]]] = None, residents: Optional[List[Dict[str, Any]]] = None, rooms: Optional[List[Dict[str, Any]]] = None, sites: Optional[List[Dict[str, Any]]] = None, spaces: Optional[List[Dict[str, Any]]] = None, staff_members: Optional[List[Dict[str, Any]]] = None, tenants: Optional[List[Dict[str, Any]]] = None, units: Optional[List[Dict[str, Any]]] = None, user_identities: Optional[List[Dict[str, Any]]] = None, users: Optional[List[Dict[str, Any]]] = None) -> None: """Pushes customer data including resources like spaces, properties, rooms, users, etc. :param customer_key: Your unique identifier for the customer. @@ -192,26 +134,8 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/customers/create_portal", - has_required_parameters=False, - has_pagination=False, - ) - def create_portal( - self, - *, - customer_resources_filters: Optional[List[Dict[str, Any]]] = None, - customization_profile_id: Optional[str] = None, - deep_link: Optional[Dict[str, Any]] = None, - exclude_locale_picker: Optional[bool] = None, - features: Optional[Dict[str, Any]] = None, - is_embedded: Optional[bool] = None, - landing_page: Optional[Dict[str, Any]] = None, - locale: Optional[str] = None, - navigation_mode: Optional[str] = None, - read_only: Optional[bool] = None, - customer_data: Optional[Dict[str, Any]] = None, - ) -> CustomerPortal: + @route_metadata(path="/customers/create_portal", has_required_parameters=False, has_pagination=False) + def create_portal(self, *, customer_resources_filters: Optional[List[Dict[str, Any]]] = None, customization_profile_id: Optional[str] = None, deep_link: Optional[Dict[str, Any]] = None, exclude_locale_picker: Optional[bool] = None, features: Optional[Dict[str, Any]] = None, is_embedded: Optional[bool] = None, landing_page: Optional[Dict[str, Any]] = None, locale: Optional[str] = None, navigation_mode: Optional[str] = None, read_only: Optional[bool] = None, customer_data: Optional[Dict[str, Any]] = None) -> CustomerPortal: """Creates a new customer portal magic link with configurable features. :param customer_resources_filters: Filter configuration for resources based on their custom_metadata. Each filter specifies a field, operation, and value to match against resource custom_metadata. @@ -222,7 +146,7 @@ def create_portal( :param exclude_locale_picker: Whether to exclude the option to select a locale within the portal UI. - :param features: + :param features: :param is_embedded: Whether the portal is embedded in another application. @@ -234,7 +158,7 @@ def create_portal( :param read_only: Whether the portal is read-only. When true, the customer can browse the portal but cannot perform any mutating action; write requests made with the portal's client session are rejected. - :param customer_data: + :param customer_data: :returns: OK""" json_payload: Dict[str, Any] = {} @@ -266,34 +190,8 @@ def create_portal( return CustomerPortal.from_dict(res["customer_portal"]) - @route_metadata( - path="/customers/delete_data", - has_required_parameters=False, - has_pagination=False, - ) - def delete_data( - self, - *, - access_grant_keys: Optional[List[str]] = None, - booking_keys: Optional[List[str]] = None, - building_keys: Optional[List[str]] = None, - common_area_keys: Optional[List[str]] = None, - customer_keys: Optional[List[str]] = None, - facility_keys: Optional[List[str]] = None, - guest_keys: Optional[List[str]] = None, - listing_keys: Optional[List[str]] = None, - property_keys: Optional[List[str]] = None, - property_listing_keys: Optional[List[str]] = None, - reservation_keys: Optional[List[str]] = None, - resident_keys: Optional[List[str]] = None, - room_keys: Optional[List[str]] = None, - space_keys: Optional[List[str]] = None, - staff_member_keys: Optional[List[str]] = None, - tenant_keys: Optional[List[str]] = None, - unit_keys: Optional[List[str]] = None, - user_identity_keys: Optional[List[str]] = None, - user_keys: Optional[List[str]] = None, - ) -> None: + @route_metadata(path="/customers/delete_data", has_required_parameters=False, has_pagination=False) + def delete_data(self, *, access_grant_keys: Optional[List[str]] = None, booking_keys: Optional[List[str]] = None, building_keys: Optional[List[str]] = None, common_area_keys: Optional[List[str]] = None, customer_keys: Optional[List[str]] = None, facility_keys: Optional[List[str]] = None, guest_keys: Optional[List[str]] = None, listing_keys: Optional[List[str]] = None, property_keys: Optional[List[str]] = None, property_listing_keys: Optional[List[str]] = None, reservation_keys: Optional[List[str]] = None, resident_keys: Optional[List[str]] = None, room_keys: Optional[List[str]] = None, space_keys: Optional[List[str]] = None, staff_member_keys: Optional[List[str]] = None, tenant_keys: Optional[List[str]] = None, unit_keys: Optional[List[str]] = None, user_identity_keys: Optional[List[str]] = None, user_keys: Optional[List[str]] = None) -> None: """Deletes customer data including resources like spaces, properties, rooms, users, etc. This will delete the partner resources and any related Seam resources (user identities, access grants, spaces). @@ -379,33 +277,8 @@ def delete_data( return None - @route_metadata( - path="/customers/push_data", has_required_parameters=True, has_pagination=False - ) - def push_data( - self, - *, - customer_key: str, - access_grants: Optional[List[Dict[str, Any]]] = None, - bookings: Optional[List[Dict[str, Any]]] = None, - buildings: Optional[List[Dict[str, Any]]] = None, - common_areas: Optional[List[Dict[str, Any]]] = None, - facilities: Optional[List[Dict[str, Any]]] = None, - guests: Optional[List[Dict[str, Any]]] = None, - listings: Optional[List[Dict[str, Any]]] = None, - properties: Optional[List[Dict[str, Any]]] = None, - property_listings: Optional[List[Dict[str, Any]]] = None, - reservations: Optional[List[Dict[str, Any]]] = None, - residents: Optional[List[Dict[str, Any]]] = None, - rooms: Optional[List[Dict[str, Any]]] = None, - sites: Optional[List[Dict[str, Any]]] = None, - spaces: Optional[List[Dict[str, Any]]] = None, - staff_members: Optional[List[Dict[str, Any]]] = None, - tenants: Optional[List[Dict[str, Any]]] = None, - units: Optional[List[Dict[str, Any]]] = None, - user_identities: Optional[List[Dict[str, Any]]] = None, - users: Optional[List[Dict[str, Any]]] = None, - ) -> None: + @route_metadata(path="/customers/push_data", has_required_parameters=True, has_pagination=False) + def push_data(self, *, customer_key: str, access_grants: Optional[List[Dict[str, Any]]] = None, bookings: Optional[List[Dict[str, Any]]] = None, buildings: Optional[List[Dict[str, Any]]] = None, common_areas: Optional[List[Dict[str, Any]]] = None, facilities: Optional[List[Dict[str, Any]]] = None, guests: Optional[List[Dict[str, Any]]] = None, listings: Optional[List[Dict[str, Any]]] = None, properties: Optional[List[Dict[str, Any]]] = None, property_listings: Optional[List[Dict[str, Any]]] = None, reservations: Optional[List[Dict[str, Any]]] = None, residents: Optional[List[Dict[str, Any]]] = None, rooms: Optional[List[Dict[str, Any]]] = None, sites: Optional[List[Dict[str, Any]]] = None, spaces: Optional[List[Dict[str, Any]]] = None, staff_members: Optional[List[Dict[str, Any]]] = None, tenants: Optional[List[Dict[str, Any]]] = None, units: Optional[List[Dict[str, Any]]] = None, user_identities: Optional[List[Dict[str, Any]]] = None, users: Optional[List[Dict[str, Any]]] = None) -> None: """Pushes customer data including resources like spaces, properties, rooms, users, etc. :param customer_key: Your unique identifier for the customer. @@ -493,9 +366,7 @@ def push_data( json_payload["users"] = users if not json_payload: - raise ValueError( - "At least one parameter is required for /customers/push_data" - ) + raise ValueError("At least one parameter is required for /customers/push_data") self.client.post("/customers/push_data", json=json_payload) diff --git a/seam/routes/devices.py b/seam/routes/devices.py index 8fe8841c..dc95c503 100644 --- a/seam/routes/devices.py +++ b/seam/routes/devices.py @@ -2,7 +2,8 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import Device, DeviceProvider +from ..null import Null +from ..resources import (Device,DeviceProvider) from .devices_simulate import AbstractDevicesSimulate, DevicesSimulate from .devices_unmanaged import AbstractDevicesUnmanaged, DevicesUnmanaged @@ -20,11 +21,9 @@ def unmanaged(self) -> AbstractDevicesUnmanaged: raise NotImplementedError() @abc.abstractmethod - def get( - self, *, device_id: Optional[str] = None, name: Optional[str] = None - ) -> Device: + def get(self, *, device_id: Optional[str] = None, name: Optional[str] = None) -> Device: """Returns a specified `device `_. - + You must specify either ``device_id`` or ``name``. :param device_id: ID of the device that you want to get. @@ -37,26 +36,7 @@ def get( raise NotImplementedError() @abc.abstractmethod - def list( - self, - *, - connect_webview_id: Optional[str] = None, - connected_account_id: Optional[str] = None, - connected_account_ids: Optional[List[str]] = None, - created_before: Optional[str] = None, - custom_metadata_has: Optional[Dict[str, Any]] = None, - customer_key: Optional[str] = None, - device_ids: Optional[List[str]] = None, - device_type: Optional[str] = None, - device_types: Optional[List[str]] = None, - limit: Optional[float] = None, - manufacturer: Optional[str] = None, - page_cursor: Optional[str] = None, - search: Optional[str] = None, - space_id: Optional[str] = None, - unstable_location_id: Optional[str] = None, - user_identifier_key: Optional[str] = None, - ) -> List[Device]: + def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id: Optional[str] = None, connected_account_ids: Optional[List[str]] = None, created_before: Optional[str] = None, custom_metadata_has: Optional[Dict[str, Any]] = None, customer_key: Optional[str] = None, device_ids: Optional[List[str]] = None, device_type: Optional[str] = None, device_types: Optional[List[str]] = None, limit: Optional[float] = None, manufacturer: Optional[str] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, space_id: Optional[str] = None, unstable_location_id: Optional[Union[str, Null]] = None, user_identifier_key: Optional[str] = None) -> List[Device]: """Returns a list of all `devices `_. :param connect_webview_id: ID of the Connect Webview for which you want to list devices. @@ -95,13 +75,11 @@ def list( raise NotImplementedError() @abc.abstractmethod - def list_device_providers( - self, *, provider_category: Optional[str] = None - ) -> List[DeviceProvider]: + def list_device_providers(self, *, provider_category: Optional[str] = None) -> List[DeviceProvider]: """Returns a list of all device providers. - + The information that this endpoint returns for each provider includes a set of `capability flags `_, such as ``device_provider.can_remotely_unlock``. If at least one supported device from a provider has a specific capability, the corresponding capability flag is ``true``. - + When you create a `Connect Webview `_, you can customize the providers—that is, the brands—that it displays. In the ``/connect_webviews/create`` request, include the desired set of device provider keys in the ``accepted_providers`` parameter. See also `Customize the Brands to Display in Your Connect Webviews `_. :param provider_category: Category for which you want to list providers. @@ -119,18 +97,9 @@ def report_provider_metadata(self, *, devices: List[Dict[str, Any]]) -> None: raise NotImplementedError() @abc.abstractmethod - def update( - self, - *, - device_id: str, - backup_access_code_pool_enabled: Optional[bool] = None, - custom_metadata: Optional[Dict[str, Any]] = None, - is_managed: Optional[bool] = None, - name: Optional[str] = None, - properties: Optional[Dict[str, Any]] = None, - ) -> None: + def update(self, *, device_id: str, backup_access_code_pool_enabled: Optional[bool] = None, custom_metadata: Optional[Dict[str, Any]] = None, is_managed: Optional[bool] = None, name: Optional[Union[str, Null]] = None, properties: Optional[Dict[str, Any]] = None) -> None: """Updates a specified `device `_. - + You can add or change `custom metadata `_ for a device, change the device's name, or `convert a managed device to unmanaged `_. :param device_id: ID of the device that you want to update. @@ -143,7 +112,7 @@ def update( :param name: Name for the device. - :param properties: + :param properties: :raises ValueError: At least one parameter must be provided.""" raise NotImplementedError() @@ -164,14 +133,10 @@ def simulate(self) -> DevicesSimulate: def unmanaged(self) -> DevicesUnmanaged: return self._unmanaged - @route_metadata( - path="/devices/get", has_required_parameters=True, has_pagination=False - ) - def get( - self, *, device_id: Optional[str] = None, name: Optional[str] = None - ) -> Device: + @route_metadata(path="/devices/get", has_required_parameters=True, has_pagination=False) + def get(self, *, device_id: Optional[str] = None, name: Optional[str] = None) -> Device: """Returns a specified `device `_. - + You must specify either ``device_id`` or ``name``. :param device_id: ID of the device that you want to get. @@ -195,29 +160,8 @@ def get( return Device.from_dict(res["device"]) - @route_metadata( - path="/devices/list", has_required_parameters=False, has_pagination=True - ) - def list( - self, - *, - connect_webview_id: Optional[str] = None, - connected_account_id: Optional[str] = None, - connected_account_ids: Optional[List[str]] = None, - created_before: Optional[str] = None, - custom_metadata_has: Optional[Dict[str, Any]] = None, - customer_key: Optional[str] = None, - device_ids: Optional[List[str]] = None, - device_type: Optional[str] = None, - device_types: Optional[List[str]] = None, - limit: Optional[float] = None, - manufacturer: Optional[str] = None, - page_cursor: Optional[str] = None, - search: Optional[str] = None, - space_id: Optional[str] = None, - unstable_location_id: Optional[str] = None, - user_identifier_key: Optional[str] = None, - ) -> List[Device]: + @route_metadata(path="/devices/list", has_required_parameters=False, has_pagination=True) + def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id: Optional[str] = None, connected_account_ids: Optional[List[str]] = None, created_before: Optional[str] = None, custom_metadata_has: Optional[Dict[str, Any]] = None, customer_key: Optional[str] = None, device_ids: Optional[List[str]] = None, device_type: Optional[str] = None, device_types: Optional[List[str]] = None, limit: Optional[float] = None, manufacturer: Optional[str] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, space_id: Optional[str] = None, unstable_location_id: Optional[Union[str, Null]] = None, user_identifier_key: Optional[str] = None) -> List[Device]: """Returns a list of all `devices `_. :param connect_webview_id: ID of the Connect Webview for which you want to list devices. @@ -292,18 +236,12 @@ def list( return [Device.from_dict(item) for item in res["devices"]] - @route_metadata( - path="/devices/list_device_providers", - has_required_parameters=False, - has_pagination=False, - ) - def list_device_providers( - self, *, provider_category: Optional[str] = None - ) -> List[DeviceProvider]: + @route_metadata(path="/devices/list_device_providers", has_required_parameters=False, has_pagination=False) + def list_device_providers(self, *, provider_category: Optional[str] = None) -> List[DeviceProvider]: """Returns a list of all device providers. - + The information that this endpoint returns for each provider includes a set of `capability flags `_, such as ``device_provider.can_remotely_unlock``. If at least one supported device from a provider has a specific capability, the corresponding capability flag is ``true``. - + When you create a `Connect Webview `_, you can customize the providers—that is, the brands—that it displays. In the ``/connect_webviews/create`` request, include the desired set of device provider keys in the ``accepted_providers`` parameter. See also `Customize the Brands to Display in Your Connect Webviews `_. :param provider_category: Category for which you want to list providers. @@ -318,11 +256,7 @@ def list_device_providers( return [DeviceProvider.from_dict(item) for item in res["device_providers"]] - @route_metadata( - path="/devices/report_provider_metadata", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/devices/report_provider_metadata", has_required_parameters=True, has_pagination=False) def report_provider_metadata(self, *, devices: List[Dict[str, Any]]) -> None: """Updates provider-specific metadata for devices. @@ -335,29 +269,16 @@ def report_provider_metadata(self, *, devices: List[Dict[str, Any]]) -> None: json_payload["devices"] = devices if not json_payload: - raise ValueError( - "At least one parameter is required for /devices/report_provider_metadata" - ) + raise ValueError("At least one parameter is required for /devices/report_provider_metadata") self.client.post("/devices/report_provider_metadata", json=json_payload) return None - @route_metadata( - path="/devices/update", has_required_parameters=True, has_pagination=False - ) - def update( - self, - *, - device_id: str, - backup_access_code_pool_enabled: Optional[bool] = None, - custom_metadata: Optional[Dict[str, Any]] = None, - is_managed: Optional[bool] = None, - name: Optional[str] = None, - properties: Optional[Dict[str, Any]] = None, - ) -> None: + @route_metadata(path="/devices/update", has_required_parameters=True, has_pagination=False) + def update(self, *, device_id: str, backup_access_code_pool_enabled: Optional[bool] = None, custom_metadata: Optional[Dict[str, Any]] = None, is_managed: Optional[bool] = None, name: Optional[Union[str, Null]] = None, properties: Optional[Dict[str, Any]] = None) -> None: """Updates a specified `device `_. - + You can add or change `custom metadata `_ for a device, change the device's name, or `convert a managed device to unmanaged `_. :param device_id: ID of the device that you want to update. @@ -370,7 +291,7 @@ def update( :param name: Name for the device. - :param properties: + :param properties: :raises ValueError: At least one parameter must be provided.""" json_payload: Dict[str, Any] = {} @@ -378,9 +299,7 @@ def update( if device_id is not None: json_payload["device_id"] = device_id if backup_access_code_pool_enabled is not None: - json_payload["backup_access_code_pool_enabled"] = ( - backup_access_code_pool_enabled - ) + json_payload["backup_access_code_pool_enabled"] = backup_access_code_pool_enabled if custom_metadata is not None: json_payload["custom_metadata"] = custom_metadata if is_managed is not None: diff --git a/seam/routes/devices_simulate.py b/seam/routes/devices_simulate.py index dfdfdfed..3aab8490 100644 --- a/seam/routes/devices_simulate.py +++ b/seam/routes/devices_simulate.py @@ -2,6 +2,7 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata +from ..null import Null class AbstractDevicesSimulate(abc.ABC): @@ -55,9 +56,9 @@ def paid_subscription(self, *, device_id: str, is_expired: bool) -> None: Send ``is_expired: true`` to simulate an expired subscription, or ``false`` to simulate an active subscription. The actual device error is created/cleared by the poller after this state change. - :param device_id: + :param device_id: - :param is_expired: + :param is_expired: :raises ValueError: At least one parameter must be provided.""" raise NotImplementedError() @@ -77,11 +78,7 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/devices/simulate/connect", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/devices/simulate/connect", has_required_parameters=True, has_pagination=False) def connect(self, *, device_id: str) -> None: """Simulates connecting a device to Seam. Only applicable for `sandbox devices `_. See also `Testing Your App Against Device Disconnection and Removal `_. @@ -94,19 +91,13 @@ def connect(self, *, device_id: str) -> None: json_payload["device_id"] = device_id if not json_payload: - raise ValueError( - "At least one parameter is required for /devices/simulate/connect" - ) + raise ValueError("At least one parameter is required for /devices/simulate/connect") self.client.post("/devices/simulate/connect", json=json_payload) return None - @route_metadata( - path="/devices/simulate/connect_to_hub", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/devices/simulate/connect_to_hub", has_required_parameters=True, has_pagination=False) def connect_to_hub(self, *, device_id: str) -> None: """Simulates bringing the Wi‑Fi hub (bridge) back online for a device. Only applicable for sandbox workspaces and currently @@ -122,19 +113,13 @@ def connect_to_hub(self, *, device_id: str) -> None: json_payload["device_id"] = device_id if not json_payload: - raise ValueError( - "At least one parameter is required for /devices/simulate/connect_to_hub" - ) + raise ValueError("At least one parameter is required for /devices/simulate/connect_to_hub") self.client.post("/devices/simulate/connect_to_hub", json=json_payload) return None - @route_metadata( - path="/devices/simulate/disconnect", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/devices/simulate/disconnect", has_required_parameters=True, has_pagination=False) def disconnect(self, *, device_id: str) -> None: """Simulates disconnecting a device from Seam. Only applicable for `sandbox devices `_. See also `Testing Your App Against Device Disconnection and Removal `_. @@ -147,19 +132,13 @@ def disconnect(self, *, device_id: str) -> None: json_payload["device_id"] = device_id if not json_payload: - raise ValueError( - "At least one parameter is required for /devices/simulate/disconnect" - ) + raise ValueError("At least one parameter is required for /devices/simulate/disconnect") self.client.post("/devices/simulate/disconnect", json=json_payload) return None - @route_metadata( - path="/devices/simulate/disconnect_from_hub", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/devices/simulate/disconnect_from_hub", has_required_parameters=True, has_pagination=False) def disconnect_from_hub(self, *, device_id: str) -> None: """Simulates taking the Wi‑Fi hub (bridge) offline for a device. Only applicable for sandbox workspaces and currently @@ -176,27 +155,21 @@ def disconnect_from_hub(self, *, device_id: str) -> None: json_payload["device_id"] = device_id if not json_payload: - raise ValueError( - "At least one parameter is required for /devices/simulate/disconnect_from_hub" - ) + raise ValueError("At least one parameter is required for /devices/simulate/disconnect_from_hub") self.client.post("/devices/simulate/disconnect_from_hub", json=json_payload) return None - @route_metadata( - path="/devices/simulate/paid_subscription", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/devices/simulate/paid_subscription", has_required_parameters=True, has_pagination=False) def paid_subscription(self, *, device_id: str, is_expired: bool) -> None: """Toggle the simulated Nuki Smart Hosting subscription for a device (sandbox only). Send ``is_expired: true`` to simulate an expired subscription, or ``false`` to simulate an active subscription. The actual device error is created/cleared by the poller after this state change. - :param device_id: + :param device_id: - :param is_expired: + :param is_expired: :raises ValueError: At least one parameter must be provided.""" json_payload: Dict[str, Any] = {} @@ -207,19 +180,13 @@ def paid_subscription(self, *, device_id: str, is_expired: bool) -> None: json_payload["is_expired"] = is_expired if not json_payload: - raise ValueError( - "At least one parameter is required for /devices/simulate/paid_subscription" - ) + raise ValueError("At least one parameter is required for /devices/simulate/paid_subscription") self.client.post("/devices/simulate/paid_subscription", json=json_payload) return None - @route_metadata( - path="/devices/simulate/remove", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/devices/simulate/remove", has_required_parameters=True, has_pagination=False) def remove(self, *, device_id: str) -> None: """Simulates removing a device from Seam. Only applicable for `sandbox devices `_. See also `Testing Your App Against Device Disconnection and Removal `_. @@ -232,9 +199,7 @@ def remove(self, *, device_id: str) -> None: json_payload["device_id"] = device_id if not json_payload: - raise ValueError( - "At least one parameter is required for /devices/simulate/remove" - ) + raise ValueError("At least one parameter is required for /devices/simulate/remove") self.client.post("/devices/simulate/remove", json=json_payload) diff --git a/seam/routes/devices_unmanaged.py b/seam/routes/devices_unmanaged.py index 4b8b5792..6643a8e1 100644 --- a/seam/routes/devices_unmanaged.py +++ b/seam/routes/devices_unmanaged.py @@ -2,19 +2,18 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import UnmanagedDevice +from ..null import Null +from ..resources import (UnmanagedDevice) class AbstractDevicesUnmanaged(abc.ABC): @abc.abstractmethod - def get( - self, *, device_id: Optional[str] = None, name: Optional[str] = None - ) -> UnmanagedDevice: + def get(self, *, device_id: Optional[str] = None, name: Optional[str] = None) -> UnmanagedDevice: """Returns a specified `unmanaged device `_. - + An unmanaged device has a limited set of visible properties and a subset of supported events. You cannot control an unmanaged device. Any `access codes `_ on an unmanaged device are unmanaged. To control an unmanaged device with Seam, `convert it to a managed device `_. - + You must specify either ``device_id`` or ``name``. :param device_id: ID of the unmanaged device that you want to get. @@ -27,24 +26,9 @@ def get( raise NotImplementedError() @abc.abstractmethod - def list( - self, - *, - connect_webview_id: Optional[str] = None, - connected_account_id: Optional[str] = None, - connected_account_ids: Optional[List[str]] = None, - created_before: Optional[str] = None, - customer_key: Optional[str] = None, - device_ids: Optional[List[str]] = None, - device_type: Optional[str] = None, - device_types: Optional[List[str]] = None, - limit: Optional[float] = None, - manufacturer: Optional[str] = None, - page_cursor: Optional[str] = None, - search: Optional[str] = None, - ) -> List[UnmanagedDevice]: + def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id: Optional[str] = None, connected_account_ids: Optional[List[str]] = None, created_before: Optional[str] = None, customer_key: Optional[str] = None, device_ids: Optional[List[str]] = None, device_type: Optional[str] = None, device_types: Optional[List[str]] = None, limit: Optional[float] = None, manufacturer: Optional[str] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None) -> List[UnmanagedDevice]: """Returns a list of all `unmanaged devices `_. - + An unmanaged device has a limited set of visible properties and a subset of supported events. You cannot control an unmanaged device. Any `access codes `_ on an unmanaged device are unmanaged. To control an unmanaged device with Seam, `convert it to a managed device `_. :param connect_webview_id: ID of the Connect Webview for which you want to list devices. @@ -75,15 +59,9 @@ def list( raise NotImplementedError() @abc.abstractmethod - def update( - self, - *, - device_id: str, - custom_metadata: Optional[Dict[str, Any]] = None, - is_managed: Optional[bool] = None, - ) -> None: + def update(self, *, device_id: str, custom_metadata: Optional[Dict[str, Any]] = None, is_managed: Optional[bool] = None) -> None: """Updates a specified `unmanaged device `_. To convert an unmanaged device to managed, set ``is_managed`` to ``true``. - + An unmanaged device has a limited set of visible properties and a subset of supported events. You cannot control an unmanaged device. Any `access codes `_ on an unmanaged device are unmanaged. To control an unmanaged device with Seam, `convert it to a managed device `_. :param device_id: ID of the unmanaged device that you want to update. @@ -101,18 +79,12 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/devices/unmanaged/get", - has_required_parameters=True, - has_pagination=False, - ) - def get( - self, *, device_id: Optional[str] = None, name: Optional[str] = None - ) -> UnmanagedDevice: + @route_metadata(path="/devices/unmanaged/get", has_required_parameters=True, has_pagination=False) + def get(self, *, device_id: Optional[str] = None, name: Optional[str] = None) -> UnmanagedDevice: """Returns a specified `unmanaged device `_. - + An unmanaged device has a limited set of visible properties and a subset of supported events. You cannot control an unmanaged device. Any `access codes `_ on an unmanaged device are unmanaged. To control an unmanaged device with Seam, `convert it to a managed device `_. - + You must specify either ``device_id`` or ``name``. :param device_id: ID of the unmanaged device that you want to get. @@ -130,37 +102,16 @@ def get( params["name"] = name if not params: - raise ValueError( - "At least one parameter is required for /devices/unmanaged/get" - ) + raise ValueError("At least one parameter is required for /devices/unmanaged/get") res = self.client.get("/devices/unmanaged/get", params=params) return UnmanagedDevice.from_dict(res["device"]) - @route_metadata( - path="/devices/unmanaged/list", - has_required_parameters=False, - has_pagination=True, - ) - def list( - self, - *, - connect_webview_id: Optional[str] = None, - connected_account_id: Optional[str] = None, - connected_account_ids: Optional[List[str]] = None, - created_before: Optional[str] = None, - customer_key: Optional[str] = None, - device_ids: Optional[List[str]] = None, - device_type: Optional[str] = None, - device_types: Optional[List[str]] = None, - limit: Optional[float] = None, - manufacturer: Optional[str] = None, - page_cursor: Optional[str] = None, - search: Optional[str] = None, - ) -> List[UnmanagedDevice]: + @route_metadata(path="/devices/unmanaged/list", has_required_parameters=False, has_pagination=True) + def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id: Optional[str] = None, connected_account_ids: Optional[List[str]] = None, created_before: Optional[str] = None, customer_key: Optional[str] = None, device_ids: Optional[List[str]] = None, device_type: Optional[str] = None, device_types: Optional[List[str]] = None, limit: Optional[float] = None, manufacturer: Optional[str] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None) -> List[UnmanagedDevice]: """Returns a list of all `unmanaged devices `_. - + An unmanaged device has a limited set of visible properties and a subset of supported events. You cannot control an unmanaged device. Any `access codes `_ on an unmanaged device are unmanaged. To control an unmanaged device with Seam, `convert it to a managed device `_. :param connect_webview_id: ID of the Connect Webview for which you want to list devices. @@ -219,20 +170,10 @@ def list( return [UnmanagedDevice.from_dict(item) for item in res["devices"]] - @route_metadata( - path="/devices/unmanaged/update", - has_required_parameters=True, - has_pagination=False, - ) - def update( - self, - *, - device_id: str, - custom_metadata: Optional[Dict[str, Any]] = None, - is_managed: Optional[bool] = None, - ) -> None: + @route_metadata(path="/devices/unmanaged/update", has_required_parameters=True, has_pagination=False) + def update(self, *, device_id: str, custom_metadata: Optional[Dict[str, Any]] = None, is_managed: Optional[bool] = None) -> None: """Updates a specified `unmanaged device `_. To convert an unmanaged device to managed, set ``is_managed`` to ``true``. - + An unmanaged device has a limited set of visible properties and a subset of supported events. You cannot control an unmanaged device. Any `access codes `_ on an unmanaged device are unmanaged. To control an unmanaged device with Seam, `convert it to a managed device `_. :param device_id: ID of the unmanaged device that you want to update. @@ -252,9 +193,7 @@ def update( json_payload["is_managed"] = is_managed if not json_payload: - raise ValueError( - "At least one parameter is required for /devices/unmanaged/update" - ) + raise ValueError("At least one parameter is required for /devices/unmanaged/update") self.client.patch("/devices/unmanaged/update", json=json_payload) diff --git a/seam/routes/events.py b/seam/routes/events.py index 8613b639..938c269d 100644 --- a/seam/routes/events.py +++ b/seam/routes/events.py @@ -2,19 +2,14 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import SeamEvent +from ..null import Null +from ..resources import (SeamEvent) class AbstractEvents(abc.ABC): @abc.abstractmethod - def get( - self, - *, - event_id: Optional[str] = None, - device_id: Optional[str] = None, - event_type: Optional[str] = None, - ) -> SeamEvent: + def get(self, *, event_id: Optional[str] = None, device_id: Optional[str] = None, event_type: Optional[str] = None) -> SeamEvent: """Returns a specified event. This endpoint returns the same event that would be sent to a `webhook `_, but it enables you to retrieve an event that already took place. :param event_id: Unique identifier for the event that you want to get. @@ -29,38 +24,7 @@ def get( raise NotImplementedError() @abc.abstractmethod - def list( - self, - *, - access_code_id: Optional[str] = None, - access_code_ids: Optional[List[str]] = None, - access_grant_id: Optional[str] = None, - access_grant_ids: Optional[List[str]] = None, - access_method_id: Optional[str] = None, - access_method_ids: Optional[List[str]] = None, - acs_access_group_id: Optional[str] = None, - acs_credential_id: Optional[str] = None, - acs_encoder_id: Optional[str] = None, - acs_entrance_id: Optional[str] = None, - acs_system_id: Optional[str] = None, - acs_system_ids: Optional[List[str]] = None, - acs_user_id: Optional[str] = None, - between: Optional[List[str]] = None, - connect_webview_id: Optional[str] = None, - connected_account_id: Optional[str] = None, - customer_key: Optional[str] = None, - device_id: Optional[str] = None, - device_ids: Optional[List[str]] = None, - event_ids: Optional[List[str]] = None, - event_type: Optional[str] = None, - event_types: Optional[List[str]] = None, - limit: Optional[float] = None, - since: Optional[str] = None, - space_id: Optional[str] = None, - space_ids: Optional[List[str]] = None, - unstable_offset: Optional[float] = None, - user_identity_id: Optional[str] = None, - ) -> List[SeamEvent]: + def list(self, *, access_code_id: Optional[str] = None, access_code_ids: Optional[List[str]] = None, access_grant_id: Optional[str] = None, access_grant_ids: Optional[List[str]] = None, access_method_id: Optional[str] = None, access_method_ids: Optional[List[str]] = None, acs_access_group_id: Optional[str] = None, acs_credential_id: Optional[str] = None, acs_encoder_id: Optional[str] = None, acs_entrance_id: Optional[str] = None, acs_system_id: Optional[str] = None, acs_system_ids: Optional[List[str]] = None, acs_user_id: Optional[str] = None, between: Optional[List[str]] = None, connect_webview_id: Optional[str] = None, connected_account_id: Optional[str] = None, customer_key: Optional[str] = None, device_id: Optional[str] = None, device_ids: Optional[List[str]] = None, event_ids: Optional[List[str]] = None, event_type: Optional[str] = None, event_types: Optional[List[str]] = None, limit: Optional[float] = None, since: Optional[str] = None, space_id: Optional[str] = None, space_ids: Optional[List[str]] = None, unstable_offset: Optional[float] = None, user_identity_id: Optional[str] = None) -> List[SeamEvent]: """Returns a list of all events. This endpoint returns the same events that would be sent to a `webhook `_, but it enables you to filter or see events that already took place. :param access_code_id: ID of the access code for which you want to list events. @@ -130,16 +94,8 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/events/get", has_required_parameters=True, has_pagination=False - ) - def get( - self, - *, - event_id: Optional[str] = None, - device_id: Optional[str] = None, - event_type: Optional[str] = None, - ) -> SeamEvent: + @route_metadata(path="/events/get", has_required_parameters=True, has_pagination=False) + def get(self, *, event_id: Optional[str] = None, device_id: Optional[str] = None, event_type: Optional[str] = None) -> SeamEvent: """Returns a specified event. This endpoint returns the same event that would be sent to a `webhook `_, but it enables you to retrieve an event that already took place. :param event_id: Unique identifier for the event that you want to get. @@ -167,41 +123,8 @@ def get( return SeamEvent.from_dict(res["event"]) - @route_metadata( - path="/events/list", has_required_parameters=True, has_pagination=False - ) - def list( - self, - *, - access_code_id: Optional[str] = None, - access_code_ids: Optional[List[str]] = None, - access_grant_id: Optional[str] = None, - access_grant_ids: Optional[List[str]] = None, - access_method_id: Optional[str] = None, - access_method_ids: Optional[List[str]] = None, - acs_access_group_id: Optional[str] = None, - acs_credential_id: Optional[str] = None, - acs_encoder_id: Optional[str] = None, - acs_entrance_id: Optional[str] = None, - acs_system_id: Optional[str] = None, - acs_system_ids: Optional[List[str]] = None, - acs_user_id: Optional[str] = None, - between: Optional[List[str]] = None, - connect_webview_id: Optional[str] = None, - connected_account_id: Optional[str] = None, - customer_key: Optional[str] = None, - device_id: Optional[str] = None, - device_ids: Optional[List[str]] = None, - event_ids: Optional[List[str]] = None, - event_type: Optional[str] = None, - event_types: Optional[List[str]] = None, - limit: Optional[float] = None, - since: Optional[str] = None, - space_id: Optional[str] = None, - space_ids: Optional[List[str]] = None, - unstable_offset: Optional[float] = None, - user_identity_id: Optional[str] = None, - ) -> List[SeamEvent]: + @route_metadata(path="/events/list", has_required_parameters=True, has_pagination=False) + def list(self, *, access_code_id: Optional[str] = None, access_code_ids: Optional[List[str]] = None, access_grant_id: Optional[str] = None, access_grant_ids: Optional[List[str]] = None, access_method_id: Optional[str] = None, access_method_ids: Optional[List[str]] = None, acs_access_group_id: Optional[str] = None, acs_credential_id: Optional[str] = None, acs_encoder_id: Optional[str] = None, acs_entrance_id: Optional[str] = None, acs_system_id: Optional[str] = None, acs_system_ids: Optional[List[str]] = None, acs_user_id: Optional[str] = None, between: Optional[List[str]] = None, connect_webview_id: Optional[str] = None, connected_account_id: Optional[str] = None, customer_key: Optional[str] = None, device_id: Optional[str] = None, device_ids: Optional[List[str]] = None, event_ids: Optional[List[str]] = None, event_type: Optional[str] = None, event_types: Optional[List[str]] = None, limit: Optional[float] = None, since: Optional[str] = None, space_id: Optional[str] = None, space_ids: Optional[List[str]] = None, unstable_offset: Optional[float] = None, user_identity_id: Optional[str] = None) -> List[SeamEvent]: """Returns a list of all events. This endpoint returns the same events that would be sent to a `webhook `_, but it enables you to filter or see events that already took place. :param access_code_id: ID of the access code for which you want to list events. diff --git a/seam/routes/instant_keys.py b/seam/routes/instant_keys.py index 48787987..4afea042 100644 --- a/seam/routes/instant_keys.py +++ b/seam/routes/instant_keys.py @@ -2,7 +2,8 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import InstantKey +from ..null import Null +from ..resources import (InstantKey) class AbstractInstantKeys(abc.ABC): @@ -17,12 +18,7 @@ def delete(self, *, instant_key_id: str) -> None: raise NotImplementedError() @abc.abstractmethod - def get( - self, - *, - instant_key_id: Optional[str] = None, - instant_key_url: Optional[str] = None, - ) -> InstantKey: + def get(self, *, instant_key_id: Optional[str] = None, instant_key_url: Optional[str] = None) -> InstantKey: """Gets an `instant key `_. :param instant_key_id: ID of the instant key to get. @@ -49,9 +45,7 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/instant_keys/delete", has_required_parameters=True, has_pagination=False - ) + @route_metadata(path="/instant_keys/delete", has_required_parameters=True, has_pagination=False) def delete(self, *, instant_key_id: str) -> None: """Deletes a specified `Instant Key `_. @@ -64,23 +58,14 @@ def delete(self, *, instant_key_id: str) -> None: params["instant_key_id"] = instant_key_id if not params: - raise ValueError( - "At least one parameter is required for /instant_keys/delete" - ) + raise ValueError("At least one parameter is required for /instant_keys/delete") self.client.delete("/instant_keys/delete", params=params) return None - @route_metadata( - path="/instant_keys/get", has_required_parameters=True, has_pagination=False - ) - def get( - self, - *, - instant_key_id: Optional[str] = None, - instant_key_url: Optional[str] = None, - ) -> InstantKey: + @route_metadata(path="/instant_keys/get", has_required_parameters=True, has_pagination=False) + def get(self, *, instant_key_id: Optional[str] = None, instant_key_url: Optional[str] = None) -> InstantKey: """Gets an `instant key `_. :param instant_key_id: ID of the instant key to get. @@ -104,9 +89,7 @@ def get( return InstantKey.from_dict(res["instant_key"]) - @route_metadata( - path="/instant_keys/list", has_required_parameters=False, has_pagination=False - ) + @route_metadata(path="/instant_keys/list", has_required_parameters=False, has_pagination=False) def list(self, *, user_identity_id: Optional[str] = None) -> List[InstantKey]: """Returns a list of all `instant keys `_. diff --git a/seam/routes/locks.py b/seam/routes/locks.py index fdf7843f..8803f722 100644 --- a/seam/routes/locks.py +++ b/seam/routes/locks.py @@ -2,7 +2,8 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import ActionAttempt, Device +from ..null import Null +from ..resources import (ActionAttempt,Device) from .locks_simulate import AbstractLocksSimulate, LocksSimulate from ..modules.action_attempts import resolve_action_attempt @@ -15,14 +16,7 @@ def simulate(self) -> AbstractLocksSimulate: raise NotImplementedError() @abc.abstractmethod - def configure_auto_lock( - self, - *, - auto_lock_enabled: bool, - device_id: str, - auto_lock_delay_seconds: Optional[float] = None, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + def configure_auto_lock(self, *, auto_lock_enabled: bool, device_id: str, auto_lock_delay_seconds: Optional[float] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Configures the auto-lock setting for a specified `lock `_. :param auto_lock_enabled: Whether to enable or disable auto-lock. @@ -39,9 +33,7 @@ def configure_auto_lock( raise NotImplementedError() @abc.abstractmethod - def get( - self, *, device_id: Optional[str] = None, name: Optional[str] = None - ) -> Device: + def get(self, *, device_id: Optional[str] = None, name: Optional[str] = None) -> Device: """Returns a specified `lock `_. :param device_id: ID of the lock that you want to get. @@ -57,16 +49,7 @@ def get( raise NotImplementedError() @abc.abstractmethod - def list( - self, - *, - connect_webview_id: Optional[str] = None, - connected_account_id: Optional[str] = None, - customer_key: Optional[str] = None, - device_type: Optional[str] = None, - device_types: Optional[List[str]] = None, - manufacturer: Optional[str] = None, - ) -> List[Device]: + def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id: Optional[str] = None, customer_key: Optional[str] = None, device_type: Optional[str] = None, device_types: Optional[List[str]] = None, manufacturer: Optional[str] = None) -> List[Device]: """Returns a list of all `locks `_. :param connect_webview_id: ID of the Connect Webview for which you want to list devices. @@ -85,12 +68,7 @@ def list( raise NotImplementedError() @abc.abstractmethod - def lock_door( - self, - *, - device_id: str, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + def lock_door(self, *, device_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Locks a `lock `_. See also `Locking and Unlocking Smart Locks `_. :param device_id: ID of the lock that you want to lock. @@ -103,12 +81,7 @@ def lock_door( raise NotImplementedError() @abc.abstractmethod - def unlock_door( - self, - *, - device_id: str, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + def unlock_door(self, *, device_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Unlocks a `lock `_. See also `Locking and Unlocking Smart Locks `_. :param device_id: ID of the lock that you want to unlock. @@ -131,19 +104,8 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): def simulate(self) -> LocksSimulate: return self._simulate - @route_metadata( - path="/locks/configure_auto_lock", - has_required_parameters=True, - has_pagination=False, - ) - def configure_auto_lock( - self, - *, - auto_lock_enabled: bool, - device_id: str, - auto_lock_delay_seconds: Optional[float] = None, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + @route_metadata(path="/locks/configure_auto_lock", has_required_parameters=True, has_pagination=False) + def configure_auto_lock(self, *, auto_lock_enabled: bool, device_id: str, auto_lock_delay_seconds: Optional[float] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Configures the auto-lock setting for a specified `lock `_. :param auto_lock_enabled: Whether to enable or disable auto-lock. @@ -167,9 +129,7 @@ def configure_auto_lock( json_payload["auto_lock_delay_seconds"] = auto_lock_delay_seconds if not json_payload: - raise ValueError( - "At least one parameter is required for /locks/configure_auto_lock" - ) + raise ValueError("At least one parameter is required for /locks/configure_auto_lock") res = self.client.post("/locks/configure_auto_lock", json=json_payload) @@ -182,15 +142,11 @@ def configure_auto_lock( return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt, + wait_for_action_attempt=wait_for_action_attempt ) - @route_metadata( - path="/locks/get", has_required_parameters=True, has_pagination=False - ) - def get( - self, *, device_id: Optional[str] = None, name: Optional[str] = None - ) -> Device: + @route_metadata(path="/locks/get", has_required_parameters=True, has_pagination=False) + def get(self, *, device_id: Optional[str] = None, name: Optional[str] = None) -> Device: """Returns a specified `lock `_. :param device_id: ID of the lock that you want to get. @@ -217,19 +173,8 @@ def get( return Device.from_dict(res["device"]) - @route_metadata( - path="/locks/list", has_required_parameters=False, has_pagination=False - ) - def list( - self, - *, - connect_webview_id: Optional[str] = None, - connected_account_id: Optional[str] = None, - customer_key: Optional[str] = None, - device_type: Optional[str] = None, - device_types: Optional[List[str]] = None, - manufacturer: Optional[str] = None, - ) -> List[Device]: + @route_metadata(path="/locks/list", has_required_parameters=False, has_pagination=False) + def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id: Optional[str] = None, customer_key: Optional[str] = None, device_type: Optional[str] = None, device_types: Optional[List[str]] = None, manufacturer: Optional[str] = None) -> List[Device]: """Returns a list of all `locks `_. :param connect_webview_id: ID of the Connect Webview for which you want to list devices. @@ -264,15 +209,8 @@ def list( return [Device.from_dict(item) for item in res["devices"]] - @route_metadata( - path="/locks/lock_door", has_required_parameters=True, has_pagination=False - ) - def lock_door( - self, - *, - device_id: str, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + @route_metadata(path="/locks/lock_door", has_required_parameters=True, has_pagination=False) + def lock_door(self, *, device_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Locks a `lock `_. See also `Locking and Unlocking Smart Locks `_. :param device_id: ID of the lock that you want to lock. @@ -301,18 +239,11 @@ def lock_door( return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt, + wait_for_action_attempt=wait_for_action_attempt ) - @route_metadata( - path="/locks/unlock_door", has_required_parameters=True, has_pagination=False - ) - def unlock_door( - self, - *, - device_id: str, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + @route_metadata(path="/locks/unlock_door", has_required_parameters=True, has_pagination=False) + def unlock_door(self, *, device_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Unlocks a `lock `_. See also `Locking and Unlocking Smart Locks `_. :param device_id: ID of the lock that you want to unlock. @@ -328,9 +259,7 @@ def unlock_door( json_payload["device_id"] = device_id if not json_payload: - raise ValueError( - "At least one parameter is required for /locks/unlock_door" - ) + raise ValueError("At least one parameter is required for /locks/unlock_door") res = self.client.post("/locks/unlock_door", json=json_payload) @@ -343,5 +272,5 @@ def unlock_door( return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt, + wait_for_action_attempt=wait_for_action_attempt ) diff --git a/seam/routes/locks_simulate.py b/seam/routes/locks_simulate.py index 6979c9c2..c8df2ddc 100644 --- a/seam/routes/locks_simulate.py +++ b/seam/routes/locks_simulate.py @@ -2,20 +2,15 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import ActionAttempt +from ..null import Null +from ..resources import (ActionAttempt) from ..modules.action_attempts import resolve_action_attempt class AbstractLocksSimulate(abc.ABC): @abc.abstractmethod - def keypad_code_entry( - self, - *, - code: str, - device_id: str, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + def keypad_code_entry(self, *, code: str, device_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Simulates the entry of a code on a keypad. You can only perform this action for `August `_ devices within `sandbox workspaces `_. :param code: Code that you want to simulate entering on a keypad. @@ -30,12 +25,7 @@ def keypad_code_entry( raise NotImplementedError() @abc.abstractmethod - def manual_lock_via_keypad( - self, - *, - device_id: str, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + def manual_lock_via_keypad(self, *, device_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Simulates a manual lock action using a keypad. You can only perform this action for `August `_ devices within `sandbox workspaces `_. :param device_id: ID of the device for which you want to simulate a manual lock action using a keypad. @@ -53,18 +43,8 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/locks/simulate/keypad_code_entry", - has_required_parameters=True, - has_pagination=False, - ) - def keypad_code_entry( - self, - *, - code: str, - device_id: str, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + @route_metadata(path="/locks/simulate/keypad_code_entry", has_required_parameters=True, has_pagination=False) + def keypad_code_entry(self, *, code: str, device_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Simulates the entry of a code on a keypad. You can only perform this action for `August `_ devices within `sandbox workspaces `_. :param code: Code that you want to simulate entering on a keypad. @@ -84,9 +64,7 @@ def keypad_code_entry( json_payload["device_id"] = device_id if not json_payload: - raise ValueError( - "At least one parameter is required for /locks/simulate/keypad_code_entry" - ) + raise ValueError("At least one parameter is required for /locks/simulate/keypad_code_entry") res = self.client.post("/locks/simulate/keypad_code_entry", json=json_payload) @@ -99,20 +77,11 @@ def keypad_code_entry( return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt, + wait_for_action_attempt=wait_for_action_attempt ) - @route_metadata( - path="/locks/simulate/manual_lock_via_keypad", - has_required_parameters=True, - has_pagination=False, - ) - def manual_lock_via_keypad( - self, - *, - device_id: str, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + @route_metadata(path="/locks/simulate/manual_lock_via_keypad", has_required_parameters=True, has_pagination=False) + def manual_lock_via_keypad(self, *, device_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Simulates a manual lock action using a keypad. You can only perform this action for `August `_ devices within `sandbox workspaces `_. :param device_id: ID of the device for which you want to simulate a manual lock action using a keypad. @@ -128,13 +97,9 @@ def manual_lock_via_keypad( json_payload["device_id"] = device_id if not json_payload: - raise ValueError( - "At least one parameter is required for /locks/simulate/manual_lock_via_keypad" - ) + raise ValueError("At least one parameter is required for /locks/simulate/manual_lock_via_keypad") - res = self.client.post( - "/locks/simulate/manual_lock_via_keypad", json=json_payload - ) + res = self.client.post("/locks/simulate/manual_lock_via_keypad", json=json_payload) wait_for_action_attempt = ( self.defaults.get("wait_for_action_attempt") @@ -145,5 +110,5 @@ def manual_lock_via_keypad( return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt, + wait_for_action_attempt=wait_for_action_attempt ) diff --git a/seam/routes/noise_sensors.py b/seam/routes/noise_sensors.py index 6ab4492b..d1320f1a 100644 --- a/seam/routes/noise_sensors.py +++ b/seam/routes/noise_sensors.py @@ -2,11 +2,9 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import Device -from .noise_sensors_noise_thresholds import ( - AbstractNoiseSensorsNoiseThresholds, - NoiseSensorsNoiseThresholds, -) +from ..null import Null +from ..resources import (Device) +from .noise_sensors_noise_thresholds import AbstractNoiseSensorsNoiseThresholds, NoiseSensorsNoiseThresholds from .noise_sensors_simulate import AbstractNoiseSensorsSimulate, NoiseSensorsSimulate @@ -23,16 +21,7 @@ def simulate(self) -> AbstractNoiseSensorsSimulate: raise NotImplementedError() @abc.abstractmethod - def list( - self, - *, - connect_webview_id: Optional[str] = None, - connected_account_id: Optional[str] = None, - customer_key: Optional[str] = None, - device_type: Optional[str] = None, - device_types: Optional[List[str]] = None, - manufacturer: Optional[str] = None, - ) -> List[Device]: + def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id: Optional[str] = None, customer_key: Optional[str] = None, device_type: Optional[str] = None, device_types: Optional[List[str]] = None, manufacturer: Optional[str] = None) -> List[Device]: """Returns a list of all `noise sensors `_. :param connect_webview_id: ID of the Connect Webview for which you want to list devices. @@ -55,9 +44,7 @@ class NoiseSensors(AbstractNoiseSensors): def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - self._noise_thresholds = NoiseSensorsNoiseThresholds( - client=client, defaults=defaults - ) + self._noise_thresholds = NoiseSensorsNoiseThresholds(client=client, defaults=defaults) self._simulate = NoiseSensorsSimulate(client=client, defaults=defaults) @property @@ -68,19 +55,8 @@ def noise_thresholds(self) -> NoiseSensorsNoiseThresholds: def simulate(self) -> NoiseSensorsSimulate: return self._simulate - @route_metadata( - path="/noise_sensors/list", has_required_parameters=False, has_pagination=False - ) - def list( - self, - *, - connect_webview_id: Optional[str] = None, - connected_account_id: Optional[str] = None, - customer_key: Optional[str] = None, - device_type: Optional[str] = None, - device_types: Optional[List[str]] = None, - manufacturer: Optional[str] = None, - ) -> List[Device]: + @route_metadata(path="/noise_sensors/list", has_required_parameters=False, has_pagination=False) + def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id: Optional[str] = None, customer_key: Optional[str] = None, device_type: Optional[str] = None, device_types: Optional[List[str]] = None, manufacturer: Optional[str] = None) -> List[Device]: """Returns a list of all `noise sensors `_. :param connect_webview_id: ID of the Connect Webview for which you want to list devices. diff --git a/seam/routes/noise_sensors_noise_thresholds.py b/seam/routes/noise_sensors_noise_thresholds.py index 54c90ce8..94550dcc 100644 --- a/seam/routes/noise_sensors_noise_thresholds.py +++ b/seam/routes/noise_sensors_noise_thresholds.py @@ -2,22 +2,14 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import NoiseThreshold +from ..null import Null +from ..resources import (NoiseThreshold) class AbstractNoiseSensorsNoiseThresholds(abc.ABC): @abc.abstractmethod - def create( - self, - *, - device_id: str, - ends_daily_at: str, - starts_daily_at: str, - name: Optional[str] = None, - noise_threshold_decibels: Optional[float] = None, - noise_threshold_nrs: Optional[float] = None, - ) -> NoiseThreshold: + def create(self, *, device_id: str, ends_daily_at: str, starts_daily_at: str, name: Optional[str] = None, noise_threshold_decibels: Optional[float] = None, noise_threshold_nrs: Optional[float] = None) -> NoiseThreshold: """Creates a new `noise threshold `_ for a `noise sensor `_. Thresholds represent the limits of noise tolerated at a property, which can be customized for each hour of the day. Each device has its own default thresholds, but you can use the Seam API to modify them. :param device_id: ID of the device for which you want to create a noise threshold. @@ -71,17 +63,7 @@ def list(self, *, device_id: str) -> List[NoiseThreshold]: raise NotImplementedError() @abc.abstractmethod - def update( - self, - *, - device_id: str, - noise_threshold_id: str, - ends_daily_at: Optional[str] = None, - name: Optional[str] = None, - noise_threshold_decibels: Optional[float] = None, - noise_threshold_nrs: Optional[float] = None, - starts_daily_at: Optional[str] = None, - ) -> None: + def update(self, *, device_id: str, noise_threshold_id: str, ends_daily_at: Optional[str] = None, name: Optional[str] = None, noise_threshold_decibels: Optional[float] = None, noise_threshold_nrs: Optional[float] = None, starts_daily_at: Optional[str] = None) -> None: """Updates a `noise threshold `_ for a `noise sensor `_. :param device_id: ID of the device that contains the noise threshold that you want to update. @@ -107,21 +89,8 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/noise_sensors/noise_thresholds/create", - has_required_parameters=True, - has_pagination=False, - ) - def create( - self, - *, - device_id: str, - ends_daily_at: str, - starts_daily_at: str, - name: Optional[str] = None, - noise_threshold_decibels: Optional[float] = None, - noise_threshold_nrs: Optional[float] = None, - ) -> NoiseThreshold: + @route_metadata(path="/noise_sensors/noise_thresholds/create", has_required_parameters=True, has_pagination=False) + def create(self, *, device_id: str, ends_daily_at: str, starts_daily_at: str, name: Optional[str] = None, noise_threshold_decibels: Optional[float] = None, noise_threshold_nrs: Optional[float] = None) -> NoiseThreshold: """Creates a new `noise threshold `_ for a `noise sensor `_. Thresholds represent the limits of noise tolerated at a property, which can be customized for each hour of the day. Each device has its own default thresholds, but you can use the Seam API to modify them. :param device_id: ID of the device for which you want to create a noise threshold. @@ -155,21 +124,13 @@ def create( json_payload["noise_threshold_nrs"] = noise_threshold_nrs if not json_payload: - raise ValueError( - "At least one parameter is required for /noise_sensors/noise_thresholds/create" - ) + raise ValueError("At least one parameter is required for /noise_sensors/noise_thresholds/create") - res = self.client.post( - "/noise_sensors/noise_thresholds/create", json=json_payload - ) + res = self.client.post("/noise_sensors/noise_thresholds/create", json=json_payload) return NoiseThreshold.from_dict(res["noise_threshold"]) - @route_metadata( - path="/noise_sensors/noise_thresholds/delete", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/noise_sensors/noise_thresholds/delete", has_required_parameters=True, has_pagination=False) def delete(self, *, device_id: str, noise_threshold_id: str) -> None: """Deletes a `noise threshold `_ from a `noise sensor `_. @@ -186,19 +147,13 @@ def delete(self, *, device_id: str, noise_threshold_id: str) -> None: params["noise_threshold_id"] = noise_threshold_id if not params: - raise ValueError( - "At least one parameter is required for /noise_sensors/noise_thresholds/delete" - ) + raise ValueError("At least one parameter is required for /noise_sensors/noise_thresholds/delete") self.client.delete("/noise_sensors/noise_thresholds/delete", params=params) return None - @route_metadata( - path="/noise_sensors/noise_thresholds/get", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/noise_sensors/noise_thresholds/get", has_required_parameters=True, has_pagination=False) def get(self, *, noise_threshold_id: str) -> NoiseThreshold: """Returns a specified `noise threshold `_ for a `noise sensor `_. @@ -213,19 +168,13 @@ def get(self, *, noise_threshold_id: str) -> NoiseThreshold: params["noise_threshold_id"] = noise_threshold_id if not params: - raise ValueError( - "At least one parameter is required for /noise_sensors/noise_thresholds/get" - ) + raise ValueError("At least one parameter is required for /noise_sensors/noise_thresholds/get") res = self.client.get("/noise_sensors/noise_thresholds/get", params=params) return NoiseThreshold.from_dict(res["noise_threshold"]) - @route_metadata( - path="/noise_sensors/noise_thresholds/list", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/noise_sensors/noise_thresholds/list", has_required_parameters=True, has_pagination=False) def list(self, *, device_id: str) -> List[NoiseThreshold]: """Returns a list of all `noise thresholds `_ for a `noise sensor `_. @@ -240,30 +189,14 @@ def list(self, *, device_id: str) -> List[NoiseThreshold]: params["device_id"] = device_id if not params: - raise ValueError( - "At least one parameter is required for /noise_sensors/noise_thresholds/list" - ) + raise ValueError("At least one parameter is required for /noise_sensors/noise_thresholds/list") res = self.client.get("/noise_sensors/noise_thresholds/list", params=params) return [NoiseThreshold.from_dict(item) for item in res["noise_thresholds"]] - @route_metadata( - path="/noise_sensors/noise_thresholds/update", - has_required_parameters=True, - has_pagination=False, - ) - def update( - self, - *, - device_id: str, - noise_threshold_id: str, - ends_daily_at: Optional[str] = None, - name: Optional[str] = None, - noise_threshold_decibels: Optional[float] = None, - noise_threshold_nrs: Optional[float] = None, - starts_daily_at: Optional[str] = None, - ) -> None: + @route_metadata(path="/noise_sensors/noise_thresholds/update", has_required_parameters=True, has_pagination=False) + def update(self, *, device_id: str, noise_threshold_id: str, ends_daily_at: Optional[str] = None, name: Optional[str] = None, noise_threshold_decibels: Optional[float] = None, noise_threshold_nrs: Optional[float] = None, starts_daily_at: Optional[str] = None) -> None: """Updates a `noise threshold `_ for a `noise sensor `_. :param device_id: ID of the device that contains the noise threshold that you want to update. @@ -299,9 +232,7 @@ def update( json_payload["starts_daily_at"] = starts_daily_at if not json_payload: - raise ValueError( - "At least one parameter is required for /noise_sensors/noise_thresholds/update" - ) + raise ValueError("At least one parameter is required for /noise_sensors/noise_thresholds/update") self.client.put("/noise_sensors/noise_thresholds/update", json=json_payload) diff --git a/seam/routes/noise_sensors_simulate.py b/seam/routes/noise_sensors_simulate.py index 6c527582..1d4e7b11 100644 --- a/seam/routes/noise_sensors_simulate.py +++ b/seam/routes/noise_sensors_simulate.py @@ -2,6 +2,7 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata +from ..null import Null class AbstractNoiseSensorsSimulate(abc.ABC): @@ -21,11 +22,7 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/noise_sensors/simulate/trigger_noise_threshold", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/noise_sensors/simulate/trigger_noise_threshold", has_required_parameters=True, has_pagination=False) def trigger_noise_threshold(self, *, device_id: str) -> None: """Simulates the triggering of a `noise threshold `_ for a `noise sensor `_ in a `sandbox workspace `_. @@ -38,12 +35,8 @@ def trigger_noise_threshold(self, *, device_id: str) -> None: json_payload["device_id"] = device_id if not json_payload: - raise ValueError( - "At least one parameter is required for /noise_sensors/simulate/trigger_noise_threshold" - ) + raise ValueError("At least one parameter is required for /noise_sensors/simulate/trigger_noise_threshold") - self.client.post( - "/noise_sensors/simulate/trigger_noise_threshold", json=json_payload - ) + self.client.post("/noise_sensors/simulate/trigger_noise_threshold", json=json_payload) return None diff --git a/seam/routes/phones.py b/seam/routes/phones.py index a00d49f4..4f623969 100644 --- a/seam/routes/phones.py +++ b/seam/routes/phones.py @@ -2,7 +2,8 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import Phone +from ..null import Null +from ..resources import (Phone) from .phones_simulate import AbstractPhonesSimulate, PhonesSimulate @@ -34,12 +35,7 @@ def get(self, *, device_id: str) -> Phone: raise NotImplementedError() @abc.abstractmethod - def list( - self, - *, - acs_credential_id: Optional[str] = None, - owner_user_identity_id: Optional[str] = None, - ) -> List[Phone]: + def list(self, *, acs_credential_id: Optional[str] = None, owner_user_identity_id: Optional[str] = None) -> List[Phone]: """Returns a list of all `phones `_. To filter the list of returned phones by a specific owner user identity or credential, include the ``owner_user_identity_id`` or ``acs_credential_id``, respectively, in the request body. :param acs_credential_id: ID of the `credential `_ by which you want to filter the list of returned phones. @@ -60,9 +56,7 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): def simulate(self) -> PhonesSimulate: return self._simulate - @route_metadata( - path="/phones/deactivate", has_required_parameters=True, has_pagination=False - ) + @route_metadata(path="/phones/deactivate", has_required_parameters=True, has_pagination=False) def deactivate(self, *, device_id: str) -> None: """Deactivates a phone, which is useful, for example, if a user has lost their phone. For more information, see `App User Lost Phone Process `_. @@ -75,17 +69,13 @@ def deactivate(self, *, device_id: str) -> None: params["device_id"] = device_id if not params: - raise ValueError( - "At least one parameter is required for /phones/deactivate" - ) + raise ValueError("At least one parameter is required for /phones/deactivate") self.client.delete("/phones/deactivate", params=params) return None - @route_metadata( - path="/phones/get", has_required_parameters=True, has_pagination=False - ) + @route_metadata(path="/phones/get", has_required_parameters=True, has_pagination=False) def get(self, *, device_id: str) -> Phone: """Returns a specified `phone `_. @@ -106,15 +96,8 @@ def get(self, *, device_id: str) -> Phone: return Phone.from_dict(res["phone"]) - @route_metadata( - path="/phones/list", has_required_parameters=False, has_pagination=False - ) - def list( - self, - *, - acs_credential_id: Optional[str] = None, - owner_user_identity_id: Optional[str] = None, - ) -> List[Phone]: + @route_metadata(path="/phones/list", has_required_parameters=False, has_pagination=False) + def list(self, *, acs_credential_id: Optional[str] = None, owner_user_identity_id: Optional[str] = None) -> List[Phone]: """Returns a list of all `phones `_. To filter the list of returned phones by a specific owner user identity or credential, include the ``owner_user_identity_id`` or ``acs_credential_id``, respectively, in the request body. :param acs_credential_id: ID of the `credential `_ by which you want to filter the list of returned phones. diff --git a/seam/routes/phones_simulate.py b/seam/routes/phones_simulate.py index 58b47aca..8b79cf0e 100644 --- a/seam/routes/phones_simulate.py +++ b/seam/routes/phones_simulate.py @@ -2,20 +2,14 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import Phone +from ..null import Null +from ..resources import (Phone) class AbstractPhonesSimulate(abc.ABC): @abc.abstractmethod - def create_sandbox_phone( - self, - *, - user_identity_id: str, - assa_abloy_metadata: Optional[Dict[str, Any]] = None, - custom_sdk_installation_id: Optional[str] = None, - phone_metadata: Optional[Dict[str, Any]] = None, - ) -> Phone: + def create_sandbox_phone(self, *, user_identity_id: str, assa_abloy_metadata: Optional[Dict[str, Any]] = None, custom_sdk_installation_id: Optional[str] = None, phone_metadata: Optional[Dict[str, Any]] = None) -> Phone: """Creates a new simulated phone in a `sandbox workspace `_. See also `Creating a Simulated Phone for a User Identity `_. :param user_identity_id: ID of the user identity that you want to associate with the simulated phone. @@ -37,19 +31,8 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/phones/simulate/create_sandbox_phone", - has_required_parameters=True, - has_pagination=False, - ) - def create_sandbox_phone( - self, - *, - user_identity_id: str, - assa_abloy_metadata: Optional[Dict[str, Any]] = None, - custom_sdk_installation_id: Optional[str] = None, - phone_metadata: Optional[Dict[str, Any]] = None, - ) -> Phone: + @route_metadata(path="/phones/simulate/create_sandbox_phone", has_required_parameters=True, has_pagination=False) + def create_sandbox_phone(self, *, user_identity_id: str, assa_abloy_metadata: Optional[Dict[str, Any]] = None, custom_sdk_installation_id: Optional[str] = None, phone_metadata: Optional[Dict[str, Any]] = None) -> Phone: """Creates a new simulated phone in a `sandbox workspace `_. See also `Creating a Simulated Phone for a User Identity `_. :param user_identity_id: ID of the user identity that you want to associate with the simulated phone. @@ -75,12 +58,8 @@ def create_sandbox_phone( json_payload["phone_metadata"] = phone_metadata if not json_payload: - raise ValueError( - "At least one parameter is required for /phones/simulate/create_sandbox_phone" - ) + raise ValueError("At least one parameter is required for /phones/simulate/create_sandbox_phone") - res = self.client.post( - "/phones/simulate/create_sandbox_phone", json=json_payload - ) + res = self.client.post("/phones/simulate/create_sandbox_phone", json=json_payload) return Phone.from_dict(res["phone"]) diff --git a/seam/routes/spaces.py b/seam/routes/spaces.py index b72ea972..9262eea1 100644 --- a/seam/routes/spaces.py +++ b/seam/routes/spaces.py @@ -2,7 +2,8 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import Space, Batch +from ..null import Null +from ..resources import (Space,Batch) class AbstractSpaces(abc.ABC): @@ -19,9 +20,7 @@ def add_acs_entrances(self, *, acs_entrance_ids: List[str], space_id: str) -> No raise NotImplementedError() @abc.abstractmethod - def add_connected_account( - self, *, connected_account_id: str, space_id: str - ) -> None: + def add_connected_account(self, *, connected_account_id: str, space_id: str) -> None: """Adds a `connected account `_ to a specific space. :param connected_account_id: ID of the connected account that you want to add to the space. @@ -43,17 +42,7 @@ def add_devices(self, *, device_ids: List[str], space_id: str) -> None: raise NotImplementedError() @abc.abstractmethod - def create( - self, - *, - name: str, - acs_entrance_ids: Optional[List[str]] = None, - connected_account_ids: Optional[List[str]] = None, - customer_data: Optional[Dict[str, Any]] = None, - customer_key: Optional[str] = None, - device_ids: Optional[List[str]] = None, - space_key: Optional[str] = None, - ) -> Space: + def create(self, *, name: str, acs_entrance_ids: Optional[List[str]] = None, connected_account_ids: Optional[List[str]] = None, customer_data: Optional[Dict[str, Any]] = None, customer_key: Optional[str] = None, device_ids: Optional[List[str]] = None, space_key: Optional[str] = None) -> Space: """Creates a new space. :param name: Name of the space that you want to create. @@ -85,9 +74,7 @@ def delete(self, *, space_id: str) -> None: raise NotImplementedError() @abc.abstractmethod - def get( - self, *, space_id: Optional[str] = None, space_key: Optional[str] = None - ) -> Space: + def get(self, *, space_id: Optional[str] = None, space_key: Optional[str] = None) -> Space: """Gets a space. :param space_id: ID of the space that you want to get. @@ -100,19 +87,12 @@ def get( raise NotImplementedError() @abc.abstractmethod - def get_related( - self, - *, - exclude: Optional[List[str]] = None, - include: Optional[List[str]] = None, - space_ids: Optional[List[str]] = None, - space_keys: Optional[List[str]] = None, - ) -> Batch: + def get_related(self, *, exclude: Optional[List[str]] = None, include: Optional[List[str]] = None, space_ids: Optional[List[str]] = None, space_keys: Optional[List[str]] = None) -> Batch: """Gets all related resources for one or more Spaces. - :param exclude: + :param exclude: - :param include: + :param include: :param space_ids: IDs of the spaces that you want to get along with their related resources. @@ -124,15 +104,7 @@ def get_related( raise NotImplementedError() @abc.abstractmethod - def list( - self, - *, - customer_key: Optional[str] = None, - limit: Optional[float] = None, - page_cursor: Optional[str] = None, - search: Optional[str] = None, - space_key: Optional[str] = None, - ) -> List[Space]: + def list(self, *, customer_key: Optional[str] = None, limit: Optional[float] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, space_key: Optional[str] = None) -> List[Space]: """Returns a list of all spaces. :param customer_key: Customer key for which you want to list spaces. @@ -149,9 +121,7 @@ def list( raise NotImplementedError() @abc.abstractmethod - def remove_acs_entrances( - self, *, acs_entrance_ids: List[str], space_id: str - ) -> None: + def remove_acs_entrances(self, *, acs_entrance_ids: List[str], space_id: str) -> None: """Removes `entrances `_ from a specific space. :param acs_entrance_ids: IDs of the entrances that you want to remove from the space. @@ -162,9 +132,7 @@ def remove_acs_entrances( raise NotImplementedError() @abc.abstractmethod - def remove_connected_account( - self, *, connected_account_id: str, space_id: str - ) -> None: + def remove_connected_account(self, *, connected_account_id: str, space_id: str) -> None: """Removes a `connected account `_ from a specific space. :param connected_account_id: ID of the connected account that you want to remove from the space. @@ -186,16 +154,7 @@ def remove_devices(self, *, device_ids: List[str], space_id: str) -> None: raise NotImplementedError() @abc.abstractmethod - def update( - self, - *, - acs_entrance_ids: Optional[List[str]] = None, - customer_data: Optional[Dict[str, Any]] = None, - device_ids: Optional[List[str]] = None, - name: Optional[str] = None, - space_id: Optional[str] = None, - space_key: Optional[str] = None, - ) -> Space: + def update(self, *, acs_entrance_ids: Optional[List[str]] = None, customer_data: Optional[Dict[str, Any]] = None, device_ids: Optional[List[str]] = None, name: Optional[str] = None, space_id: Optional[str] = None, space_key: Optional[str] = None) -> Space: """Updates an existing space. :param acs_entrance_ids: IDs of the entrances that you want to set for the space. If specified, this will replace all existing entrances. @@ -219,11 +178,7 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/spaces/add_acs_entrances", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/spaces/add_acs_entrances", has_required_parameters=True, has_pagination=False) def add_acs_entrances(self, *, acs_entrance_ids: List[str], space_id: str) -> None: """Adds `entrances `_ to a specific space. @@ -240,22 +195,14 @@ def add_acs_entrances(self, *, acs_entrance_ids: List[str], space_id: str) -> No json_payload["space_id"] = space_id if not json_payload: - raise ValueError( - "At least one parameter is required for /spaces/add_acs_entrances" - ) + raise ValueError("At least one parameter is required for /spaces/add_acs_entrances") self.client.put("/spaces/add_acs_entrances", json=json_payload) return None - @route_metadata( - path="/spaces/add_connected_account", - has_required_parameters=True, - has_pagination=False, - ) - def add_connected_account( - self, *, connected_account_id: str, space_id: str - ) -> None: + @route_metadata(path="/spaces/add_connected_account", has_required_parameters=True, has_pagination=False) + def add_connected_account(self, *, connected_account_id: str, space_id: str) -> None: """Adds a `connected account `_ to a specific space. :param connected_account_id: ID of the connected account that you want to add to the space. @@ -271,17 +218,13 @@ def add_connected_account( json_payload["space_id"] = space_id if not json_payload: - raise ValueError( - "At least one parameter is required for /spaces/add_connected_account" - ) + raise ValueError("At least one parameter is required for /spaces/add_connected_account") self.client.put("/spaces/add_connected_account", json=json_payload) return None - @route_metadata( - path="/spaces/add_devices", has_required_parameters=True, has_pagination=False - ) + @route_metadata(path="/spaces/add_devices", has_required_parameters=True, has_pagination=False) def add_devices(self, *, device_ids: List[str], space_id: str) -> None: """Adds devices to a specific space. @@ -298,28 +241,14 @@ def add_devices(self, *, device_ids: List[str], space_id: str) -> None: json_payload["space_id"] = space_id if not json_payload: - raise ValueError( - "At least one parameter is required for /spaces/add_devices" - ) + raise ValueError("At least one parameter is required for /spaces/add_devices") self.client.put("/spaces/add_devices", json=json_payload) return None - @route_metadata( - path="/spaces/create", has_required_parameters=True, has_pagination=False - ) - def create( - self, - *, - name: str, - acs_entrance_ids: Optional[List[str]] = None, - connected_account_ids: Optional[List[str]] = None, - customer_data: Optional[Dict[str, Any]] = None, - customer_key: Optional[str] = None, - device_ids: Optional[List[str]] = None, - space_key: Optional[str] = None, - ) -> Space: + @route_metadata(path="/spaces/create", has_required_parameters=True, has_pagination=False) + def create(self, *, name: str, acs_entrance_ids: Optional[List[str]] = None, connected_account_ids: Optional[List[str]] = None, customer_data: Optional[Dict[str, Any]] = None, customer_key: Optional[str] = None, device_ids: Optional[List[str]] = None, space_key: Optional[str] = None) -> Space: """Creates a new space. :param name: Name of the space that you want to create. @@ -363,9 +292,7 @@ def create( return Space.from_dict(res["space"]) - @route_metadata( - path="/spaces/delete", has_required_parameters=True, has_pagination=False - ) + @route_metadata(path="/spaces/delete", has_required_parameters=True, has_pagination=False) def delete(self, *, space_id: str) -> None: """Deletes a space. @@ -384,12 +311,8 @@ def delete(self, *, space_id: str) -> None: return None - @route_metadata( - path="/spaces/get", has_required_parameters=True, has_pagination=False - ) - def get( - self, *, space_id: Optional[str] = None, space_key: Optional[str] = None - ) -> Space: + @route_metadata(path="/spaces/get", has_required_parameters=True, has_pagination=False) + def get(self, *, space_id: Optional[str] = None, space_key: Optional[str] = None) -> Space: """Gets a space. :param space_id: ID of the space that you want to get. @@ -413,22 +336,13 @@ def get( return Space.from_dict(res["space"]) - @route_metadata( - path="/spaces/get_related", has_required_parameters=True, has_pagination=False - ) - def get_related( - self, - *, - exclude: Optional[List[str]] = None, - include: Optional[List[str]] = None, - space_ids: Optional[List[str]] = None, - space_keys: Optional[List[str]] = None, - ) -> Batch: + @route_metadata(path="/spaces/get_related", has_required_parameters=True, has_pagination=False) + def get_related(self, *, exclude: Optional[List[str]] = None, include: Optional[List[str]] = None, space_ids: Optional[List[str]] = None, space_keys: Optional[List[str]] = None) -> Batch: """Gets all related resources for one or more Spaces. - :param exclude: + :param exclude: - :param include: + :param include: :param space_ids: IDs of the spaces that you want to get along with their related resources. @@ -449,26 +363,14 @@ def get_related( json_payload["space_keys"] = space_keys if not json_payload: - raise ValueError( - "At least one parameter is required for /spaces/get_related" - ) + raise ValueError("At least one parameter is required for /spaces/get_related") res = self.client.post("/spaces/get_related", json=json_payload) return Batch.from_dict(res["batch"]) - @route_metadata( - path="/spaces/list", has_required_parameters=False, has_pagination=True - ) - def list( - self, - *, - customer_key: Optional[str] = None, - limit: Optional[float] = None, - page_cursor: Optional[str] = None, - search: Optional[str] = None, - space_key: Optional[str] = None, - ) -> List[Space]: + @route_metadata(path="/spaces/list", has_required_parameters=False, has_pagination=True) + def list(self, *, customer_key: Optional[str] = None, limit: Optional[float] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, space_key: Optional[str] = None) -> List[Space]: """Returns a list of all spaces. :param customer_key: Customer key for which you want to list spaces. @@ -499,14 +401,8 @@ def list( return [Space.from_dict(item) for item in res["spaces"]] - @route_metadata( - path="/spaces/remove_acs_entrances", - has_required_parameters=True, - has_pagination=False, - ) - def remove_acs_entrances( - self, *, acs_entrance_ids: List[str], space_id: str - ) -> None: + @route_metadata(path="/spaces/remove_acs_entrances", has_required_parameters=True, has_pagination=False) + def remove_acs_entrances(self, *, acs_entrance_ids: List[str], space_id: str) -> None: """Removes `entrances `_ from a specific space. :param acs_entrance_ids: IDs of the entrances that you want to remove from the space. @@ -522,22 +418,14 @@ def remove_acs_entrances( json_payload["space_id"] = space_id if not json_payload: - raise ValueError( - "At least one parameter is required for /spaces/remove_acs_entrances" - ) + raise ValueError("At least one parameter is required for /spaces/remove_acs_entrances") self.client.post("/spaces/remove_acs_entrances", json=json_payload) return None - @route_metadata( - path="/spaces/remove_connected_account", - has_required_parameters=True, - has_pagination=False, - ) - def remove_connected_account( - self, *, connected_account_id: str, space_id: str - ) -> None: + @route_metadata(path="/spaces/remove_connected_account", has_required_parameters=True, has_pagination=False) + def remove_connected_account(self, *, connected_account_id: str, space_id: str) -> None: """Removes a `connected account `_ from a specific space. :param connected_account_id: ID of the connected account that you want to remove from the space. @@ -553,19 +441,13 @@ def remove_connected_account( params["space_id"] = space_id if not params: - raise ValueError( - "At least one parameter is required for /spaces/remove_connected_account" - ) + raise ValueError("At least one parameter is required for /spaces/remove_connected_account") self.client.delete("/spaces/remove_connected_account", params=params) return None - @route_metadata( - path="/spaces/remove_devices", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/spaces/remove_devices", has_required_parameters=True, has_pagination=False) def remove_devices(self, *, device_ids: List[str], space_id: str) -> None: """Removes devices from a specific space. @@ -582,27 +464,14 @@ def remove_devices(self, *, device_ids: List[str], space_id: str) -> None: json_payload["space_id"] = space_id if not json_payload: - raise ValueError( - "At least one parameter is required for /spaces/remove_devices" - ) + raise ValueError("At least one parameter is required for /spaces/remove_devices") self.client.post("/spaces/remove_devices", json=json_payload) return None - @route_metadata( - path="/spaces/update", has_required_parameters=False, has_pagination=False - ) - def update( - self, - *, - acs_entrance_ids: Optional[List[str]] = None, - customer_data: Optional[Dict[str, Any]] = None, - device_ids: Optional[List[str]] = None, - name: Optional[str] = None, - space_id: Optional[str] = None, - space_key: Optional[str] = None, - ) -> Space: + @route_metadata(path="/spaces/update", has_required_parameters=False, has_pagination=False) + def update(self, *, acs_entrance_ids: Optional[List[str]] = None, customer_data: Optional[Dict[str, Any]] = None, device_ids: Optional[List[str]] = None, name: Optional[str] = None, space_id: Optional[str] = None, space_key: Optional[str] = None) -> Space: """Updates an existing space. :param acs_entrance_ids: IDs of the entrances that you want to set for the space. If specified, this will replace all existing entrances. diff --git a/seam/routes/thermostats.py b/seam/routes/thermostats.py index b5ec077f..b3f8dd33 100644 --- a/seam/routes/thermostats.py +++ b/seam/routes/thermostats.py @@ -2,11 +2,9 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import ActionAttempt, Device -from .thermostats_daily_programs import ( - AbstractThermostatsDailyPrograms, - ThermostatsDailyPrograms, -) +from ..null import Null +from ..resources import (ActionAttempt,Device) +from .thermostats_daily_programs import AbstractThermostatsDailyPrograms, ThermostatsDailyPrograms from .thermostats_schedules import AbstractThermostatsSchedules, ThermostatsSchedules from .thermostats_simulate import AbstractThermostatsSimulate, ThermostatsSimulate from ..modules.action_attempts import resolve_action_attempt @@ -30,13 +28,7 @@ def simulate(self) -> AbstractThermostatsSimulate: raise NotImplementedError() @abc.abstractmethod - def activate_climate_preset( - self, - *, - climate_preset_key: str, - device_id: str, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + def activate_climate_preset(self, *, climate_preset_key: str, device_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Activates a specified `climate preset `_ for a specified `thermostat `_. :param climate_preset_key: Climate preset key of the climate preset that you want to activate. @@ -51,14 +43,7 @@ def activate_climate_preset( raise NotImplementedError() @abc.abstractmethod - def cool( - self, - *, - device_id: str, - cooling_set_point_celsius: Optional[float] = None, - cooling_set_point_fahrenheit: Optional[float] = None, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + def cool(self, *, device_id: str, cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Sets a specified `thermostat `_ to `cool mode `_. :param device_id: ID of the thermostat device that you want to set to cool mode. @@ -75,22 +60,7 @@ def cool( raise NotImplementedError() @abc.abstractmethod - def create_climate_preset( - self, - *, - climate_preset_key: str, - device_id: str, - climate_preset_mode: Optional[str] = None, - cooling_set_point_celsius: Optional[float] = None, - cooling_set_point_fahrenheit: Optional[float] = None, - ecobee_metadata: Optional[Dict[str, Any]] = None, - fan_mode_setting: Optional[str] = None, - heating_set_point_celsius: Optional[float] = None, - heating_set_point_fahrenheit: Optional[float] = None, - hvac_mode_setting: Optional[str] = None, - manual_override_allowed: Optional[bool] = None, - name: Optional[str] = None, - ) -> None: + def create_climate_preset(self, *, climate_preset_key: str, device_id: str, climate_preset_mode: Optional[str] = None, cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, ecobee_metadata: Optional[Dict[str, Any]] = None, fan_mode_setting: Optional[str] = None, heating_set_point_celsius: Optional[float] = None, heating_set_point_fahrenheit: Optional[float] = None, hvac_mode_setting: Optional[str] = None, manual_override_allowed: Optional[bool] = None, name: Optional[Union[str, Null]] = None) -> None: """Creates a `climate preset `_ for a specified `thermostat `_. :param climate_preset_key: Unique key to identify the `climate preset `_. @@ -132,14 +102,7 @@ def delete_climate_preset(self, *, climate_preset_key: str, device_id: str) -> N raise NotImplementedError() @abc.abstractmethod - def heat( - self, - *, - device_id: str, - heating_set_point_celsius: Optional[float] = None, - heating_set_point_fahrenheit: Optional[float] = None, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + def heat(self, *, device_id: str, heating_set_point_celsius: Optional[float] = None, heating_set_point_fahrenheit: Optional[float] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Sets a specified `thermostat `_ to `heat mode `_. :param device_id: ID of the thermostat device that you want to set to heat mode. @@ -156,16 +119,7 @@ def heat( raise NotImplementedError() @abc.abstractmethod - def heat_cool( - self, - *, - device_id: str, - cooling_set_point_celsius: Optional[float] = None, - cooling_set_point_fahrenheit: Optional[float] = None, - heating_set_point_celsius: Optional[float] = None, - heating_set_point_fahrenheit: Optional[float] = None, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + def heat_cool(self, *, device_id: str, cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, heating_set_point_celsius: Optional[float] = None, heating_set_point_fahrenheit: Optional[float] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Sets a specified `thermostat `_ to `heat-cool ("auto") mode `_. :param device_id: ID of the thermostat device that you want to set to heat-cool mode. @@ -186,16 +140,7 @@ def heat_cool( raise NotImplementedError() @abc.abstractmethod - def list( - self, - *, - connect_webview_id: Optional[str] = None, - connected_account_id: Optional[str] = None, - customer_key: Optional[str] = None, - device_type: Optional[str] = None, - device_types: Optional[List[str]] = None, - manufacturer: Optional[str] = None, - ) -> List[Device]: + def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id: Optional[str] = None, customer_key: Optional[str] = None, device_type: Optional[str] = None, device_types: Optional[List[str]] = None, manufacturer: Optional[str] = None) -> List[Device]: """Returns a list of all `thermostats `_. :param connect_webview_id: ID of the Connect Webview for which you want to list devices. @@ -214,12 +159,7 @@ def list( raise NotImplementedError() @abc.abstractmethod - def off( - self, - *, - device_id: str, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + def off(self, *, device_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Sets a specified `thermostat `_ to `"off" mode `_. :param device_id: ID of the thermostat device that you want to set to off mode. @@ -232,9 +172,7 @@ def off( raise NotImplementedError() @abc.abstractmethod - def set_fallback_climate_preset( - self, *, climate_preset_key: str, device_id: str - ) -> None: + def set_fallback_climate_preset(self, *, climate_preset_key: str, device_id: str) -> None: """Sets a specified `climate preset `_ as the `"fallback" `_ preset for a specified `thermostat `_. :param climate_preset_key: Climate preset key of the climate preset that you want to set as the fallback climate preset. @@ -245,14 +183,7 @@ def set_fallback_climate_preset( raise NotImplementedError() @abc.abstractmethod - def set_fan_mode( - self, - *, - device_id: str, - fan_mode: Optional[str] = None, - fan_mode_setting: Optional[str] = None, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + def set_fan_mode(self, *, device_id: str, fan_mode: Optional[str] = None, fan_mode_setting: Optional[str] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Sets the `fan mode setting `_ for a specified `thermostat `_. :param device_id: ID of the thermostat device for which you want to set the fan mode. @@ -269,22 +200,12 @@ def set_fan_mode( raise NotImplementedError() @abc.abstractmethod - def set_hvac_mode( - self, - *, - device_id: str, - hvac_mode_setting: str, - cooling_set_point_celsius: Optional[float] = None, - cooling_set_point_fahrenheit: Optional[float] = None, - heating_set_point_celsius: Optional[float] = None, - heating_set_point_fahrenheit: Optional[float] = None, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + def set_hvac_mode(self, *, device_id: str, hvac_mode_setting: str, cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, heating_set_point_celsius: Optional[float] = None, heating_set_point_fahrenheit: Optional[float] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Sets the `HVAC mode `_ for a specified `thermostat `_. :param device_id: ID of the thermostat device for which you want to set the HVAC mode. - :param hvac_mode_setting: + :param hvac_mode_setting: :param cooling_set_point_celsius: `Cooling set point `_ in °C that you want to set for the thermostat. You must set one of the ``cooling_set_point`` parameters. @@ -302,15 +223,7 @@ def set_hvac_mode( raise NotImplementedError() @abc.abstractmethod - def set_temperature_threshold( - self, - *, - device_id: str, - lower_limit_celsius: Optional[float] = None, - lower_limit_fahrenheit: Optional[float] = None, - upper_limit_celsius: Optional[float] = None, - upper_limit_fahrenheit: Optional[float] = None, - ) -> None: + def set_temperature_threshold(self, *, device_id: str, lower_limit_celsius: Optional[Union[float, Null]] = None, lower_limit_fahrenheit: Optional[Union[float, Null]] = None, upper_limit_celsius: Optional[Union[float, Null]] = None, upper_limit_fahrenheit: Optional[Union[float, Null]] = None) -> None: """Sets a `temperature threshold `_ for a specified thermostat. Seam emits a ``thermostat.temperature_threshold_exceeded`` event and adds a warning on a thermostat if it reports a temperature outside the threshold range. :param device_id: ID of the thermostat device for which you want to set a temperature threshold. @@ -327,22 +240,7 @@ def set_temperature_threshold( raise NotImplementedError() @abc.abstractmethod - def update_climate_preset( - self, - *, - climate_preset_key: str, - device_id: str, - climate_preset_mode: Optional[str] = None, - cooling_set_point_celsius: Optional[float] = None, - cooling_set_point_fahrenheit: Optional[float] = None, - ecobee_metadata: Optional[Dict[str, Any]] = None, - fan_mode_setting: Optional[str] = None, - heating_set_point_celsius: Optional[float] = None, - heating_set_point_fahrenheit: Optional[float] = None, - hvac_mode_setting: Optional[str] = None, - manual_override_allowed: Optional[bool] = None, - name: Optional[str] = None, - ) -> None: + def update_climate_preset(self, *, climate_preset_key: str, device_id: str, climate_preset_mode: Optional[str] = None, cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, ecobee_metadata: Optional[Dict[str, Any]] = None, fan_mode_setting: Optional[str] = None, heating_set_point_celsius: Optional[float] = None, heating_set_point_fahrenheit: Optional[float] = None, hvac_mode_setting: Optional[str] = None, manual_override_allowed: Optional[bool] = None, name: Optional[Union[str, Null]] = None) -> None: """Updates a specified `climate preset `_ for a specified `thermostat `_. :param climate_preset_key: Unique key to identify the `climate preset `_. @@ -373,19 +271,7 @@ def update_climate_preset( raise NotImplementedError() @abc.abstractmethod - def update_weekly_program( - self, - *, - device_id: str, - friday_program_id: Optional[str] = None, - monday_program_id: Optional[str] = None, - saturday_program_id: Optional[str] = None, - sunday_program_id: Optional[str] = None, - thursday_program_id: Optional[str] = None, - tuesday_program_id: Optional[str] = None, - wednesday_program_id: Optional[str] = None, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + def update_weekly_program(self, *, device_id: str, friday_program_id: Optional[Union[str, Null]] = None, monday_program_id: Optional[Union[str, Null]] = None, saturday_program_id: Optional[Union[str, Null]] = None, sunday_program_id: Optional[Union[str, Null]] = None, thursday_program_id: Optional[Union[str, Null]] = None, tuesday_program_id: Optional[Union[str, Null]] = None, wednesday_program_id: Optional[Union[str, Null]] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Updates the thermostat weekly program for a thermostat device. To configure a weekly program, specify the ID of the daily program that you want to use for each day of the week. When you update a weekly program, the set of programs that you specify overwrites any previous weekly program for the thermostat. :param device_id: ID of the thermostat device for which you want to update the weekly program. @@ -416,9 +302,7 @@ class Thermostats(AbstractThermostats): def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - self._daily_programs = ThermostatsDailyPrograms( - client=client, defaults=defaults - ) + self._daily_programs = ThermostatsDailyPrograms(client=client, defaults=defaults) self._schedules = ThermostatsSchedules(client=client, defaults=defaults) self._simulate = ThermostatsSimulate(client=client, defaults=defaults) @@ -434,18 +318,8 @@ def schedules(self) -> ThermostatsSchedules: def simulate(self) -> ThermostatsSimulate: return self._simulate - @route_metadata( - path="/thermostats/activate_climate_preset", - has_required_parameters=True, - has_pagination=False, - ) - def activate_climate_preset( - self, - *, - climate_preset_key: str, - device_id: str, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + @route_metadata(path="/thermostats/activate_climate_preset", has_required_parameters=True, has_pagination=False) + def activate_climate_preset(self, *, climate_preset_key: str, device_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Activates a specified `climate preset `_ for a specified `thermostat `_. :param climate_preset_key: Climate preset key of the climate preset that you want to activate. @@ -465,13 +339,9 @@ def activate_climate_preset( json_payload["device_id"] = device_id if not json_payload: - raise ValueError( - "At least one parameter is required for /thermostats/activate_climate_preset" - ) + raise ValueError("At least one parameter is required for /thermostats/activate_climate_preset") - res = self.client.post( - "/thermostats/activate_climate_preset", json=json_payload - ) + res = self.client.post("/thermostats/activate_climate_preset", json=json_payload) wait_for_action_attempt = ( self.defaults.get("wait_for_action_attempt") @@ -482,20 +352,11 @@ def activate_climate_preset( return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt, + wait_for_action_attempt=wait_for_action_attempt ) - @route_metadata( - path="/thermostats/cool", has_required_parameters=True, has_pagination=False - ) - def cool( - self, - *, - device_id: str, - cooling_set_point_celsius: Optional[float] = None, - cooling_set_point_fahrenheit: Optional[float] = None, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + @route_metadata(path="/thermostats/cool", has_required_parameters=True, has_pagination=False) + def cool(self, *, device_id: str, cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Sets a specified `thermostat `_ to `cool mode `_. :param device_id: ID of the thermostat device that you want to set to cool mode. @@ -532,30 +393,11 @@ def cool( return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt, + wait_for_action_attempt=wait_for_action_attempt ) - @route_metadata( - path="/thermostats/create_climate_preset", - has_required_parameters=True, - has_pagination=False, - ) - def create_climate_preset( - self, - *, - climate_preset_key: str, - device_id: str, - climate_preset_mode: Optional[str] = None, - cooling_set_point_celsius: Optional[float] = None, - cooling_set_point_fahrenheit: Optional[float] = None, - ecobee_metadata: Optional[Dict[str, Any]] = None, - fan_mode_setting: Optional[str] = None, - heating_set_point_celsius: Optional[float] = None, - heating_set_point_fahrenheit: Optional[float] = None, - hvac_mode_setting: Optional[str] = None, - manual_override_allowed: Optional[bool] = None, - name: Optional[str] = None, - ) -> None: + @route_metadata(path="/thermostats/create_climate_preset", has_required_parameters=True, has_pagination=False) + def create_climate_preset(self, *, climate_preset_key: str, device_id: str, climate_preset_mode: Optional[str] = None, cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, ecobee_metadata: Optional[Dict[str, Any]] = None, fan_mode_setting: Optional[str] = None, heating_set_point_celsius: Optional[float] = None, heating_set_point_fahrenheit: Optional[float] = None, hvac_mode_setting: Optional[str] = None, manual_override_allowed: Optional[bool] = None, name: Optional[Union[str, Null]] = None) -> None: """Creates a `climate preset `_ for a specified `thermostat `_. :param climate_preset_key: Unique key to identify the `climate preset `_. @@ -611,19 +453,13 @@ def create_climate_preset( json_payload["name"] = name if not json_payload: - raise ValueError( - "At least one parameter is required for /thermostats/create_climate_preset" - ) + raise ValueError("At least one parameter is required for /thermostats/create_climate_preset") self.client.post("/thermostats/create_climate_preset", json=json_payload) return None - @route_metadata( - path="/thermostats/delete_climate_preset", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/thermostats/delete_climate_preset", has_required_parameters=True, has_pagination=False) def delete_climate_preset(self, *, climate_preset_key: str, device_id: str) -> None: """Deletes a specified `climate preset `_ for a specified `thermostat `_. @@ -640,25 +476,14 @@ def delete_climate_preset(self, *, climate_preset_key: str, device_id: str) -> N params["device_id"] = device_id if not params: - raise ValueError( - "At least one parameter is required for /thermostats/delete_climate_preset" - ) + raise ValueError("At least one parameter is required for /thermostats/delete_climate_preset") self.client.delete("/thermostats/delete_climate_preset", params=params) return None - @route_metadata( - path="/thermostats/heat", has_required_parameters=True, has_pagination=False - ) - def heat( - self, - *, - device_id: str, - heating_set_point_celsius: Optional[float] = None, - heating_set_point_fahrenheit: Optional[float] = None, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + @route_metadata(path="/thermostats/heat", has_required_parameters=True, has_pagination=False) + def heat(self, *, device_id: str, heating_set_point_celsius: Optional[float] = None, heating_set_point_fahrenheit: Optional[float] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Sets a specified `thermostat `_ to `heat mode `_. :param device_id: ID of the thermostat device that you want to set to heat mode. @@ -695,24 +520,11 @@ def heat( return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt, + wait_for_action_attempt=wait_for_action_attempt ) - @route_metadata( - path="/thermostats/heat_cool", - has_required_parameters=True, - has_pagination=False, - ) - def heat_cool( - self, - *, - device_id: str, - cooling_set_point_celsius: Optional[float] = None, - cooling_set_point_fahrenheit: Optional[float] = None, - heating_set_point_celsius: Optional[float] = None, - heating_set_point_fahrenheit: Optional[float] = None, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + @route_metadata(path="/thermostats/heat_cool", has_required_parameters=True, has_pagination=False) + def heat_cool(self, *, device_id: str, cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, heating_set_point_celsius: Optional[float] = None, heating_set_point_fahrenheit: Optional[float] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Sets a specified `thermostat `_ to `heat-cool ("auto") mode `_. :param device_id: ID of the thermostat device that you want to set to heat-cool mode. @@ -744,9 +556,7 @@ def heat_cool( json_payload["heating_set_point_fahrenheit"] = heating_set_point_fahrenheit if not json_payload: - raise ValueError( - "At least one parameter is required for /thermostats/heat_cool" - ) + raise ValueError("At least one parameter is required for /thermostats/heat_cool") res = self.client.post("/thermostats/heat_cool", json=json_payload) @@ -759,22 +569,11 @@ def heat_cool( return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt, + wait_for_action_attempt=wait_for_action_attempt ) - @route_metadata( - path="/thermostats/list", has_required_parameters=False, has_pagination=False - ) - def list( - self, - *, - connect_webview_id: Optional[str] = None, - connected_account_id: Optional[str] = None, - customer_key: Optional[str] = None, - device_type: Optional[str] = None, - device_types: Optional[List[str]] = None, - manufacturer: Optional[str] = None, - ) -> List[Device]: + @route_metadata(path="/thermostats/list", has_required_parameters=False, has_pagination=False) + def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id: Optional[str] = None, customer_key: Optional[str] = None, device_type: Optional[str] = None, device_types: Optional[List[str]] = None, manufacturer: Optional[str] = None) -> List[Device]: """Returns a list of all `thermostats `_. :param connect_webview_id: ID of the Connect Webview for which you want to list devices. @@ -809,15 +608,8 @@ def list( return [Device.from_dict(item) for item in res["devices"]] - @route_metadata( - path="/thermostats/off", has_required_parameters=True, has_pagination=False - ) - def off( - self, - *, - device_id: str, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + @route_metadata(path="/thermostats/off", has_required_parameters=True, has_pagination=False) + def off(self, *, device_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Sets a specified `thermostat `_ to `"off" mode `_. :param device_id: ID of the thermostat device that you want to set to off mode. @@ -846,17 +638,11 @@ def off( return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt, + wait_for_action_attempt=wait_for_action_attempt ) - @route_metadata( - path="/thermostats/set_fallback_climate_preset", - has_required_parameters=True, - has_pagination=False, - ) - def set_fallback_climate_preset( - self, *, climate_preset_key: str, device_id: str - ) -> None: + @route_metadata(path="/thermostats/set_fallback_climate_preset", has_required_parameters=True, has_pagination=False) + def set_fallback_climate_preset(self, *, climate_preset_key: str, device_id: str) -> None: """Sets a specified `climate preset `_ as the `"fallback" `_ preset for a specified `thermostat `_. :param climate_preset_key: Climate preset key of the climate preset that you want to set as the fallback climate preset. @@ -872,27 +658,14 @@ def set_fallback_climate_preset( json_payload["device_id"] = device_id if not json_payload: - raise ValueError( - "At least one parameter is required for /thermostats/set_fallback_climate_preset" - ) + raise ValueError("At least one parameter is required for /thermostats/set_fallback_climate_preset") self.client.post("/thermostats/set_fallback_climate_preset", json=json_payload) return None - @route_metadata( - path="/thermostats/set_fan_mode", - has_required_parameters=True, - has_pagination=False, - ) - def set_fan_mode( - self, - *, - device_id: str, - fan_mode: Optional[str] = None, - fan_mode_setting: Optional[str] = None, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + @route_metadata(path="/thermostats/set_fan_mode", has_required_parameters=True, has_pagination=False) + def set_fan_mode(self, *, device_id: str, fan_mode: Optional[str] = None, fan_mode_setting: Optional[str] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Sets the `fan mode setting `_ for a specified `thermostat `_. :param device_id: ID of the thermostat device for which you want to set the fan mode. @@ -916,9 +689,7 @@ def set_fan_mode( json_payload["fan_mode_setting"] = fan_mode_setting if not json_payload: - raise ValueError( - "At least one parameter is required for /thermostats/set_fan_mode" - ) + raise ValueError("At least one parameter is required for /thermostats/set_fan_mode") res = self.client.post("/thermostats/set_fan_mode", json=json_payload) @@ -931,30 +702,16 @@ def set_fan_mode( return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt, + wait_for_action_attempt=wait_for_action_attempt ) - @route_metadata( - path="/thermostats/set_hvac_mode", - has_required_parameters=True, - has_pagination=False, - ) - def set_hvac_mode( - self, - *, - device_id: str, - hvac_mode_setting: str, - cooling_set_point_celsius: Optional[float] = None, - cooling_set_point_fahrenheit: Optional[float] = None, - heating_set_point_celsius: Optional[float] = None, - heating_set_point_fahrenheit: Optional[float] = None, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + @route_metadata(path="/thermostats/set_hvac_mode", has_required_parameters=True, has_pagination=False) + def set_hvac_mode(self, *, device_id: str, hvac_mode_setting: str, cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, heating_set_point_celsius: Optional[float] = None, heating_set_point_fahrenheit: Optional[float] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Sets the `HVAC mode `_ for a specified `thermostat `_. :param device_id: ID of the thermostat device for which you want to set the HVAC mode. - :param hvac_mode_setting: + :param hvac_mode_setting: :param cooling_set_point_celsius: `Cooling set point `_ in °C that you want to set for the thermostat. You must set one of the ``cooling_set_point`` parameters. @@ -985,9 +742,7 @@ def set_hvac_mode( json_payload["heating_set_point_fahrenheit"] = heating_set_point_fahrenheit if not json_payload: - raise ValueError( - "At least one parameter is required for /thermostats/set_hvac_mode" - ) + raise ValueError("At least one parameter is required for /thermostats/set_hvac_mode") res = self.client.post("/thermostats/set_hvac_mode", json=json_payload) @@ -1000,23 +755,11 @@ def set_hvac_mode( return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt, + wait_for_action_attempt=wait_for_action_attempt ) - @route_metadata( - path="/thermostats/set_temperature_threshold", - has_required_parameters=True, - has_pagination=False, - ) - def set_temperature_threshold( - self, - *, - device_id: str, - lower_limit_celsius: Optional[float] = None, - lower_limit_fahrenheit: Optional[float] = None, - upper_limit_celsius: Optional[float] = None, - upper_limit_fahrenheit: Optional[float] = None, - ) -> None: + @route_metadata(path="/thermostats/set_temperature_threshold", has_required_parameters=True, has_pagination=False) + def set_temperature_threshold(self, *, device_id: str, lower_limit_celsius: Optional[Union[float, Null]] = None, lower_limit_fahrenheit: Optional[Union[float, Null]] = None, upper_limit_celsius: Optional[Union[float, Null]] = None, upper_limit_fahrenheit: Optional[Union[float, Null]] = None) -> None: """Sets a `temperature threshold `_ for a specified thermostat. Seam emits a ``thermostat.temperature_threshold_exceeded`` event and adds a warning on a thermostat if it reports a temperature outside the threshold range. :param device_id: ID of the thermostat device for which you want to set a temperature threshold. @@ -1044,35 +787,14 @@ def set_temperature_threshold( json_payload["upper_limit_fahrenheit"] = upper_limit_fahrenheit if not json_payload: - raise ValueError( - "At least one parameter is required for /thermostats/set_temperature_threshold" - ) + raise ValueError("At least one parameter is required for /thermostats/set_temperature_threshold") self.client.patch("/thermostats/set_temperature_threshold", json=json_payload) return None - @route_metadata( - path="/thermostats/update_climate_preset", - has_required_parameters=True, - has_pagination=False, - ) - def update_climate_preset( - self, - *, - climate_preset_key: str, - device_id: str, - climate_preset_mode: Optional[str] = None, - cooling_set_point_celsius: Optional[float] = None, - cooling_set_point_fahrenheit: Optional[float] = None, - ecobee_metadata: Optional[Dict[str, Any]] = None, - fan_mode_setting: Optional[str] = None, - heating_set_point_celsius: Optional[float] = None, - heating_set_point_fahrenheit: Optional[float] = None, - hvac_mode_setting: Optional[str] = None, - manual_override_allowed: Optional[bool] = None, - name: Optional[str] = None, - ) -> None: + @route_metadata(path="/thermostats/update_climate_preset", has_required_parameters=True, has_pagination=False) + def update_climate_preset(self, *, climate_preset_key: str, device_id: str, climate_preset_mode: Optional[str] = None, cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, ecobee_metadata: Optional[Dict[str, Any]] = None, fan_mode_setting: Optional[str] = None, heating_set_point_celsius: Optional[float] = None, heating_set_point_fahrenheit: Optional[float] = None, hvac_mode_setting: Optional[str] = None, manual_override_allowed: Optional[bool] = None, name: Optional[Union[str, Null]] = None) -> None: """Updates a specified `climate preset `_ for a specified `thermostat `_. :param climate_preset_key: Unique key to identify the `climate preset `_. @@ -1128,32 +850,14 @@ def update_climate_preset( json_payload["name"] = name if not json_payload: - raise ValueError( - "At least one parameter is required for /thermostats/update_climate_preset" - ) + raise ValueError("At least one parameter is required for /thermostats/update_climate_preset") self.client.patch("/thermostats/update_climate_preset", json=json_payload) return None - @route_metadata( - path="/thermostats/update_weekly_program", - has_required_parameters=True, - has_pagination=False, - ) - def update_weekly_program( - self, - *, - device_id: str, - friday_program_id: Optional[str] = None, - monday_program_id: Optional[str] = None, - saturday_program_id: Optional[str] = None, - sunday_program_id: Optional[str] = None, - thursday_program_id: Optional[str] = None, - tuesday_program_id: Optional[str] = None, - wednesday_program_id: Optional[str] = None, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + @route_metadata(path="/thermostats/update_weekly_program", has_required_parameters=True, has_pagination=False) + def update_weekly_program(self, *, device_id: str, friday_program_id: Optional[Union[str, Null]] = None, monday_program_id: Optional[Union[str, Null]] = None, saturday_program_id: Optional[Union[str, Null]] = None, sunday_program_id: Optional[Union[str, Null]] = None, thursday_program_id: Optional[Union[str, Null]] = None, tuesday_program_id: Optional[Union[str, Null]] = None, wednesday_program_id: Optional[Union[str, Null]] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Updates the thermostat weekly program for a thermostat device. To configure a weekly program, specify the ID of the daily program that you want to use for each day of the week. When you update a weekly program, the set of programs that you specify overwrites any previous weekly program for the thermostat. :param device_id: ID of the thermostat device for which you want to update the weekly program. @@ -1197,9 +901,7 @@ def update_weekly_program( json_payload["wednesday_program_id"] = wednesday_program_id if not json_payload: - raise ValueError( - "At least one parameter is required for /thermostats/update_weekly_program" - ) + raise ValueError("At least one parameter is required for /thermostats/update_weekly_program") res = self.client.post("/thermostats/update_weekly_program", json=json_payload) @@ -1212,5 +914,5 @@ def update_weekly_program( return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt, + wait_for_action_attempt=wait_for_action_attempt ) diff --git a/seam/routes/thermostats_daily_programs.py b/seam/routes/thermostats_daily_programs.py index cff60944..9245322b 100644 --- a/seam/routes/thermostats_daily_programs.py +++ b/seam/routes/thermostats_daily_programs.py @@ -2,16 +2,15 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import ThermostatDailyProgram, ActionAttempt +from ..null import Null +from ..resources import (ThermostatDailyProgram,ActionAttempt) from ..modules.action_attempts import resolve_action_attempt class AbstractThermostatsDailyPrograms(abc.ABC): @abc.abstractmethod - def create( - self, *, device_id: str, name: str, periods: List[Dict[str, Any]] - ) -> ThermostatDailyProgram: + def create(self, *, device_id: str, name: str, periods: List[Dict[str, Any]]) -> ThermostatDailyProgram: """Creates a new thermostat daily program. A daily program consists of a set of periods, where each period includes a start time and the key of a configured climate preset. Once you have defined a daily program, you can assign it to one or more days within a weekly program. :param device_id: ID of the thermostat device for which you want to create a daily program. @@ -35,14 +34,7 @@ def delete(self, *, thermostat_daily_program_id: str) -> None: raise NotImplementedError() @abc.abstractmethod - def update( - self, - *, - name: str, - periods: List[Dict[str, Any]], - thermostat_daily_program_id: str, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + def update(self, *, name: str, periods: List[Dict[str, Any]], thermostat_daily_program_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Updates a specified thermostat daily program. The periods that you specify overwrite any existing periods for the daily program. :param name: Name of the thermostat daily program that you want to update. @@ -64,14 +56,8 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/thermostats/daily_programs/create", - has_required_parameters=True, - has_pagination=False, - ) - def create( - self, *, device_id: str, name: str, periods: List[Dict[str, Any]] - ) -> ThermostatDailyProgram: + @route_metadata(path="/thermostats/daily_programs/create", has_required_parameters=True, has_pagination=False) + def create(self, *, device_id: str, name: str, periods: List[Dict[str, Any]]) -> ThermostatDailyProgram: """Creates a new thermostat daily program. A daily program consists of a set of periods, where each period includes a start time and the key of a configured climate preset. Once you have defined a daily program, you can assign it to one or more days within a weekly program. :param device_id: ID of the thermostat device for which you want to create a daily program. @@ -93,19 +79,13 @@ def create( json_payload["periods"] = periods if not json_payload: - raise ValueError( - "At least one parameter is required for /thermostats/daily_programs/create" - ) + raise ValueError("At least one parameter is required for /thermostats/daily_programs/create") res = self.client.post("/thermostats/daily_programs/create", json=json_payload) return ThermostatDailyProgram.from_dict(res["thermostat_daily_program"]) - @route_metadata( - path="/thermostats/daily_programs/delete", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/thermostats/daily_programs/delete", has_required_parameters=True, has_pagination=False) def delete(self, *, thermostat_daily_program_id: str) -> None: """Deletes a thermostat daily program. @@ -118,27 +98,14 @@ def delete(self, *, thermostat_daily_program_id: str) -> None: params["thermostat_daily_program_id"] = thermostat_daily_program_id if not params: - raise ValueError( - "At least one parameter is required for /thermostats/daily_programs/delete" - ) + raise ValueError("At least one parameter is required for /thermostats/daily_programs/delete") self.client.delete("/thermostats/daily_programs/delete", params=params) return None - @route_metadata( - path="/thermostats/daily_programs/update", - has_required_parameters=True, - has_pagination=False, - ) - def update( - self, - *, - name: str, - periods: List[Dict[str, Any]], - thermostat_daily_program_id: str, - wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, - ) -> ActionAttempt: + @route_metadata(path="/thermostats/daily_programs/update", has_required_parameters=True, has_pagination=False) + def update(self, *, name: str, periods: List[Dict[str, Any]], thermostat_daily_program_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Updates a specified thermostat daily program. The periods that you specify overwrite any existing periods for the daily program. :param name: Name of the thermostat daily program that you want to update. @@ -162,9 +129,7 @@ def update( json_payload["thermostat_daily_program_id"] = thermostat_daily_program_id if not json_payload: - raise ValueError( - "At least one parameter is required for /thermostats/daily_programs/update" - ) + raise ValueError("At least one parameter is required for /thermostats/daily_programs/update") res = self.client.patch("/thermostats/daily_programs/update", json=json_payload) @@ -177,5 +142,5 @@ def update( return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt, + wait_for_action_attempt=wait_for_action_attempt ) diff --git a/seam/routes/thermostats_schedules.py b/seam/routes/thermostats_schedules.py index d708149e..d806918f 100644 --- a/seam/routes/thermostats_schedules.py +++ b/seam/routes/thermostats_schedules.py @@ -2,23 +2,14 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import ThermostatSchedule +from ..null import Null +from ..resources import (ThermostatSchedule) class AbstractThermostatsSchedules(abc.ABC): @abc.abstractmethod - def create( - self, - *, - climate_preset_key: str, - device_id: str, - ends_at: str, - starts_at: str, - is_override_allowed: Optional[bool] = None, - max_override_period_minutes: Optional[int] = None, - name: Optional[str] = None, - ) -> ThermostatSchedule: + def create(self, *, climate_preset_key: str, device_id: str, ends_at: str, starts_at: str, is_override_allowed: Optional[bool] = None, max_override_period_minutes: Optional[Union[int, Null]] = None, name: Optional[str] = None) -> ThermostatSchedule: """Creates a new `thermostat schedule `_ for a specified `thermostat `_. :param climate_preset_key: Key of the `climate preset `_ to use for the new thermostat schedule. @@ -61,9 +52,7 @@ def get(self, *, thermostat_schedule_id: str) -> ThermostatSchedule: raise NotImplementedError() @abc.abstractmethod - def list( - self, *, device_id: str, user_identifier_key: Optional[str] = None - ) -> List[ThermostatSchedule]: + def list(self, *, device_id: str, user_identifier_key: Optional[str] = None) -> List[ThermostatSchedule]: """Returns a list of all `thermostat schedules `_ for a specified `thermostat `_. :param device_id: ID of the thermostat device for which you want to list schedules. @@ -76,17 +65,7 @@ def list( raise NotImplementedError() @abc.abstractmethod - def update( - self, - *, - thermostat_schedule_id: str, - climate_preset_key: Optional[str] = None, - ends_at: Optional[str] = None, - is_override_allowed: Optional[bool] = None, - max_override_period_minutes: Optional[int] = None, - name: Optional[str] = None, - starts_at: Optional[str] = None, - ) -> None: + def update(self, *, thermostat_schedule_id: str, climate_preset_key: Optional[str] = None, ends_at: Optional[str] = None, is_override_allowed: Optional[bool] = None, max_override_period_minutes: Optional[Union[int, Null]] = None, name: Optional[str] = None, starts_at: Optional[str] = None) -> None: """Updates a specified `thermostat schedule `_. :param thermostat_schedule_id: ID of the thermostat schedule that you want to update. @@ -112,22 +91,8 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/thermostats/schedules/create", - has_required_parameters=True, - has_pagination=False, - ) - def create( - self, - *, - climate_preset_key: str, - device_id: str, - ends_at: str, - starts_at: str, - is_override_allowed: Optional[bool] = None, - max_override_period_minutes: Optional[int] = None, - name: Optional[str] = None, - ) -> ThermostatSchedule: + @route_metadata(path="/thermostats/schedules/create", has_required_parameters=True, has_pagination=False) + def create(self, *, climate_preset_key: str, device_id: str, ends_at: str, starts_at: str, is_override_allowed: Optional[bool] = None, max_override_period_minutes: Optional[Union[int, Null]] = None, name: Optional[str] = None) -> ThermostatSchedule: """Creates a new `thermostat schedule `_ for a specified `thermostat `_. :param climate_preset_key: Key of the `climate preset `_ to use for the new thermostat schedule. @@ -165,19 +130,13 @@ def create( json_payload["name"] = name if not json_payload: - raise ValueError( - "At least one parameter is required for /thermostats/schedules/create" - ) + raise ValueError("At least one parameter is required for /thermostats/schedules/create") res = self.client.post("/thermostats/schedules/create", json=json_payload) return ThermostatSchedule.from_dict(res["thermostat_schedule"]) - @route_metadata( - path="/thermostats/schedules/delete", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/thermostats/schedules/delete", has_required_parameters=True, has_pagination=False) def delete(self, *, thermostat_schedule_id: str) -> None: """Deletes a `thermostat schedule `_ for a specified `thermostat `_. @@ -190,19 +149,13 @@ def delete(self, *, thermostat_schedule_id: str) -> None: params["thermostat_schedule_id"] = thermostat_schedule_id if not params: - raise ValueError( - "At least one parameter is required for /thermostats/schedules/delete" - ) + raise ValueError("At least one parameter is required for /thermostats/schedules/delete") self.client.delete("/thermostats/schedules/delete", params=params) return None - @route_metadata( - path="/thermostats/schedules/get", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/thermostats/schedules/get", has_required_parameters=True, has_pagination=False) def get(self, *, thermostat_schedule_id: str) -> ThermostatSchedule: """Returns a specified `thermostat schedule `_. @@ -217,22 +170,14 @@ def get(self, *, thermostat_schedule_id: str) -> ThermostatSchedule: params["thermostat_schedule_id"] = thermostat_schedule_id if not params: - raise ValueError( - "At least one parameter is required for /thermostats/schedules/get" - ) + raise ValueError("At least one parameter is required for /thermostats/schedules/get") res = self.client.get("/thermostats/schedules/get", params=params) return ThermostatSchedule.from_dict(res["thermostat_schedule"]) - @route_metadata( - path="/thermostats/schedules/list", - has_required_parameters=True, - has_pagination=False, - ) - def list( - self, *, device_id: str, user_identifier_key: Optional[str] = None - ) -> List[ThermostatSchedule]: + @route_metadata(path="/thermostats/schedules/list", has_required_parameters=True, has_pagination=False) + def list(self, *, device_id: str, user_identifier_key: Optional[str] = None) -> List[ThermostatSchedule]: """Returns a list of all `thermostat schedules `_ for a specified `thermostat `_. :param device_id: ID of the thermostat device for which you want to list schedules. @@ -250,32 +195,14 @@ def list( params["user_identifier_key"] = user_identifier_key if not params: - raise ValueError( - "At least one parameter is required for /thermostats/schedules/list" - ) + raise ValueError("At least one parameter is required for /thermostats/schedules/list") res = self.client.get("/thermostats/schedules/list", params=params) - return [ - ThermostatSchedule.from_dict(item) for item in res["thermostat_schedules"] - ] - - @route_metadata( - path="/thermostats/schedules/update", - has_required_parameters=True, - has_pagination=False, - ) - def update( - self, - *, - thermostat_schedule_id: str, - climate_preset_key: Optional[str] = None, - ends_at: Optional[str] = None, - is_override_allowed: Optional[bool] = None, - max_override_period_minutes: Optional[int] = None, - name: Optional[str] = None, - starts_at: Optional[str] = None, - ) -> None: + return [ThermostatSchedule.from_dict(item) for item in res["thermostat_schedules"]] + + @route_metadata(path="/thermostats/schedules/update", has_required_parameters=True, has_pagination=False) + def update(self, *, thermostat_schedule_id: str, climate_preset_key: Optional[str] = None, ends_at: Optional[str] = None, is_override_allowed: Optional[bool] = None, max_override_period_minutes: Optional[Union[int, Null]] = None, name: Optional[str] = None, starts_at: Optional[str] = None) -> None: """Updates a specified `thermostat schedule `_. :param thermostat_schedule_id: ID of the thermostat schedule that you want to update. @@ -311,9 +238,7 @@ def update( json_payload["starts_at"] = starts_at if not json_payload: - raise ValueError( - "At least one parameter is required for /thermostats/schedules/update" - ) + raise ValueError("At least one parameter is required for /thermostats/schedules/update") self.client.patch("/thermostats/schedules/update", json=json_payload) diff --git a/seam/routes/thermostats_simulate.py b/seam/routes/thermostats_simulate.py index 69dec843..f0ea820e 100644 --- a/seam/routes/thermostats_simulate.py +++ b/seam/routes/thermostats_simulate.py @@ -2,21 +2,13 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata +from ..null import Null class AbstractThermostatsSimulate(abc.ABC): @abc.abstractmethod - def hvac_mode_adjusted( - self, - *, - device_id: str, - hvac_mode: str, - cooling_set_point_celsius: Optional[float] = None, - cooling_set_point_fahrenheit: Optional[float] = None, - heating_set_point_celsius: Optional[float] = None, - heating_set_point_fahrenheit: Optional[float] = None, - ) -> None: + def hvac_mode_adjusted(self, *, device_id: str, hvac_mode: str, cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, heating_set_point_celsius: Optional[float] = None, heating_set_point_fahrenheit: Optional[float] = None) -> None: """Simulates having adjusted the `HVAC mode `_ for a `thermostat `_. Only applicable for `sandbox devices `_. See also `Testing Your Thermostat App with Simulate Endpoints `_. :param device_id: ID of the thermostat device for which you want to simulate having adjusted the HVAC mode. @@ -35,13 +27,7 @@ def hvac_mode_adjusted( raise NotImplementedError() @abc.abstractmethod - def temperature_reached( - self, - *, - device_id: str, - temperature_celsius: Optional[float] = None, - temperature_fahrenheit: Optional[float] = None, - ) -> None: + def temperature_reached(self, *, device_id: str, temperature_celsius: Optional[float] = None, temperature_fahrenheit: Optional[float] = None) -> None: """Simulates a `thermostat `_ reaching a specified temperature. Only applicable for `sandbox devices `_. See also `Testing Your Thermostat App with Simulate Endpoints `_. :param device_id: ID of the thermostat device that you want to simulate reaching a specified temperature. @@ -59,21 +45,8 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/thermostats/simulate/hvac_mode_adjusted", - has_required_parameters=True, - has_pagination=False, - ) - def hvac_mode_adjusted( - self, - *, - device_id: str, - hvac_mode: str, - cooling_set_point_celsius: Optional[float] = None, - cooling_set_point_fahrenheit: Optional[float] = None, - heating_set_point_celsius: Optional[float] = None, - heating_set_point_fahrenheit: Optional[float] = None, - ) -> None: + @route_metadata(path="/thermostats/simulate/hvac_mode_adjusted", has_required_parameters=True, has_pagination=False) + def hvac_mode_adjusted(self, *, device_id: str, hvac_mode: str, cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, heating_set_point_celsius: Optional[float] = None, heating_set_point_fahrenheit: Optional[float] = None) -> None: """Simulates having adjusted the `HVAC mode `_ for a `thermostat `_. Only applicable for `sandbox devices `_. See also `Testing Your Thermostat App with Simulate Endpoints `_. :param device_id: ID of the thermostat device for which you want to simulate having adjusted the HVAC mode. @@ -105,26 +78,14 @@ def hvac_mode_adjusted( json_payload["heating_set_point_fahrenheit"] = heating_set_point_fahrenheit if not json_payload: - raise ValueError( - "At least one parameter is required for /thermostats/simulate/hvac_mode_adjusted" - ) + raise ValueError("At least one parameter is required for /thermostats/simulate/hvac_mode_adjusted") self.client.post("/thermostats/simulate/hvac_mode_adjusted", json=json_payload) return None - @route_metadata( - path="/thermostats/simulate/temperature_reached", - has_required_parameters=True, - has_pagination=False, - ) - def temperature_reached( - self, - *, - device_id: str, - temperature_celsius: Optional[float] = None, - temperature_fahrenheit: Optional[float] = None, - ) -> None: + @route_metadata(path="/thermostats/simulate/temperature_reached", has_required_parameters=True, has_pagination=False) + def temperature_reached(self, *, device_id: str, temperature_celsius: Optional[float] = None, temperature_fahrenheit: Optional[float] = None) -> None: """Simulates a `thermostat `_ reaching a specified temperature. Only applicable for `sandbox devices `_. See also `Testing Your Thermostat App with Simulate Endpoints `_. :param device_id: ID of the thermostat device that you want to simulate reaching a specified temperature. @@ -144,9 +105,7 @@ def temperature_reached( json_payload["temperature_fahrenheit"] = temperature_fahrenheit if not json_payload: - raise ValueError( - "At least one parameter is required for /thermostats/simulate/temperature_reached" - ) + raise ValueError("At least one parameter is required for /thermostats/simulate/temperature_reached") self.client.post("/thermostats/simulate/temperature_reached", json=json_payload) diff --git a/seam/routes/user_identities.py b/seam/routes/user_identities.py index ab7442b2..ac481ca0 100644 --- a/seam/routes/user_identities.py +++ b/seam/routes/user_identities.py @@ -2,18 +2,9 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import ( - UserIdentity, - InstantKey, - Device, - AcsEntrance, - AcsSystem, - AcsUser, -) -from .user_identities_unmanaged import ( - AbstractUserIdentitiesUnmanaged, - UserIdentitiesUnmanaged, -) +from ..null import Null +from ..resources import (UserIdentity,InstantKey,Device,AcsEntrance,AcsSystem,AcsUser) +from .user_identities_unmanaged import AbstractUserIdentitiesUnmanaged, UserIdentitiesUnmanaged class AbstractUserIdentities(abc.ABC): @@ -24,17 +15,11 @@ def unmanaged(self) -> AbstractUserIdentitiesUnmanaged: raise NotImplementedError() @abc.abstractmethod - def add_acs_user( - self, - *, - acs_user_id: str, - user_identity_id: Optional[str] = None, - user_identity_key: Optional[str] = None, - ) -> None: + def add_acs_user(self, *, acs_user_id: str, user_identity_id: Optional[str] = None, user_identity_key: Optional[str] = None) -> None: """Adds a specified `access system user `_ to a specified `user identity `_. - + You must specify either ``user_identity_id`` or ``user_identity_key`` to identify the user identity. - + If ``user_identity_key`` is provided, but the user identity doesn't exist, a new user identity will be created automatically using information from the ACS user. :param acs_user_id: ID of the access system user that you want to add to the user identity. @@ -47,15 +32,7 @@ def add_acs_user( raise NotImplementedError() @abc.abstractmethod - def create( - self, - *, - acs_system_ids: Optional[List[str]] = None, - email_address: Optional[str] = None, - full_name: Optional[str] = None, - phone_number: Optional[str] = None, - user_identity_key: Optional[str] = None, - ) -> UserIdentity: + def create(self, *, acs_system_ids: Optional[List[str]] = None, email_address: Optional[Union[str, Null]] = None, full_name: Optional[Union[str, Null]] = None, phone_number: Optional[Union[str, Null]] = None, user_identity_key: Optional[Union[str, Null]] = None) -> UserIdentity: """Creates a new `user identity `_. :param acs_system_ids: List of access system IDs to associate with the new user identity through access system users. If there's no user with the same email address or phone number in the specified access systems, a new access system user is created. If there is an existing user with the same email or phone number in the specified access systems, the user is linked to the user identity. @@ -81,18 +58,12 @@ def delete(self, *, user_identity_id: str) -> None: raise NotImplementedError() @abc.abstractmethod - def generate_instant_key( - self, - *, - user_identity_id: str, - customization_profile_id: Optional[str] = None, - max_use_count: Optional[float] = None, - ) -> InstantKey: + def generate_instant_key(self, *, user_identity_id: str, customization_profile_id: Optional[str] = None, max_use_count: Optional[float] = None) -> InstantKey: """Generates a new `instant key `_ for a specified `user identity `_. :param user_identity_id: ID of the user identity for which you want to generate an instant key. - :param customization_profile_id: + :param customization_profile_id: :param max_use_count: Maximum number of times the instant key can be used. Default: 1. @@ -102,17 +73,12 @@ def generate_instant_key( raise NotImplementedError() @abc.abstractmethod - def get( - self, - *, - user_identity_id: Optional[str] = None, - user_identity_key: Optional[str] = None, - ) -> UserIdentity: + def get(self, *, user_identity_id: Optional[str] = None, user_identity_key: Optional[str] = None) -> UserIdentity: """Returns a specified `user identity `_. :param user_identity_id: ID of the user identity that you want to get. - :param user_identity_key: + :param user_identity_key: :returns: OK @@ -131,16 +97,7 @@ def grant_access_to_device(self, *, device_id: str, user_identity_id: str) -> No raise NotImplementedError() @abc.abstractmethod - def list( - self, - *, - created_before: Optional[str] = None, - credential_manager_acs_system_id: Optional[str] = None, - limit: Optional[int] = None, - page_cursor: Optional[str] = None, - search: Optional[str] = None, - user_identity_ids: Optional[List[str]] = None, - ) -> List[UserIdentity]: + def list(self, *, created_before: Optional[str] = None, credential_manager_acs_system_id: Optional[str] = None, limit: Optional[int] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, user_identity_ids: Optional[List[str]] = None) -> List[UserIdentity]: """Returns a list of all `user identities `_. :param created_before: Timestamp by which to limit returned user identities. Returns user identities created before this timestamp. @@ -225,15 +182,7 @@ def revoke_access_to_device(self, *, device_id: str, user_identity_id: str) -> N raise NotImplementedError() @abc.abstractmethod - def update( - self, - *, - user_identity_id: str, - email_address: Optional[str] = None, - full_name: Optional[str] = None, - phone_number: Optional[str] = None, - user_identity_key: Optional[str] = None, - ) -> None: + def update(self, *, user_identity_id: str, email_address: Optional[Union[str, Null]] = None, full_name: Optional[Union[str, Null]] = None, phone_number: Optional[Union[str, Null]] = None, user_identity_key: Optional[Union[str, Null]] = None) -> None: """Updates a specified `user identity `_. :param user_identity_id: ID of the user identity that you want to update. @@ -260,22 +209,12 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): def unmanaged(self) -> UserIdentitiesUnmanaged: return self._unmanaged - @route_metadata( - path="/user_identities/add_acs_user", - has_required_parameters=True, - has_pagination=False, - ) - def add_acs_user( - self, - *, - acs_user_id: str, - user_identity_id: Optional[str] = None, - user_identity_key: Optional[str] = None, - ) -> None: + @route_metadata(path="/user_identities/add_acs_user", has_required_parameters=True, has_pagination=False) + def add_acs_user(self, *, acs_user_id: str, user_identity_id: Optional[str] = None, user_identity_key: Optional[str] = None) -> None: """Adds a specified `access system user `_ to a specified `user identity `_. - + You must specify either ``user_identity_id`` or ``user_identity_key`` to identify the user identity. - + If ``user_identity_key`` is provided, but the user identity doesn't exist, a new user identity will be created automatically using information from the ACS user. :param acs_user_id: ID of the access system user that you want to add to the user identity. @@ -295,28 +234,14 @@ def add_acs_user( json_payload["user_identity_key"] = user_identity_key if not json_payload: - raise ValueError( - "At least one parameter is required for /user_identities/add_acs_user" - ) + raise ValueError("At least one parameter is required for /user_identities/add_acs_user") self.client.put("/user_identities/add_acs_user", json=json_payload) return None - @route_metadata( - path="/user_identities/create", - has_required_parameters=False, - has_pagination=False, - ) - def create( - self, - *, - acs_system_ids: Optional[List[str]] = None, - email_address: Optional[str] = None, - full_name: Optional[str] = None, - phone_number: Optional[str] = None, - user_identity_key: Optional[str] = None, - ) -> UserIdentity: + @route_metadata(path="/user_identities/create", has_required_parameters=False, has_pagination=False) + def create(self, *, acs_system_ids: Optional[List[str]] = None, email_address: Optional[Union[str, Null]] = None, full_name: Optional[Union[str, Null]] = None, phone_number: Optional[Union[str, Null]] = None, user_identity_key: Optional[Union[str, Null]] = None) -> UserIdentity: """Creates a new `user identity `_. :param acs_system_ids: List of access system IDs to associate with the new user identity through access system users. If there's no user with the same email address or phone number in the specified access systems, a new access system user is created. If there is an existing user with the same email or phone number in the specified access systems, the user is linked to the user identity. @@ -347,11 +272,7 @@ def create( return UserIdentity.from_dict(res["user_identity"]) - @route_metadata( - path="/user_identities/delete", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/user_identities/delete", has_required_parameters=True, has_pagination=False) def delete(self, *, user_identity_id: str) -> None: """Deletes a specified `user identity `_. This deletes the user identity and all associated resources, including any `credentials `_, `acs users `_ and `client sessions `_. @@ -364,31 +285,19 @@ def delete(self, *, user_identity_id: str) -> None: params["user_identity_id"] = user_identity_id if not params: - raise ValueError( - "At least one parameter is required for /user_identities/delete" - ) + raise ValueError("At least one parameter is required for /user_identities/delete") self.client.delete("/user_identities/delete", params=params) return None - @route_metadata( - path="/user_identities/generate_instant_key", - has_required_parameters=True, - has_pagination=False, - ) - def generate_instant_key( - self, - *, - user_identity_id: str, - customization_profile_id: Optional[str] = None, - max_use_count: Optional[float] = None, - ) -> InstantKey: + @route_metadata(path="/user_identities/generate_instant_key", has_required_parameters=True, has_pagination=False) + def generate_instant_key(self, *, user_identity_id: str, customization_profile_id: Optional[str] = None, max_use_count: Optional[float] = None) -> InstantKey: """Generates a new `instant key `_ for a specified `user identity `_. :param user_identity_id: ID of the user identity for which you want to generate an instant key. - :param customization_profile_id: + :param customization_profile_id: :param max_use_count: Maximum number of times the instant key can be used. Default: 1. @@ -405,30 +314,19 @@ def generate_instant_key( json_payload["max_use_count"] = max_use_count if not json_payload: - raise ValueError( - "At least one parameter is required for /user_identities/generate_instant_key" - ) + raise ValueError("At least one parameter is required for /user_identities/generate_instant_key") - res = self.client.post( - "/user_identities/generate_instant_key", json=json_payload - ) + res = self.client.post("/user_identities/generate_instant_key", json=json_payload) return InstantKey.from_dict(res["instant_key"]) - @route_metadata( - path="/user_identities/get", has_required_parameters=True, has_pagination=False - ) - def get( - self, - *, - user_identity_id: Optional[str] = None, - user_identity_key: Optional[str] = None, - ) -> UserIdentity: + @route_metadata(path="/user_identities/get", has_required_parameters=True, has_pagination=False) + def get(self, *, user_identity_id: Optional[str] = None, user_identity_key: Optional[str] = None) -> UserIdentity: """Returns a specified `user identity `_. :param user_identity_id: ID of the user identity that you want to get. - :param user_identity_key: + :param user_identity_key: :returns: OK @@ -441,19 +339,13 @@ def get( params["user_identity_key"] = user_identity_key if not params: - raise ValueError( - "At least one parameter is required for /user_identities/get" - ) + raise ValueError("At least one parameter is required for /user_identities/get") res = self.client.get("/user_identities/get", params=params) return UserIdentity.from_dict(res["user_identity"]) - @route_metadata( - path="/user_identities/grant_access_to_device", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/user_identities/grant_access_to_device", has_required_parameters=True, has_pagination=False) def grant_access_to_device(self, *, device_id: str, user_identity_id: str) -> None: """Grants a specified `user identity `_ access to a specified `device `_. @@ -470,27 +362,14 @@ def grant_access_to_device(self, *, device_id: str, user_identity_id: str) -> No json_payload["user_identity_id"] = user_identity_id if not json_payload: - raise ValueError( - "At least one parameter is required for /user_identities/grant_access_to_device" - ) + raise ValueError("At least one parameter is required for /user_identities/grant_access_to_device") self.client.put("/user_identities/grant_access_to_device", json=json_payload) return None - @route_metadata( - path="/user_identities/list", has_required_parameters=False, has_pagination=True - ) - def list( - self, - *, - created_before: Optional[str] = None, - credential_manager_acs_system_id: Optional[str] = None, - limit: Optional[int] = None, - page_cursor: Optional[str] = None, - search: Optional[str] = None, - user_identity_ids: Optional[List[str]] = None, - ) -> List[UserIdentity]: + @route_metadata(path="/user_identities/list", has_required_parameters=False, has_pagination=True) + def list(self, *, created_before: Optional[str] = None, credential_manager_acs_system_id: Optional[str] = None, limit: Optional[int] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, user_identity_ids: Optional[List[str]] = None) -> List[UserIdentity]: """Returns a list of all `user identities `_. :param created_before: Timestamp by which to limit returned user identities. Returns user identities created before this timestamp. @@ -511,9 +390,7 @@ def list( if created_before is not None: json_payload["created_before"] = created_before if credential_manager_acs_system_id is not None: - json_payload["credential_manager_acs_system_id"] = ( - credential_manager_acs_system_id - ) + json_payload["credential_manager_acs_system_id"] = credential_manager_acs_system_id if limit is not None: json_payload["limit"] = limit if page_cursor is not None: @@ -527,11 +404,7 @@ def list( return [UserIdentity.from_dict(item) for item in res["user_identities"]] - @route_metadata( - path="/user_identities/list_accessible_devices", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/user_identities/list_accessible_devices", has_required_parameters=True, has_pagination=False) def list_accessible_devices(self, *, user_identity_id: str) -> List[Device]: """Returns a list of all `devices `_ associated with a specified `user identity `_. This includes devices derived from the access grants assigned to the user identity and devices directly linked to the user identity. @@ -546,19 +419,13 @@ def list_accessible_devices(self, *, user_identity_id: str) -> List[Device]: params["user_identity_id"] = user_identity_id if not params: - raise ValueError( - "At least one parameter is required for /user_identities/list_accessible_devices" - ) + raise ValueError("At least one parameter is required for /user_identities/list_accessible_devices") res = self.client.get("/user_identities/list_accessible_devices", params=params) return [Device.from_dict(item) for item in res["devices"]] - @route_metadata( - path="/user_identities/list_accessible_entrances", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/user_identities/list_accessible_entrances", has_required_parameters=True, has_pagination=False) def list_accessible_entrances(self, *, user_identity_id: str) -> List[AcsEntrance]: """Returns a list of all `ACS entrances `_ accessible to a specified `user identity `_. This includes entrances derived from the access grants assigned to the user identity and entrances accessible through ACS users linked to the user identity. @@ -573,21 +440,13 @@ def list_accessible_entrances(self, *, user_identity_id: str) -> List[AcsEntranc params["user_identity_id"] = user_identity_id if not params: - raise ValueError( - "At least one parameter is required for /user_identities/list_accessible_entrances" - ) + raise ValueError("At least one parameter is required for /user_identities/list_accessible_entrances") - res = self.client.get( - "/user_identities/list_accessible_entrances", params=params - ) + res = self.client.get("/user_identities/list_accessible_entrances", params=params) return [AcsEntrance.from_dict(item) for item in res["acs_entrances"]] - @route_metadata( - path="/user_identities/list_acs_systems", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/user_identities/list_acs_systems", has_required_parameters=True, has_pagination=False) def list_acs_systems(self, *, user_identity_id: str) -> List[AcsSystem]: """Returns a list of all `access systems `_ associated with a specified `user identity `_. @@ -602,19 +461,13 @@ def list_acs_systems(self, *, user_identity_id: str) -> List[AcsSystem]: params["user_identity_id"] = user_identity_id if not params: - raise ValueError( - "At least one parameter is required for /user_identities/list_acs_systems" - ) + raise ValueError("At least one parameter is required for /user_identities/list_acs_systems") res = self.client.get("/user_identities/list_acs_systems", params=params) return [AcsSystem.from_dict(item) for item in res["acs_systems"]] - @route_metadata( - path="/user_identities/list_acs_users", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/user_identities/list_acs_users", has_required_parameters=True, has_pagination=False) def list_acs_users(self, *, user_identity_id: str) -> List[AcsUser]: """Returns a list of all `access system users `_ assigned to a specified `user identity `_. @@ -629,19 +482,13 @@ def list_acs_users(self, *, user_identity_id: str) -> List[AcsUser]: params["user_identity_id"] = user_identity_id if not params: - raise ValueError( - "At least one parameter is required for /user_identities/list_acs_users" - ) + raise ValueError("At least one parameter is required for /user_identities/list_acs_users") res = self.client.get("/user_identities/list_acs_users", params=params) return [AcsUser.from_dict(item) for item in res["acs_users"]] - @route_metadata( - path="/user_identities/remove_acs_user", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/user_identities/remove_acs_user", has_required_parameters=True, has_pagination=False) def remove_acs_user(self, *, acs_user_id: str, user_identity_id: str) -> None: """Removes a specified `access system user `_ from a specified `user identity `_. @@ -658,19 +505,13 @@ def remove_acs_user(self, *, acs_user_id: str, user_identity_id: str) -> None: params["user_identity_id"] = user_identity_id if not params: - raise ValueError( - "At least one parameter is required for /user_identities/remove_acs_user" - ) + raise ValueError("At least one parameter is required for /user_identities/remove_acs_user") self.client.delete("/user_identities/remove_acs_user", params=params) return None - @route_metadata( - path="/user_identities/revoke_access_to_device", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/user_identities/revoke_access_to_device", has_required_parameters=True, has_pagination=False) def revoke_access_to_device(self, *, device_id: str, user_identity_id: str) -> None: """Revokes access to a specified `device `_ from a specified `user identity `_. @@ -687,28 +528,14 @@ def revoke_access_to_device(self, *, device_id: str, user_identity_id: str) -> N params["user_identity_id"] = user_identity_id if not params: - raise ValueError( - "At least one parameter is required for /user_identities/revoke_access_to_device" - ) + raise ValueError("At least one parameter is required for /user_identities/revoke_access_to_device") self.client.delete("/user_identities/revoke_access_to_device", params=params) return None - @route_metadata( - path="/user_identities/update", - has_required_parameters=True, - has_pagination=False, - ) - def update( - self, - *, - user_identity_id: str, - email_address: Optional[str] = None, - full_name: Optional[str] = None, - phone_number: Optional[str] = None, - user_identity_key: Optional[str] = None, - ) -> None: + @route_metadata(path="/user_identities/update", has_required_parameters=True, has_pagination=False) + def update(self, *, user_identity_id: str, email_address: Optional[Union[str, Null]] = None, full_name: Optional[Union[str, Null]] = None, phone_number: Optional[Union[str, Null]] = None, user_identity_key: Optional[Union[str, Null]] = None) -> None: """Updates a specified `user identity `_. :param user_identity_id: ID of the user identity that you want to update. @@ -736,9 +563,7 @@ def update( json_payload["user_identity_key"] = user_identity_key if not json_payload: - raise ValueError( - "At least one parameter is required for /user_identities/update" - ) + raise ValueError("At least one parameter is required for /user_identities/update") self.client.patch("/user_identities/update", json=json_payload) diff --git a/seam/routes/user_identities_unmanaged.py b/seam/routes/user_identities_unmanaged.py index 31627ad2..91a71518 100644 --- a/seam/routes/user_identities_unmanaged.py +++ b/seam/routes/user_identities_unmanaged.py @@ -2,7 +2,8 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import UnmanagedUserIdentity +from ..null import Null +from ..resources import (UnmanagedUserIdentity) class AbstractUserIdentitiesUnmanaged(abc.ABC): @@ -19,14 +20,7 @@ def get(self, *, user_identity_id: str) -> UnmanagedUserIdentity: raise NotImplementedError() @abc.abstractmethod - def list( - self, - *, - created_before: Optional[str] = None, - limit: Optional[int] = None, - page_cursor: Optional[str] = None, - search: Optional[str] = None, - ) -> List[UnmanagedUserIdentity]: + def list(self, *, created_before: Optional[str] = None, limit: Optional[int] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None) -> List[UnmanagedUserIdentity]: """Returns a list of all unmanaged `user identities `_ (where is_managed = false). :param created_before: Timestamp by which to limit returned unmanaged user identities. Returns user identities created before this timestamp. @@ -41,15 +35,9 @@ def list( raise NotImplementedError() @abc.abstractmethod - def update( - self, - *, - is_managed: bool, - user_identity_id: str, - user_identity_key: Optional[str] = None, - ) -> None: + def update(self, *, is_managed: bool, user_identity_id: str, user_identity_key: Optional[str] = None) -> None: """Updates an unmanaged `user identity `_ to make it managed. - + This endpoint can only be used to convert unmanaged user identities to managed ones by setting ``is_managed`` to ``true``. It cannot be used to convert managed user identities back to unmanaged. :param is_managed: Must be set to true to convert the unmanaged user identity to managed. @@ -67,11 +55,7 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/user_identities/unmanaged/get", - has_required_parameters=True, - has_pagination=False, - ) + @route_metadata(path="/user_identities/unmanaged/get", has_required_parameters=True, has_pagination=False) def get(self, *, user_identity_id: str) -> UnmanagedUserIdentity: """Returns a specified unmanaged `user identity `_ (where is_managed = false). @@ -86,27 +70,14 @@ def get(self, *, user_identity_id: str) -> UnmanagedUserIdentity: params["user_identity_id"] = user_identity_id if not params: - raise ValueError( - "At least one parameter is required for /user_identities/unmanaged/get" - ) + raise ValueError("At least one parameter is required for /user_identities/unmanaged/get") res = self.client.get("/user_identities/unmanaged/get", params=params) return UnmanagedUserIdentity.from_dict(res["user_identity"]) - @route_metadata( - path="/user_identities/unmanaged/list", - has_required_parameters=False, - has_pagination=True, - ) - def list( - self, - *, - created_before: Optional[str] = None, - limit: Optional[int] = None, - page_cursor: Optional[str] = None, - search: Optional[str] = None, - ) -> List[UnmanagedUserIdentity]: + @route_metadata(path="/user_identities/unmanaged/list", has_required_parameters=False, has_pagination=True) + def list(self, *, created_before: Optional[str] = None, limit: Optional[int] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None) -> List[UnmanagedUserIdentity]: """Returns a list of all unmanaged `user identities `_ (where is_managed = false). :param created_before: Timestamp by which to limit returned unmanaged user identities. Returns user identities created before this timestamp. @@ -131,24 +102,12 @@ def list( res = self.client.get("/user_identities/unmanaged/list", params=params) - return [ - UnmanagedUserIdentity.from_dict(item) for item in res["user_identities"] - ] - - @route_metadata( - path="/user_identities/unmanaged/update", - has_required_parameters=True, - has_pagination=False, - ) - def update( - self, - *, - is_managed: bool, - user_identity_id: str, - user_identity_key: Optional[str] = None, - ) -> None: - """Updates an unmanaged `user identity `_ to make it managed. + return [UnmanagedUserIdentity.from_dict(item) for item in res["user_identities"]] + @route_metadata(path="/user_identities/unmanaged/update", has_required_parameters=True, has_pagination=False) + def update(self, *, is_managed: bool, user_identity_id: str, user_identity_key: Optional[str] = None) -> None: + """Updates an unmanaged `user identity `_ to make it managed. + This endpoint can only be used to convert unmanaged user identities to managed ones by setting ``is_managed`` to ``true``. It cannot be used to convert managed user identities back to unmanaged. :param is_managed: Must be set to true to convert the unmanaged user identity to managed. @@ -168,9 +127,7 @@ def update( json_payload["user_identity_key"] = user_identity_key if not json_payload: - raise ValueError( - "At least one parameter is required for /user_identities/unmanaged/update" - ) + raise ValueError("At least one parameter is required for /user_identities/unmanaged/update") self.client.patch("/user_identities/unmanaged/update", json=json_payload) diff --git a/seam/routes/webhooks.py b/seam/routes/webhooks.py index 8aa22a82..99da3a4d 100644 --- a/seam/routes/webhooks.py +++ b/seam/routes/webhooks.py @@ -2,7 +2,8 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import Webhook +from ..null import Null +from ..resources import (Webhook) class AbstractWebhooks(abc.ABC): @@ -64,9 +65,7 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/webhooks/create", has_required_parameters=True, has_pagination=False - ) + @route_metadata(path="/webhooks/create", has_required_parameters=True, has_pagination=False) def create(self, *, url: str, event_types: Optional[List[str]] = None) -> Webhook: """Creates a new `webhook `_. @@ -91,9 +90,7 @@ def create(self, *, url: str, event_types: Optional[List[str]] = None) -> Webhoo return Webhook.from_dict(res["webhook"]) - @route_metadata( - path="/webhooks/delete", has_required_parameters=True, has_pagination=False - ) + @route_metadata(path="/webhooks/delete", has_required_parameters=True, has_pagination=False) def delete(self, *, webhook_id: str) -> None: """Deletes a specified `webhook `_. @@ -112,9 +109,7 @@ def delete(self, *, webhook_id: str) -> None: return None - @route_metadata( - path="/webhooks/get", has_required_parameters=True, has_pagination=False - ) + @route_metadata(path="/webhooks/get", has_required_parameters=True, has_pagination=False) def get(self, *, webhook_id: str) -> Webhook: """Gets a specified `webhook `_. @@ -135,22 +130,19 @@ def get(self, *, webhook_id: str) -> Webhook: return Webhook.from_dict(res["webhook"]) - @route_metadata( - path="/webhooks/list", has_required_parameters=False, has_pagination=False - ) + @route_metadata(path="/webhooks/list", has_required_parameters=False, has_pagination=False) def list(self) -> List[Webhook]: """Returns a list of all `webhooks `_. :returns: OK""" params: Dict[str, Any] = {} + res = self.client.get("/webhooks/list", params=params) return [Webhook.from_dict(item) for item in res["webhooks"]] - @route_metadata( - path="/webhooks/update", has_required_parameters=True, has_pagination=False - ) + @route_metadata(path="/webhooks/update", has_required_parameters=True, has_pagination=False) def update(self, *, event_types: List[str], webhook_id: str) -> None: """Updates a specified `webhook `_. diff --git a/seam/routes/workspaces.py b/seam/routes/workspaces.py index 09bbb529..407df2c1 100644 --- a/seam/routes/workspaces.py +++ b/seam/routes/workspaces.py @@ -2,27 +2,15 @@ import abc from ..client import SeamHttpClient from ..route import route_metadata -from ..resources import Workspace, ActionAttempt +from ..null import Null +from ..resources import (Workspace,ActionAttempt) from ..modules.action_attempts import resolve_action_attempt class AbstractWorkspaces(abc.ABC): @abc.abstractmethod - def create( - self, - *, - name: str, - company_name: Optional[str] = None, - connect_partner_name: Optional[str] = None, - connect_webview_customization: Optional[Dict[str, Any]] = None, - is_sandbox: Optional[bool] = None, - organization_id: Optional[str] = None, - webview_logo_shape: Optional[str] = None, - webview_primary_button_color: Optional[str] = None, - webview_primary_button_text_color: Optional[str] = None, - webview_success_message: Optional[str] = None, - ) -> Workspace: + def create(self, *, name: str, company_name: Optional[str] = None, connect_partner_name: Optional[Union[str, Null]] = None, connect_webview_customization: Optional[Dict[str, Any]] = None, is_sandbox: Optional[bool] = None, organization_id: Optional[str] = None, webview_logo_shape: Optional[str] = None, webview_primary_button_color: Optional[str] = None, webview_primary_button_text_color: Optional[str] = None, webview_success_message: Optional[str] = None) -> Workspace: """Creates a new `workspace `_. :param name: Name of the new workspace. @@ -65,9 +53,7 @@ def list(self) -> List[Workspace]: raise NotImplementedError() @abc.abstractmethod - def reset_sandbox( - self, *, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None - ) -> ActionAttempt: + def reset_sandbox(self, *, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Resets the `sandbox workspace `_ associated with the authentication value. Note that this endpoint is only available for sandbox workspaces. :param wait_for_action_attempt: Whether, and for how long, to wait for the action attempt to finish. @@ -76,16 +62,7 @@ def reset_sandbox( raise NotImplementedError() @abc.abstractmethod - def update( - self, - *, - connect_partner_name: Optional[str] = None, - connect_webview_customization: Optional[Dict[str, Any]] = None, - is_publishable_key_auth_enabled: Optional[bool] = None, - is_suspended: Optional[bool] = None, - name: Optional[str] = None, - organization_id: Optional[str] = None, - ) -> None: + def update(self, *, connect_partner_name: Optional[str] = None, connect_webview_customization: Optional[Dict[str, Any]] = None, is_publishable_key_auth_enabled: Optional[bool] = None, is_suspended: Optional[bool] = None, name: Optional[str] = None, organization_id: Optional[str] = None) -> None: """Updates the `workspace `_ associated with the authentication value. :param connect_partner_name: Connect partner name for the workspace. @@ -98,8 +75,7 @@ def update( :param name: Name of the workspace. - :param organization_id: ID of the organization to assign the workspace to. The authenticated user must be the owner of the workspace and an admin of the target organization. - """ + :param organization_id: ID of the organization to assign the workspace to. The authenticated user must be the owner of the workspace and an admin of the target organization.""" raise NotImplementedError() @@ -108,23 +84,8 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata( - path="/workspaces/create", has_required_parameters=True, has_pagination=False - ) - def create( - self, - *, - name: str, - company_name: Optional[str] = None, - connect_partner_name: Optional[str] = None, - connect_webview_customization: Optional[Dict[str, Any]] = None, - is_sandbox: Optional[bool] = None, - organization_id: Optional[str] = None, - webview_logo_shape: Optional[str] = None, - webview_primary_button_color: Optional[str] = None, - webview_primary_button_text_color: Optional[str] = None, - webview_success_message: Optional[str] = None, - ) -> Workspace: + @route_metadata(path="/workspaces/create", has_required_parameters=True, has_pagination=False) + def create(self, *, name: str, company_name: Optional[str] = None, connect_partner_name: Optional[Union[str, Null]] = None, connect_webview_customization: Optional[Dict[str, Any]] = None, is_sandbox: Optional[bool] = None, organization_id: Optional[str] = None, webview_logo_shape: Optional[str] = None, webview_primary_button_color: Optional[str] = None, webview_primary_button_text_color: Optional[str] = None, webview_success_message: Optional[str] = None) -> Workspace: """Creates a new `workspace `_. :param name: Name of the new workspace. @@ -159,9 +120,7 @@ def create( if connect_partner_name is not None: json_payload["connect_partner_name"] = connect_partner_name if connect_webview_customization is not None: - json_payload["connect_webview_customization"] = ( - connect_webview_customization - ) + json_payload["connect_webview_customization"] = connect_webview_customization if is_sandbox is not None: json_payload["is_sandbox"] = is_sandbox if organization_id is not None: @@ -171,55 +130,43 @@ def create( if webview_primary_button_color is not None: json_payload["webview_primary_button_color"] = webview_primary_button_color if webview_primary_button_text_color is not None: - json_payload["webview_primary_button_text_color"] = ( - webview_primary_button_text_color - ) + json_payload["webview_primary_button_text_color"] = webview_primary_button_text_color if webview_success_message is not None: json_payload["webview_success_message"] = webview_success_message if not json_payload: - raise ValueError( - "At least one parameter is required for /workspaces/create" - ) + raise ValueError("At least one parameter is required for /workspaces/create") res = self.client.post("/workspaces/create", json=json_payload) return Workspace.from_dict(res["workspace"]) - @route_metadata( - path="/workspaces/get", has_required_parameters=False, has_pagination=False - ) + @route_metadata(path="/workspaces/get", has_required_parameters=False, has_pagination=False) def get(self) -> Workspace: """Returns the `workspace `_ associated with the authentication value. :returns: OK""" params: Dict[str, Any] = {} + res = self.client.get("/workspaces/get", params=params) return Workspace.from_dict(res["workspace"]) - @route_metadata( - path="/workspaces/list", has_required_parameters=False, has_pagination=False - ) + @route_metadata(path="/workspaces/list", has_required_parameters=False, has_pagination=False) def list(self) -> List[Workspace]: """Returns a list of `workspaces `_ associated with the authentication value. :returns: OK""" params: Dict[str, Any] = {} + res = self.client.get("/workspaces/list", params=params) return [Workspace.from_dict(item) for item in res["workspaces"]] - @route_metadata( - path="/workspaces/reset_sandbox", - has_required_parameters=False, - has_pagination=False, - ) - def reset_sandbox( - self, *, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None - ) -> ActionAttempt: + @route_metadata(path="/workspaces/reset_sandbox", has_required_parameters=False, has_pagination=False) + def reset_sandbox(self, *, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: """Resets the `sandbox workspace `_ associated with the authentication value. Note that this endpoint is only available for sandbox workspaces. :param wait_for_action_attempt: Whether, and for how long, to wait for the action attempt to finish. @@ -227,6 +174,7 @@ def reset_sandbox( :returns: OK""" json_payload: Dict[str, Any] = {} + res = self.client.post("/workspaces/reset_sandbox", json=json_payload) wait_for_action_attempt = ( @@ -238,22 +186,11 @@ def reset_sandbox( return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt, + wait_for_action_attempt=wait_for_action_attempt ) - @route_metadata( - path="/workspaces/update", has_required_parameters=False, has_pagination=False - ) - def update( - self, - *, - connect_partner_name: Optional[str] = None, - connect_webview_customization: Optional[Dict[str, Any]] = None, - is_publishable_key_auth_enabled: Optional[bool] = None, - is_suspended: Optional[bool] = None, - name: Optional[str] = None, - organization_id: Optional[str] = None, - ) -> None: + @route_metadata(path="/workspaces/update", has_required_parameters=False, has_pagination=False) + def update(self, *, connect_partner_name: Optional[str] = None, connect_webview_customization: Optional[Dict[str, Any]] = None, is_publishable_key_auth_enabled: Optional[bool] = None, is_suspended: Optional[bool] = None, name: Optional[str] = None, organization_id: Optional[str] = None) -> None: """Updates the `workspace `_ associated with the authentication value. :param connect_partner_name: Connect partner name for the workspace. @@ -266,20 +203,15 @@ def update( :param name: Name of the workspace. - :param organization_id: ID of the organization to assign the workspace to. The authenticated user must be the owner of the workspace and an admin of the target organization. - """ + :param organization_id: ID of the organization to assign the workspace to. The authenticated user must be the owner of the workspace and an admin of the target organization.""" json_payload: Dict[str, Any] = {} if connect_partner_name is not None: json_payload["connect_partner_name"] = connect_partner_name if connect_webview_customization is not None: - json_payload["connect_webview_customization"] = ( - connect_webview_customization - ) + json_payload["connect_webview_customization"] = connect_webview_customization if is_publishable_key_auth_enabled is not None: - json_payload["is_publishable_key_auth_enabled"] = ( - is_publishable_key_auth_enabled - ) + json_payload["is_publishable_key_auth_enabled"] = is_publishable_key_auth_enabled if is_suspended is not None: json_payload["is_suspended"] = is_suspended if name is not None: From 75710311a0975209001c94d87bdf1708c73de358 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 00:18:55 +0000 Subject: [PATCH 04/11] test: upgrade fake-seam-connect to 2.0.3 The fake Seam Connect server now parses URL search params with @seamapi/url-search-params-parser, the inverse of the serialization standard this SDK implements, and fixes the status codes it returns for unauthenticated requests. The previous release did not have the parser, so it read the serialization of the empty array as an array containing the empty string and then failed to serialize that back into its pagination links, answering a request for device_ids= with a 500. It now reads that as the empty array. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PpqwDhZmyikGbjmCPqFW2A --- package-lock.json | 426 ++++++++++++++++++++++------------------------ package.json | 2 +- 2 files changed, 205 insertions(+), 223 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3e6a6f45..9f3147c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,7 @@ "name": "@seamapi/python", "devDependencies": { "@seamapi/blueprint": "^1.5.1", - "@seamapi/fake-seam-connect": "1.86.0", + "@seamapi/fake-seam-connect": "2.0.3", "@seamapi/smith": "^1.1.0", "@seamapi/types": "1.1001.0", "change-case": "^5.4.4", @@ -19,9 +19,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", - "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", "cpu": [ "ppc64" ], @@ -37,9 +37,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", - "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", "cpu": [ "arm" ], @@ -55,9 +55,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", - "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", "cpu": [ "arm64" ], @@ -73,9 +73,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", - "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", "cpu": [ "x64" ], @@ -91,9 +91,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", - "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", "cpu": [ "arm64" ], @@ -109,9 +109,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", - "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", "cpu": [ "x64" ], @@ -127,9 +127,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", - "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", "cpu": [ "arm64" ], @@ -145,9 +145,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", - "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", "cpu": [ "x64" ], @@ -163,9 +163,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", - "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", "cpu": [ "arm" ], @@ -181,9 +181,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", - "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", "cpu": [ "arm64" ], @@ -199,9 +199,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", - "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", "cpu": [ "ia32" ], @@ -217,9 +217,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", - "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", "cpu": [ "loong64" ], @@ -235,9 +235,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", - "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", "cpu": [ "mips64el" ], @@ -253,9 +253,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", - "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", "cpu": [ "ppc64" ], @@ -271,9 +271,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", - "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", "cpu": [ "riscv64" ], @@ -289,9 +289,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", - "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", "cpu": [ "s390x" ], @@ -307,9 +307,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", - "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", "cpu": [ "x64" ], @@ -325,9 +325,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", - "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", "cpu": [ "arm64" ], @@ -343,9 +343,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", - "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", "cpu": [ "x64" ], @@ -361,9 +361,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", - "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", "cpu": [ "arm64" ], @@ -379,9 +379,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", - "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", "cpu": [ "x64" ], @@ -397,9 +397,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", - "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", "cpu": [ "arm64" ], @@ -415,9 +415,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", - "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", "cpu": [ "x64" ], @@ -433,9 +433,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", - "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", "cpu": [ "arm64" ], @@ -451,9 +451,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", - "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", "cpu": [ "ia32" ], @@ -469,9 +469,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", - "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", "cpu": [ "x64" ], @@ -801,38 +801,20 @@ "npm": ">=10.0.0" } }, - "node_modules/@seamapi/fake-devicedb": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@seamapi/fake-devicedb/-/fake-devicedb-1.6.1.tgz", - "integrity": "sha512-w4Ar/s2kPnE5ExJSlpD3sKL8lkF+rLHRROArIRxtR2reHfnDSVwnDt9TzBYkHqgMP/x4o7LUzlRRpobn2xn24A==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=18.12.0", - "npm": ">= 9.0.0" - }, - "optionalDependencies": { - "zod": "^3.21.4", - "zustand": "^4.3.7", - "zustand-hoist": "^2.0.0" - } - }, "node_modules/@seamapi/fake-seam-connect": { - "version": "1.86.0", - "resolved": "https://registry.npmjs.org/@seamapi/fake-seam-connect/-/fake-seam-connect-1.86.0.tgz", - "integrity": "sha512-iO5fwtSIPhzmIiLxrFDtCYF/7HTb+ywcGmc3WzWt8Sr0bLQlmwCyTJ8YYZy++Hx0FsnNpa5AmlJuJGul9Y5gZA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@seamapi/fake-seam-connect/-/fake-seam-connect-2.0.3.tgz", + "integrity": "sha512-XJsdSBvBNpm/k7CUttFSOxM41WlOY/65bTXCdUKewr5k1Pj31g2Dmz2mVUCNB5nPgLTC4gXbzpB+2eyakzkD0A==", "dev": true, "license": "MIT", "bin": { "fake-seam-connect": "dist/server.js" }, "engines": { - "node": ">=18.12.0", - "npm": ">= 9.0.0" + "node": ">=22.12.0", + "npm": ">=10.0.0" }, "optionalDependencies": { - "@seamapi/fake-devicedb": ">=1.0.0-rc.0", "zustand": "^4.3.7", "zustand-hoist": "^2.0.0" } @@ -965,17 +947,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.67.0.tgz", - "integrity": "sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", + "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.67.0", - "@typescript-eslint/type-utils": "8.67.0", - "@typescript-eslint/utils": "8.67.0", - "@typescript-eslint/visitor-keys": "8.67.0", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/type-utils": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -988,7 +970,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.67.0", + "@typescript-eslint/parser": "^8.65.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } @@ -1004,16 +986,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.67.0.tgz", - "integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.65.0.tgz", + "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.67.0", - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/typescript-estree": "8.67.0", - "@typescript-eslint/visitor-keys": "8.67.0", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", "debug": "^4.4.3" }, "engines": { @@ -1029,14 +1011,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.67.0.tgz", - "integrity": "sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.67.0", - "@typescript-eslint/types": "^8.67.0", + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", "debug": "^4.4.3" }, "engines": { @@ -1051,14 +1033,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.67.0.tgz", - "integrity": "sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/visitor-keys": "8.67.0" + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1069,9 +1051,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.67.0.tgz", - "integrity": "sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", "dev": true, "license": "MIT", "engines": { @@ -1086,15 +1068,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.67.0.tgz", - "integrity": "sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz", + "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/typescript-estree": "8.67.0", - "@typescript-eslint/utils": "8.67.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -1111,9 +1093,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.67.0.tgz", - "integrity": "sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", "dev": true, "license": "MIT", "engines": { @@ -1125,16 +1107,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.67.0.tgz", - "integrity": "sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.67.0", - "@typescript-eslint/tsconfig-utils": "8.67.0", - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/visitor-keys": "8.67.0", + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -1163,9 +1145,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", + "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", "dev": true, "license": "MIT", "dependencies": { @@ -1205,16 +1187,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.67.0.tgz", - "integrity": "sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", + "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.67.0", - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/typescript-estree": "8.67.0" + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1229,13 +1211,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.67.0.tgz", - "integrity": "sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", + "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/types": "8.65.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -1527,9 +1509,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "dependencies": { @@ -1886,9 +1868,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.24.5", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", - "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", + "version": "5.24.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.4.tgz", + "integrity": "sha512-GVoi+ICHocoOIU7qVVM48wOJziRsqrsyqlI0Ce0LdowRn6v3bcH2zUa9kp85ncx0nwIb9/HOCOLS3fdThDG/XQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2099,9 +2081,9 @@ } }, "node_modules/esbuild": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", - "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -2113,32 +2095,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.2", - "@esbuild/android-arm": "0.28.2", - "@esbuild/android-arm64": "0.28.2", - "@esbuild/android-x64": "0.28.2", - "@esbuild/darwin-arm64": "0.28.2", - "@esbuild/darwin-x64": "0.28.2", - "@esbuild/freebsd-arm64": "0.28.2", - "@esbuild/freebsd-x64": "0.28.2", - "@esbuild/linux-arm": "0.28.2", - "@esbuild/linux-arm64": "0.28.2", - "@esbuild/linux-ia32": "0.28.2", - "@esbuild/linux-loong64": "0.28.2", - "@esbuild/linux-mips64el": "0.28.2", - "@esbuild/linux-ppc64": "0.28.2", - "@esbuild/linux-riscv64": "0.28.2", - "@esbuild/linux-s390x": "0.28.2", - "@esbuild/linux-x64": "0.28.2", - "@esbuild/netbsd-arm64": "0.28.2", - "@esbuild/netbsd-x64": "0.28.2", - "@esbuild/openbsd-arm64": "0.28.2", - "@esbuild/openbsd-x64": "0.28.2", - "@esbuild/openharmony-arm64": "0.28.2", - "@esbuild/sunos-x64": "0.28.2", - "@esbuild/win32-arm64": "0.28.2", - "@esbuild/win32-ia32": "0.28.2", - "@esbuild/win32-x64": "0.28.2" + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" } }, "node_modules/escape-string-regexp": { @@ -2747,9 +2729,9 @@ } }, "node_modules/flatted": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", - "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.3.tgz", + "integrity": "sha512-/zipXxyO6rGvuNGDiULY9MvEGSkb2gaG4GGH4ygMi0ZZzyMHdUZBmntJmx5x1G2VuPytCwGN4xsJP6cw+sK+vQ==", "dev": true, "license": "ISC", "peer": true @@ -2898,9 +2880,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.14.2", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.2.tgz", - "integrity": "sha512-XpwZALwwl/BaKTAyC6+c5T8y6kCg2jk+XGqOVrKIQmW49pNypYLMRjCUXqa28tQgJlhS2RlzP7sc+Rx7W6qsfw==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", "dev": true, "license": "MIT", "dependencies": { @@ -2953,9 +2935,9 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", + "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", "dev": true, "license": "MIT", "dependencies": { @@ -3101,9 +3083,9 @@ } }, "node_modules/gray-matter/node_modules/js-yaml": { - "version": "3.15.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.1.tgz", - "integrity": "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.0.tgz", + "integrity": "sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==", "dev": true, "license": "MIT", "peer": true, @@ -3817,9 +3799,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", - "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", "dev": true, "funding": [ { @@ -4190,9 +4172,9 @@ } }, "node_modules/neostandard/node_modules/globals": { - "version": "17.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.11.0.tgz", - "integrity": "sha512-Z2I8hM+PbJDXQDq3Icgpzv+mPdwr68iZUU9d5WW4FuXfDUQfkZaZuvjMv42/5crNyw154+9+VWXbYrUgDXbxNw==", + "version": "17.8.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.8.0.tgz", + "integrity": "sha512-Zz/LMDZScFmkakeL2cTHzf+PbWKdpU3uclqkZT7TjDG58j5WPt0PpA+n9uPI24fZtlw07q0OtEi84K+umsRzqQ==", "dev": true, "license": "MIT", "engines": { @@ -5453,9 +5435,9 @@ } }, "node_modules/tsx": { - "version": "4.23.12", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.12.tgz", - "integrity": "sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q==", + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.1.tgz", + "integrity": "sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==", "dev": true, "license": "MIT", "peer": true, @@ -5580,16 +5562,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.67.0.tgz", - "integrity": "sha512-S2udFs8tCKEKffuJ4TB1idGUZiXdCPGi3IPBGWXarbLQ5UPXORV8QEVzJ4gCRduURMb5EkpNCdjbk0eDIuI8Yg==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.65.0.tgz", + "integrity": "sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.67.0", - "@typescript-eslint/parser": "8.67.0", - "@typescript-eslint/typescript-estree": "8.67.0", - "@typescript-eslint/utils": "8.67.0" + "@typescript-eslint/eslint-plugin": "8.65.0", + "@typescript-eslint/parser": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" diff --git a/package.json b/package.json index 83d09c74..f02e9eb2 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ }, "devDependencies": { "@seamapi/blueprint": "^1.5.1", - "@seamapi/fake-seam-connect": "1.86.0", + "@seamapi/fake-seam-connect": "2.0.3", "@seamapi/smith": "^1.1.0", "@seamapi/types": "1.1001.0", "change-case": "^5.4.4", From a04e859854f0d6cbbfb7a78bc6051d559c5bbf5a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 00:42:28 +0000 Subject: [PATCH 05/11] test: use snake_case param names in the serializer test The example was carried over from the reference implementation, which is JavaScript, leaving a camelCase parameter name in a Python test. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PpqwDhZmyikGbjmCPqFW2A --- test/url_search_params_serializer_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/url_search_params_serializer_test.py b/test/url_search_params_serializer_test.py index 6ce3a8e2..f56a601e 100644 --- a/test/url_search_params_serializer_test.py +++ b/test/url_search_params_serializer_test.py @@ -371,11 +371,11 @@ def test_update_url_search_params_preserves_existing_params(): search_params = UrlSearchParams([("foo", "bar")]) update_url_search_params( search_params, - {"name": "Dax", "age": 27, "isAdmin": True, "tags": ["cars", "planes"]}, + {"name": "Dax", "age": 27, "is_admin": True, "tags": ["cars", "planes"]}, ) assert search_params.to_string() == ( - "age=27&foo=bar&isAdmin=true&name=Dax&tags=cars&tags=planes" + "age=27&foo=bar&is_admin=true&name=Dax&tags=cars&tags=planes" ) From f83215c15b3a4bc3628dbdbcf6a9cd21625c3cd0 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 03:57:21 +0000 Subject: [PATCH 06/11] feat: call each endpoint with its semantic method Generate route methods against the semantic method of the endpoint instead of the preferred method. The preferred method falls back to POST when an endpoint takes array or object parameters, because those have no unambiguous query string representation. This SDK now implements the Seam URL search params serialization standard, so they do, and the fake Seam Connect server parses that serialization with the standard's parser. The 132 endpoints whose semantics are those of a GET now send their params in the query string. Every other method, DELETE included, reads them from a JSON request body, so only GET moves. niquests omits json from its delete signature, so the client provides one that accepts it. Search params are serialized to the standard rather than left to niquests, which implements a different one. Requests made against an endpoint directly, in tests and when polling an action attempt, use its semantic method too. Two consequences worth knowing: - A query string carries no types, so a value of the wrong type is no longer rejected as invalid input when it can be read as the expected type, e.g. a device_id of 4242 is now read as the id "4242" and reported as not found. - GET is idempotent, so urllib3 retries it under its default policy, where it never retried a POST. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PpqwDhZmyikGbjmCPqFW2A --- codegen/layouts/partials/route-method.hbs | 1 + codegen/lib/class-model.ts | 2 +- codegen/lib/layouts/route.ts | 10 +- codegen/lib/routes.ts | 2 +- seam/client.py | 9 + seam/modules/action_attempts.py | 4 +- seam/resources/access_code.py | 71 +- seam/resources/access_grant.py | 38 +- seam/resources/access_method.py | 27 +- seam/resources/acs_access_group.py | 43 +- seam/resources/acs_credential.py | 58 +- seam/resources/acs_encoder.py | 16 +- seam/resources/acs_entrance.py | 103 ++- seam/resources/acs_system.py | 36 +- seam/resources/acs_user.py | 62 +- seam/resources/action_attempt.py | 201 +++-- seam/resources/batch.py | 77 +- seam/resources/client_session.py | 8 +- seam/resources/connect_webview.py | 14 +- seam/resources/connected_account.py | 59 +- seam/resources/customer_portal.py | 4 +- seam/resources/device.py | 820 ++++++++++++++---- seam/resources/device_provider.py | 31 +- seam/resources/instant_key.py | 8 +- seam/resources/noise_threshold.py | 3 +- seam/resources/phone.py | 43 +- seam/resources/seam_event.py | 136 ++- seam/resources/space.py | 12 +- seam/resources/thermostat_daily_program.py | 6 +- seam/resources/thermostat_schedule.py | 3 +- seam/resources/unmanaged_access_code.py | 48 +- seam/resources/unmanaged_access_grant.py | 38 +- seam/resources/unmanaged_access_method.py | 27 +- seam/resources/unmanaged_device.py | 118 ++- seam/resources/unmanaged_user_identity.py | 6 +- seam/resources/user_identity.py | 6 +- seam/resources/workspace.py | 17 +- seam/routes/access_codes.py | 444 +++++++--- seam/routes/access_codes_simulate.py | 24 +- seam/routes/access_codes_unmanaged.py | 154 +++- seam/routes/access_grants.py | 259 ++++-- seam/routes/access_grants_unmanaged.py | 74 +- seam/routes/access_methods.py | 205 ++++- seam/routes/access_methods_unmanaged.py | 40 +- seam/routes/acs_access_groups.py | 130 ++- seam/routes/acs_credentials.py | 196 ++++- seam/routes/acs_encoders.py | 138 ++- seam/routes/acs_encoders_simulate.py | 124 ++- seam/routes/acs_entrances.py | 154 +++- seam/routes/acs_systems.py | 82 +- seam/routes/acs_users.py | 278 +++++- seam/routes/action_attempts.py | 60 +- seam/routes/client_sessions.py | 162 +++- seam/routes/connect_webviews.py | 124 ++- seam/routes/connected_accounts.py | 137 ++- seam/routes/connected_accounts_simulate.py | 10 +- seam/routes/customers.py | 202 ++++- seam/routes/devices.py | 170 +++- seam/routes/devices_simulate.py | 68 +- seam/routes/devices_unmanaged.py | 130 ++- seam/routes/events.py | 154 +++- seam/routes/instant_keys.py | 32 +- seam/routes/locks.py | 130 ++- seam/routes/locks_simulate.py | 60 +- seam/routes/noise_sensors.py | 53 +- seam/routes/noise_sensors_noise_thresholds.py | 102 ++- seam/routes/noise_sensors_simulate.py | 14 +- seam/routes/phones.py | 32 +- seam/routes/phones_simulate.py | 34 +- seam/routes/spaces.py | 246 ++++-- seam/routes/thermostats.py | 443 ++++++++-- seam/routes/thermostats_daily_programs.py | 60 +- seam/routes/thermostats_schedules.py | 114 ++- seam/routes/thermostats_simulate.py | 58 +- seam/routes/user_identities.py | 292 +++++-- seam/routes/user_identities_unmanaged.py | 72 +- seam/routes/webhooks.py | 23 +- seam/routes/workspaces.py | 115 ++- test/client_test.py | 4 +- test/conftest.py | 22 +- test/headers_test.py | 4 +- test/http_error_test.py | 1 + test/null_test.py | 6 +- test/serialization_test.py | 61 +- test/timeout_test.py | 14 +- 85 files changed, 5984 insertions(+), 1694 deletions(-) diff --git a/codegen/layouts/partials/route-method.hbs b/codegen/layouts/partials/route-method.hbs index b615e7f8..50c5fe44 100644 --- a/codegen/layouts/partials/route-method.hbs +++ b/codegen/layouts/partials/route-method.hbs @@ -13,6 +13,7 @@ raise ValueError("At least one parameter is required for {{path}}") {{/if}} + {{#unless (eq returnType "None")}}res = {{/unless}}self.client.{{httpVerb}}("{{path}}", {{payloadArg}}={{payloadVar}}) {{#if (eq returnType "ActionAttempt")}} diff --git a/codegen/lib/class-model.ts b/codegen/lib/class-model.ts index 3fd3cf17..3945c982 100644 --- a/codegen/lib/class-model.ts +++ b/codegen/lib/class-model.ts @@ -15,7 +15,7 @@ export interface ClassMethodParameter { export interface ClassMethod { methodName: string path: string - preferredMethod: string + semanticMethod: string hasRequiredParameters: boolean hasPagination: boolean description: string diff --git a/codegen/lib/layouts/route.ts b/codegen/lib/layouts/route.ts index 53c623e1..94a12452 100644 --- a/codegen/lib/layouts/route.ts +++ b/codegen/lib/layouts/route.ts @@ -57,12 +57,14 @@ export interface RouteLayoutContext { methods: MethodLayoutContext[] } +// GET and DELETE carry their params in the query string, as the OpenAPI +// operations for those methods declare them; the rest read a JSON body. const getRequestLayoutContext = ( - preferredMethod: string, + semanticMethod: string, ): Pick => { - const httpVerb = preferredMethod.toLowerCase() + const httpVerb = semanticMethod.toLowerCase() - if (preferredMethod === 'GET' || preferredMethod === 'DELETE') { + if (semanticMethod === 'GET' || semanticMethod === 'DELETE') { return { httpVerb, payloadVar: 'params', payloadArg: 'params' } } @@ -74,7 +76,7 @@ export const getMethodLayoutContext = ( ): MethodLayoutContext => ({ name: method.methodName, path: method.path, - ...getRequestLayoutContext(method.preferredMethod), + ...getRequestLayoutContext(method.semanticMethod), hasRequiredParameters: method.hasRequiredParameters, hasPagination: method.hasPagination, description: method.description, diff --git a/codegen/lib/routes.ts b/codegen/lib/routes.ts index d1d197ea..fb1c44d2 100644 --- a/codegen/lib/routes.ts +++ b/codegen/lib/routes.ts @@ -89,7 +89,7 @@ export const routes = ( cls.methods.push({ methodName: endpoint.name, path: endpoint.path, - preferredMethod: endpoint.request.preferredMethod, + semanticMethod: endpoint.request.semanticMethod, hasRequiredParameters: endpoint.request.hasRequiredParameters, hasPagination: endpoint.hasPagination, description: endpoint.description, diff --git a/seam/client.py b/seam/client.py index b9ff0c90..daa83ed6 100644 --- a/seam/client.py +++ b/seam/client.py @@ -1,3 +1,4 @@ +from collections.abc import Mapping from typing import Any, Dict, Optional from importlib.metadata import version import abc @@ -13,6 +14,7 @@ SeamHttpUnauthorizedError, ) from .null import replace_null +from .utils.url_search_params_serializer import serialize_url_search_params SDK_HEADERS = { "seam-sdk-name": "seamapi/python", @@ -87,6 +89,8 @@ def _init_proxy_transport(self, *args, **kwargs) -> httpx.BaseTransport: # httpx.Client promises, so the verb helpers routed through it have to # say so too. Without these overrides callers see the inherited Response # type and indexing the returned payload does not type check. + # httpx also omits json from its get and delete signatures, though the + # Seam API reads the params of a delete from the request body. def get(self, url, **kwargs) -> Any: return self.request("GET", url, **kwargs) @@ -108,6 +112,11 @@ def request(self, method, url, *args, **kwargs) -> Any: if "json" in kwargs: kwargs["json"] = replace_null(kwargs["json"]) + # Search params are serialized to the Seam API standard, which httpx + # does not implement. The NULL sentinel is serialized to an empty value. + if isinstance(kwargs.get("params"), Mapping): + kwargs["params"] = serialize_url_search_params(kwargs["params"]) + response = super().request(method, url, *args, **kwargs) return self._handle_response(response) diff --git a/seam/modules/action_attempts.py b/seam/modules/action_attempts.py index d764d3cb..07a9efb6 100644 --- a/seam/modules/action_attempts.py +++ b/seam/modules/action_attempts.py @@ -10,8 +10,8 @@ def get_action_attempt(client: SeamHttpClient, action_attempt_id: str) -> ActionAttempt: - res = client.post( - "/action_attempts/get", json={"action_attempt_id": action_attempt_id} + res = client.get( + "/action_attempts/get", params={"action_attempt_id": action_attempt_id} ) return ActionAttempt.from_dict(res["action_attempt"]) diff --git a/seam/resources/access_code.py b/seam/resources/access_code.py index e77d6bfe..7991193d 100644 --- a/seam/resources/access_code.py +++ b/seam/resources/access_code.py @@ -7,13 +7,13 @@ @dataclass class AccessCode: """Represents a smart lock `access code `_. - + An access code is a code used for a keypad or pinpad device. Unlike physical keys, which can easily be lost or duplicated, PIN codes can be customized, tracked, and altered on the fly. Using the Seam Access Code API, you can easily generate access codes on the hundreds of door lock models with which we integrate. - + Seam supports programming two types of access codes: `ongoing `_ and `time-bound `_. To differentiate between the two, refer to the ``type`` property of the access code. Ongoing codes display as ``ongoing``, whereas time-bound codes are labeled ``time_bound``. An ongoing access code is active, until it has been removed from the device. To specify an ongoing access code, leave both ``starts_at`` and ``ends_at`` empty. A time-bound access code will be programmed at the ``starts_at`` time and removed at the ``ends_at`` time. - + In addition, for certain devices, Seam also supports `offline access codes `_. Offline access (PIN) codes are designed for door locks that might not always maintain an internet connection. For this type of access code, the device manufacturer uses encryption keys (tokens) to create server-based registries of algorithmically-generated offline PIN codes. Because the tokens remain synchronized with the managed devices, the locks do not require an active internet connection—and you do not need to be near the locks—to create an offline access code. Then, owners or managers can share these offline codes with users through a variety of mechanisms, such as messaging applications. That is, lock users do not need to install a smartphone application to receive an offline access code. - + For granting a person access to a space, `Access Grants `_ are the default and recommended approach and work across both standalone smart locks and access systems. Use the lower-level Access Codes API directly only when you specifically need to manage individual PIN codes. :ivar access_code_id: Unique identifier for the access code. @@ -62,7 +62,8 @@ class AccessCode: :ivar warnings: Warnings associated with the `access code `_. - :ivar workspace_id: Unique identifier for the Seam workspace associated with the access code.""" + :ivar workspace_id: Unique identifier for the Seam workspace associated with the access code. + """ @dataclass class DormakabaOracodeMetadata(ResourceMapping): @@ -82,7 +83,8 @@ class DormakabaOracodeMetadata(ResourceMapping): :ivar user_level_id: Dormakaba Oracode user level ID associated with this access code. - :ivar user_level_name: Dormakaba Oracode user level name associated with this access code.""" + :ivar user_level_name: Dormakaba Oracode user level name associated with this access code. + """ is_cancellable: Optional[bool] is_early_checkin_able: Optional[bool] @@ -126,11 +128,12 @@ class Errors(ResourceMapping): :ivar modified_fields: List of fields that were changed externally, with their previous and new values. - :ivar is_connected_account_error: + :ivar is_connected_account_error: - :ivar is_device_error: + :ivar is_device_error: - :ivar is_bridge_error: Indicates whether the error is related to `Seam Bridge `_.""" + :ivar is_bridge_error: Indicates whether the error is related to `Seam Bridge `_. + """ @dataclass class ModifiedFields(ResourceMapping): @@ -176,7 +179,10 @@ def from_dict(cls, d: Any): managed_access_code_id=d.get("managed_access_code_id", None), unmanaged_access_code_id=d.get("unmanaged_access_code_id", None), change_type=d.get("change_type", None), - modified_fields=[cls.ModifiedFields.from_dict(i) for i in d.get("modified_fields") or []], + modified_fields=[ + cls.ModifiedFields.from_dict(i) + for i in d.get("modified_fields") or [] + ], is_connected_account_error=d.get("is_connected_account_error", None), is_device_error=d.get("is_device_error", None), is_bridge_error=d.get("is_bridge_error", None), @@ -190,13 +196,13 @@ class PendingMutations(ResourceMapping): :ivar message: Detailed description of the mutation. - :ivar mutation_code: + :ivar mutation_code: :ivar scheduled_at: Date and time at which Seam will attempt to program this access code on the device. - :ivar from_: + :ivar from_: - :ivar to: """ + :ivar to:""" @dataclass class From(ResourceMapping): @@ -264,7 +270,11 @@ def from_dict(cls, d: Any): message=d.get("message", None), mutation_code=d.get("mutation_code", None), scheduled_at=d.get("scheduled_at", None), - from_=cls.From.from_dict(d.get("from")) if d.get("from") is not None else None, + from_=( + cls.From.from_dict(d.get("from")) + if d.get("from") is not None + else None + ), to=cls.To.from_dict(d.get("to")) if d.get("to") is not None else None, ) @@ -280,7 +290,8 @@ class Warnings(ResourceMapping): :ivar change_type: Indicates the type of external modification. ``modified`` means the code's PIN or schedule was changed. ``removed`` means the code was deleted from the device. - :ivar modified_fields: List of fields that were changed externally, with their previous and new values.""" + :ivar modified_fields: List of fields that were changed externally, with their previous and new values. + """ @dataclass class ModifiedFields(ResourceMapping): @@ -317,7 +328,10 @@ def from_dict(cls, d: Any): message=d.get("message", None), warning_code=d.get("warning_code", None), change_type=d.get("change_type", None), - modified_fields=[cls.ModifiedFields.from_dict(i) for i in d.get("modified_fields") or []], + modified_fields=[ + cls.ModifiedFields.from_dict(i) + for i in d.get("modified_fields") or [] + ], ) access_code_id: str @@ -353,19 +367,34 @@ def from_dict(cls, d: Any): common_code_key=d.get("common_code_key", None), created_at=d.get("created_at", None), device_id=d.get("device_id", None), - dormakaba_oracode_metadata=cls.DormakabaOracodeMetadata.from_dict(d.get("dormakaba_oracode_metadata")) if d.get("dormakaba_oracode_metadata") is not None else None, + dormakaba_oracode_metadata=( + cls.DormakabaOracodeMetadata.from_dict( + d.get("dormakaba_oracode_metadata") + ) + if d.get("dormakaba_oracode_metadata") is not None + else None + ), ends_at=d.get("ends_at", None), errors=[cls.Errors.from_dict(i) for i in d.get("errors") or []], is_backup=d.get("is_backup", None), - is_backup_access_code_available=d.get("is_backup_access_code_available", None), - is_external_modification_allowed=d.get("is_external_modification_allowed", None), + is_backup_access_code_available=d.get( + "is_backup_access_code_available", None + ), + is_external_modification_allowed=d.get( + "is_external_modification_allowed", None + ), is_managed=d.get("is_managed", None), is_offline_access_code=d.get("is_offline_access_code", None), is_one_time_use=d.get("is_one_time_use", None), is_scheduled_on_device=d.get("is_scheduled_on_device", None), - is_waiting_for_code_assignment=d.get("is_waiting_for_code_assignment", None), + is_waiting_for_code_assignment=d.get( + "is_waiting_for_code_assignment", None + ), name=d.get("name", None), - pending_mutations=[cls.PendingMutations.from_dict(i) for i in d.get("pending_mutations") or []], + pending_mutations=[ + cls.PendingMutations.from_dict(i) + for i in d.get("pending_mutations") or [] + ], pulled_backup_access_code_id=d.get("pulled_backup_access_code_id", None), starts_at=d.get("starts_at", None), status=d.get("status", None), diff --git a/seam/resources/access_grant.py b/seam/resources/access_grant.py index 8bb1a57f..1d442c5f 100644 --- a/seam/resources/access_grant.py +++ b/seam/resources/access_grant.py @@ -58,7 +58,8 @@ class Errors(ResourceMapping): :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - :ivar missing_device_ids: IDs of the devices that did not receive an access code at grant creation. Use these to identify which specific devices failed when the message reports a partial failure.""" + :ivar missing_device_ids: IDs of the devices that did not receive an access code at grant creation. Use these to identify which specific devices failed when the message reports a partial failure. + """ created_at: str error_code: str @@ -80,13 +81,13 @@ class PendingMutations(ResourceMapping): :ivar created_at: Date and time at which the mutation was created. - :ivar from_: + :ivar from_: :ivar message: Detailed description of the mutation. - :ivar mutation_code: + :ivar mutation_code: - :ivar to: + :ivar to: :ivar access_method_ids: IDs of the access methods being updated.""" @@ -149,7 +150,11 @@ def from_dict(cls, d: Any): def from_dict(cls, d: Any): return cls( created_at=d.get("created_at", None), - from_=cls.From.from_dict(d.get("from")) if d.get("from") is not None else None, + from_=( + cls.From.from_dict(d.get("from")) + if d.get("from") is not None + else None + ), message=d.get("message", None), mutation_code=d.get("mutation_code", None), to=cls.To.from_dict(d.get("to")) if d.get("to") is not None else None, @@ -170,7 +175,8 @@ class RequestedAccessMethods(ResourceMapping): :ivar instant_key_max_use_count: Maximum number of times the instant key can be used. Only applicable when mode is 'mobile_key'. Defaults to 1 if not specified. - :ivar mode: Access method mode. Supported values: ``code``, ``card``, ``mobile_key``, ``cloud_key``.""" + :ivar mode: Access method mode. Supported values: ``code``, ``card``, ``mobile_key``, ``cloud_key``. + """ code: Optional[str] created_access_method_ids: List[str] @@ -204,13 +210,14 @@ class Warnings(ResourceMapping): :ivar access_method_ids: IDs of the access methods being updated. - :ivar device_id: + :ivar device_id: :ivar new_code: The new PIN code that was assigned instead. :ivar original_code: The originally requested PIN code that was unavailable. - :ivar reason: Specific reason why the grant's times are not programmable on the device.""" + :ivar reason: Specific reason why the grant's times are not programmable on the device. + """ @dataclass class FailedDevices(ResourceMapping): @@ -250,7 +257,10 @@ def from_dict(cls, d: Any): created_at=d.get("created_at", None), message=d.get("message", None), warning_code=d.get("warning_code", None), - failed_devices=[cls.FailedDevices.from_dict(i) for i in d.get("failed_devices") or []], + failed_devices=[ + cls.FailedDevices.from_dict(i) + for i in d.get("failed_devices") or [] + ], access_method_ids=d.get("access_method_ids", None), device_id=d.get("device_id", None), new_code=d.get("new_code", None), @@ -294,8 +304,14 @@ def from_dict(cls, d: Any): instant_key_url=d.get("instant_key_url", None), location_ids=d.get("location_ids", None), name=d.get("name", None), - pending_mutations=[cls.PendingMutations.from_dict(i) for i in d.get("pending_mutations") or []], - requested_access_methods=[cls.RequestedAccessMethods.from_dict(i) for i in d.get("requested_access_methods") or []], + pending_mutations=[ + cls.PendingMutations.from_dict(i) + for i in d.get("pending_mutations") or [] + ], + requested_access_methods=[ + cls.RequestedAccessMethods.from_dict(i) + for i in d.get("requested_access_methods") or [] + ], reservation_key=d.get("reservation_key", None), space_ids=d.get("space_ids", None), starts_at=d.get("starts_at", None), diff --git a/seam/resources/access_method.py b/seam/resources/access_method.py index b5f1aae6..d7614ada 100644 --- a/seam/resources/access_method.py +++ b/seam/resources/access_method.py @@ -52,7 +52,8 @@ class Errors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + """ created_at: str error_code: str @@ -72,19 +73,19 @@ class PendingMutations(ResourceMapping): :ivar created_at: Date and time at which the mutation was created. - :ivar from_: + :ivar from_: :ivar message: Detailed description of the mutation. - :ivar mutation_code: + :ivar mutation_code: - :ivar to: """ + :ivar to:""" @dataclass class From(ResourceMapping): """ - :ivar device_ids: + :ivar device_ids: :ivar ends_at: Previous end time for access. @@ -106,7 +107,7 @@ def from_dict(cls, d: Any): class To(ResourceMapping): """ - :ivar device_ids: + :ivar device_ids: :ivar ends_at: New end time for access. @@ -134,7 +135,11 @@ def from_dict(cls, d: Any): def from_dict(cls, d: Any): return cls( created_at=d.get("created_at", None), - from_=cls.From.from_dict(d.get("from")) if d.get("from") is not None else None, + from_=( + cls.From.from_dict(d.get("from")) + if d.get("from") is not None + else None + ), message=d.get("message", None), mutation_code=d.get("mutation_code", None), to=cls.To.from_dict(d.get("to")) if d.get("to") is not None else None, @@ -150,7 +155,8 @@ class Warnings(ResourceMapping): :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - :ivar original_access_method_id: ID of the original access method from which this backup access method was split, if applicable.""" + :ivar original_access_method_id: ID of the original access method from which this backup access method was split, if applicable. + """ created_at: str message: str @@ -203,7 +209,10 @@ def from_dict(cls, d: Any): is_ready_for_encoding=d.get("is_ready_for_encoding", None), issued_at=d.get("issued_at", None), mode=d.get("mode", None), - pending_mutations=[cls.PendingMutations.from_dict(i) for i in d.get("pending_mutations") or []], + pending_mutations=[ + cls.PendingMutations.from_dict(i) + for i in d.get("pending_mutations") or [] + ], warnings=[cls.Warnings.from_dict(i) for i in d.get("warnings") or []], workspace_id=d.get("workspace_id", None), ) diff --git a/seam/resources/acs_access_group.py b/seam/resources/acs_access_group.py index c9ce5840..ca32527b 100644 --- a/seam/resources/acs_access_group.py +++ b/seam/resources/acs_access_group.py @@ -7,9 +7,9 @@ @dataclass class AcsAccessGroup: """Group that defines the entrances to which a set of users has access and, in some cases, the access schedule for these entrances and users. - + Some access control systems use `access group `_, which are sets of users, combined with sets of permissions. These permissions include both the set of areas or assets that the users can access and the schedule during which the users can access these areas or assets. Instead of assigning access rights individually to each access control system user, which can be time-consuming and error-prone, administrators can assign users to an access group, thereby ensuring that the users inherit all the permissions associated with the access group. Using access groups streamlines the process of managing large numbers of access control system users, especially in bigger organizations or complexes. - + To learn whether your access control system supports access groups, see the corresponding `system integration guide `_. :ivar access_group_type: Deprecated: Use ``external_type``. @@ -50,7 +50,8 @@ class AccessSchedule(ResourceMapping): :ivar ends_at: Date and time at which the user's access ends, in `ISO 8601 `_ format. - :ivar starts_at: Date and time at which the user's access starts, in `ISO 8601 `_ format.""" + :ivar starts_at: Date and time at which the user's access starts, in `ISO 8601 `_ format. + """ ends_at: Optional[str] starts_at: str @@ -70,7 +71,8 @@ class Errors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + """ created_at: str error_code: str @@ -92,15 +94,16 @@ class PendingMutations(ResourceMapping): :ivar message: Detailed description of the mutation. - :ivar mutation_code: + :ivar mutation_code: - :ivar from_: + :ivar from_: - :ivar to: + :ivar to: :ivar acs_user_id: ID of the user involved in the scheduled change. - :ivar variant: Whether the user is scheduled to be added to or removed from this access group.""" + :ivar variant: Whether the user is scheduled to be added to or removed from this access group. + """ @dataclass class From(ResourceMapping): @@ -176,7 +179,11 @@ def from_dict(cls, d: Any): created_at=d.get("created_at", None), message=d.get("message", None), mutation_code=d.get("mutation_code", None), - from_=cls.From.from_dict(d.get("from")) if d.get("from") is not None else None, + from_=( + cls.From.from_dict(d.get("from")) + if d.get("from") is not None + else None + ), to=cls.To.from_dict(d.get("to")) if d.get("to") is not None else None, acs_user_id=d.get("acs_user_id", None), variant=d.get("variant", None), @@ -190,7 +197,8 @@ class Warnings(ResourceMapping): :ivar message: Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue.""" + :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + """ created_at: str message: str @@ -225,8 +233,14 @@ def from_dict(cls, d: Any): def from_dict(cls, d: Any): return cls( access_group_type=d.get("access_group_type", None), - access_group_type_display_name=d.get("access_group_type_display_name", None), - access_schedule=cls.AccessSchedule.from_dict(d.get("access_schedule")) if d.get("access_schedule") is not None else None, + access_group_type_display_name=d.get( + "access_group_type_display_name", None + ), + access_schedule=( + cls.AccessSchedule.from_dict(d.get("access_schedule")) + if d.get("access_schedule") is not None + else None + ), acs_access_group_id=d.get("acs_access_group_id", None), acs_system_id=d.get("acs_system_id", None), connected_account_id=d.get("connected_account_id", None), @@ -237,7 +251,10 @@ def from_dict(cls, d: Any): external_type_display_name=d.get("external_type_display_name", None), is_managed=d.get("is_managed", None), name=d.get("name", None), - pending_mutations=[cls.PendingMutations.from_dict(i) for i in d.get("pending_mutations") or []], + pending_mutations=[ + cls.PendingMutations.from_dict(i) + for i in d.get("pending_mutations") or [] + ], warnings=[cls.Warnings.from_dict(i) for i in d.get("warnings") or []], workspace_id=d.get("workspace_id", None), ) diff --git a/seam/resources/acs_credential.py b/seam/resources/acs_credential.py index a29f5b81..b8f09b54 100644 --- a/seam/resources/acs_credential.py +++ b/seam/resources/acs_credential.py @@ -7,11 +7,11 @@ @dataclass class AcsCredential: """Means by which an `access control system user `_ gains access at an `entrance `_. The ``acs_credential`` object represents a `credential `_ that provides an ACS user access within an `access control system `_. - + An access control system generally uses digital means of access to authorize a user trying to get through a specific entrance. Examples of credentials include plastic key cards, mobile keys, biometric identifiers, and PIN codes. The electronic nature of these credentials, as well as the fact that access is centralized, enables both the rapid provisioning and rescinding of access and the ability to compile access audit logs. - + For each ``acs_credential``, you define the access method. You can also specify additional properties, such as a PIN code, depending on the credential type. - + For granting a person access to a space, `Access Grants `_ are the default and recommended approach. Use the lower-level ACS credential API directly only when you specifically need to manage individual credentials. :ivar access_method: Access method for the `credential `_. Supported values: ``code``, ``card``, ``mobile_key``, ``cloud_key``. @@ -70,7 +70,8 @@ class AcsCredential: :ivar warnings: Warnings associated with the `credential `_. - :ivar workspace_id: ID of the workspace that contains the `credential `_.""" + :ivar workspace_id: ID of the workspace that contains the `credential `_. + """ @dataclass class AkilesMetadata(ResourceMapping): @@ -100,7 +101,8 @@ class AssaAbloyVostioMetadata(ResourceMapping): :ivar key_issuing_request_id: Key issuing request ID in the Vostio access system. - :ivar override_guest_acs_entrance_ids: IDs of the guest entrances to override in the Vostio access system.""" + :ivar override_guest_acs_entrance_ids: IDs of the guest entrances to override in the Vostio access system. + """ auto_join: Optional[bool] door_names: Optional[List[str]] @@ -117,7 +119,9 @@ def from_dict(cls, d: Any): endpoint_id=d.get("endpoint_id", None), key_id=d.get("key_id", None), key_issuing_request_id=d.get("key_issuing_request_id", None), - override_guest_acs_entrance_ids=d.get("override_guest_acs_entrance_ids", None), + override_guest_acs_entrance_ids=d.get( + "override_guest_acs_entrance_ids", None + ), ) @dataclass @@ -126,9 +130,9 @@ class Errors(ResourceMapping): :ivar created_at: Date and time at which Seam created the error. - :ivar error_code: + :ivar error_code: - :ivar message: """ + :ivar message:""" created_at: str error_code: str @@ -160,7 +164,8 @@ class VisionlineMetadata(ResourceMapping): :ivar is_valid: Indicates whether the credential is valid. - :ivar joiner_acs_credential_ids: IDs of the credentials to which you want to join.""" + :ivar joiner_acs_credential_ids: IDs of the credentials to which you want to join. + """ auto_join: Optional[bool] card_function_type: Optional[str] @@ -196,7 +201,8 @@ class Warnings(ResourceMapping): :ivar new_code: The PIN code that was assigned instead. - :ivar original_code: The originally requested PIN code that could not be used.""" + :ivar original_code: The originally requested PIN code that could not be used. + """ created_at: str message: str @@ -252,8 +258,18 @@ def from_dict(cls, d: Any): acs_credential_pool_id=d.get("acs_credential_pool_id", None), acs_system_id=d.get("acs_system_id", None), acs_user_id=d.get("acs_user_id", None), - akiles_metadata=cls.AkilesMetadata.from_dict(d.get("akiles_metadata")) if d.get("akiles_metadata") is not None else None, - assa_abloy_vostio_metadata=cls.AssaAbloyVostioMetadata.from_dict(d.get("assa_abloy_vostio_metadata")) if d.get("assa_abloy_vostio_metadata") is not None else None, + akiles_metadata=( + cls.AkilesMetadata.from_dict(d.get("akiles_metadata")) + if d.get("akiles_metadata") is not None + else None + ), + assa_abloy_vostio_metadata=( + cls.AssaAbloyVostioMetadata.from_dict( + d.get("assa_abloy_vostio_metadata") + ) + if d.get("assa_abloy_vostio_metadata") is not None + else None + ), card_number=d.get("card_number", None), code=d.get("code", None), connected_account_id=d.get("connected_account_id", None), @@ -264,16 +280,26 @@ def from_dict(cls, d: Any): external_type=d.get("external_type", None), external_type_display_name=d.get("external_type_display_name", None), is_issued=d.get("is_issued", None), - is_latest_desired_state_synced_with_provider=d.get("is_latest_desired_state_synced_with_provider", None), + is_latest_desired_state_synced_with_provider=d.get( + "is_latest_desired_state_synced_with_provider", None + ), is_managed=d.get("is_managed", None), - is_multi_phone_sync_credential=d.get("is_multi_phone_sync_credential", None), + is_multi_phone_sync_credential=d.get( + "is_multi_phone_sync_credential", None + ), is_one_time_use=d.get("is_one_time_use", None), issued_at=d.get("issued_at", None), - latest_desired_state_synced_with_provider_at=d.get("latest_desired_state_synced_with_provider_at", None), + latest_desired_state_synced_with_provider_at=d.get( + "latest_desired_state_synced_with_provider_at", None + ), parent_acs_credential_id=d.get("parent_acs_credential_id", None), starts_at=d.get("starts_at", None), user_identity_id=d.get("user_identity_id", None), - visionline_metadata=cls.VisionlineMetadata.from_dict(d.get("visionline_metadata")) if d.get("visionline_metadata") is not None else None, + visionline_metadata=( + cls.VisionlineMetadata.from_dict(d.get("visionline_metadata")) + if d.get("visionline_metadata") is not None + else None + ), warnings=[cls.Warnings.from_dict(i) for i in d.get("warnings") or []], workspace_id=d.get("workspace_id", None), ) diff --git a/seam/resources/acs_encoder.py b/seam/resources/acs_encoder.py index ba7c2d3b..c1e4f936 100644 --- a/seam/resources/acs_encoder.py +++ b/seam/resources/acs_encoder.py @@ -7,18 +7,18 @@ @dataclass class AcsEncoder: """Represents a hardware device that encodes `credential `_ data onto physical cards within an `access control system `_. - + Some access control systems require credentials to be encoded onto plastic key cards using a card encoder. This process involves the following two key steps: - + 1. Credential creation Configure the access parameters for the credential. 2. Card encoding Write the credential data onto the card using a compatible card encoder. - + Separately, the Seam API also supports card scanning, which enables you to scan and read the encoded data on a card. You can use this action to confirm consistency with access control system records or diagnose discrepancies if needed. - + See `Working with Card Encoders and Scanners `_. - + To verify if your access control system requires a card encoder, see the corresponding `system integration guide `_. :ivar acs_encoder_id: ID of the `encoder `_. @@ -33,7 +33,8 @@ class AcsEncoder: :ivar errors: Errors associated with the `encoder `_. - :ivar workspace_id: ID of the workspace that contains the `encoder `_.""" + :ivar workspace_id: ID of the workspace that contains the `encoder `_. + """ @dataclass class Errors(ResourceMapping): @@ -43,7 +44,8 @@ class Errors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + """ created_at: str error_code: str diff --git a/seam/resources/acs_entrance.py b/seam/resources/acs_entrance.py index ff1f82ca..491e6677 100644 --- a/seam/resources/acs_entrance.py +++ b/seam/resources/acs_entrance.py @@ -7,7 +7,7 @@ @dataclass class AcsEntrance: """Represents an `entrance `_ within an `access control system `_. - + In an access control system, an entrance is a secured door, gate, zone, or other method of entry. You can list details for all the ``acs_entrance`` resources in your workspace or get these details for a specific ``acs_entrance``. You can also list all entrances associated with a specific credential, and you can list all credentials associated with a specific entrance. :ivar acs_entrance_id: ID of the `entrance `_. @@ -58,7 +58,8 @@ class AcsEntrance: :ivar visionline_metadata: Visionline-specific metadata associated with the `entrance `_. - :ivar warnings: Warnings associated with the `entrance `_.""" + :ivar warnings: Warnings associated with the `entrance `_. + """ @dataclass class AkilesMetadata(ResourceMapping): @@ -116,7 +117,8 @@ class AssaAbloyVostioMetadata(ResourceMapping): :ivar pms_id: PMS ID of the door in the Vostio access system. - :ivar stand_open: Indicates whether keys are allowed to set the door in stand open mode in the Vostio access system.""" + :ivar stand_open: Indicates whether keys are allowed to set the door in stand open mode in the Vostio access system. + """ door_name: Optional[str] door_number: Optional[float] @@ -198,7 +200,8 @@ def from_dict(cls, d: Any): class DormakabaAmbianceMetadata(ResourceMapping): """dormakaba Ambiance-specific metadata associated with the `entrance `_. - :ivar access_point_name: Name of the access point in the dormakaba Ambiance access system.""" + :ivar access_point_name: Name of the access point in the dormakaba Ambiance access system. + """ access_point_name: Optional[str] @@ -212,7 +215,8 @@ def from_dict(cls, d: Any): class DormakabaCommunityMetadata(ResourceMapping): """dormakaba Community-specific metadata associated with the `entrance `_. - :ivar access_point_profile: Type of access point profile in the dormakaba Community access system.""" + :ivar access_point_profile: Type of access point profile in the dormakaba Community access system. + """ access_point_profile: Optional[str] @@ -230,7 +234,8 @@ class Errors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + """ created_at: str error_code: str @@ -384,7 +389,8 @@ class Profiles(ResourceMapping): :ivar visionline_door_profile_id: Door profile ID in the Visionline access system. - :ivar visionline_door_profile_type: Door profile type in the Visionline access system.""" + :ivar visionline_door_profile_type: Door profile type in the Visionline access system. + """ visionline_door_profile_id: Optional[str] visionline_door_profile_type: Optional[str] @@ -392,8 +398,12 @@ class Profiles(ResourceMapping): @classmethod def from_dict(cls, d: Any): return cls( - visionline_door_profile_id=d.get("visionline_door_profile_id", None), - visionline_door_profile_type=d.get("visionline_door_profile_type", None), + visionline_door_profile_id=d.get( + "visionline_door_profile_id", None + ), + visionline_door_profile_type=d.get( + "visionline_door_profile_type", None + ), ) door_category: Optional[str] @@ -416,7 +426,8 @@ class Warnings(ResourceMapping): :ivar message: Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue.""" + :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + """ created_at: str message: str @@ -461,10 +472,28 @@ def from_dict(cls, d: Any): return cls( acs_entrance_id=d.get("acs_entrance_id", None), acs_system_id=d.get("acs_system_id", None), - akiles_metadata=cls.AkilesMetadata.from_dict(d.get("akiles_metadata")) if d.get("akiles_metadata") is not None else None, - assa_abloy_vostio_metadata=cls.AssaAbloyVostioMetadata.from_dict(d.get("assa_abloy_vostio_metadata")) if d.get("assa_abloy_vostio_metadata") is not None else None, - avigilon_alta_metadata=cls.AvigilonAltaMetadata.from_dict(d.get("avigilon_alta_metadata")) if d.get("avigilon_alta_metadata") is not None else None, - brivo_metadata=cls.BrivoMetadata.from_dict(d.get("brivo_metadata")) if d.get("brivo_metadata") is not None else None, + akiles_metadata=( + cls.AkilesMetadata.from_dict(d.get("akiles_metadata")) + if d.get("akiles_metadata") is not None + else None + ), + assa_abloy_vostio_metadata=( + cls.AssaAbloyVostioMetadata.from_dict( + d.get("assa_abloy_vostio_metadata") + ) + if d.get("assa_abloy_vostio_metadata") is not None + else None + ), + avigilon_alta_metadata=( + cls.AvigilonAltaMetadata.from_dict(d.get("avigilon_alta_metadata")) + if d.get("avigilon_alta_metadata") is not None + else None + ), + brivo_metadata=( + cls.BrivoMetadata.from_dict(d.get("brivo_metadata")) + if d.get("brivo_metadata") is not None + else None + ), can_belong_to_reservation=d.get("can_belong_to_reservation", None), can_unlock_with_card=d.get("can_unlock_with_card", None), can_unlock_with_cloud_key=d.get("can_unlock_with_cloud_key", None), @@ -473,15 +502,47 @@ def from_dict(cls, d: Any): connected_account_id=d.get("connected_account_id", None), created_at=d.get("created_at", None), display_name=d.get("display_name", None), - dormakaba_ambiance_metadata=cls.DormakabaAmbianceMetadata.from_dict(d.get("dormakaba_ambiance_metadata")) if d.get("dormakaba_ambiance_metadata") is not None else None, - dormakaba_community_metadata=cls.DormakabaCommunityMetadata.from_dict(d.get("dormakaba_community_metadata")) if d.get("dormakaba_community_metadata") is not None else None, + dormakaba_ambiance_metadata=( + cls.DormakabaAmbianceMetadata.from_dict( + d.get("dormakaba_ambiance_metadata") + ) + if d.get("dormakaba_ambiance_metadata") is not None + else None + ), + dormakaba_community_metadata=( + cls.DormakabaCommunityMetadata.from_dict( + d.get("dormakaba_community_metadata") + ) + if d.get("dormakaba_community_metadata") is not None + else None + ), errors=[cls.Errors.from_dict(i) for i in d.get("errors") or []], - hotek_metadata=cls.HotekMetadata.from_dict(d.get("hotek_metadata")) if d.get("hotek_metadata") is not None else None, + hotek_metadata=( + cls.HotekMetadata.from_dict(d.get("hotek_metadata")) + if d.get("hotek_metadata") is not None + else None + ), is_locked=d.get("is_locked", None), - latch_metadata=cls.LatchMetadata.from_dict(d.get("latch_metadata")) if d.get("latch_metadata") is not None else None, - salto_ks_metadata=cls.SaltoKsMetadata.from_dict(d.get("salto_ks_metadata")) if d.get("salto_ks_metadata") is not None else None, - salto_space_metadata=cls.SaltoSpaceMetadata.from_dict(d.get("salto_space_metadata")) if d.get("salto_space_metadata") is not None else None, + latch_metadata=( + cls.LatchMetadata.from_dict(d.get("latch_metadata")) + if d.get("latch_metadata") is not None + else None + ), + salto_ks_metadata=( + cls.SaltoKsMetadata.from_dict(d.get("salto_ks_metadata")) + if d.get("salto_ks_metadata") is not None + else None + ), + salto_space_metadata=( + cls.SaltoSpaceMetadata.from_dict(d.get("salto_space_metadata")) + if d.get("salto_space_metadata") is not None + else None + ), space_ids=d.get("space_ids", None), - visionline_metadata=cls.VisionlineMetadata.from_dict(d.get("visionline_metadata")) if d.get("visionline_metadata") is not None else None, + visionline_metadata=( + cls.VisionlineMetadata.from_dict(d.get("visionline_metadata")) + if d.get("visionline_metadata") is not None + else None + ), warnings=[cls.Warnings.from_dict(i) for i in d.get("warnings") or []], ) diff --git a/seam/resources/acs_system.py b/seam/resources/acs_system.py index e3e51f07..976a5070 100644 --- a/seam/resources/acs_system.py +++ b/seam/resources/acs_system.py @@ -7,9 +7,9 @@ @dataclass class AcsSystem: """Represents an `access control system `_. - + Within an ``acs_system``, create ```acs_user``s `_ and ```acs_credential``s `_ to grant access to the ``acs_user``s. - + For details about the resources associated with an access control system, see the `access control systems namespace `_. :ivar acs_access_group_count: Number of access groups in the `access control system `_. @@ -50,7 +50,8 @@ class AcsSystem: :ivar warnings: Warnings associated with the `access control system `_. - :ivar workspace_id: ID of the workspace that contains the `access control system `_.""" + :ivar workspace_id: ID of the workspace that contains the `access control system `_. + """ @dataclass class Errors(ResourceMapping): @@ -62,7 +63,8 @@ class Errors(ResourceMapping): :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - :ivar is_bridge_error: Indicates whether the error is related to the `Seam Bridge `_.""" + :ivar is_bridge_error: Indicates whether the error is related to the `Seam Bridge `_. + """ created_at: str error_code: str @@ -82,7 +84,8 @@ def from_dict(cls, d: Any): class Location(ResourceMapping): """Location information for the `access control system `_. - :ivar time_zone: Time zone in which the `access control system `_ is located.""" + :ivar time_zone: Time zone in which the `access control system `_ is located. + """ time_zone: Optional[str] @@ -100,7 +103,8 @@ class VisionlineMetadata(ResourceMapping): :ivar mobile_access_uuid: Keyset loaded into a reader. Mobile keys and reader administration tools securely authenticate only with readers programmed with a matching keyset. - :ivar system_id: Unique ID assigned by the ASSA ABLOY licensing team that identifies each hotel in your credential manager.""" + :ivar system_id: Unique ID assigned by the ASSA ABLOY licensing team that identifies each hotel in your credential manager. + """ lan_address: Optional[str] mobile_access_uuid: Optional[str] @@ -137,7 +141,9 @@ def from_dict(cls, d: Any): created_at=d.get("created_at", None), message=d.get("message", None), warning_code=d.get("warning_code", None), - misconfigured_acs_entrance_ids=d.get("misconfigured_acs_entrance_ids", None), + misconfigured_acs_entrance_ids=d.get( + "misconfigured_acs_entrance_ids", None + ), ) acs_access_group_count: Optional[float] @@ -170,18 +176,28 @@ def from_dict(cls, d: Any): connected_account_id=d.get("connected_account_id", None), connected_account_ids=d.get("connected_account_ids", None), created_at=d.get("created_at", None), - default_credential_manager_acs_system_id=d.get("default_credential_manager_acs_system_id", None), + default_credential_manager_acs_system_id=d.get( + "default_credential_manager_acs_system_id", None + ), errors=[cls.Errors.from_dict(i) for i in d.get("errors") or []], external_type=d.get("external_type", None), external_type_display_name=d.get("external_type_display_name", None), image_alt_text=d.get("image_alt_text", None), image_url=d.get("image_url", None), is_credential_manager=d.get("is_credential_manager", None), - location=cls.Location.from_dict(d.get("location")) if d.get("location") is not None else None, + location=( + cls.Location.from_dict(d.get("location")) + if d.get("location") is not None + else None + ), name=d.get("name", None), system_type=d.get("system_type", None), system_type_display_name=d.get("system_type_display_name", None), - visionline_metadata=cls.VisionlineMetadata.from_dict(d.get("visionline_metadata")) if d.get("visionline_metadata") is not None else None, + visionline_metadata=( + cls.VisionlineMetadata.from_dict(d.get("visionline_metadata")) + if d.get("visionline_metadata") is not None + else None + ), warnings=[cls.Warnings.from_dict(i) for i in d.get("warnings") or []], workspace_id=d.get("workspace_id", None), ) diff --git a/seam/resources/acs_user.py b/seam/resources/acs_user.py index b3cefaaa..3c794ab2 100644 --- a/seam/resources/acs_user.py +++ b/seam/resources/acs_user.py @@ -7,9 +7,9 @@ @dataclass class AcsUser: """Represents a `user `_ in an `access system `_. - + An access system user typically refers to an individual who requires access, like an employee or resident. Each user can possess multiple credentials that serve as their keys or identifiers for access. The type of credential can vary widely. For example, in the Salto system, a user can have a PIN code, a mobile app account, and a fob. In other platforms, it is not uncommon for a user to have more than one of the same credential type, such as multiple key cards. Additionally, these credentials can have a schedule or validity period. - + For details about how to configure users in your access system, see the corresponding `system integration guide `_. :ivar access_schedule: ``starts_at`` and ``ends_at`` timestamps for the `access system user's `_ access. @@ -60,7 +60,8 @@ class AcsUser: :ivar warnings: Warnings associated with the `access system user `_. - :ivar workspace_id: ID of the workspace that contains the `access system user `_.""" + :ivar workspace_id: ID of the workspace that contains the `access system user `_. + """ @dataclass class AccessSchedule(ResourceMapping): @@ -68,7 +69,8 @@ class AccessSchedule(ResourceMapping): :ivar ends_at: Date and time at which the user's access ends, in `ISO 8601 `_ format. - :ivar starts_at: Date and time at which the user's access starts, in `ISO 8601 `_ format.""" + :ivar starts_at: Date and time at which the user's access starts, in `ISO 8601 `_ format. + """ ends_at: Optional[str] starts_at: str @@ -86,9 +88,10 @@ class Errors(ResourceMapping): :ivar created_at: Date and time at which Seam created the error. - :ivar error_code: + :ivar error_code: - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + """ created_at: str error_code: str @@ -110,17 +113,18 @@ class PendingMutations(ResourceMapping): :ivar message: Detailed description of the mutation. - :ivar mutation_code: + :ivar mutation_code: :ivar scheduled_at: Optional: When the user creation is scheduled to occur. - :ivar from_: + :ivar from_: - :ivar to: + :ivar to: :ivar acs_access_group_id: ID of the access group involved in the scheduled change. - :ivar variant: Whether the user is scheduled to be added to or removed from the access group.""" + :ivar variant: Whether the user is scheduled to be added to or removed from the access group. + """ @dataclass class From(ResourceMapping): @@ -136,7 +140,7 @@ class From(ResourceMapping): :ivar starts_at: Starting time for the access schedule. - :ivar is_suspended: + :ivar is_suspended: :ivar acs_access_group_id: Old access group ID. @@ -178,7 +182,7 @@ class To(ResourceMapping): :ivar starts_at: Starting time for the access schedule. - :ivar is_suspended: + :ivar is_suspended: :ivar acs_access_group_id: New access group ID. @@ -222,7 +226,11 @@ def from_dict(cls, d: Any): message=d.get("message", None), mutation_code=d.get("mutation_code", None), scheduled_at=d.get("scheduled_at", None), - from_=cls.From.from_dict(d.get("from")) if d.get("from") is not None else None, + from_=( + cls.From.from_dict(d.get("from")) + if d.get("from") is not None + else None + ), to=cls.To.from_dict(d.get("to")) if d.get("to") is not None else None, acs_access_group_id=d.get("acs_access_group_id", None), variant=d.get("variant", None), @@ -232,7 +240,8 @@ def from_dict(cls, d: Any): class SaltoKsMetadata(ResourceMapping): """Salto KS-specific metadata associated with the `access system user `_. - :ivar is_subscribed: Indicates whether the user holds an active subscription slot on the Salto KS site. Only subscribed users can unlock doors and count against the site's user-subscription limit. A user may not be subscribed because their access schedule has not started or has ended, the site has reached its subscription limit, or they were manually unsubscribed. This is distinct from ``is_suspended``, which reflects whether the user has been explicitly blocked.""" + :ivar is_subscribed: Indicates whether the user holds an active subscription slot on the Salto KS site. Only subscribed users can unlock doors and count against the site's user-subscription limit. A user may not be subscribed because their access schedule has not started or has ended, the site has reached its subscription limit, or they were manually unsubscribed. This is distinct from ``is_suspended``, which reflects whether the user has been explicitly blocked. + """ is_subscribed: Optional[bool] @@ -268,7 +277,7 @@ class Warnings(ResourceMapping): :ivar message: Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - :ivar warning_code: """ + :ivar warning_code:""" created_at: str message: str @@ -311,7 +320,11 @@ def from_dict(cls, d: Any): @classmethod def from_dict(cls, d: Any): return cls( - access_schedule=cls.AccessSchedule.from_dict(d.get("access_schedule")) if d.get("access_schedule") is not None else None, + access_schedule=( + cls.AccessSchedule.from_dict(d.get("access_schedule")) + if d.get("access_schedule") is not None + else None + ), acs_system_id=d.get("acs_system_id", None), acs_user_id=d.get("acs_user_id", None), connected_account_id=d.get("connected_account_id", None), @@ -326,10 +339,21 @@ def from_dict(cls, d: Any): hid_acs_system_id=d.get("hid_acs_system_id", None), is_managed=d.get("is_managed", None), is_suspended=d.get("is_suspended", None), - pending_mutations=[cls.PendingMutations.from_dict(i) for i in d.get("pending_mutations") or []], + pending_mutations=[ + cls.PendingMutations.from_dict(i) + for i in d.get("pending_mutations") or [] + ], phone_number=d.get("phone_number", None), - salto_ks_metadata=cls.SaltoKsMetadata.from_dict(d.get("salto_ks_metadata")) if d.get("salto_ks_metadata") is not None else None, - salto_space_metadata=cls.SaltoSpaceMetadata.from_dict(d.get("salto_space_metadata")) if d.get("salto_space_metadata") is not None else None, + salto_ks_metadata=( + cls.SaltoKsMetadata.from_dict(d.get("salto_ks_metadata")) + if d.get("salto_ks_metadata") is not None + else None + ), + salto_space_metadata=( + cls.SaltoSpaceMetadata.from_dict(d.get("salto_space_metadata")) + if d.get("salto_space_metadata") is not None + else None + ), user_identity_email_address=d.get("user_identity_email_address", None), user_identity_full_name=d.get("user_identity_full_name", None), user_identity_id=d.get("user_identity_id", None), diff --git a/seam/resources/action_attempt.py b/seam/resources/action_attempt.py index d3f61968..0c0ff07e 100644 --- a/seam/resources/action_attempt.py +++ b/seam/resources/action_attempt.py @@ -10,13 +10,13 @@ class ActionAttempt: :ivar action_attempt_id: ID of the action attempt. - :ivar action_type: + :ivar action_type: :ivar error: Error associated with the action. - :ivar result: + :ivar result: - :ivar status: """ + :ivar status:""" @dataclass class Error(ResourceMapping): @@ -24,7 +24,7 @@ class Error(ResourceMapping): :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - :ivar type: """ + :ivar type:""" message: str type: str @@ -40,13 +40,13 @@ def from_dict(cls, d: Any): class Result(ResourceMapping): """ - :ivar was_confirmed_by_device: + :ivar was_confirmed_by_device: :ivar acs_credential_on_encoder: Snapshot of credential data read from the physical encoder. :ivar acs_credential_on_seam: Corresponding credential data as stored on Seam and the access system. - :ivar warnings: + :ivar warnings: :ivar access_method: Access method for the `credential `_. Supported values: ``code``, ``card``, ``mobile_key``, ``cloud_key``. @@ -64,23 +64,23 @@ class Result(ResourceMapping): :ivar card_number: Number of the card associated with the `credential `_. - :ivar code: + :ivar code: :ivar connected_account_id: ID of the `connected account `_ to which the `credential `_ belongs. - :ivar created_at: + :ivar created_at: - :ivar display_name: + :ivar display_name: :ivar ends_at: Date and time at which the `credential `_ validity ends, in `ISO 8601 `_ format. Must be a time in the future and after ``starts_at``. - :ivar errors: + :ivar errors: :ivar external_type: Brand-specific terminology for the `credential `_ type. Supported values: ``pti_card``, ``brivo_credential``, ``hid_credential``, ``visionline_card``. :ivar external_type_display_name: Display name that corresponds to the brand-specific terminology for the `credential `_ type. - :ivar is_issued: + :ivar is_issued: :ivar is_latest_desired_state_synced_with_provider: Indicates whether the latest state of the `credential `_ has been synced from Seam to the provider. @@ -90,7 +90,7 @@ class Result(ResourceMapping): :ivar is_one_time_use: Indicates whether the `credential `_ can only be used once. If ``true``, the code becomes invalid after the first use. - :ivar issued_at: + :ivar issued_at: :ivar latest_desired_state_synced_with_provider_at: Date and time at which the state of the `credential `_ was most recently synced from Seam to the provider. @@ -102,7 +102,7 @@ class Result(ResourceMapping): :ivar visionline_metadata: Visionline-specific metadata for the `credential `_. - :ivar workspace_id: + :ivar workspace_id: :ivar access_method_id: ID of the access method. @@ -124,9 +124,9 @@ class Result(ResourceMapping): :ivar pending_mutations: Pending mutations for the `access method `_. Indicates operations that are in progress. - :ivar access_code: + :ivar access_code: - :ivar noise_threshold: """ + :ivar noise_threshold:""" @dataclass class AcsCredentialOnEncoder(ResourceMapping): @@ -142,7 +142,8 @@ class AcsCredentialOnEncoder(ResourceMapping): :ivar starts_at: Date and time at which the `credential `_ becomes usable. - :ivar visionline_metadata: Visionline-specific metadata for the `credential `_.""" + :ivar visionline_metadata: Visionline-specific metadata for the `credential `_. + """ @dataclass class VisionlineMetadata(ResourceMapping): @@ -170,7 +171,8 @@ class VisionlineMetadata(ResourceMapping): :ivar overwritten: Indicates whether the card associated with the `credential `_ is overwritten. - :ivar pending_auto_update: Indicates whether the card associated with the `credential `_ is pending auto-update.""" + :ivar pending_auto_update: Indicates whether the card associated with the `credential `_ is pending auto-update. + """ cancelled: Optional[bool] card_format: Optional[str] @@ -217,7 +219,11 @@ def from_dict(cls, d: Any): ends_at=d.get("ends_at", None), is_issued=d.get("is_issued", None), starts_at=d.get("starts_at", None), - visionline_metadata=cls.VisionlineMetadata.from_dict(d.get("visionline_metadata")) if d.get("visionline_metadata") is not None else None, + visionline_metadata=( + cls.VisionlineMetadata.from_dict(d.get("visionline_metadata")) + if d.get("visionline_metadata") is not None + else None + ), ) @dataclass @@ -260,7 +266,7 @@ class AcsCredentialOnSeam(ResourceMapping): :ivar is_latest_desired_state_synced_with_provider: Indicates whether the latest state of the `credential `_ has been synced from Seam to the provider. - :ivar is_managed: + :ivar is_managed: :ivar is_multi_phone_sync_credential: Indicates whether the `credential `_ is a `multi-phone sync credential `_. @@ -280,7 +286,8 @@ class AcsCredentialOnSeam(ResourceMapping): :ivar warnings: Warnings associated with the `credential `_. - :ivar workspace_id: ID of the workspace that contains the `credential `_.""" + :ivar workspace_id: ID of the workspace that contains the `credential `_. + """ @dataclass class AkilesMetadata(ResourceMapping): @@ -310,7 +317,8 @@ class AssaAbloyVostioMetadata(ResourceMapping): :ivar key_issuing_request_id: Key issuing request ID in the Vostio access system. - :ivar override_guest_acs_entrance_ids: IDs of the guest entrances to override in the Vostio access system.""" + :ivar override_guest_acs_entrance_ids: IDs of the guest entrances to override in the Vostio access system. + """ auto_join: Optional[bool] door_names: Optional[List[str]] @@ -327,7 +335,9 @@ def from_dict(cls, d: Any): endpoint_id=d.get("endpoint_id", None), key_id=d.get("key_id", None), key_issuing_request_id=d.get("key_issuing_request_id", None), - override_guest_acs_entrance_ids=d.get("override_guest_acs_entrance_ids", None), + override_guest_acs_entrance_ids=d.get( + "override_guest_acs_entrance_ids", None + ), ) @dataclass @@ -336,9 +346,9 @@ class Errors(ResourceMapping): :ivar created_at: Date and time at which Seam created the error. - :ivar error_code: + :ivar error_code: - :ivar message: """ + :ivar message:""" created_at: str error_code: str @@ -370,7 +380,8 @@ class VisionlineMetadata(ResourceMapping): :ivar is_valid: Indicates whether the credential is valid. - :ivar joiner_acs_credential_ids: IDs of the credentials to which you want to join.""" + :ivar joiner_acs_credential_ids: IDs of the credentials to which you want to join. + """ auto_join: Optional[bool] card_function_type: Optional[str] @@ -391,7 +402,9 @@ def from_dict(cls, d: Any): credential_id=d.get("credential_id", None), guest_acs_entrance_ids=d.get("guest_acs_entrance_ids", None), is_valid=d.get("is_valid", None), - joiner_acs_credential_ids=d.get("joiner_acs_credential_ids", None), + joiner_acs_credential_ids=d.get( + "joiner_acs_credential_ids", None + ), ) @dataclass @@ -406,7 +419,8 @@ class Warnings(ResourceMapping): :ivar new_code: The PIN code that was assigned instead. - :ivar original_code: The originally requested PIN code that could not be used.""" + :ivar original_code: The originally requested PIN code that could not be used. + """ created_at: str message: str @@ -462,8 +476,18 @@ def from_dict(cls, d: Any): acs_credential_pool_id=d.get("acs_credential_pool_id", None), acs_system_id=d.get("acs_system_id", None), acs_user_id=d.get("acs_user_id", None), - akiles_metadata=cls.AkilesMetadata.from_dict(d.get("akiles_metadata")) if d.get("akiles_metadata") is not None else None, - assa_abloy_vostio_metadata=cls.AssaAbloyVostioMetadata.from_dict(d.get("assa_abloy_vostio_metadata")) if d.get("assa_abloy_vostio_metadata") is not None else None, + akiles_metadata=( + cls.AkilesMetadata.from_dict(d.get("akiles_metadata")) + if d.get("akiles_metadata") is not None + else None + ), + assa_abloy_vostio_metadata=( + cls.AssaAbloyVostioMetadata.from_dict( + d.get("assa_abloy_vostio_metadata") + ) + if d.get("assa_abloy_vostio_metadata") is not None + else None + ), card_number=d.get("card_number", None), code=d.get("code", None), connected_account_id=d.get("connected_account_id", None), @@ -472,19 +496,33 @@ def from_dict(cls, d: Any): ends_at=d.get("ends_at", None), errors=[cls.Errors.from_dict(i) for i in d.get("errors") or []], external_type=d.get("external_type", None), - external_type_display_name=d.get("external_type_display_name", None), + external_type_display_name=d.get( + "external_type_display_name", None + ), is_issued=d.get("is_issued", None), - is_latest_desired_state_synced_with_provider=d.get("is_latest_desired_state_synced_with_provider", None), + is_latest_desired_state_synced_with_provider=d.get( + "is_latest_desired_state_synced_with_provider", None + ), is_managed=d.get("is_managed", None), - is_multi_phone_sync_credential=d.get("is_multi_phone_sync_credential", None), + is_multi_phone_sync_credential=d.get( + "is_multi_phone_sync_credential", None + ), is_one_time_use=d.get("is_one_time_use", None), issued_at=d.get("issued_at", None), - latest_desired_state_synced_with_provider_at=d.get("latest_desired_state_synced_with_provider_at", None), + latest_desired_state_synced_with_provider_at=d.get( + "latest_desired_state_synced_with_provider_at", None + ), parent_acs_credential_id=d.get("parent_acs_credential_id", None), starts_at=d.get("starts_at", None), user_identity_id=d.get("user_identity_id", None), - visionline_metadata=cls.VisionlineMetadata.from_dict(d.get("visionline_metadata")) if d.get("visionline_metadata") is not None else None, - warnings=[cls.Warnings.from_dict(i) for i in d.get("warnings") or []], + visionline_metadata=( + cls.VisionlineMetadata.from_dict(d.get("visionline_metadata")) + if d.get("visionline_metadata") is not None + else None + ), + warnings=[ + cls.Warnings.from_dict(i) for i in d.get("warnings") or [] + ], workspace_id=d.get("workspace_id", None), ) @@ -492,7 +530,7 @@ def from_dict(cls, d: Any): class Warnings(ResourceMapping): """ - :ivar warning_code: + :ivar warning_code: :ivar warning_message: Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. @@ -504,7 +542,8 @@ class Warnings(ResourceMapping): :ivar original_code: The originally requested PIN code that could not be used. - :ivar original_access_method_id: ID of the original access method from which this backup access method was split, if applicable.""" + :ivar original_access_method_id: ID of the original access method from which this backup access method was split, if applicable. + """ warning_code: str warning_message: Optional[str] @@ -554,7 +593,8 @@ class AssaAbloyVostioMetadata(ResourceMapping): :ivar key_issuing_request_id: Key issuing request ID in the Vostio access system. - :ivar override_guest_acs_entrance_ids: IDs of the guest entrances to override in the Vostio access system.""" + :ivar override_guest_acs_entrance_ids: IDs of the guest entrances to override in the Vostio access system. + """ auto_join: Optional[bool] door_names: Optional[List[str]] @@ -571,7 +611,9 @@ def from_dict(cls, d: Any): endpoint_id=d.get("endpoint_id", None), key_id=d.get("key_id", None), key_issuing_request_id=d.get("key_issuing_request_id", None), - override_guest_acs_entrance_ids=d.get("override_guest_acs_entrance_ids", None), + override_guest_acs_entrance_ids=d.get( + "override_guest_acs_entrance_ids", None + ), ) @dataclass @@ -582,7 +624,8 @@ class Errors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + """ created_at: str error_code: str @@ -614,7 +657,8 @@ class VisionlineMetadata(ResourceMapping): :ivar is_valid: Indicates whether the credential is valid. - :ivar joiner_acs_credential_ids: IDs of the credentials to which you want to join.""" + :ivar joiner_acs_credential_ids: IDs of the credentials to which you want to join. + """ auto_join: Optional[bool] card_function_type: Optional[str] @@ -698,10 +742,18 @@ def from_dict(cls, d: Any): def from_dict(cls, d: Any): return cls( created_at=d.get("created_at", None), - from_=cls.From.from_dict(d.get("from")) if d.get("from") is not None else None, + from_=( + cls.From.from_dict(d.get("from")) + if d.get("from") is not None + else None + ), message=d.get("message", None), mutation_code=d.get("mutation_code", None), - to=cls.To.from_dict(d.get("to")) if d.get("to") is not None else None, + to=( + cls.To.from_dict(d.get("to")) + if d.get("to") is not None + else None + ), ) was_confirmed_by_device: Optional[bool] @@ -753,16 +805,36 @@ def from_dict(cls, d: Any): def from_dict(cls, d: Any): return cls( was_confirmed_by_device=d.get("was_confirmed_by_device", None), - acs_credential_on_encoder=cls.AcsCredentialOnEncoder.from_dict(d.get("acs_credential_on_encoder")) if d.get("acs_credential_on_encoder") is not None else None, - acs_credential_on_seam=cls.AcsCredentialOnSeam.from_dict(d.get("acs_credential_on_seam")) if d.get("acs_credential_on_seam") is not None else None, + acs_credential_on_encoder=( + cls.AcsCredentialOnEncoder.from_dict( + d.get("acs_credential_on_encoder") + ) + if d.get("acs_credential_on_encoder") is not None + else None + ), + acs_credential_on_seam=( + cls.AcsCredentialOnSeam.from_dict(d.get("acs_credential_on_seam")) + if d.get("acs_credential_on_seam") is not None + else None + ), warnings=[cls.Warnings.from_dict(i) for i in d.get("warnings") or []], access_method=d.get("access_method", None), acs_credential_id=d.get("acs_credential_id", None), acs_credential_pool_id=d.get("acs_credential_pool_id", None), acs_system_id=d.get("acs_system_id", None), acs_user_id=d.get("acs_user_id", None), - akiles_metadata=cls.AkilesMetadata.from_dict(d.get("akiles_metadata")) if d.get("akiles_metadata") is not None else None, - assa_abloy_vostio_metadata=cls.AssaAbloyVostioMetadata.from_dict(d.get("assa_abloy_vostio_metadata")) if d.get("assa_abloy_vostio_metadata") is not None else None, + akiles_metadata=( + cls.AkilesMetadata.from_dict(d.get("akiles_metadata")) + if d.get("akiles_metadata") is not None + else None + ), + assa_abloy_vostio_metadata=( + cls.AssaAbloyVostioMetadata.from_dict( + d.get("assa_abloy_vostio_metadata") + ) + if d.get("assa_abloy_vostio_metadata") is not None + else None + ), card_number=d.get("card_number", None), code=d.get("code", None), connected_account_id=d.get("connected_account_id", None), @@ -773,16 +845,26 @@ def from_dict(cls, d: Any): external_type=d.get("external_type", None), external_type_display_name=d.get("external_type_display_name", None), is_issued=d.get("is_issued", None), - is_latest_desired_state_synced_with_provider=d.get("is_latest_desired_state_synced_with_provider", None), + is_latest_desired_state_synced_with_provider=d.get( + "is_latest_desired_state_synced_with_provider", None + ), is_managed=d.get("is_managed", None), - is_multi_phone_sync_credential=d.get("is_multi_phone_sync_credential", None), + is_multi_phone_sync_credential=d.get( + "is_multi_phone_sync_credential", None + ), is_one_time_use=d.get("is_one_time_use", None), issued_at=d.get("issued_at", None), - latest_desired_state_synced_with_provider_at=d.get("latest_desired_state_synced_with_provider_at", None), + latest_desired_state_synced_with_provider_at=d.get( + "latest_desired_state_synced_with_provider_at", None + ), parent_acs_credential_id=d.get("parent_acs_credential_id", None), starts_at=d.get("starts_at", None), user_identity_id=d.get("user_identity_id", None), - visionline_metadata=cls.VisionlineMetadata.from_dict(d.get("visionline_metadata")) if d.get("visionline_metadata") is not None else None, + visionline_metadata=( + cls.VisionlineMetadata.from_dict(d.get("visionline_metadata")) + if d.get("visionline_metadata") is not None + else None + ), workspace_id=d.get("workspace_id", None), access_method_id=d.get("access_method_id", None), client_session_token=d.get("client_session_token", None), @@ -793,7 +875,10 @@ def from_dict(cls, d: Any): is_ready_for_assignment=d.get("is_ready_for_assignment", None), is_ready_for_encoding=d.get("is_ready_for_encoding", None), mode=d.get("mode", None), - pending_mutations=[cls.PendingMutations.from_dict(i) for i in d.get("pending_mutations") or []], + pending_mutations=[ + cls.PendingMutations.from_dict(i) + for i in d.get("pending_mutations") or [] + ], access_code=DeepAttrDict(d.get("access_code", None)), noise_threshold=DeepAttrDict(d.get("noise_threshold", None)), ) @@ -809,7 +894,15 @@ 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")) + 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 + ), status=d.get("status", None), ) diff --git a/seam/resources/batch.py b/seam/resources/batch.py index aaa1b466..02ae27b1 100644 --- a/seam/resources/batch.py +++ b/seam/resources/batch.py @@ -9,13 +9,13 @@ class Batch: """A batch of workspace resources. :ivar access_codes: Represents a smart lock `access code `_. - + An access code is a code used for a keypad or pinpad device. Unlike physical keys, which can easily be lost or duplicated, PIN codes can be customized, tracked, and altered on the fly. Using the Seam Access Code API, you can easily generate access codes on the hundreds of door lock models with which we integrate. - + Seam supports programming two types of access codes: `ongoing `_ and `time-bound `_. To differentiate between the two, refer to the ``type`` property of the access code. Ongoing codes display as ``ongoing``, whereas time-bound codes are labeled ``time_bound``. An ongoing access code is active, until it has been removed from the device. To specify an ongoing access code, leave both ``starts_at`` and ``ends_at`` empty. A time-bound access code will be programmed at the ``starts_at`` time and removed at the ``ends_at`` time. - + In addition, for certain devices, Seam also supports `offline access codes `_. Offline access (PIN) codes are designed for door locks that might not always maintain an internet connection. For this type of access code, the device manufacturer uses encryption keys (tokens) to create server-based registries of algorithmically-generated offline PIN codes. Because the tokens remain synchronized with the managed devices, the locks do not require an active internet connection—and you do not need to be near the locks—to create an offline access code. Then, owners or managers can share these offline codes with users through a variety of mechanisms, such as messaging applications. That is, lock users do not need to install a smartphone application to receive an offline access code. - + For granting a person access to a space, `Access Grants `_ are the default and recommended approach and work across both standalone smart locks and access systems. Use the lower-level Access Codes API directly only when you specifically need to manage individual PIN codes. :ivar access_grants: Represents an Access Grant. Access Grants enable you to grant a user identity access to spaces, entrances, and devices through one or more access methods, such as mobile keys, plastic cards, and PIN codes. You can create an Access Grant for an existing user identity, or you can create a new user identity *while* creating the new Access Grant. @@ -23,76 +23,76 @@ class Batch: :ivar access_methods: Represents an access method for an Access Grant. Access methods describe the modes of access, such as PIN codes, plastic cards, and mobile keys. For a mobile key, the access method also stores the URL for the associated Instant Key. :ivar acs_access_groups: Group that defines the entrances to which a set of users has access and, in some cases, the access schedule for these entrances and users. - + Some access control systems use `access group `_, which are sets of users, combined with sets of permissions. These permissions include both the set of areas or assets that the users can access and the schedule during which the users can access these areas or assets. Instead of assigning access rights individually to each access control system user, which can be time-consuming and error-prone, administrators can assign users to an access group, thereby ensuring that the users inherit all the permissions associated with the access group. Using access groups streamlines the process of managing large numbers of access control system users, especially in bigger organizations or complexes. - + To learn whether your access control system supports access groups, see the corresponding `system integration guide `_. :ivar acs_credentials: Means by which an `access control system user `_ gains access at an `entrance `_. The ``acs_credential`` object represents a `credential `_ that provides an ACS user access within an `access control system `_. - + An access control system generally uses digital means of access to authorize a user trying to get through a specific entrance. Examples of credentials include plastic key cards, mobile keys, biometric identifiers, and PIN codes. The electronic nature of these credentials, as well as the fact that access is centralized, enables both the rapid provisioning and rescinding of access and the ability to compile access audit logs. - + For each ``acs_credential``, you define the access method. You can also specify additional properties, such as a PIN code, depending on the credential type. - + For granting a person access to a space, `Access Grants `_ are the default and recommended approach. Use the lower-level ACS credential API directly only when you specifically need to manage individual credentials. :ivar acs_encoders: Represents a hardware device that encodes `credential `_ data onto physical cards within an `access control system `_. - + Some access control systems require credentials to be encoded onto plastic key cards using a card encoder. This process involves the following two key steps: - + 1. Credential creation Configure the access parameters for the credential. 2. Card encoding Write the credential data onto the card using a compatible card encoder. - + Separately, the Seam API also supports card scanning, which enables you to scan and read the encoded data on a card. You can use this action to confirm consistency with access control system records or diagnose discrepancies if needed. - + See `Working with Card Encoders and Scanners `_. - + To verify if your access control system requires a card encoder, see the corresponding `system integration guide `_. :ivar acs_entrances: Represents an `entrance `_ within an `access control system `_. - + In an access control system, an entrance is a secured door, gate, zone, or other method of entry. You can list details for all the ``acs_entrance`` resources in your workspace or get these details for a specific ``acs_entrance``. You can also list all entrances associated with a specific credential, and you can list all credentials associated with a specific entrance. :ivar acs_systems: Represents an `access control system `_. - + Within an ``acs_system``, create ```acs_user``s `_ and ```acs_credential``s `_ to grant access to the ``acs_user``s. - + For details about the resources associated with an access control system, see the `access control systems namespace `_. :ivar acs_users: Represents a `user `_ in an `access system `_. - + An access system user typically refers to an individual who requires access, like an employee or resident. Each user can possess multiple credentials that serve as their keys or identifiers for access. The type of credential can vary widely. For example, in the Salto system, a user can have a PIN code, a mobile app account, and a fob. In other platforms, it is not uncommon for a user to have more than one of the same credential type, such as multiple key cards. Additionally, these credentials can have a schedule or validity period. - + For details about how to configure users in your access system, see the corresponding `system integration guide `_. :ivar action_attempts: Represents an action attempt that enables you to keep track of the progress of your action that affects a physical device or system.actions against a device. Action attempts are useful because the physical world is intrinsically asynchronous. - + When you request for a device to perform an action, the Seam API immediately returns an action attempt object. In the background, the Seam API performs the action. - + See also `Action Attempts `_. :ivar client_sessions: Represents a `client session `_. If you want to restrict your users' access to their own devices, use client sessions. - + You create each client session with a custom ``user_identifier_key``. Normally, the ``user_identifier_key`` is a user ID that your application provides. - + When calling the Seam API from your backend using an API key, you can pass the ``user_identifier_key`` as a parameter to limit results to the associated client session. For example, ``/devices/list?user_identifier_key=123`` only returns devices associated with the client session created with the ``user_identifier_key`` ``123``. - + A client session has a token that you can use with the Seam JavaScript SDK to make requests from the client (browser) directly to the Seam API. The token restricts the user's access to only the devices that they own. - + See also `Get Started with React `_. :ivar connect_webviews: Represents a `Connect Webview `_. - + Connect Webviews are fully-embedded client-side components that you add to your app. Your users interact with your embedded Connect Webviews to link their IoT device or system accounts to Seam. That is, Connect Webviews walk your users through the process of logging in to their device or system accounts. Seam handles all the authentication steps, and—once your user has completed the authorization through your app—you can access and control their devices or systems using the Seam API. - + Connect Webviews perform credential validation, multifactor authentication (when applicable), and error handling for each brand that Seam supports. Further, Connect Webviews work across all modern browsers and platforms, including Chrome, Safari, and Firefox. - + To enable a user to connect their device or system account to Seam through your app, first create a ``connect_webview``. Once created, this ``connect_webview`` includes a URL that you can use to open an `iframe `_ or new window containing the Connect Webview for your user. - + When you create a Connect Webview, specify the desired provider category key in the ``provider_category`` parameter. Alternately, to specify a list of providers explicitly, use the ``accepted_providers`` parameter with a list of device provider keys. - + To list all providers within a category, use ``/devices/list_device_providers`` with the desired ``provider_category`` filter. To list all provider keys, use ``/devices/list_device_providers`` with no filters. :ivar connected_accounts: Represents a `connected account `_. A connected account is an external third-party account to which your user has authorized Seam to get access, for example, an August account with a list of door locks. @@ -100,11 +100,11 @@ class Batch: :ivar devices: Represents a `device `_ that has been connected to Seam. :ivar events: Represents an event. Events let you know when something interesting happens in your workspace. For example, when a lock is unlocked, Seam creates a ``lock.unlocked`` event. When a device's battery level is low, Seam creates a ``device.battery_low`` event. - + As with other API resources, you can retrieve an individual event or a list of events. Seam also provides a separate webhook system for sending the event objects directly to an endpoint on your sever. Manage webhooks through `Seam Console `_. You can also use the webhooks sandbox in Seam Console to see the different payloads for each event and test them against your own endpoints. :ivar instant_keys: Represents a Seam Instant Key. For issuing Bluetooth mobile keys, Instant Keys are the fastest way to share access. With a single API call, you can create a mobile key and send it through text or email or embed it in your own app. - + There’s no app to install, nor account to create. Your user just taps a link and gets a lightweight, native-feeling experience using iOS App Clip or Instant Apps on Android. Further, Instant Keys work offline, so even in areas with poor cellular or Wi-Fi, like elevator banks or concrete-walled hallways, the Instant Keys still work. :ivar noise_thresholds: Represents a `noise threshold `_ for a `noise sensor `_. Thresholds represent the limits of noise tolerated at a property, which can be customized for each hour of the day. Each device has its own default thresholds, but you can use the Seam API to modify them. @@ -116,22 +116,23 @@ class Batch: :ivar thermostat_schedules: Represents a `thermostat schedule `_ that activates a configured `climate preset `_ on a `thermostat `_ at a specified starting time and deactivates the climate preset at a specified ending time. :ivar unmanaged_access_codes: Represents an `unmanaged smart lock access code `_. - + An access code is a code used for a keypad or pinpad device. Unlike physical keys, which can easily be lost or duplicated, PIN codes can be customized, tracked, and altered on the fly. - + When you create an access code on a device in Seam, it is created as a managed access code. Access codes that exist on a device that were not created through Seam are considered unmanaged codes. We strictly limit the operations that can be performed on unmanaged codes. - + Prior to using Seam to manage your devices, you may have used another lock management system to manage the access codes on your devices. Where possible, we help you keep any existing access codes on devices and transition those codes to ones managed by your Seam workspace. - + Not all providers support unmanaged access codes. The following providers do not support unmanaged access codes: - + - `Kwikset `_ :ivar unmanaged_devices: Represents an `unmanaged device `_. An unmanaged device has a limited set of visible properties and a subset of supported events. You cannot control an unmanaged device. Any `access codes `_ on an unmanaged device are unmanaged. To control an unmanaged device with Seam, `convert it to a managed device `_. :ivar user_identities: Represents a `user identity `_ associated with an application user account. - :ivar workspaces: Represents a Seam `workspace `_. A workspace is a top-level entity that encompasses all other resources below it, such as devices, connected accounts, and Connect Webviews. Seam provides two types of workspaces. A `sandbox workspace `_ is a special type of workspace designed for testing code. Sandbox workspaces offer test device accounts and virtual devices that you can connect and control. This ability to work with virtual devices is quite handy because it removes the need to own physical devices from multiple brands. To connect real devices and systems to Seam, use a `production workspace `_.""" + :ivar workspaces: Represents a Seam `workspace `_. A workspace is a top-level entity that encompasses all other resources below it, such as devices, connected accounts, and Connect Webviews. Seam provides two types of workspaces. A `sandbox workspace `_ is a special type of workspace designed for testing code. Sandbox workspaces offer test device accounts and virtual devices that you can connect and control. This ability to work with virtual devices is quite handy because it removes the need to own physical devices from multiple brands. To connect real devices and systems to Seam, use a `production workspace `_. + """ access_codes: Optional[List[Dict[str, Any]]] access_grants: Optional[List[Dict[str, Any]]] diff --git a/seam/resources/client_session.py b/seam/resources/client_session.py index ff77ff6a..c4ec26b1 100644 --- a/seam/resources/client_session.py +++ b/seam/resources/client_session.py @@ -7,13 +7,13 @@ @dataclass class ClientSession: """Represents a `client session `_. If you want to restrict your users' access to their own devices, use client sessions. - + You create each client session with a custom ``user_identifier_key``. Normally, the ``user_identifier_key`` is a user ID that your application provides. - + When calling the Seam API from your backend using an API key, you can pass the ``user_identifier_key`` as a parameter to limit results to the associated client session. For example, ``/devices/list?user_identifier_key=123`` only returns devices associated with the client session created with the ``user_identifier_key`` ``123``. - + A client session has a token that you can use with the Seam JavaScript SDK to make requests from the client (browser) directly to the Seam API. The token restricts the user's access to only the devices that they own. - + See also `Get Started with React `_. :ivar client_session_id: ID of the client session. diff --git a/seam/resources/connect_webview.py b/seam/resources/connect_webview.py index 344b8c0e..582894a2 100644 --- a/seam/resources/connect_webview.py +++ b/seam/resources/connect_webview.py @@ -7,15 +7,15 @@ @dataclass class ConnectWebview: """Represents a `Connect Webview `_. - + Connect Webviews are fully-embedded client-side components that you add to your app. Your users interact with your embedded Connect Webviews to link their IoT device or system accounts to Seam. That is, Connect Webviews walk your users through the process of logging in to their device or system accounts. Seam handles all the authentication steps, and—once your user has completed the authorization through your app—you can access and control their devices or systems using the Seam API. - + Connect Webviews perform credential validation, multifactor authentication (when applicable), and error handling for each brand that Seam supports. Further, Connect Webviews work across all modern browsers and platforms, including Chrome, Safari, and Firefox. - + To enable a user to connect their device or system account to Seam through your app, first create a ``connect_webview``. Once created, this ``connect_webview`` includes a URL that you can use to open an `iframe `_ or new window containing the Connect Webview for your user. - + When you create a Connect Webview, specify the desired provider category key in the ``provider_category`` parameter. Alternately, to specify a list of providers explicitly, use the ``accepted_providers`` parameter with a list of device provider keys. - + To list all providers within a category, use ``/devices/list_device_providers`` with the desired ``provider_category`` filter. To list all provider keys, use ``/devices/list_device_providers`` with no filters. :ivar accepted_capabilities: High-level device capabilities that the Connect Webview can accept. When creating a Connect Webview, you can specify the types of devices that it can connect to Seam. If you do not set custom ``accepted_capabilities``, Seam uses a default set of ``accepted_capabilities`` for each provider. For example, if you create a Connect Webview that accepts SmartThing devices, without specifying ``accepted_capabilities``, Seam accepts only SmartThings locks. To connect SmartThings thermostats and locks to Seam, create a Connect Webview and include both ``thermostat`` and ``lock`` in the ``accepted_capabilities``. @@ -83,7 +83,9 @@ def from_dict(cls, d: Any): accepted_providers=d.get("accepted_providers", None), any_provider_allowed=d.get("any_provider_allowed", None), authorized_at=d.get("authorized_at", None), - automatically_manage_new_devices=d.get("automatically_manage_new_devices", None), + automatically_manage_new_devices=d.get( + "automatically_manage_new_devices", None + ), connect_webview_id=d.get("connect_webview_id", None), connected_account_id=d.get("connected_account_id", None), created_at=d.get("created_at", None), diff --git a/seam/resources/connected_account.py b/seam/resources/connected_account.py index af65c1e3..c27e4e7f 100644 --- a/seam/resources/connected_account.py +++ b/seam/resources/connected_account.py @@ -58,13 +58,15 @@ class Errors(ResourceMapping): :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - :ivar salto_ks_metadata: Salto KS metadata associated with the connected account that has an error.""" + :ivar salto_ks_metadata: Salto KS metadata associated with the connected account that has an error. + """ @dataclass class SaltoKsMetadata(ResourceMapping): """Salto KS metadata associated with the connected account that has an error. - :ivar sites: Salto sites associated with the connected account that has an error.""" + :ivar sites: Salto sites associated with the connected account that has an error. + """ @dataclass class Sites(ResourceMapping): @@ -76,7 +78,8 @@ class Sites(ResourceMapping): :ivar site_user_subscription_limit: Subscription limit of site users for a Salto site associated with the connected account that has an error. - :ivar subscribed_site_user_count: Count of subscribed site users for a Salto site associated with the connected account that has an error.""" + :ivar subscribed_site_user_count: Count of subscribed site users for a Salto site associated with the connected account that has an error. + """ site_id: Optional[str] site_name: Optional[str] @@ -88,8 +91,12 @@ def from_dict(cls, d: Any): return cls( site_id=d.get("site_id", None), site_name=d.get("site_name", None), - site_user_subscription_limit=d.get("site_user_subscription_limit", None), - subscribed_site_user_count=d.get("subscribed_site_user_count", None), + site_user_subscription_limit=d.get( + "site_user_subscription_limit", None + ), + subscribed_site_user_count=d.get( + "subscribed_site_user_count", None + ), ) sites: Optional[List[Sites]] @@ -115,7 +122,11 @@ def from_dict(cls, d: Any): is_bridge_error=d.get("is_bridge_error", None), is_connected_account_error=d.get("is_connected_account_error", None), message=d.get("message", None), - salto_ks_metadata=cls.SaltoKsMetadata.from_dict(d.get("salto_ks_metadata")) if d.get("salto_ks_metadata") is not None else None, + salto_ks_metadata=( + cls.SaltoKsMetadata.from_dict(d.get("salto_ks_metadata")) + if d.get("salto_ks_metadata") is not None + else None + ), ) @dataclass @@ -130,7 +141,8 @@ class UserIdentifier(ResourceMapping): :ivar phone: Phone number of the user identifier associated with the connected account. - :ivar username: Username of the user identifier associated with the connected account.""" + :ivar username: Username of the user identifier associated with the connected account. + """ api_url: Optional[str] email: Optional[str] @@ -158,13 +170,15 @@ class Warnings(ResourceMapping): :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - :ivar salto_ks_metadata: Salto KS metadata associated with the connected account that has a warning.""" + :ivar salto_ks_metadata: Salto KS metadata associated with the connected account that has a warning. + """ @dataclass class SaltoKsMetadata(ResourceMapping): """Salto KS metadata associated with the connected account that has a warning. - :ivar sites: Salto sites associated with the connected account that has a warning.""" + :ivar sites: Salto sites associated with the connected account that has a warning. + """ @dataclass class Sites(ResourceMapping): @@ -176,7 +190,8 @@ class Sites(ResourceMapping): :ivar site_user_subscription_limit: Subscription limit of site users for a Salto site associated with the connected account that has a warning. - :ivar subscribed_site_user_count: Count of subscribed site users for a Salto site associated with the connected account that has a warning.""" + :ivar subscribed_site_user_count: Count of subscribed site users for a Salto site associated with the connected account that has a warning. + """ site_id: Optional[str] site_name: Optional[str] @@ -188,8 +203,12 @@ def from_dict(cls, d: Any): return cls( site_id=d.get("site_id", None), site_name=d.get("site_name", None), - site_user_subscription_limit=d.get("site_user_subscription_limit", None), - subscribed_site_user_count=d.get("subscribed_site_user_count", None), + site_user_subscription_limit=d.get( + "site_user_subscription_limit", None + ), + subscribed_site_user_count=d.get( + "subscribed_site_user_count", None + ), ) sites: Optional[List[Sites]] @@ -211,7 +230,11 @@ def from_dict(cls, d: Any): created_at=d.get("created_at", None), message=d.get("message", None), warning_code=d.get("warning_code", None), - salto_ks_metadata=cls.SaltoKsMetadata.from_dict(d.get("salto_ks_metadata")) if d.get("salto_ks_metadata") is not None else None, + salto_ks_metadata=( + cls.SaltoKsMetadata.from_dict(d.get("salto_ks_metadata")) + if d.get("salto_ks_metadata") is not None + else None + ), ) accepted_capabilities: List[str] @@ -239,7 +262,9 @@ def from_dict(cls, d: Any): accepted_capabilities=d.get("accepted_capabilities", None), account_type=d.get("account_type", None), account_type_display_name=d.get("account_type_display_name", None), - automatically_manage_new_devices=d.get("automatically_manage_new_devices", None), + automatically_manage_new_devices=d.get( + "automatically_manage_new_devices", None + ), connected_account_id=d.get("connected_account_id", None), created_at=d.get("created_at", None), custom_metadata=DeepAttrDict(d.get("custom_metadata", None)), @@ -252,6 +277,10 @@ def from_dict(cls, d: Any): ical_url=d.get("ical_url", None), image_url=d.get("image_url", None), time_zone=d.get("time_zone", None), - user_identifier=cls.UserIdentifier.from_dict(d.get("user_identifier")) if d.get("user_identifier") is not None else None, + user_identifier=( + cls.UserIdentifier.from_dict(d.get("user_identifier")) + if d.get("user_identifier") is not None + else None + ), warnings=[cls.Warnings.from_dict(i) for i in d.get("warnings") or []], ) diff --git a/seam/resources/customer_portal.py b/seam/resources/customer_portal.py index 9ab2ddc1..d49625df 100644 --- a/seam/resources/customer_portal.py +++ b/seam/resources/customer_portal.py @@ -7,9 +7,9 @@ @dataclass class CustomerPortal: """Represents a Customer Portal. Customer Portal is a hosted, customizable interface for managing device access. It enables you to embed secure, pre-authenticated access flows into your product—either by sharing a link with users or embedding a view in an iframe. - + With Customer Portal, you no longer need to build out frontend experiences for physical access, thermostats, and sensors. Instead, you can ship enterprise-grade access control experiences in a fraction of the time, while maintaining your product's branding and user experience. - + Seam hosts these flows, handling everything from account connection and device mapping to full-featured device control. :ivar created_at: Date and time at which the customer portal link was created. diff --git a/seam/resources/device.py b/seam/resources/device.py index 290e8ae9..827983a7 100644 --- a/seam/resources/device.py +++ b/seam/resources/device.py @@ -80,7 +80,8 @@ class Device: :ivar warnings: Array of warnings associated with the device. Each warning object within the array contains two fields: ``warning_code`` and ``message``. ``warning_code`` is a string that uniquely identifies the type of warning, enabling quick recognition and categorization of the issue. ``message`` provides a more detailed description of the warning, offering insights into the issue and potentially how to rectify it. - :ivar workspace_id: Unique identifier for the Seam workspace associated with the device.""" + :ivar workspace_id: Unique identifier for the Seam workspace associated with the device. + """ @dataclass class DeviceManufacturer(ResourceMapping): @@ -90,7 +91,8 @@ class DeviceManufacturer(ResourceMapping): :ivar image_url: Image URL for the manufacturer logo. - :ivar manufacturer: Manufacturer identifier, such as ``august``, ``yale``, ``salto``, and so on.""" + :ivar manufacturer: Manufacturer identifier, such as ``august``, ``yale``, ``salto``, and so on. + """ display_name: str image_url: Optional[str] @@ -114,7 +116,8 @@ class DeviceProvider(ResourceMapping): :ivar image_url: Image URL for the device provider. - :ivar provider_category: Provider category. Indicates the third-party provider type, such as ``stable``, for stable integrations, or ``internal``, for internal integrations.""" + :ivar provider_category: Provider category. Indicates the third-party provider type, such as ``stable``, for stable integrations, or ``internal``, for internal integrations. + """ device_provider_name: str display_name: str @@ -138,13 +141,14 @@ class Errors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar is_connected_account_error: + :ivar is_connected_account_error: - :ivar is_device_error: + :ivar is_device_error: :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - :ivar is_bridge_error: Indicates whether the error is related to `Seam Bridge `_.""" + :ivar is_bridge_error: Indicates whether the error is related to `Seam Bridge `_. + """ created_at: str error_code: str @@ -174,7 +178,8 @@ class Location(ResourceMapping): :ivar time_zone: Time zone of the device location. - :ivar timezone: Deprecated: Use ``time_zone`` instead. Time zone of the device location.""" + :ivar timezone: Deprecated: Use ``time_zone`` instead. Time zone of the device location. + """ location_name: Optional[str] room_name: Optional[str] @@ -402,7 +407,8 @@ class Properties(ResourceMapping): :ivar thermostat_daily_programs: Configured `daily programs `_ for the thermostat. - :ivar thermostat_weekly_program: Current `weekly program `_ for the thermostat.""" + :ivar thermostat_weekly_program: Current `weekly program `_ for the thermostat. + """ @dataclass class AccessoryKeypad(ResourceMapping): @@ -410,13 +416,14 @@ class AccessoryKeypad(ResourceMapping): :ivar battery: Keypad battery properties. - :ivar is_connected: Indicates if an accessory keypad is connected to the device.""" + :ivar is_connected: Indicates if an accessory keypad is connected to the device. + """ @dataclass class Battery(ResourceMapping): """Keypad battery properties. - :ivar level: """ + :ivar level:""" level: float @@ -432,7 +439,11 @@ def from_dict(cls, d: Any): @classmethod def from_dict(cls, d: Any): return cls( - battery=cls.Battery.from_dict(d.get("battery")) if d.get("battery") is not None else None, + battery=( + cls.Battery.from_dict(d.get("battery")) + if d.get("battery") is not None + else None + ), is_connected=d.get("is_connected", None), ) @@ -440,7 +451,8 @@ def from_dict(cls, d: Any): class Appearance(ResourceMapping): """Appearance-related properties, as reported by the device. - :ivar name: Name of the device as seen from the provider API and application, not settable through Seam.""" + :ivar name: Name of the device as seen from the provider API and application, not settable through Seam. + """ name: str @@ -456,7 +468,8 @@ class Battery(ResourceMapping): :ivar level: Battery charge level as a value between 0 and 1, inclusive. - :ivar status: Represents the current status of the battery charge level. Values are ``critical``, which indicates an extremely low level, suggesting imminent shutdown or an urgent need for charging; ``low``, which signifies that the battery is under the preferred threshold and should be charged soon; ``good``, which denotes a satisfactory charge level, adequate for normal use without the immediate need for recharging; and ``full``, which represents a battery that is fully charged, providing the maximum duration of usage.""" + :ivar status: Represents the current status of the battery charge level. Values are ``critical``, which indicates an extremely low level, suggesting imminent shutdown or an urgent need for charging; ``low``, which signifies that the battery is under the preferred threshold and should be charged soon; ``good``, which denotes a satisfactory charge level, adequate for normal use without the immediate need for recharging; and ``full``, which represents a battery that is fully charged, providing the maximum duration of usage. + """ level: float status: str @@ -484,7 +497,8 @@ class Model(ResourceMapping): :ivar offline_access_codes_supported: Deprecated: use device.can_program_offline_access_codes. - :ivar online_access_codes_supported: Deprecated: use device.can_program_online_access_codes.""" + :ivar online_access_codes_supported: Deprecated: use device.can_program_online_access_codes. + """ accessory_keypad_supported: Optional[bool] can_connect_accessory_keypad: Optional[bool] @@ -497,13 +511,21 @@ class Model(ResourceMapping): @classmethod def from_dict(cls, d: Any): return cls( - accessory_keypad_supported=d.get("accessory_keypad_supported", None), - can_connect_accessory_keypad=d.get("can_connect_accessory_keypad", None), + accessory_keypad_supported=d.get( + "accessory_keypad_supported", None + ), + can_connect_accessory_keypad=d.get( + "can_connect_accessory_keypad", None + ), display_name=d.get("display_name", None), has_built_in_keypad=d.get("has_built_in_keypad", None), manufacturer_display_name=d.get("manufacturer_display_name", None), - offline_access_codes_supported=d.get("offline_access_codes_supported", None), - online_access_codes_supported=d.get("online_access_codes_supported", None), + offline_access_codes_supported=d.get( + "offline_access_codes_supported", None + ), + online_access_codes_supported=d.get( + "online_access_codes_supported", None + ), ) @dataclass @@ -512,7 +534,8 @@ class AssaAbloyCredentialServiceMetadata(ResourceMapping): :ivar endpoints: Endpoints associated with the phone. - :ivar has_active_endpoint: Indicates whether the credential service has active endpoints associated with the phone.""" + :ivar has_active_endpoint: Indicates whether the credential service has active endpoints associated with the phone. + """ @dataclass class Endpoints(ResourceMapping): @@ -538,7 +561,9 @@ def from_dict(cls, d: Any): @classmethod def from_dict(cls, d: Any): return cls( - endpoints=[cls.Endpoints.from_dict(i) for i in d.get("endpoints") or []], + endpoints=[ + cls.Endpoints.from_dict(i) for i in d.get("endpoints") or [] + ], has_active_endpoint=d.get("has_active_endpoint", None), ) @@ -546,7 +571,8 @@ def from_dict(cls, d: Any): class SaltoSpaceCredentialServiceMetadata(ResourceMapping): """Salto Space credential service metadata for the phone. - :ivar has_active_phone: Indicates whether the credential service has an active associated phone.""" + :ivar has_active_phone: Indicates whether the credential service has an active associated phone. + """ has_active_phone: Optional[bool] @@ -814,8 +840,12 @@ def from_dict(cls, d: Any): return cls( check_in_time=d.get("check_in_time", None), check_out_time=d.get("check_out_time", None), - dormakaba_oracode_user_level_id=d.get("dormakaba_oracode_user_level_id", None), - dormakaba_oracode_user_level_prefix=d.get("dormakaba_oracode_user_level_prefix", None), + dormakaba_oracode_user_level_id=d.get( + "dormakaba_oracode_user_level_id", None + ), + dormakaba_oracode_user_level_prefix=d.get( + "dormakaba_oracode_user_level_prefix", None + ), is_24_hour=d.get("is_24_hour", None), is_biweekly_mode=d.get("is_biweekly_mode", None), is_master=d.get("is_master", None), @@ -841,7 +871,10 @@ def from_dict(cls, d: Any): door_is_wireless=d.get("door_is_wireless", None), door_name=d.get("door_name", None), iana_timezone=d.get("iana_timezone", None), - predefined_time_slots=[cls.PredefinedTimeSlots.from_dict(i) for i in d.get("predefined_time_slots") or []], + predefined_time_slots=[ + cls.PredefinedTimeSlots.from_dict(i) + for i in d.get("predefined_time_slots") or [] + ], site_id=d.get("site_id", None), site_name=d.get("site_name", None), ) @@ -872,7 +905,8 @@ class FourSuitesMetadata(ResourceMapping): :ivar device_name: Device name for a 4SUITES device. - :ivar reclose_delay_in_seconds: Reclose delay, in seconds, for a 4SUITES device.""" + :ivar reclose_delay_in_seconds: Reclose delay, in seconds, for a 4SUITES device. + """ device_id: Optional[float] device_name: Optional[str] @@ -910,7 +944,8 @@ class HoneywellResideoMetadata(ResourceMapping): :ivar device_name: Device name for a Honeywell Resideo device. - :ivar honeywell_resideo_device_id: Device ID for a Honeywell Resideo device.""" + :ivar honeywell_resideo_device_id: Device ID for a Honeywell Resideo device. + """ device_name: Optional[str] honeywell_resideo_device_id: Optional[str] @@ -919,7 +954,9 @@ class HoneywellResideoMetadata(ResourceMapping): def from_dict(cls, d: Any): return cls( device_name=d.get("device_name", None), - honeywell_resideo_device_id=d.get("honeywell_resideo_device_id", None), + honeywell_resideo_device_id=d.get( + "honeywell_resideo_device_id", None + ), ) @dataclass @@ -974,7 +1011,9 @@ def from_dict(cls, d: Any): bridge_name=d.get("bridge_name", None), device_id=d.get("device_id", None), device_name=d.get("device_name", None), - is_accessory_keypad_linked_to_bridge=d.get("is_accessory_keypad_linked_to_bridge", None), + is_accessory_keypad_linked_to_bridge=d.get( + "is_accessory_keypad_linked_to_bridge", None + ), keypad_id=d.get("keypad_id", None), ) @@ -1114,7 +1153,8 @@ class KorelockMetadata(ResourceMapping): :ivar serial_number: Serial number for a Korelock device. - :ivar wifi_signal_strength: WiFi signal strength (0-1) for a Korelock device.""" + :ivar wifi_signal_strength: WiFi signal strength (0-1) for a Korelock device. + """ device_id: Optional[str] device_name: Optional[str] @@ -1210,7 +1250,8 @@ class AccelerometerZ(ResourceMapping): :ivar time: Time of latest accelerometer Z-axis reading for a Minut device. - :ivar value: Value of latest accelerometer Z-axis reading for a Minut device.""" + :ivar value: Value of latest accelerometer Z-axis reading for a Minut device. + """ time: Optional[str] value: Optional[float] @@ -1282,7 +1323,8 @@ class Temperature(ResourceMapping): :ivar time: Time of latest temperature reading for a Minut device. - :ivar value: Value of latest temperature reading for a Minut device.""" + :ivar value: Value of latest temperature reading for a Minut device. + """ time: Optional[str] value: Optional[float] @@ -1303,11 +1345,31 @@ def from_dict(cls, d: Any): @classmethod def from_dict(cls, d: Any): return cls( - accelerometer_z=cls.AccelerometerZ.from_dict(d.get("accelerometer_z")) if d.get("accelerometer_z") is not None else None, - humidity=cls.Humidity.from_dict(d.get("humidity")) if d.get("humidity") is not None else None, - pressure=cls.Pressure.from_dict(d.get("pressure")) if d.get("pressure") is not None else None, - sound=cls.Sound.from_dict(d.get("sound")) if d.get("sound") is not None else None, - temperature=cls.Temperature.from_dict(d.get("temperature")) if d.get("temperature") is not None else None, + accelerometer_z=( + cls.AccelerometerZ.from_dict(d.get("accelerometer_z")) + if d.get("accelerometer_z") is not None + else None + ), + humidity=( + cls.Humidity.from_dict(d.get("humidity")) + if d.get("humidity") is not None + else None + ), + pressure=( + cls.Pressure.from_dict(d.get("pressure")) + if d.get("pressure") is not None + else None + ), + sound=( + cls.Sound.from_dict(d.get("sound")) + if d.get("sound") is not None + else None + ), + temperature=( + cls.Temperature.from_dict(d.get("temperature")) + if d.get("temperature") is not None + else None + ), ) device_id: Optional[str] @@ -1319,7 +1381,11 @@ def from_dict(cls, d: Any): return cls( device_id=d.get("device_id", None), device_name=d.get("device_name", None), - latest_sensor_values=cls.LatestSensorValues.from_dict(d.get("latest_sensor_values")) if d.get("latest_sensor_values") is not None else None, + latest_sensor_values=( + cls.LatestSensorValues.from_dict(d.get("latest_sensor_values")) + if d.get("latest_sensor_values") is not None + else None + ), ) @dataclass @@ -1336,7 +1402,8 @@ class NestMetadata(ResourceMapping): :ivar nest_structure_id: ID of the Google Nest structure containing the device. - :ivar structure_name: Name of the Google Nest structure containing the device. The device owner sets this value.""" + :ivar structure_name: Name of the Google Nest structure containing the device. The device owner sets this value. + """ device_custom_name: Optional[str] device_name: Optional[str] @@ -1368,7 +1435,8 @@ class NoiseawareMetadata(ResourceMapping): :ivar noise_level_decibel: Noise level, in decibels, for a NoiseAware device. - :ivar noise_level_nrs: Noise level, expressed as a Noise Risk Score (NRS), for a NoiseAware device.""" + :ivar noise_level_nrs: Noise level, expressed as a Noise Risk Score (NRS), for a NoiseAware device. + """ device_id: Optional[str] device_model: Optional[str] @@ -1398,7 +1466,8 @@ class NukiMetadata(ResourceMapping): :ivar keypad_battery_critical: Indicates whether the keypad battery is in a critical state for a Nuki device. - :ivar keypad_paired: Indicates whether the keypad is paired for a Nuki device.""" + :ivar keypad_paired: Indicates whether the keypad is paired for a Nuki device. + """ device_id: Optional[str] device_name: Optional[str] @@ -1432,7 +1501,8 @@ class OmnitecMetadata(ResourceMapping): :ivar time_zone: IANA time zone for the Omnitec device, used to schedule time-bound access codes at the correct local time (accounting for DST). - :ivar timezone_raw_offset_ms: Static UTC offset of the Omnitec lock in milliseconds. Does not account for DST.""" + :ivar timezone_raw_offset_ms: Static UTC offset of the Omnitec lock in milliseconds. Does not account for DST. + """ has_gateway: Optional[bool] lock_alias: Optional[str] @@ -1492,7 +1562,8 @@ class SaltoKsMetadata(ResourceMapping): :ivar site_id: Site ID for the Salto KS site to which the device belongs. - :ivar site_name: Site name for the Salto KS site to which the device belongs.""" + :ivar site_name: Site name for the Salto KS site to which the device belongs. + """ battery_level: Optional[str] customer_reference: Optional[str] @@ -1509,7 +1580,9 @@ def from_dict(cls, d: Any): return cls( battery_level=d.get("battery_level", None), customer_reference=d.get("customer_reference", None), - has_custom_pin_subscription=d.get("has_custom_pin_subscription", None), + has_custom_pin_subscription=d.get( + "has_custom_pin_subscription", None + ), lock_id=d.get("lock_id", None), lock_type=d.get("lock_type", None), locked_state=d.get("locked_state", None), @@ -1536,7 +1609,8 @@ class SaltoMetadata(ResourceMapping): :ivar site_id: Site ID for the Salto KS site to which the device belongs. - :ivar site_name: Site name for the Salto KS site to which the device belongs.""" + :ivar site_name: Site name for the Salto KS site to which the device belongs. + """ battery_level: Optional[str] customer_reference: Optional[str] @@ -1629,8 +1703,12 @@ def from_dict(cls, d: Any): return cls( device_id=d.get("device_id", None), device_name=d.get("device_name", None), - dual_setpoints_not_supported=d.get("dual_setpoints_not_supported", None), - enforced_setpoint_range_celsius=d.get("enforced_setpoint_range_celsius", None), + dual_setpoints_not_supported=d.get( + "dual_setpoints_not_supported", None + ), + enforced_setpoint_range_celsius=d.get( + "enforced_setpoint_range_celsius", None + ), product_type=d.get("product_type", None), ) @@ -1764,7 +1842,9 @@ class Features(ResourceMapping): def from_dict(cls, d: Any): return cls( auto_lock_time_config=d.get("auto_lock_time_config", None), - incomplete_keyboard_passcode=d.get("incomplete_keyboard_passcode", None), + incomplete_keyboard_passcode=d.get( + "incomplete_keyboard_passcode", None + ), lock_command=d.get("lock_command", None), passcode=d.get("passcode", None), passcode_management=d.get("passcode_management", None), @@ -1778,7 +1858,8 @@ class WirelessKeypads(ResourceMapping): :ivar wireless_keypad_id: ID for a wireless keypad for a TTLock device. - :ivar wireless_keypad_name: Name for a wireless keypad for a TTLock device.""" + :ivar wireless_keypad_name: Name for a wireless keypad for a TTLock device. + """ wireless_keypad_id: Optional[float] wireless_keypad_name: Optional[str] @@ -1802,12 +1883,19 @@ def from_dict(cls, d: Any): def from_dict(cls, d: Any): return cls( feature_value=d.get("feature_value", None), - features=cls.Features.from_dict(d.get("features")) if d.get("features") is not None else None, + features=( + cls.Features.from_dict(d.get("features")) + if d.get("features") is not None + else None + ), has_gateway=d.get("has_gateway", None), lock_alias=d.get("lock_alias", None), lock_id=d.get("lock_id", None), timezone_raw_offset_ms=d.get("timezone_raw_offset_ms", None), - wireless_keypads=[cls.WirelessKeypads.from_dict(i) for i in d.get("wireless_keypads") or []], + wireless_keypads=[ + cls.WirelessKeypads.from_dict(i) + for i in d.get("wireless_keypads") or [] + ], ) @dataclass @@ -1940,7 +2028,7 @@ def from_dict(cls, d: Any): class CodeConstraints(ResourceMapping): """Constraints on access codes for the device. Seam represents each constraint as an object with a ``constraint_type`` property. Depending on the constraint type, there may also be additional properties. Note that some constraints are manufacturer- or device-specific. - :ivar constraint_type: + :ivar constraint_type: :ivar max_length: Maximum name length constraint for access codes. @@ -1990,7 +2078,8 @@ class OfflineTimeFrameOptions(ResourceMapping): :ivar time_pairs: Fixed start/end time pairings the caller chooses from. Mutually exclusive with ``matching_start_end_time``. - :ivar time_zone: IANA time zone for interpreting ``time_pairs`` and the date recurrence rules. Present only when the option fixes times or dates.""" + :ivar time_zone: IANA time zone for interpreting ``time_pairs`` and the date recurrence rules. Present only when the option fixes times or dates. + """ @dataclass class TimePairs(ResourceMapping): @@ -2000,7 +2089,8 @@ class TimePairs(ResourceMapping): :ivar end_time: End time of day as a 24-hour ``HH:MM`` value, interpreted in the option's ``time_zone``. An ``end_time`` earlier on the clock than ``start_time`` means the end falls on a later date. - :ivar start_time: Start time of day as a 24-hour ``HH:MM`` value, interpreted in the option's ``time_zone``.""" + :ivar start_time: Start time of day as a 24-hour ``HH:MM`` value, interpreted in the option's ``time_zone``. + """ display_name: str end_time: str @@ -2031,8 +2121,12 @@ def from_dict(cls, d: Any): matching_start_end_time=d.get("matching_start_end_time", None), max_duration=d.get("max_duration", None), min_duration=d.get("min_duration", None), - start_date_recurrence_rule=d.get("start_date_recurrence_rule", None), - time_pairs=[cls.TimePairs.from_dict(i) for i in d.get("time_pairs") or []], + start_date_recurrence_rule=d.get( + "start_date_recurrence_rule", None + ), + time_pairs=[ + cls.TimePairs.from_dict(i) for i in d.get("time_pairs") or [] + ], time_zone=d.get("time_zone", None), ) @@ -2054,7 +2148,8 @@ class OnlineTimeFrameOptions(ResourceMapping): :ivar time_pairs: Fixed start/end time pairings the caller chooses from. Mutually exclusive with ``matching_start_end_time``. - :ivar time_zone: IANA time zone for interpreting ``time_pairs`` and the date recurrence rules. Present only when the option fixes times or dates.""" + :ivar time_zone: IANA time zone for interpreting ``time_pairs`` and the date recurrence rules. Present only when the option fixes times or dates. + """ @dataclass class TimePairs(ResourceMapping): @@ -2064,7 +2159,8 @@ class TimePairs(ResourceMapping): :ivar end_time: End time of day as a 24-hour ``HH:MM`` value, interpreted in the option's ``time_zone``. An ``end_time`` earlier on the clock than ``start_time`` means the end falls on a later date. - :ivar start_time: Start time of day as a 24-hour ``HH:MM`` value, interpreted in the option's ``time_zone``.""" + :ivar start_time: Start time of day as a 24-hour ``HH:MM`` value, interpreted in the option's ``time_zone``. + """ display_name: str end_time: str @@ -2095,8 +2191,12 @@ def from_dict(cls, d: Any): matching_start_end_time=d.get("matching_start_end_time", None), max_duration=d.get("max_duration", None), min_duration=d.get("min_duration", None), - start_date_recurrence_rule=d.get("start_date_recurrence_rule", None), - time_pairs=[cls.TimePairs.from_dict(i) for i in d.get("time_pairs") or []], + start_date_recurrence_rule=d.get( + "start_date_recurrence_rule", None + ), + time_pairs=[ + cls.TimePairs.from_dict(i) for i in d.get("time_pairs") or [] + ], time_zone=d.get("time_zone", None), ) @@ -2124,7 +2224,8 @@ class ActiveThermostatSchedule(ResourceMapping): :ivar thermostat_schedule_id: ID of the `thermostat schedule `_. - :ivar workspace_id: ID of the workspace that contains the thermostat schedule.""" + :ivar workspace_id: ID of the workspace that contains the thermostat schedule. + """ @dataclass class Errors(ResourceMapping): @@ -2134,7 +2235,8 @@ class Errors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + """ created_at: str error_code: str @@ -2169,7 +2271,9 @@ def from_dict(cls, d: Any): ends_at=d.get("ends_at", None), errors=[cls.Errors.from_dict(i) for i in d.get("errors") or []], is_override_allowed=d.get("is_override_allowed", None), - max_override_period_minutes=d.get("max_override_period_minutes", None), + max_override_period_minutes=d.get( + "max_override_period_minutes", None + ), name=d.get("name", None), starts_at=d.get("starts_at", None), thermostat_schedule_id=d.get("thermostat_schedule_id", None), @@ -2208,7 +2312,8 @@ class AvailableClimatePresets(ResourceMapping): :ivar manual_override_allowed: Deprecated: Use 'thermostat_schedule.is_override_allowed' Indicates whether a person at the thermostat can change the thermostat's settings. See `Specifying Manual Override Permissions `_. - :ivar name: User-friendly name to identify the `climate preset `_.""" + :ivar name: User-friendly name to identify the `climate preset `_. + """ @dataclass class EcobeeMetadata(ResourceMapping): @@ -2218,7 +2323,8 @@ class EcobeeMetadata(ResourceMapping): :ivar is_optimized: Indicates if the climate preset is optimized by Ecobee. - :ivar owner: Indicates whether the climate preset is owned by the user or the system.""" + :ivar owner: Indicates whether the climate preset is owned by the user or the system. + """ climate_ref: Optional[str] is_optimized: Optional[bool] @@ -2253,16 +2359,26 @@ def from_dict(cls, d: Any): return cls( can_delete=d.get("can_delete", None), can_edit=d.get("can_edit", None), - can_use_with_thermostat_daily_programs=d.get("can_use_with_thermostat_daily_programs", None), + can_use_with_thermostat_daily_programs=d.get( + "can_use_with_thermostat_daily_programs", None + ), climate_preset_key=d.get("climate_preset_key", None), climate_preset_mode=d.get("climate_preset_mode", None), cooling_set_point_celsius=d.get("cooling_set_point_celsius", None), - cooling_set_point_fahrenheit=d.get("cooling_set_point_fahrenheit", None), + cooling_set_point_fahrenheit=d.get( + "cooling_set_point_fahrenheit", None + ), display_name=d.get("display_name", None), - ecobee_metadata=cls.EcobeeMetadata.from_dict(d.get("ecobee_metadata")) if d.get("ecobee_metadata") is not None else None, + ecobee_metadata=( + cls.EcobeeMetadata.from_dict(d.get("ecobee_metadata")) + if d.get("ecobee_metadata") is not None + else None + ), fan_mode_setting=d.get("fan_mode_setting", None), heating_set_point_celsius=d.get("heating_set_point_celsius", None), - heating_set_point_fahrenheit=d.get("heating_set_point_fahrenheit", None), + heating_set_point_fahrenheit=d.get( + "heating_set_point_fahrenheit", None + ), hvac_mode_setting=d.get("hvac_mode_setting", None), manual_override_allowed=d.get("manual_override_allowed", None), name=d.get("name", None), @@ -2300,7 +2416,8 @@ class CurrentClimateSetting(ResourceMapping): :ivar manual_override_allowed: Deprecated: Use 'thermostat_schedule.is_override_allowed' Indicates whether a person at the thermostat can change the thermostat's settings. See `Specifying Manual Override Permissions `_. - :ivar name: User-friendly name to identify the `climate preset `_.""" + :ivar name: User-friendly name to identify the `climate preset `_. + """ @dataclass class EcobeeMetadata(ResourceMapping): @@ -2310,7 +2427,8 @@ class EcobeeMetadata(ResourceMapping): :ivar is_optimized: Indicates if the climate preset is optimized by Ecobee. - :ivar owner: Indicates whether the climate preset is owned by the user or the system.""" + :ivar owner: Indicates whether the climate preset is owned by the user or the system. + """ climate_ref: Optional[str] is_optimized: Optional[bool] @@ -2345,16 +2463,26 @@ def from_dict(cls, d: Any): return cls( can_delete=d.get("can_delete", None), can_edit=d.get("can_edit", None), - can_use_with_thermostat_daily_programs=d.get("can_use_with_thermostat_daily_programs", None), + can_use_with_thermostat_daily_programs=d.get( + "can_use_with_thermostat_daily_programs", None + ), climate_preset_key=d.get("climate_preset_key", None), climate_preset_mode=d.get("climate_preset_mode", None), cooling_set_point_celsius=d.get("cooling_set_point_celsius", None), - cooling_set_point_fahrenheit=d.get("cooling_set_point_fahrenheit", None), + cooling_set_point_fahrenheit=d.get( + "cooling_set_point_fahrenheit", None + ), display_name=d.get("display_name", None), - ecobee_metadata=cls.EcobeeMetadata.from_dict(d.get("ecobee_metadata")) if d.get("ecobee_metadata") is not None else None, + ecobee_metadata=( + cls.EcobeeMetadata.from_dict(d.get("ecobee_metadata")) + if d.get("ecobee_metadata") is not None + else None + ), fan_mode_setting=d.get("fan_mode_setting", None), heating_set_point_celsius=d.get("heating_set_point_celsius", None), - heating_set_point_fahrenheit=d.get("heating_set_point_fahrenheit", None), + heating_set_point_fahrenheit=d.get( + "heating_set_point_fahrenheit", None + ), hvac_mode_setting=d.get("hvac_mode_setting", None), manual_override_allowed=d.get("manual_override_allowed", None), name=d.get("name", None), @@ -2392,7 +2520,8 @@ class DefaultClimateSetting(ResourceMapping): :ivar manual_override_allowed: Deprecated: Use 'thermostat_schedule.is_override_allowed' Indicates whether a person at the thermostat can change the thermostat's settings. See `Specifying Manual Override Permissions `_. - :ivar name: User-friendly name to identify the `climate preset `_.""" + :ivar name: User-friendly name to identify the `climate preset `_. + """ @dataclass class EcobeeMetadata(ResourceMapping): @@ -2402,7 +2531,8 @@ class EcobeeMetadata(ResourceMapping): :ivar is_optimized: Indicates if the climate preset is optimized by Ecobee. - :ivar owner: Indicates whether the climate preset is owned by the user or the system.""" + :ivar owner: Indicates whether the climate preset is owned by the user or the system. + """ climate_ref: Optional[str] is_optimized: Optional[bool] @@ -2437,16 +2567,26 @@ def from_dict(cls, d: Any): return cls( can_delete=d.get("can_delete", None), can_edit=d.get("can_edit", None), - can_use_with_thermostat_daily_programs=d.get("can_use_with_thermostat_daily_programs", None), + can_use_with_thermostat_daily_programs=d.get( + "can_use_with_thermostat_daily_programs", None + ), climate_preset_key=d.get("climate_preset_key", None), climate_preset_mode=d.get("climate_preset_mode", None), cooling_set_point_celsius=d.get("cooling_set_point_celsius", None), - cooling_set_point_fahrenheit=d.get("cooling_set_point_fahrenheit", None), + cooling_set_point_fahrenheit=d.get( + "cooling_set_point_fahrenheit", None + ), display_name=d.get("display_name", None), - ecobee_metadata=cls.EcobeeMetadata.from_dict(d.get("ecobee_metadata")) if d.get("ecobee_metadata") is not None else None, + ecobee_metadata=( + cls.EcobeeMetadata.from_dict(d.get("ecobee_metadata")) + if d.get("ecobee_metadata") is not None + else None + ), fan_mode_setting=d.get("fan_mode_setting", None), heating_set_point_celsius=d.get("heating_set_point_celsius", None), - heating_set_point_fahrenheit=d.get("heating_set_point_fahrenheit", None), + heating_set_point_fahrenheit=d.get( + "heating_set_point_fahrenheit", None + ), hvac_mode_setting=d.get("hvac_mode_setting", None), manual_override_allowed=d.get("manual_override_allowed", None), name=d.get("name", None), @@ -2462,7 +2602,8 @@ class TemperatureThreshold(ResourceMapping): :ivar upper_limit_celsius: Upper limit in °C within the current `temperature threshold `_ set for the thermostat. - :ivar upper_limit_fahrenheit: Upper limit in °F within the current `temperature threshold `_ set for the thermostat.""" + :ivar upper_limit_fahrenheit: Upper limit in °F within the current `temperature threshold `_ set for the thermostat. + """ lower_limit_celsius: Optional[float] lower_limit_fahrenheit: Optional[float] @@ -2492,7 +2633,8 @@ class ThermostatDailyPrograms(ResourceMapping): :ivar thermostat_daily_program_id: ID of the thermostat daily program. - :ivar workspace_id: ID of the workspace that contains the thermostat daily program.""" + :ivar workspace_id: ID of the workspace that contains the thermostat daily program. + """ @dataclass class Periods(ResourceMapping): @@ -2500,7 +2642,8 @@ class Periods(ResourceMapping): :ivar climate_preset_key: Key of the `climate preset `_ to activate at the ``starts_at_time``. - :ivar starts_at_time: Time at which the thermostat daily program period starts, in `ISO 8601 `_ format.""" + :ivar starts_at_time: Time at which the thermostat daily program period starts, in `ISO 8601 `_ format. + """ climate_preset_key: str starts_at_time: str @@ -2526,7 +2669,9 @@ def from_dict(cls, d: Any): device_id=d.get("device_id", None), name=d.get("name", None), periods=[cls.Periods.from_dict(i) for i in d.get("periods") or []], - thermostat_daily_program_id=d.get("thermostat_daily_program_id", None), + thermostat_daily_program_id=d.get( + "thermostat_daily_program_id", None + ), workspace_id=d.get("workspace_id", None), ) @@ -2548,7 +2693,8 @@ class ThermostatWeeklyProgram(ResourceMapping): :ivar tuesday_program_id: ID of the thermostat daily program to run on Tuesdays. - :ivar wednesday_program_id: ID of the thermostat daily program to run on Wednesdays.""" + :ivar wednesday_program_id: ID of the thermostat daily program to run on Wednesdays. + """ created_at: str friday_program_id: Optional[str] @@ -2590,8 +2736,12 @@ def from_dict(cls, d: Any): serial_number: Optional[str] supports_accessory_keypad: Optional[bool] supports_offline_access_codes: Optional[bool] - assa_abloy_credential_service_metadata: Optional[AssaAbloyCredentialServiceMetadata] - salto_space_credential_service_metadata: Optional[SaltoSpaceCredentialServiceMetadata] + assa_abloy_credential_service_metadata: Optional[ + AssaAbloyCredentialServiceMetadata + ] + salto_space_credential_service_metadata: Optional[ + SaltoSpaceCredentialServiceMetadata + ] akiles_metadata: Optional[AkilesMetadata] aqara_metadata: Optional[AqaraMetadata] assa_abloy_vostio_metadata: Optional[AssaAbloyVostioMetadata] @@ -2681,111 +2831,392 @@ def from_dict(cls, d: Any): @classmethod def from_dict(cls, d: Any): return cls( - accessory_keypad=cls.AccessoryKeypad.from_dict(d.get("accessory_keypad")) if d.get("accessory_keypad") is not None else None, - appearance=cls.Appearance.from_dict(d.get("appearance")) if d.get("appearance") is not None else None, - battery=cls.Battery.from_dict(d.get("battery")) if d.get("battery") is not None else None, + accessory_keypad=( + cls.AccessoryKeypad.from_dict(d.get("accessory_keypad")) + if d.get("accessory_keypad") is not None + else None + ), + appearance=( + cls.Appearance.from_dict(d.get("appearance")) + if d.get("appearance") is not None + else None + ), + battery=( + cls.Battery.from_dict(d.get("battery")) + if d.get("battery") is not None + else None + ), battery_level=d.get("battery_level", None), - currently_triggering_noise_threshold_ids=d.get("currently_triggering_noise_threshold_ids", None), + currently_triggering_noise_threshold_ids=d.get( + "currently_triggering_noise_threshold_ids", None + ), has_direct_power=d.get("has_direct_power", None), image_alt_text=d.get("image_alt_text", None), image_url=d.get("image_url", None), manufacturer=d.get("manufacturer", None), - model=cls.Model.from_dict(d.get("model")) if d.get("model") is not None else None, + model=( + cls.Model.from_dict(d.get("model")) + if d.get("model") is not None + else None + ), name=d.get("name", None), noise_level_decibels=d.get("noise_level_decibels", None), - offline_access_codes_enabled=d.get("offline_access_codes_enabled", None), + offline_access_codes_enabled=d.get( + "offline_access_codes_enabled", None + ), online=d.get("online", None), online_access_codes_enabled=d.get("online_access_codes_enabled", None), serial_number=d.get("serial_number", None), supports_accessory_keypad=d.get("supports_accessory_keypad", None), - supports_offline_access_codes=d.get("supports_offline_access_codes", None), - assa_abloy_credential_service_metadata=cls.AssaAbloyCredentialServiceMetadata.from_dict(d.get("assa_abloy_credential_service_metadata")) if d.get("assa_abloy_credential_service_metadata") is not None else None, - salto_space_credential_service_metadata=cls.SaltoSpaceCredentialServiceMetadata.from_dict(d.get("salto_space_credential_service_metadata")) if d.get("salto_space_credential_service_metadata") is not None else None, - akiles_metadata=cls.AkilesMetadata.from_dict(d.get("akiles_metadata")) if d.get("akiles_metadata") is not None else None, - aqara_metadata=cls.AqaraMetadata.from_dict(d.get("aqara_metadata")) if d.get("aqara_metadata") is not None else None, - assa_abloy_vostio_metadata=cls.AssaAbloyVostioMetadata.from_dict(d.get("assa_abloy_vostio_metadata")) if d.get("assa_abloy_vostio_metadata") is not None else None, - august_metadata=cls.AugustMetadata.from_dict(d.get("august_metadata")) if d.get("august_metadata") is not None else None, - avigilon_alta_metadata=cls.AvigilonAltaMetadata.from_dict(d.get("avigilon_alta_metadata")) if d.get("avigilon_alta_metadata") is not None else None, - brivo_metadata=cls.BrivoMetadata.from_dict(d.get("brivo_metadata")) if d.get("brivo_metadata") is not None else None, - controlbyweb_metadata=cls.ControlbywebMetadata.from_dict(d.get("controlbyweb_metadata")) if d.get("controlbyweb_metadata") is not None else None, - dormakaba_oracode_metadata=cls.DormakabaOracodeMetadata.from_dict(d.get("dormakaba_oracode_metadata")) if d.get("dormakaba_oracode_metadata") is not None else None, - ecobee_metadata=cls.EcobeeMetadata.from_dict(d.get("ecobee_metadata")) if d.get("ecobee_metadata") is not None else None, - four_suites_metadata=cls.FourSuitesMetadata.from_dict(d.get("four_suites_metadata")) if d.get("four_suites_metadata") is not None else None, - genie_metadata=cls.GenieMetadata.from_dict(d.get("genie_metadata")) if d.get("genie_metadata") is not None else None, - honeywell_resideo_metadata=cls.HoneywellResideoMetadata.from_dict(d.get("honeywell_resideo_metadata")) if d.get("honeywell_resideo_metadata") is not None else None, - igloo_metadata=cls.IglooMetadata.from_dict(d.get("igloo_metadata")) if d.get("igloo_metadata") is not None else None, - igloohome_metadata=cls.IgloohomeMetadata.from_dict(d.get("igloohome_metadata")) if d.get("igloohome_metadata") is not None else None, - keynest_metadata=cls.KeynestMetadata.from_dict(d.get("keynest_metadata")) if d.get("keynest_metadata") is not None else None, - kisi_metadata=cls.KisiMetadata.from_dict(d.get("kisi_metadata")) if d.get("kisi_metadata") is not None else None, - korelock_metadata=cls.KorelockMetadata.from_dict(d.get("korelock_metadata")) if d.get("korelock_metadata") is not None else None, - kwikset_metadata=cls.KwiksetMetadata.from_dict(d.get("kwikset_metadata")) if d.get("kwikset_metadata") is not None else None, - lockly_metadata=cls.LocklyMetadata.from_dict(d.get("lockly_metadata")) if d.get("lockly_metadata") is not None else None, - minut_metadata=cls.MinutMetadata.from_dict(d.get("minut_metadata")) if d.get("minut_metadata") is not None else None, - nest_metadata=cls.NestMetadata.from_dict(d.get("nest_metadata")) if d.get("nest_metadata") is not None else None, - noiseaware_metadata=cls.NoiseawareMetadata.from_dict(d.get("noiseaware_metadata")) if d.get("noiseaware_metadata") is not None else None, - nuki_metadata=cls.NukiMetadata.from_dict(d.get("nuki_metadata")) if d.get("nuki_metadata") is not None else None, - omnitec_metadata=cls.OmnitecMetadata.from_dict(d.get("omnitec_metadata")) if d.get("omnitec_metadata") is not None else None, - ring_metadata=cls.RingMetadata.from_dict(d.get("ring_metadata")) if d.get("ring_metadata") is not None else None, - salto_ks_metadata=cls.SaltoKsMetadata.from_dict(d.get("salto_ks_metadata")) if d.get("salto_ks_metadata") is not None else None, - salto_metadata=cls.SaltoMetadata.from_dict(d.get("salto_metadata")) if d.get("salto_metadata") is not None else None, - schlage_metadata=cls.SchlageMetadata.from_dict(d.get("schlage_metadata")) if d.get("schlage_metadata") is not None else None, - seam_bridge_metadata=cls.SeamBridgeMetadata.from_dict(d.get("seam_bridge_metadata")) if d.get("seam_bridge_metadata") is not None else None, - sensi_metadata=cls.SensiMetadata.from_dict(d.get("sensi_metadata")) if d.get("sensi_metadata") is not None else None, - smartthings_metadata=cls.SmartthingsMetadata.from_dict(d.get("smartthings_metadata")) if d.get("smartthings_metadata") is not None else None, - tado_metadata=cls.TadoMetadata.from_dict(d.get("tado_metadata")) if d.get("tado_metadata") is not None else None, - tedee_metadata=cls.TedeeMetadata.from_dict(d.get("tedee_metadata")) if d.get("tedee_metadata") is not None else None, - ttlock_metadata=cls.TtlockMetadata.from_dict(d.get("ttlock_metadata")) if d.get("ttlock_metadata") is not None else None, - two_n_metadata=cls.TwoNMetadata.from_dict(d.get("two_n_metadata")) if d.get("two_n_metadata") is not None else None, - ultraloq_metadata=cls.UltraloqMetadata.from_dict(d.get("ultraloq_metadata")) if d.get("ultraloq_metadata") is not None else None, - visionline_metadata=cls.VisionlineMetadata.from_dict(d.get("visionline_metadata")) if d.get("visionline_metadata") is not None else None, - wyze_metadata=cls.WyzeMetadata.from_dict(d.get("wyze_metadata")) if d.get("wyze_metadata") is not None else None, - yacan_metadata=cls.YacanMetadata.from_dict(d.get("yacan_metadata")) if d.get("yacan_metadata") is not None else None, + supports_offline_access_codes=d.get( + "supports_offline_access_codes", None + ), + assa_abloy_credential_service_metadata=( + cls.AssaAbloyCredentialServiceMetadata.from_dict( + d.get("assa_abloy_credential_service_metadata") + ) + if d.get("assa_abloy_credential_service_metadata") is not None + else None + ), + salto_space_credential_service_metadata=( + cls.SaltoSpaceCredentialServiceMetadata.from_dict( + d.get("salto_space_credential_service_metadata") + ) + if d.get("salto_space_credential_service_metadata") is not None + else None + ), + akiles_metadata=( + cls.AkilesMetadata.from_dict(d.get("akiles_metadata")) + if d.get("akiles_metadata") is not None + else None + ), + aqara_metadata=( + cls.AqaraMetadata.from_dict(d.get("aqara_metadata")) + if d.get("aqara_metadata") is not None + else None + ), + assa_abloy_vostio_metadata=( + cls.AssaAbloyVostioMetadata.from_dict( + d.get("assa_abloy_vostio_metadata") + ) + if d.get("assa_abloy_vostio_metadata") is not None + else None + ), + august_metadata=( + cls.AugustMetadata.from_dict(d.get("august_metadata")) + if d.get("august_metadata") is not None + else None + ), + avigilon_alta_metadata=( + cls.AvigilonAltaMetadata.from_dict(d.get("avigilon_alta_metadata")) + if d.get("avigilon_alta_metadata") is not None + else None + ), + brivo_metadata=( + cls.BrivoMetadata.from_dict(d.get("brivo_metadata")) + if d.get("brivo_metadata") is not None + else None + ), + controlbyweb_metadata=( + cls.ControlbywebMetadata.from_dict(d.get("controlbyweb_metadata")) + if d.get("controlbyweb_metadata") is not None + else None + ), + dormakaba_oracode_metadata=( + cls.DormakabaOracodeMetadata.from_dict( + d.get("dormakaba_oracode_metadata") + ) + if d.get("dormakaba_oracode_metadata") is not None + else None + ), + ecobee_metadata=( + cls.EcobeeMetadata.from_dict(d.get("ecobee_metadata")) + if d.get("ecobee_metadata") is not None + else None + ), + four_suites_metadata=( + cls.FourSuitesMetadata.from_dict(d.get("four_suites_metadata")) + if d.get("four_suites_metadata") is not None + else None + ), + genie_metadata=( + cls.GenieMetadata.from_dict(d.get("genie_metadata")) + if d.get("genie_metadata") is not None + else None + ), + honeywell_resideo_metadata=( + cls.HoneywellResideoMetadata.from_dict( + d.get("honeywell_resideo_metadata") + ) + if d.get("honeywell_resideo_metadata") is not None + else None + ), + igloo_metadata=( + cls.IglooMetadata.from_dict(d.get("igloo_metadata")) + if d.get("igloo_metadata") is not None + else None + ), + igloohome_metadata=( + cls.IgloohomeMetadata.from_dict(d.get("igloohome_metadata")) + if d.get("igloohome_metadata") is not None + else None + ), + keynest_metadata=( + cls.KeynestMetadata.from_dict(d.get("keynest_metadata")) + if d.get("keynest_metadata") is not None + else None + ), + kisi_metadata=( + cls.KisiMetadata.from_dict(d.get("kisi_metadata")) + if d.get("kisi_metadata") is not None + else None + ), + korelock_metadata=( + cls.KorelockMetadata.from_dict(d.get("korelock_metadata")) + if d.get("korelock_metadata") is not None + else None + ), + kwikset_metadata=( + cls.KwiksetMetadata.from_dict(d.get("kwikset_metadata")) + if d.get("kwikset_metadata") is not None + else None + ), + lockly_metadata=( + cls.LocklyMetadata.from_dict(d.get("lockly_metadata")) + if d.get("lockly_metadata") is not None + else None + ), + minut_metadata=( + cls.MinutMetadata.from_dict(d.get("minut_metadata")) + if d.get("minut_metadata") is not None + else None + ), + nest_metadata=( + cls.NestMetadata.from_dict(d.get("nest_metadata")) + if d.get("nest_metadata") is not None + else None + ), + noiseaware_metadata=( + cls.NoiseawareMetadata.from_dict(d.get("noiseaware_metadata")) + if d.get("noiseaware_metadata") is not None + else None + ), + nuki_metadata=( + cls.NukiMetadata.from_dict(d.get("nuki_metadata")) + if d.get("nuki_metadata") is not None + else None + ), + omnitec_metadata=( + cls.OmnitecMetadata.from_dict(d.get("omnitec_metadata")) + if d.get("omnitec_metadata") is not None + else None + ), + ring_metadata=( + cls.RingMetadata.from_dict(d.get("ring_metadata")) + if d.get("ring_metadata") is not None + else None + ), + salto_ks_metadata=( + cls.SaltoKsMetadata.from_dict(d.get("salto_ks_metadata")) + if d.get("salto_ks_metadata") is not None + else None + ), + salto_metadata=( + cls.SaltoMetadata.from_dict(d.get("salto_metadata")) + if d.get("salto_metadata") is not None + else None + ), + schlage_metadata=( + cls.SchlageMetadata.from_dict(d.get("schlage_metadata")) + if d.get("schlage_metadata") is not None + else None + ), + seam_bridge_metadata=( + cls.SeamBridgeMetadata.from_dict(d.get("seam_bridge_metadata")) + if d.get("seam_bridge_metadata") is not None + else None + ), + sensi_metadata=( + cls.SensiMetadata.from_dict(d.get("sensi_metadata")) + if d.get("sensi_metadata") is not None + else None + ), + smartthings_metadata=( + cls.SmartthingsMetadata.from_dict(d.get("smartthings_metadata")) + if d.get("smartthings_metadata") is not None + else None + ), + tado_metadata=( + cls.TadoMetadata.from_dict(d.get("tado_metadata")) + if d.get("tado_metadata") is not None + else None + ), + tedee_metadata=( + cls.TedeeMetadata.from_dict(d.get("tedee_metadata")) + if d.get("tedee_metadata") is not None + else None + ), + ttlock_metadata=( + cls.TtlockMetadata.from_dict(d.get("ttlock_metadata")) + if d.get("ttlock_metadata") is not None + else None + ), + two_n_metadata=( + cls.TwoNMetadata.from_dict(d.get("two_n_metadata")) + if d.get("two_n_metadata") is not None + else None + ), + ultraloq_metadata=( + cls.UltraloqMetadata.from_dict(d.get("ultraloq_metadata")) + if d.get("ultraloq_metadata") is not None + else None + ), + visionline_metadata=( + cls.VisionlineMetadata.from_dict(d.get("visionline_metadata")) + if d.get("visionline_metadata") is not None + else None + ), + wyze_metadata=( + cls.WyzeMetadata.from_dict(d.get("wyze_metadata")) + if d.get("wyze_metadata") is not None + else None + ), + yacan_metadata=( + cls.YacanMetadata.from_dict(d.get("yacan_metadata")) + if d.get("yacan_metadata") is not None + else None + ), auto_lock_delay_seconds=d.get("auto_lock_delay_seconds", None), auto_lock_enabled=d.get("auto_lock_enabled", None), - backup_access_code_pool_enabled=d.get("backup_access_code_pool_enabled", None), - code_constraints=[cls.CodeConstraints.from_dict(i) for i in d.get("code_constraints") or []], + backup_access_code_pool_enabled=d.get( + "backup_access_code_pool_enabled", None + ), + code_constraints=[ + cls.CodeConstraints.from_dict(i) + for i in d.get("code_constraints") or [] + ], door_open=d.get("door_open", None), has_native_entry_events=d.get("has_native_entry_events", None), - keypad_battery=cls.KeypadBattery.from_dict(d.get("keypad_battery")) if d.get("keypad_battery") is not None else None, + keypad_battery=( + cls.KeypadBattery.from_dict(d.get("keypad_battery")) + if d.get("keypad_battery") is not None + else None + ), locked=d.get("locked", None), max_active_codes_supported=d.get("max_active_codes_supported", None), - offline_time_frame_options=[cls.OfflineTimeFrameOptions.from_dict(i) for i in d.get("offline_time_frame_options") or []], - online_time_frame_options=[cls.OnlineTimeFrameOptions.from_dict(i) for i in d.get("online_time_frame_options") or []], + offline_time_frame_options=[ + cls.OfflineTimeFrameOptions.from_dict(i) + for i in d.get("offline_time_frame_options") or [] + ], + online_time_frame_options=[ + cls.OnlineTimeFrameOptions.from_dict(i) + for i in d.get("online_time_frame_options") or [] + ], supported_code_lengths=d.get("supported_code_lengths", None), - supports_backup_access_code_pool=d.get("supports_backup_access_code_pool", None), - active_thermostat_schedule=cls.ActiveThermostatSchedule.from_dict(d.get("active_thermostat_schedule")) if d.get("active_thermostat_schedule") is not None else None, - active_thermostat_schedule_id=d.get("active_thermostat_schedule_id", None), - available_climate_preset_modes=d.get("available_climate_preset_modes", None), - available_climate_presets=[cls.AvailableClimatePresets.from_dict(i) for i in d.get("available_climate_presets") or []], + supports_backup_access_code_pool=d.get( + "supports_backup_access_code_pool", None + ), + active_thermostat_schedule=( + cls.ActiveThermostatSchedule.from_dict( + d.get("active_thermostat_schedule") + ) + if d.get("active_thermostat_schedule") is not None + else None + ), + active_thermostat_schedule_id=d.get( + "active_thermostat_schedule_id", None + ), + available_climate_preset_modes=d.get( + "available_climate_preset_modes", None + ), + available_climate_presets=[ + cls.AvailableClimatePresets.from_dict(i) + for i in d.get("available_climate_presets") or [] + ], available_fan_mode_settings=d.get("available_fan_mode_settings", None), - available_hvac_mode_settings=d.get("available_hvac_mode_settings", None), - current_climate_setting=cls.CurrentClimateSetting.from_dict(d.get("current_climate_setting")) if d.get("current_climate_setting") is not None else None, - default_climate_setting=cls.DefaultClimateSetting.from_dict(d.get("default_climate_setting")) if d.get("default_climate_setting") is not None else None, + available_hvac_mode_settings=d.get( + "available_hvac_mode_settings", None + ), + current_climate_setting=( + cls.CurrentClimateSetting.from_dict( + d.get("current_climate_setting") + ) + if d.get("current_climate_setting") is not None + else None + ), + default_climate_setting=( + cls.DefaultClimateSetting.from_dict( + d.get("default_climate_setting") + ) + if d.get("default_climate_setting") is not None + else None + ), fallback_climate_preset_key=d.get("fallback_climate_preset_key", None), fan_mode_setting=d.get("fan_mode_setting", None), is_cooling=d.get("is_cooling", None), is_fan_running=d.get("is_fan_running", None), is_heating=d.get("is_heating", None), - is_temporary_manual_override_active=d.get("is_temporary_manual_override_active", None), - max_cooling_set_point_celsius=d.get("max_cooling_set_point_celsius", None), - max_cooling_set_point_fahrenheit=d.get("max_cooling_set_point_fahrenheit", None), - max_heating_set_point_celsius=d.get("max_heating_set_point_celsius", None), - max_heating_set_point_fahrenheit=d.get("max_heating_set_point_fahrenheit", None), - max_thermostat_daily_program_periods_per_day=d.get("max_thermostat_daily_program_periods_per_day", None), - max_unique_climate_presets_per_thermostat_weekly_program=d.get("max_unique_climate_presets_per_thermostat_weekly_program", None), - min_cooling_set_point_celsius=d.get("min_cooling_set_point_celsius", None), - min_cooling_set_point_fahrenheit=d.get("min_cooling_set_point_fahrenheit", None), - min_heating_cooling_delta_celsius=d.get("min_heating_cooling_delta_celsius", None), - min_heating_cooling_delta_fahrenheit=d.get("min_heating_cooling_delta_fahrenheit", None), - min_heating_set_point_celsius=d.get("min_heating_set_point_celsius", None), - min_heating_set_point_fahrenheit=d.get("min_heating_set_point_fahrenheit", None), + is_temporary_manual_override_active=d.get( + "is_temporary_manual_override_active", None + ), + max_cooling_set_point_celsius=d.get( + "max_cooling_set_point_celsius", None + ), + max_cooling_set_point_fahrenheit=d.get( + "max_cooling_set_point_fahrenheit", None + ), + max_heating_set_point_celsius=d.get( + "max_heating_set_point_celsius", None + ), + max_heating_set_point_fahrenheit=d.get( + "max_heating_set_point_fahrenheit", None + ), + max_thermostat_daily_program_periods_per_day=d.get( + "max_thermostat_daily_program_periods_per_day", None + ), + max_unique_climate_presets_per_thermostat_weekly_program=d.get( + "max_unique_climate_presets_per_thermostat_weekly_program", None + ), + min_cooling_set_point_celsius=d.get( + "min_cooling_set_point_celsius", None + ), + min_cooling_set_point_fahrenheit=d.get( + "min_cooling_set_point_fahrenheit", None + ), + min_heating_cooling_delta_celsius=d.get( + "min_heating_cooling_delta_celsius", None + ), + min_heating_cooling_delta_fahrenheit=d.get( + "min_heating_cooling_delta_fahrenheit", None + ), + min_heating_set_point_celsius=d.get( + "min_heating_set_point_celsius", None + ), + min_heating_set_point_fahrenheit=d.get( + "min_heating_set_point_fahrenheit", None + ), relative_humidity=d.get("relative_humidity", None), temperature_celsius=d.get("temperature_celsius", None), temperature_fahrenheit=d.get("temperature_fahrenheit", None), - temperature_threshold=cls.TemperatureThreshold.from_dict(d.get("temperature_threshold")) if d.get("temperature_threshold") is not None else None, - thermostat_daily_program_period_precision_minutes=d.get("thermostat_daily_program_period_precision_minutes", None), - thermostat_daily_programs=[cls.ThermostatDailyPrograms.from_dict(i) for i in d.get("thermostat_daily_programs") or []], - thermostat_weekly_program=cls.ThermostatWeeklyProgram.from_dict(d.get("thermostat_weekly_program")) if d.get("thermostat_weekly_program") is not None else None, + temperature_threshold=( + cls.TemperatureThreshold.from_dict(d.get("temperature_threshold")) + if d.get("temperature_threshold") is not None + else None + ), + thermostat_daily_program_period_precision_minutes=d.get( + "thermostat_daily_program_period_precision_minutes", None + ), + thermostat_daily_programs=[ + cls.ThermostatDailyPrograms.from_dict(i) + for i in d.get("thermostat_daily_programs") or [] + ], + thermostat_weekly_program=( + cls.ThermostatWeeklyProgram.from_dict( + d.get("thermostat_weekly_program") + ) + if d.get("thermostat_weekly_program") is not None + else None + ), ) @dataclass @@ -2800,7 +3231,8 @@ class Warnings(ResourceMapping): :ivar active_access_code_count: Number of active access codes on the device when the warning was set. - :ivar max_active_access_code_count: Maximum number of active access codes supported by the device.""" + :ivar max_active_access_code_count: Maximum number of active access codes supported by the device. + """ created_at: str message: str @@ -2815,7 +3247,9 @@ def from_dict(cls, d: Any): message=d.get("message", None), warning_code=d.get("warning_code", None), active_access_code_count=d.get("active_access_code_count", None), - max_active_access_code_count=d.get("max_active_access_code_count", None), + max_active_access_code_count=d.get( + "max_active_access_code_count", None + ), ) can_configure_auto_lock: Optional[bool] @@ -2863,19 +3297,33 @@ def from_dict(cls, d: Any): can_hvac_cool=d.get("can_hvac_cool", None), can_hvac_heat=d.get("can_hvac_heat", None), can_hvac_heat_cool=d.get("can_hvac_heat_cool", None), - can_program_offline_access_codes=d.get("can_program_offline_access_codes", None), - can_program_online_access_codes=d.get("can_program_online_access_codes", None), - can_program_thermostat_programs_as_different_each_day=d.get("can_program_thermostat_programs_as_different_each_day", None), - can_program_thermostat_programs_as_same_each_day=d.get("can_program_thermostat_programs_as_same_each_day", None), - can_program_thermostat_programs_as_weekday_weekend=d.get("can_program_thermostat_programs_as_weekday_weekend", None), + can_program_offline_access_codes=d.get( + "can_program_offline_access_codes", None + ), + can_program_online_access_codes=d.get( + "can_program_online_access_codes", None + ), + can_program_thermostat_programs_as_different_each_day=d.get( + "can_program_thermostat_programs_as_different_each_day", None + ), + can_program_thermostat_programs_as_same_each_day=d.get( + "can_program_thermostat_programs_as_same_each_day", None + ), + can_program_thermostat_programs_as_weekday_weekend=d.get( + "can_program_thermostat_programs_as_weekday_weekend", None + ), can_remotely_lock=d.get("can_remotely_lock", None), can_remotely_unlock=d.get("can_remotely_unlock", None), can_run_thermostat_programs=d.get("can_run_thermostat_programs", None), can_simulate_connection=d.get("can_simulate_connection", None), can_simulate_disconnection=d.get("can_simulate_disconnection", None), can_simulate_hub_connection=d.get("can_simulate_hub_connection", None), - can_simulate_hub_disconnection=d.get("can_simulate_hub_disconnection", None), - can_simulate_paid_subscription=d.get("can_simulate_paid_subscription", None), + can_simulate_hub_disconnection=d.get( + "can_simulate_hub_disconnection", None + ), + can_simulate_paid_subscription=d.get( + "can_simulate_paid_subscription", None + ), can_simulate_removal=d.get("can_simulate_removal", None), can_turn_off_hvac=d.get("can_turn_off_hvac", None), can_unlock_with_code=d.get("can_unlock_with_code", None), @@ -2884,15 +3332,31 @@ def from_dict(cls, d: Any): created_at=d.get("created_at", None), custom_metadata=DeepAttrDict(d.get("custom_metadata", None)), device_id=d.get("device_id", None), - device_manufacturer=cls.DeviceManufacturer.from_dict(d.get("device_manufacturer")) if d.get("device_manufacturer") is not None else None, - device_provider=cls.DeviceProvider.from_dict(d.get("device_provider")) if d.get("device_provider") is not None else None, + device_manufacturer=( + cls.DeviceManufacturer.from_dict(d.get("device_manufacturer")) + if d.get("device_manufacturer") is not None + else None + ), + device_provider=( + cls.DeviceProvider.from_dict(d.get("device_provider")) + if d.get("device_provider") is not None + else None + ), device_type=d.get("device_type", None), display_name=d.get("display_name", None), errors=[cls.Errors.from_dict(i) for i in d.get("errors") or []], is_managed=d.get("is_managed", None), - location=cls.Location.from_dict(d.get("location")) if d.get("location") is not None else None, + location=( + cls.Location.from_dict(d.get("location")) + if d.get("location") is not None + else None + ), nickname=d.get("nickname", None), - properties=cls.Properties.from_dict(d.get("properties")) if d.get("properties") is not None else None, + properties=( + cls.Properties.from_dict(d.get("properties")) + if d.get("properties") is not None + else None + ), space_ids=d.get("space_ids", None), warnings=[cls.Warnings.from_dict(i) for i in d.get("warnings") or []], workspace_id=d.get("workspace_id", None), diff --git a/seam/resources/device_provider.py b/seam/resources/device_provider.py index c663d4d4..f58fb9d9 100644 --- a/seam/resources/device_provider.py +++ b/seam/resources/device_provider.py @@ -54,7 +54,8 @@ class DeviceProvider: :ivar image_url: Image URL for the device provider. - :ivar provider_categories: List of provider categories to which the device provider belongs, such as ``stable``, ``consumer_smartlocks``, ``thermostats``, and so on.""" + :ivar provider_categories: List of provider categories to which the device provider belongs, such as ``stable``, ``consumer_smartlocks``, ``thermostats``, and so on. + """ can_configure_auto_lock: Optional[bool] can_hvac_cool: Optional[bool] @@ -88,19 +89,33 @@ def from_dict(cls, d: Any): can_hvac_cool=d.get("can_hvac_cool", None), can_hvac_heat=d.get("can_hvac_heat", None), can_hvac_heat_cool=d.get("can_hvac_heat_cool", None), - can_program_offline_access_codes=d.get("can_program_offline_access_codes", None), - can_program_online_access_codes=d.get("can_program_online_access_codes", None), - can_program_thermostat_programs_as_different_each_day=d.get("can_program_thermostat_programs_as_different_each_day", None), - can_program_thermostat_programs_as_same_each_day=d.get("can_program_thermostat_programs_as_same_each_day", None), - can_program_thermostat_programs_as_weekday_weekend=d.get("can_program_thermostat_programs_as_weekday_weekend", None), + can_program_offline_access_codes=d.get( + "can_program_offline_access_codes", None + ), + can_program_online_access_codes=d.get( + "can_program_online_access_codes", None + ), + can_program_thermostat_programs_as_different_each_day=d.get( + "can_program_thermostat_programs_as_different_each_day", None + ), + can_program_thermostat_programs_as_same_each_day=d.get( + "can_program_thermostat_programs_as_same_each_day", None + ), + can_program_thermostat_programs_as_weekday_weekend=d.get( + "can_program_thermostat_programs_as_weekday_weekend", None + ), can_remotely_lock=d.get("can_remotely_lock", None), can_remotely_unlock=d.get("can_remotely_unlock", None), can_run_thermostat_programs=d.get("can_run_thermostat_programs", None), can_simulate_connection=d.get("can_simulate_connection", None), can_simulate_disconnection=d.get("can_simulate_disconnection", None), can_simulate_hub_connection=d.get("can_simulate_hub_connection", None), - can_simulate_hub_disconnection=d.get("can_simulate_hub_disconnection", None), - can_simulate_paid_subscription=d.get("can_simulate_paid_subscription", None), + can_simulate_hub_disconnection=d.get( + "can_simulate_hub_disconnection", None + ), + can_simulate_paid_subscription=d.get( + "can_simulate_paid_subscription", None + ), can_simulate_removal=d.get("can_simulate_removal", None), can_turn_off_hvac=d.get("can_turn_off_hvac", None), can_unlock_with_code=d.get("can_unlock_with_code", None), diff --git a/seam/resources/instant_key.py b/seam/resources/instant_key.py index 42dcbb18..775e8a11 100644 --- a/seam/resources/instant_key.py +++ b/seam/resources/instant_key.py @@ -7,7 +7,7 @@ @dataclass class InstantKey: """Represents a Seam Instant Key. For issuing Bluetooth mobile keys, Instant Keys are the fastest way to share access. With a single API call, you can create a mobile key and send it through text or email or embed it in your own app. - + There’s no app to install, nor account to create. Your user just taps a link and gets a lightweight, native-feeling experience using iOS App Clip or Instant Apps on Android. Further, Instant Keys work offline, so even in areas with poor cellular or Wi-Fi, like elevator banks or concrete-walled hallways, the Instant Keys still work. :ivar client_session_id: ID of the client session associated with the Instant Key. @@ -65,7 +65,11 @@ def from_dict(cls, d: Any): return cls( client_session_id=d.get("client_session_id", None), created_at=d.get("created_at", None), - customization=cls.Customization.from_dict(d.get("customization")) if d.get("customization") is not None else None, + customization=( + cls.Customization.from_dict(d.get("customization")) + if d.get("customization") is not None + else None + ), customization_profile_id=d.get("customization_profile_id", None), expires_at=d.get("expires_at", None), instant_key_id=d.get("instant_key_id", None), diff --git a/seam/resources/noise_threshold.py b/seam/resources/noise_threshold.py index 74f82470..9918d3c3 100644 --- a/seam/resources/noise_threshold.py +++ b/seam/resources/noise_threshold.py @@ -20,7 +20,8 @@ class NoiseThreshold: :ivar noise_threshold_nrs: Noise level in Noiseaware Noise Risk Score (NRS) for the noise threshold. This parameter is only relevant for `Noiseaware sensors `_. - :ivar starts_daily_at: Time at which the noise threshold should become active daily.""" + :ivar starts_daily_at: Time at which the noise threshold should become active daily. + """ device_id: str ends_daily_at: str diff --git a/seam/resources/phone.py b/seam/resources/phone.py index fa42284b..74b1963b 100644 --- a/seam/resources/phone.py +++ b/seam/resources/phone.py @@ -56,7 +56,8 @@ class Properties(ResourceMapping): :ivar assa_abloy_credential_service_metadata: ASSA ABLOY Credential Service metadata for the phone. - :ivar salto_space_credential_service_metadata: Salto Space credential service metadata for the phone.""" + :ivar salto_space_credential_service_metadata: Salto Space credential service metadata for the phone. + """ @dataclass class AssaAbloyCredentialServiceMetadata(ResourceMapping): @@ -64,7 +65,8 @@ class AssaAbloyCredentialServiceMetadata(ResourceMapping): :ivar endpoints: Endpoints associated with the phone. - :ivar has_active_endpoint: Indicates whether the credential service has active endpoints associated with the phone.""" + :ivar has_active_endpoint: Indicates whether the credential service has active endpoints associated with the phone. + """ @dataclass class Endpoints(ResourceMapping): @@ -90,7 +92,9 @@ def from_dict(cls, d: Any): @classmethod def from_dict(cls, d: Any): return cls( - endpoints=[cls.Endpoints.from_dict(i) for i in d.get("endpoints") or []], + endpoints=[ + cls.Endpoints.from_dict(i) for i in d.get("endpoints") or [] + ], has_active_endpoint=d.get("has_active_endpoint", None), ) @@ -98,7 +102,8 @@ def from_dict(cls, d: Any): class SaltoSpaceCredentialServiceMetadata(ResourceMapping): """Salto Space credential service metadata for the phone. - :ivar has_active_phone: Indicates whether the credential service has an active associated phone.""" + :ivar has_active_phone: Indicates whether the credential service has an active associated phone. + """ has_active_phone: Optional[bool] @@ -108,14 +113,30 @@ def from_dict(cls, d: Any): has_active_phone=d.get("has_active_phone", None), ) - assa_abloy_credential_service_metadata: Optional[AssaAbloyCredentialServiceMetadata] - salto_space_credential_service_metadata: Optional[SaltoSpaceCredentialServiceMetadata] + assa_abloy_credential_service_metadata: Optional[ + AssaAbloyCredentialServiceMetadata + ] + salto_space_credential_service_metadata: Optional[ + SaltoSpaceCredentialServiceMetadata + ] @classmethod def from_dict(cls, d: Any): return cls( - assa_abloy_credential_service_metadata=cls.AssaAbloyCredentialServiceMetadata.from_dict(d.get("assa_abloy_credential_service_metadata")) if d.get("assa_abloy_credential_service_metadata") is not None else None, - salto_space_credential_service_metadata=cls.SaltoSpaceCredentialServiceMetadata.from_dict(d.get("salto_space_credential_service_metadata")) if d.get("salto_space_credential_service_metadata") is not None else None, + assa_abloy_credential_service_metadata=( + cls.AssaAbloyCredentialServiceMetadata.from_dict( + d.get("assa_abloy_credential_service_metadata") + ) + if d.get("assa_abloy_credential_service_metadata") is not None + else None + ), + salto_space_credential_service_metadata=( + cls.SaltoSpaceCredentialServiceMetadata.from_dict( + d.get("salto_space_credential_service_metadata") + ) + if d.get("salto_space_credential_service_metadata") is not None + else None + ), ) @dataclass @@ -161,7 +182,11 @@ def from_dict(cls, d: Any): display_name=d.get("display_name", None), errors=[cls.Errors.from_dict(i) for i in d.get("errors") or []], nickname=d.get("nickname", None), - properties=cls.Properties.from_dict(d.get("properties")) if d.get("properties") is not None else None, + properties=( + cls.Properties.from_dict(d.get("properties")) + if d.get("properties") is not None + else None + ), warnings=[cls.Warnings.from_dict(i) for i in d.get("warnings") or []], workspace_id=d.get("workspace_id", None), ) diff --git a/seam/resources/seam_event.py b/seam/resources/seam_event.py index 79917a02..323115b6 100644 --- a/seam/resources/seam_event.py +++ b/seam/resources/seam_event.py @@ -8,17 +8,17 @@ class SeamEvent: """ - :ivar access_code_id: + :ivar access_code_id: - :ivar connected_account_custom_metadata: + :ivar connected_account_custom_metadata: - :ivar connected_account_id: + :ivar connected_account_id: :ivar created_at: Date and time at which the event was created. - :ivar device_custom_metadata: + :ivar device_custom_metadata: - :ivar device_id: + :ivar device_id: :ivar event_description: Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. @@ -36,13 +36,13 @@ class SeamEvent: :ivar description: Human-readable description of the change and its source. - :ivar from_: + :ivar from_: - :ivar to: + :ivar to: :ivar requested_mutations: Array of mutations requested on the access code, each containing the mutation type and from/to values. - :ivar code: + :ivar code: :ivar access_code_errors: Errors associated with the access code. @@ -60,7 +60,7 @@ class SeamEvent: :ivar access_grant_id: ID of the affected Access Grant. - :ivar acs_entrance_id: + :ivar acs_entrance_id: :ivar access_grant_key: Key of the affected Access Grant (if present). @@ -96,11 +96,11 @@ class SeamEvent: :ivar client_session_id: ID of the affected client session. - :ivar connect_webview_id: + :ivar connect_webview_id: - :ivar customer_key: + :ivar customer_key: - :ivar action_attempt_id: + :ivar action_attempt_id: :ivar action_type: Type of the action. @@ -112,7 +112,7 @@ class SeamEvent: :ivar battery_status: Battery status of the affected device, calculated from the numeric ``battery_level`` value. - :ivar device_name: + :ivar device_name: :ivar minut_metadata: Metadata from Minut. @@ -128,11 +128,11 @@ class SeamEvent: :ivar access_code_is_managed: Whether the access code is managed by Seam (true) or unmanaged (false). Only present when access_code_id is set. - :ivar is_via_bluetooth: + :ivar is_via_bluetooth: - :ivar is_via_nfc: + :ivar is_via_nfc: - :ivar method: + :ivar method: :ivar reason: Why access was denied, when the provider reports a determinable cause. Omitted when unknown. @@ -172,15 +172,15 @@ class SeamEvent: :ivar activation_reason: The reason the camera was activated. - :ivar image_url: + :ivar image_url: :ivar motion_sub_type: Sub-type of motion detected, if available. - :ivar video_url: + :ivar video_url: - :ivar acs_entrance_ids: + :ivar acs_entrance_ids: - :ivar device_ids: + :ivar device_ids: :ivar space_id: ID of the affected space. @@ -268,7 +268,8 @@ class RequestedMutations(ResourceMapping): :ivar mutation_code: Code identifying the type of mutation requested, such as ``updating_name``, ``updating_code``, ``updating_time_frame``, or ``deleting``. - :ivar to: New property values after the requested change. Keys depend on the mutation type. Absent for non-property mutations like ``deleting``.""" + :ivar to: New property values after the requested change. Keys depend on the mutation type. Absent for non-property mutations like ``deleting``. + """ from_: Optional[Dict[str, Any]] mutation_code: str @@ -290,7 +291,8 @@ class AccessCodeErrors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + """ created_at: str error_code: str @@ -312,7 +314,8 @@ class AccessCodeWarnings(ResourceMapping): :ivar message: Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue.""" + :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + """ created_at: str message: str @@ -334,7 +337,8 @@ class ConnectedAccountErrors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + """ created_at: str error_code: str @@ -356,7 +360,8 @@ class ConnectedAccountWarnings(ResourceMapping): :ivar message: Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue.""" + :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + """ created_at: str message: str @@ -378,7 +383,8 @@ class DeviceErrors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + """ created_at: str error_code: str @@ -400,7 +406,8 @@ class DeviceWarnings(ResourceMapping): :ivar message: Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue.""" + :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + """ created_at: str message: str @@ -422,7 +429,8 @@ class AcsSystemErrors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + """ created_at: str error_code: str @@ -444,7 +452,8 @@ class AcsSystemWarnings(ResourceMapping): :ivar message: Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue.""" + :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + """ created_at: str message: str @@ -464,7 +473,8 @@ class Reason(ResourceMapping): :ivar message: Human-readable explanation of why access was denied. - :ivar reason_code: Normalized reason a lock denied access. Provider-agnostic; not all providers report every value.""" + :ivar reason_code: Normalized reason a lock denied access. Provider-agnostic; not all providers report every value. + """ message: str reason_code: str @@ -570,7 +580,9 @@ def from_dict(cls, d: Any): def from_dict(cls, d: Any): return cls( access_code_id=d.get("access_code_id", None), - connected_account_custom_metadata=DeepAttrDict(d.get("connected_account_custom_metadata", None)), + connected_account_custom_metadata=DeepAttrDict( + d.get("connected_account_custom_metadata", None) + ), connected_account_id=d.get("connected_account_id", None), created_at=d.get("created_at", None), device_custom_metadata=DeepAttrDict(d.get("device_custom_metadata", None)), @@ -581,18 +593,42 @@ def from_dict(cls, d: Any): occurred_at=d.get("occurred_at", None), workspace_id=d.get("workspace_id", None), change_reason=d.get("change_reason", None), - changed_properties=[cls.ChangedProperties.from_dict(i) for i in d.get("changed_properties") or []], + changed_properties=[ + cls.ChangedProperties.from_dict(i) + for i in d.get("changed_properties") or [] + ], description=d.get("description", None), - from_=cls.From.from_dict(d.get("from")) if d.get("from") is not None else None, + from_=( + cls.From.from_dict(d.get("from")) if d.get("from") is not None else None + ), to=cls.To.from_dict(d.get("to")) if d.get("to") is not None else None, - requested_mutations=[cls.RequestedMutations.from_dict(i) for i in d.get("requested_mutations") or []], + requested_mutations=[ + cls.RequestedMutations.from_dict(i) + for i in d.get("requested_mutations") or [] + ], code=d.get("code", None), - access_code_errors=[cls.AccessCodeErrors.from_dict(i) for i in d.get("access_code_errors") or []], - access_code_warnings=[cls.AccessCodeWarnings.from_dict(i) for i in d.get("access_code_warnings") or []], - connected_account_errors=[cls.ConnectedAccountErrors.from_dict(i) for i in d.get("connected_account_errors") or []], - connected_account_warnings=[cls.ConnectedAccountWarnings.from_dict(i) for i in d.get("connected_account_warnings") or []], - device_errors=[cls.DeviceErrors.from_dict(i) for i in d.get("device_errors") or []], - device_warnings=[cls.DeviceWarnings.from_dict(i) for i in d.get("device_warnings") or []], + access_code_errors=[ + cls.AccessCodeErrors.from_dict(i) + for i in d.get("access_code_errors") or [] + ], + access_code_warnings=[ + cls.AccessCodeWarnings.from_dict(i) + for i in d.get("access_code_warnings") or [] + ], + connected_account_errors=[ + cls.ConnectedAccountErrors.from_dict(i) + for i in d.get("connected_account_errors") or [] + ], + connected_account_warnings=[ + cls.ConnectedAccountWarnings.from_dict(i) + for i in d.get("connected_account_warnings") or [] + ], + device_errors=[ + cls.DeviceErrors.from_dict(i) for i in d.get("device_errors") or [] + ], + device_warnings=[ + cls.DeviceWarnings.from_dict(i) for i in d.get("device_warnings") or [] + ], backup_access_code_id=d.get("backup_access_code_id", None), access_grant_id=d.get("access_grant_id", None), acs_entrance_id=d.get("acs_entrance_id", None), @@ -606,8 +642,14 @@ def from_dict(cls, d: Any): access_method_id=d.get("access_method_id", None), is_backup_code=d.get("is_backup_code", None), acs_system_id=d.get("acs_system_id", None), - acs_system_errors=[cls.AcsSystemErrors.from_dict(i) for i in d.get("acs_system_errors") or []], - acs_system_warnings=[cls.AcsSystemWarnings.from_dict(i) for i in d.get("acs_system_warnings") or []], + acs_system_errors=[ + cls.AcsSystemErrors.from_dict(i) + for i in d.get("acs_system_errors") or [] + ], + acs_system_warnings=[ + cls.AcsSystemWarnings.from_dict(i) + for i in d.get("acs_system_warnings") or [] + ], acs_credential_id=d.get("acs_credential_id", None), acs_user_id=d.get("acs_user_id", None), acs_encoder_id=d.get("acs_encoder_id", None), @@ -632,7 +674,11 @@ def from_dict(cls, d: Any): is_via_bluetooth=d.get("is_via_bluetooth", None), is_via_nfc=d.get("is_via_nfc", None), method=d.get("method", None), - reason=cls.Reason.from_dict(d.get("reason")) if d.get("reason") is not None else None, + reason=( + cls.Reason.from_dict(d.get("reason")) + if d.get("reason") is not None + else None + ), climate_preset_key=d.get("climate_preset_key", None), is_fallback_climate_preset=d.get("is_fallback_climate_preset", None), thermostat_schedule_id=d.get("thermostat_schedule_id", None), @@ -649,7 +695,9 @@ def from_dict(cls, d: Any): upper_limit_celsius=d.get("upper_limit_celsius", None), upper_limit_fahrenheit=d.get("upper_limit_fahrenheit", None), desired_temperature_celsius=d.get("desired_temperature_celsius", None), - desired_temperature_fahrenheit=d.get("desired_temperature_fahrenheit", None), + desired_temperature_fahrenheit=d.get( + "desired_temperature_fahrenheit", None + ), activation_reason=d.get("activation_reason", None), image_url=d.get("image_url", None), motion_sub_type=d.get("motion_sub_type", None), diff --git a/seam/resources/space.py b/seam/resources/space.py index f2634480..65e68978 100644 --- a/seam/resources/space.py +++ b/seam/resources/space.py @@ -91,11 +91,19 @@ def from_dict(cls, d: Any): return cls( acs_entrance_count=d.get("acs_entrance_count", None), created_at=d.get("created_at", None), - customer_data=cls.CustomerData.from_dict(d.get("customer_data")) if d.get("customer_data") is not None else None, + customer_data=( + cls.CustomerData.from_dict(d.get("customer_data")) + if d.get("customer_data") is not None + else None + ), customer_key=d.get("customer_key", None), device_count=d.get("device_count", None), display_name=d.get("display_name", None), - geolocation=cls.Geolocation.from_dict(d.get("geolocation")) if d.get("geolocation") is not None else None, + geolocation=( + cls.Geolocation.from_dict(d.get("geolocation")) + if d.get("geolocation") is not None + else None + ), name=d.get("name", None), space_id=d.get("space_id", None), space_key=d.get("space_key", None), diff --git a/seam/resources/thermostat_daily_program.py b/seam/resources/thermostat_daily_program.py index 6ad49642..0c1c262d 100644 --- a/seam/resources/thermostat_daily_program.py +++ b/seam/resources/thermostat_daily_program.py @@ -18,7 +18,8 @@ class ThermostatDailyProgram: :ivar thermostat_daily_program_id: ID of the thermostat daily program. - :ivar workspace_id: ID of the workspace that contains the thermostat daily program.""" + :ivar workspace_id: ID of the workspace that contains the thermostat daily program. + """ @dataclass class Periods(ResourceMapping): @@ -26,7 +27,8 @@ class Periods(ResourceMapping): :ivar climate_preset_key: Key of the `climate preset `_ to activate at the ``starts_at_time``. - :ivar starts_at_time: Time at which the thermostat daily program period starts, in `ISO 8601 `_ format.""" + :ivar starts_at_time: Time at which the thermostat daily program period starts, in `ISO 8601 `_ format. + """ climate_preset_key: str starts_at_time: str diff --git a/seam/resources/thermostat_schedule.py b/seam/resources/thermostat_schedule.py index 60c96e91..2c022801 100644 --- a/seam/resources/thermostat_schedule.py +++ b/seam/resources/thermostat_schedule.py @@ -38,7 +38,8 @@ class Errors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + """ created_at: str error_code: str diff --git a/seam/resources/unmanaged_access_code.py b/seam/resources/unmanaged_access_code.py index 4311dd3a..8c1d00ed 100644 --- a/seam/resources/unmanaged_access_code.py +++ b/seam/resources/unmanaged_access_code.py @@ -7,15 +7,15 @@ @dataclass class UnmanagedAccessCode: """Represents an `unmanaged smart lock access code `_. - + An access code is a code used for a keypad or pinpad device. Unlike physical keys, which can easily be lost or duplicated, PIN codes can be customized, tracked, and altered on the fly. - + When you create an access code on a device in Seam, it is created as a managed access code. Access codes that exist on a device that were not created through Seam are considered unmanaged codes. We strictly limit the operations that can be performed on unmanaged codes. - + Prior to using Seam to manage your devices, you may have used another lock management system to manage the access codes on your devices. Where possible, we help you keep any existing access codes on devices and transition those codes to ones managed by your Seam workspace. - + Not all providers support unmanaged access codes. The following providers do not support unmanaged access codes: - + - `Kwikset `_ :ivar access_code_id: Unique identifier for the access code. @@ -48,7 +48,8 @@ class UnmanagedAccessCode: :ivar warnings: Warnings associated with the `access code `_. - :ivar workspace_id: Unique identifier for the Seam workspace associated with the access code.""" + :ivar workspace_id: Unique identifier for the Seam workspace associated with the access code. + """ @dataclass class DormakabaOracodeMetadata(ResourceMapping): @@ -68,7 +69,8 @@ class DormakabaOracodeMetadata(ResourceMapping): :ivar user_level_id: Dormakaba Oracode user level ID associated with this access code. - :ivar user_level_name: Dormakaba Oracode user level name associated with this access code.""" + :ivar user_level_name: Dormakaba Oracode user level name associated with this access code. + """ is_cancellable: Optional[bool] is_early_checkin_able: Optional[bool] @@ -112,11 +114,12 @@ class Errors(ResourceMapping): :ivar modified_fields: List of fields that were changed externally, with their previous and new values. - :ivar is_connected_account_error: + :ivar is_connected_account_error: - :ivar is_device_error: + :ivar is_device_error: - :ivar is_bridge_error: Indicates whether the error is related to `Seam Bridge `_.""" + :ivar is_bridge_error: Indicates whether the error is related to `Seam Bridge `_. + """ @dataclass class ModifiedFields(ResourceMapping): @@ -162,7 +165,10 @@ def from_dict(cls, d: Any): managed_access_code_id=d.get("managed_access_code_id", None), unmanaged_access_code_id=d.get("unmanaged_access_code_id", None), change_type=d.get("change_type", None), - modified_fields=[cls.ModifiedFields.from_dict(i) for i in d.get("modified_fields") or []], + modified_fields=[ + cls.ModifiedFields.from_dict(i) + for i in d.get("modified_fields") or [] + ], is_connected_account_error=d.get("is_connected_account_error", None), is_device_error=d.get("is_device_error", None), is_bridge_error=d.get("is_bridge_error", None), @@ -180,7 +186,8 @@ class Warnings(ResourceMapping): :ivar change_type: Indicates the type of external modification. ``modified`` means the code's PIN or schedule was changed. ``removed`` means the code was deleted from the device. - :ivar modified_fields: List of fields that were changed externally, with their previous and new values.""" + :ivar modified_fields: List of fields that were changed externally, with their previous and new values. + """ @dataclass class ModifiedFields(ResourceMapping): @@ -217,7 +224,10 @@ def from_dict(cls, d: Any): message=d.get("message", None), warning_code=d.get("warning_code", None), change_type=d.get("change_type", None), - modified_fields=[cls.ModifiedFields.from_dict(i) for i in d.get("modified_fields") or []], + modified_fields=[ + cls.ModifiedFields.from_dict(i) + for i in d.get("modified_fields") or [] + ], ) access_code_id: str @@ -242,11 +252,19 @@ def from_dict(cls, d: Any): return cls( access_code_id=d.get("access_code_id", None), cannot_be_managed=d.get("cannot_be_managed", None), - cannot_delete_unmanaged_access_code=d.get("cannot_delete_unmanaged_access_code", None), + cannot_delete_unmanaged_access_code=d.get( + "cannot_delete_unmanaged_access_code", None + ), code=d.get("code", None), created_at=d.get("created_at", None), device_id=d.get("device_id", None), - dormakaba_oracode_metadata=cls.DormakabaOracodeMetadata.from_dict(d.get("dormakaba_oracode_metadata")) if d.get("dormakaba_oracode_metadata") is not None else None, + dormakaba_oracode_metadata=( + cls.DormakabaOracodeMetadata.from_dict( + d.get("dormakaba_oracode_metadata") + ) + if d.get("dormakaba_oracode_metadata") is not None + else None + ), ends_at=d.get("ends_at", None), errors=[cls.Errors.from_dict(i) for i in d.get("errors") or []], is_managed=d.get("is_managed", None), diff --git a/seam/resources/unmanaged_access_grant.py b/seam/resources/unmanaged_access_grant.py index eeff17c3..71e7a68e 100644 --- a/seam/resources/unmanaged_access_grant.py +++ b/seam/resources/unmanaged_access_grant.py @@ -50,7 +50,8 @@ class Errors(ResourceMapping): :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - :ivar missing_device_ids: IDs of the devices that did not receive an access code at grant creation. Use these to identify which specific devices failed when the message reports a partial failure.""" + :ivar missing_device_ids: IDs of the devices that did not receive an access code at grant creation. Use these to identify which specific devices failed when the message reports a partial failure. + """ created_at: str error_code: str @@ -72,13 +73,13 @@ class PendingMutations(ResourceMapping): :ivar created_at: Date and time at which the mutation was created. - :ivar from_: + :ivar from_: :ivar message: Detailed description of the mutation. - :ivar mutation_code: + :ivar mutation_code: - :ivar to: + :ivar to: :ivar access_method_ids: IDs of the access methods being updated.""" @@ -141,7 +142,11 @@ def from_dict(cls, d: Any): def from_dict(cls, d: Any): return cls( created_at=d.get("created_at", None), - from_=cls.From.from_dict(d.get("from")) if d.get("from") is not None else None, + from_=( + cls.From.from_dict(d.get("from")) + if d.get("from") is not None + else None + ), message=d.get("message", None), mutation_code=d.get("mutation_code", None), to=cls.To.from_dict(d.get("to")) if d.get("to") is not None else None, @@ -162,7 +167,8 @@ class RequestedAccessMethods(ResourceMapping): :ivar instant_key_max_use_count: Maximum number of times the instant key can be used. Only applicable when mode is 'mobile_key'. Defaults to 1 if not specified. - :ivar mode: Access method mode. Supported values: ``code``, ``card``, ``mobile_key``, ``cloud_key``.""" + :ivar mode: Access method mode. Supported values: ``code``, ``card``, ``mobile_key``, ``cloud_key``. + """ code: Optional[str] created_access_method_ids: List[str] @@ -196,13 +202,14 @@ class Warnings(ResourceMapping): :ivar access_method_ids: IDs of the access methods being updated. - :ivar device_id: + :ivar device_id: :ivar new_code: The new PIN code that was assigned instead. :ivar original_code: The originally requested PIN code that was unavailable. - :ivar reason: Specific reason why the grant's times are not programmable on the device.""" + :ivar reason: Specific reason why the grant's times are not programmable on the device. + """ @dataclass class FailedDevices(ResourceMapping): @@ -242,7 +249,10 @@ def from_dict(cls, d: Any): created_at=d.get("created_at", None), message=d.get("message", None), warning_code=d.get("warning_code", None), - failed_devices=[cls.FailedDevices.from_dict(i) for i in d.get("failed_devices") or []], + failed_devices=[ + cls.FailedDevices.from_dict(i) + for i in d.get("failed_devices") or [] + ], access_method_ids=d.get("access_method_ids", None), device_id=d.get("device_id", None), new_code=d.get("new_code", None), @@ -278,8 +288,14 @@ def from_dict(cls, d: Any): errors=[cls.Errors.from_dict(i) for i in d.get("errors") or []], location_ids=d.get("location_ids", None), name=d.get("name", None), - pending_mutations=[cls.PendingMutations.from_dict(i) for i in d.get("pending_mutations") or []], - requested_access_methods=[cls.RequestedAccessMethods.from_dict(i) for i in d.get("requested_access_methods") or []], + pending_mutations=[ + cls.PendingMutations.from_dict(i) + for i in d.get("pending_mutations") or [] + ], + requested_access_methods=[ + cls.RequestedAccessMethods.from_dict(i) + for i in d.get("requested_access_methods") or [] + ], reservation_key=d.get("reservation_key", None), space_ids=d.get("space_ids", None), starts_at=d.get("starts_at", None), diff --git a/seam/resources/unmanaged_access_method.py b/seam/resources/unmanaged_access_method.py index b0300659..c30e9a4e 100644 --- a/seam/resources/unmanaged_access_method.py +++ b/seam/resources/unmanaged_access_method.py @@ -46,7 +46,8 @@ class Errors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + """ created_at: str error_code: str @@ -66,19 +67,19 @@ class PendingMutations(ResourceMapping): :ivar created_at: Date and time at which the mutation was created. - :ivar from_: + :ivar from_: :ivar message: Detailed description of the mutation. - :ivar mutation_code: + :ivar mutation_code: - :ivar to: """ + :ivar to:""" @dataclass class From(ResourceMapping): """ - :ivar device_ids: + :ivar device_ids: :ivar ends_at: Previous end time for access. @@ -100,7 +101,7 @@ def from_dict(cls, d: Any): class To(ResourceMapping): """ - :ivar device_ids: + :ivar device_ids: :ivar ends_at: New end time for access. @@ -128,7 +129,11 @@ def from_dict(cls, d: Any): def from_dict(cls, d: Any): return cls( created_at=d.get("created_at", None), - from_=cls.From.from_dict(d.get("from")) if d.get("from") is not None else None, + from_=( + cls.From.from_dict(d.get("from")) + if d.get("from") is not None + else None + ), message=d.get("message", None), mutation_code=d.get("mutation_code", None), to=cls.To.from_dict(d.get("to")) if d.get("to") is not None else None, @@ -144,7 +149,8 @@ class Warnings(ResourceMapping): :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - :ivar original_access_method_id: ID of the original access method from which this backup access method was split, if applicable.""" + :ivar original_access_method_id: ID of the original access method from which this backup access method was split, if applicable. + """ created_at: str message: str @@ -191,7 +197,10 @@ def from_dict(cls, d: Any): is_ready_for_encoding=d.get("is_ready_for_encoding", None), issued_at=d.get("issued_at", None), mode=d.get("mode", None), - pending_mutations=[cls.PendingMutations.from_dict(i) for i in d.get("pending_mutations") or []], + pending_mutations=[ + cls.PendingMutations.from_dict(i) + for i in d.get("pending_mutations") or [] + ], warnings=[cls.Warnings.from_dict(i) for i in d.get("warnings") or []], workspace_id=d.get("workspace_id", None), ) diff --git a/seam/resources/unmanaged_device.py b/seam/resources/unmanaged_device.py index e1432f3d..15ba2dcc 100644 --- a/seam/resources/unmanaged_device.py +++ b/seam/resources/unmanaged_device.py @@ -70,7 +70,8 @@ class UnmanagedDevice: :ivar warnings: Array of warnings associated with the device. Each warning object within the array contains two fields: ``warning_code`` and ``message``. ``warning_code`` is a string that uniquely identifies the type of warning, enabling quick recognition and categorization of the issue. ``message`` provides a more detailed description of the warning, offering insights into the issue and potentially how to rectify it. - :ivar workspace_id: Unique identifier for the Seam workspace associated with the device.""" + :ivar workspace_id: Unique identifier for the Seam workspace associated with the device. + """ @dataclass class Errors(ResourceMapping): @@ -80,13 +81,14 @@ class Errors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar is_connected_account_error: + :ivar is_connected_account_error: - :ivar is_device_error: + :ivar is_device_error: :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - :ivar is_bridge_error: Indicates whether the error is related to `Seam Bridge `_.""" + :ivar is_bridge_error: Indicates whether the error is related to `Seam Bridge `_. + """ created_at: str error_code: str @@ -116,7 +118,8 @@ class Location(ResourceMapping): :ivar time_zone: Time zone of the device location. - :ivar timezone: Deprecated: Use ``time_zone`` instead. Time zone of the device location.""" + :ivar timezone: Deprecated: Use ``time_zone`` instead. Time zone of the device location. + """ location_name: Optional[str] room_name: Optional[str] @@ -156,7 +159,8 @@ class Properties(ResourceMapping): :ivar online: Indicates whether the device is online. - :ivar online_access_codes_enabled: Deprecated: use device.can_program_online_access_codes Indicates whether it is currently possible to use online access codes for the device.""" + :ivar online_access_codes_enabled: Deprecated: use device.can_program_online_access_codes Indicates whether it is currently possible to use online access codes for the device. + """ @dataclass class AccessoryKeypad(ResourceMapping): @@ -164,13 +168,14 @@ class AccessoryKeypad(ResourceMapping): :ivar battery: Keypad battery properties. - :ivar is_connected: Indicates if an accessory keypad is connected to the device.""" + :ivar is_connected: Indicates if an accessory keypad is connected to the device. + """ @dataclass class Battery(ResourceMapping): """Keypad battery properties. - :ivar level: """ + :ivar level:""" level: float @@ -186,7 +191,11 @@ def from_dict(cls, d: Any): @classmethod def from_dict(cls, d: Any): return cls( - battery=cls.Battery.from_dict(d.get("battery")) if d.get("battery") is not None else None, + battery=( + cls.Battery.from_dict(d.get("battery")) + if d.get("battery") is not None + else None + ), is_connected=d.get("is_connected", None), ) @@ -196,7 +205,8 @@ class Battery(ResourceMapping): :ivar level: Battery charge level as a value between 0 and 1, inclusive. - :ivar status: Represents the current status of the battery charge level. Values are ``critical``, which indicates an extremely low level, suggesting imminent shutdown or an urgent need for charging; ``low``, which signifies that the battery is under the preferred threshold and should be charged soon; ``good``, which denotes a satisfactory charge level, adequate for normal use without the immediate need for recharging; and ``full``, which represents a battery that is fully charged, providing the maximum duration of usage.""" + :ivar status: Represents the current status of the battery charge level. Values are ``critical``, which indicates an extremely low level, suggesting imminent shutdown or an urgent need for charging; ``low``, which signifies that the battery is under the preferred threshold and should be charged soon; ``good``, which denotes a satisfactory charge level, adequate for normal use without the immediate need for recharging; and ``full``, which represents a battery that is fully charged, providing the maximum duration of usage. + """ level: float status: str @@ -224,7 +234,8 @@ class Model(ResourceMapping): :ivar offline_access_codes_supported: Deprecated: use device.can_program_offline_access_codes. - :ivar online_access_codes_supported: Deprecated: use device.can_program_online_access_codes.""" + :ivar online_access_codes_supported: Deprecated: use device.can_program_online_access_codes. + """ accessory_keypad_supported: Optional[bool] can_connect_accessory_keypad: Optional[bool] @@ -237,13 +248,21 @@ class Model(ResourceMapping): @classmethod def from_dict(cls, d: Any): return cls( - accessory_keypad_supported=d.get("accessory_keypad_supported", None), - can_connect_accessory_keypad=d.get("can_connect_accessory_keypad", None), + accessory_keypad_supported=d.get( + "accessory_keypad_supported", None + ), + can_connect_accessory_keypad=d.get( + "can_connect_accessory_keypad", None + ), display_name=d.get("display_name", None), has_built_in_keypad=d.get("has_built_in_keypad", None), manufacturer_display_name=d.get("manufacturer_display_name", None), - offline_access_codes_supported=d.get("offline_access_codes_supported", None), - online_access_codes_supported=d.get("online_access_codes_supported", None), + offline_access_codes_supported=d.get( + "offline_access_codes_supported", None + ), + online_access_codes_supported=d.get( + "online_access_codes_supported", None + ), ) accessory_keypad: Optional[AccessoryKeypad] @@ -261,15 +280,29 @@ def from_dict(cls, d: Any): @classmethod def from_dict(cls, d: Any): return cls( - accessory_keypad=cls.AccessoryKeypad.from_dict(d.get("accessory_keypad")) if d.get("accessory_keypad") is not None else None, - battery=cls.Battery.from_dict(d.get("battery")) if d.get("battery") is not None else None, + accessory_keypad=( + cls.AccessoryKeypad.from_dict(d.get("accessory_keypad")) + if d.get("accessory_keypad") is not None + else None + ), + battery=( + cls.Battery.from_dict(d.get("battery")) + if d.get("battery") is not None + else None + ), battery_level=d.get("battery_level", None), image_alt_text=d.get("image_alt_text", None), image_url=d.get("image_url", None), manufacturer=d.get("manufacturer", None), - model=cls.Model.from_dict(d.get("model")) if d.get("model") is not None else None, + model=( + cls.Model.from_dict(d.get("model")) + if d.get("model") is not None + else None + ), name=d.get("name", None), - offline_access_codes_enabled=d.get("offline_access_codes_enabled", None), + offline_access_codes_enabled=d.get( + "offline_access_codes_enabled", None + ), online=d.get("online", None), online_access_codes_enabled=d.get("online_access_codes_enabled", None), ) @@ -286,7 +319,8 @@ class Warnings(ResourceMapping): :ivar active_access_code_count: Number of active access codes on the device when the warning was set. - :ivar max_active_access_code_count: Maximum number of active access codes supported by the device.""" + :ivar max_active_access_code_count: Maximum number of active access codes supported by the device. + """ created_at: str message: str @@ -301,7 +335,9 @@ def from_dict(cls, d: Any): message=d.get("message", None), warning_code=d.get("warning_code", None), active_access_code_count=d.get("active_access_code_count", None), - max_active_access_code_count=d.get("max_active_access_code_count", None), + max_active_access_code_count=d.get( + "max_active_access_code_count", None + ), ) can_configure_auto_lock: Optional[bool] @@ -344,19 +380,33 @@ def from_dict(cls, d: Any): can_hvac_cool=d.get("can_hvac_cool", None), can_hvac_heat=d.get("can_hvac_heat", None), can_hvac_heat_cool=d.get("can_hvac_heat_cool", None), - can_program_offline_access_codes=d.get("can_program_offline_access_codes", None), - can_program_online_access_codes=d.get("can_program_online_access_codes", None), - can_program_thermostat_programs_as_different_each_day=d.get("can_program_thermostat_programs_as_different_each_day", None), - can_program_thermostat_programs_as_same_each_day=d.get("can_program_thermostat_programs_as_same_each_day", None), - can_program_thermostat_programs_as_weekday_weekend=d.get("can_program_thermostat_programs_as_weekday_weekend", None), + can_program_offline_access_codes=d.get( + "can_program_offline_access_codes", None + ), + can_program_online_access_codes=d.get( + "can_program_online_access_codes", None + ), + can_program_thermostat_programs_as_different_each_day=d.get( + "can_program_thermostat_programs_as_different_each_day", None + ), + can_program_thermostat_programs_as_same_each_day=d.get( + "can_program_thermostat_programs_as_same_each_day", None + ), + can_program_thermostat_programs_as_weekday_weekend=d.get( + "can_program_thermostat_programs_as_weekday_weekend", None + ), can_remotely_lock=d.get("can_remotely_lock", None), can_remotely_unlock=d.get("can_remotely_unlock", None), can_run_thermostat_programs=d.get("can_run_thermostat_programs", None), can_simulate_connection=d.get("can_simulate_connection", None), can_simulate_disconnection=d.get("can_simulate_disconnection", None), can_simulate_hub_connection=d.get("can_simulate_hub_connection", None), - can_simulate_hub_disconnection=d.get("can_simulate_hub_disconnection", None), - can_simulate_paid_subscription=d.get("can_simulate_paid_subscription", None), + can_simulate_hub_disconnection=d.get( + "can_simulate_hub_disconnection", None + ), + can_simulate_paid_subscription=d.get( + "can_simulate_paid_subscription", None + ), can_simulate_removal=d.get("can_simulate_removal", None), can_turn_off_hvac=d.get("can_turn_off_hvac", None), can_unlock_with_code=d.get("can_unlock_with_code", None), @@ -368,8 +418,16 @@ def from_dict(cls, d: Any): device_type=d.get("device_type", None), errors=[cls.Errors.from_dict(i) for i in d.get("errors") or []], is_managed=d.get("is_managed", None), - location=cls.Location.from_dict(d.get("location")) if d.get("location") is not None else None, - properties=cls.Properties.from_dict(d.get("properties")) if d.get("properties") is not None else None, + location=( + cls.Location.from_dict(d.get("location")) + if d.get("location") is not None + else None + ), + properties=( + cls.Properties.from_dict(d.get("properties")) + if d.get("properties") is not None + else None + ), warnings=[cls.Warnings.from_dict(i) for i in d.get("warnings") or []], workspace_id=d.get("workspace_id", None), ) diff --git a/seam/resources/unmanaged_user_identity.py b/seam/resources/unmanaged_user_identity.py index 2df3a66c..6905f5af 100644 --- a/seam/resources/unmanaged_user_identity.py +++ b/seam/resources/unmanaged_user_identity.py @@ -40,7 +40,8 @@ class Errors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + """ acs_system_id: str acs_user_id: str @@ -66,7 +67,8 @@ class Warnings(ResourceMapping): :ivar message: Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue.""" + :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + """ created_at: str message: str diff --git a/seam/resources/user_identity.py b/seam/resources/user_identity.py index 9b1166b0..141a82a3 100644 --- a/seam/resources/user_identity.py +++ b/seam/resources/user_identity.py @@ -42,7 +42,8 @@ class Errors(ResourceMapping): :ivar error_code: Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it.""" + :ivar message: Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + """ acs_system_id: str acs_user_id: str @@ -68,7 +69,8 @@ class Warnings(ResourceMapping): :ivar message: Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue.""" + :ivar warning_code: Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + """ created_at: str message: str diff --git a/seam/resources/workspace.py b/seam/resources/workspace.py index 4d7c5836..bbc0e01e 100644 --- a/seam/resources/workspace.py +++ b/seam/resources/workspace.py @@ -12,7 +12,7 @@ class Workspace: :ivar connect_partner_name: Deprecated: Use ``company_name`` instead. - :ivar connect_webview_customization: + :ivar connect_webview_customization: :ivar is_publishable_key_auth_enabled: Indicates whether publishable key authentication is enabled for this workspace. @@ -40,7 +40,8 @@ class ConnectWebviewCustomization(ResourceMapping): :ivar primary_button_text_color: Primary button text color for `Connect Webviews `_ in the workspace. See also `Customize the Look and Feel of Your Connect Webviews `_. - :ivar success_message: Success message for `Connect Webviews `_ in the workspace. See also `Customize the Look and Feel of Your Connect Webviews `_.""" + :ivar success_message: Success message for `Connect Webviews `_ in the workspace. See also `Customize the Look and Feel of Your Connect Webviews `_. + """ inviter_logo_url: Optional[str] logo_shape: Optional[str] @@ -74,8 +75,16 @@ def from_dict(cls, d: Any): return cls( company_name=d.get("company_name", None), connect_partner_name=d.get("connect_partner_name", None), - connect_webview_customization=cls.ConnectWebviewCustomization.from_dict(d.get("connect_webview_customization")) if d.get("connect_webview_customization") is not None else None, - is_publishable_key_auth_enabled=d.get("is_publishable_key_auth_enabled", None), + connect_webview_customization=( + cls.ConnectWebviewCustomization.from_dict( + d.get("connect_webview_customization") + ) + if d.get("connect_webview_customization") is not None + else None + ), + is_publishable_key_auth_enabled=d.get( + "is_publishable_key_auth_enabled", None + ), is_sandbox=d.get("is_sandbox", None), is_suspended=d.get("is_suspended", None), name=d.get("name", None), diff --git a/seam/routes/access_codes.py b/seam/routes/access_codes.py index 972fe15c..82f909de 100644 --- a/seam/routes/access_codes.py +++ b/seam/routes/access_codes.py @@ -3,7 +3,7 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (AccessCode) +from ..resources import AccessCode from .access_codes_simulate import AbstractAccessCodesSimulate, AccessCodesSimulate from .access_codes_unmanaged import AbstractAccessCodesUnmanaged, AccessCodesUnmanaged @@ -21,14 +21,33 @@ def unmanaged(self) -> AbstractAccessCodesUnmanaged: raise NotImplementedError() @abc.abstractmethod - def create(self, *, device_id: str, allow_external_modification: Optional[bool] = None, attempt_for_offline_device: Optional[bool] = None, code: Optional[str] = None, common_code_key: Optional[str] = None, ends_at: Optional[str] = None, is_external_modification_allowed: Optional[bool] = None, is_offline_access_code: Optional[bool] = None, is_one_time_use: Optional[bool] = None, max_time_rounding: Optional[str] = None, name: Optional[str] = None, prefer_native_scheduling: Optional[bool] = None, preferred_code_length: Optional[float] = None, starts_at: Optional[str] = None, use_backup_access_code_pool: Optional[bool] = None, use_offline_access_code: Optional[bool] = None) -> AccessCode: + def create( + self, + *, + device_id: str, + allow_external_modification: Optional[bool] = None, + attempt_for_offline_device: Optional[bool] = None, + code: Optional[str] = None, + common_code_key: Optional[str] = None, + ends_at: Optional[str] = None, + is_external_modification_allowed: Optional[bool] = None, + is_offline_access_code: Optional[bool] = None, + is_one_time_use: Optional[bool] = None, + max_time_rounding: Optional[str] = None, + name: Optional[str] = None, + prefer_native_scheduling: Optional[bool] = None, + preferred_code_length: Optional[float] = None, + starts_at: Optional[str] = None, + use_backup_access_code_pool: Optional[bool] = None, + use_offline_access_code: Optional[bool] = None, + ) -> AccessCode: """Creates a new `access code `_. For granting access, we recommend `Access Grants `_ instead: they work across both standalone smart locks and access control systems and manage the underlying codes for you. Use this low-level endpoint only when you need direct control over a code on a single device, such as setting a custom PIN value. :param device_id: ID of the device for which you want to create the new access code. :param allow_external_modification: Indicates whether `external modification `_ of the code is allowed. Default: ``false``. - :param attempt_for_offline_device: + :param attempt_for_offline_device: :param code: Code to be used for access. @@ -45,11 +64,11 @@ def create(self, *, device_id: str, allow_external_modification: Optional[bool] :param max_time_rounding: Maximum rounding adjustment. To create a daily-bound `offline access code `_ for devices that support this feature, set this parameter to ``1d``. :param name: Name of the new access code. Enables administrators and users to identify the access code easily, especially when there are numerous access codes. - + Note that the name provided on Seam is used to identify the code on Seam and is not necessarily the name that will appear in the lock provider's app or on the device. This is because lock providers may have constraints on names, such as length, uniqueness, or characters that can be used. In addition, some lock providers may break down names into components such as ``first_name`` and ``last_name``. - + To provide a consistent experience, Seam identifies the code on Seam by its name but may modify the name that appears on the lock provider's app or on the device. For example, Seam may add additional characters or truncate the name to meet provider constraints. - + To help your users identify codes set by Seam, Seam provides the name exactly as it appears on the lock provider's app or on the device as a separate property called ``appearance``. This is an object with a ``name`` property and, optionally, ``first_name`` and ``last_name`` properties (for providers that break down a name into components). :param prefer_native_scheduling: Indicates whether `native scheduling `_ should be used for time-bound codes when supported by the provider. Default: ``true``. @@ -68,24 +87,39 @@ def create(self, *, device_id: str, allow_external_modification: Optional[bool] raise NotImplementedError() @abc.abstractmethod - def create_multiple(self, *, device_ids: List[str], allow_external_modification: Optional[bool] = None, attempt_for_offline_device: Optional[bool] = None, behavior_when_code_cannot_be_shared: Optional[str] = None, code: Optional[str] = None, ends_at: Optional[str] = None, is_external_modification_allowed: Optional[bool] = None, name: Optional[str] = None, prefer_native_scheduling: Optional[bool] = None, preferred_code_length: Optional[float] = None, starts_at: Optional[str] = None, use_backup_access_code_pool: Optional[bool] = None) -> List[AccessCode]: + def create_multiple( + self, + *, + device_ids: List[str], + allow_external_modification: Optional[bool] = None, + attempt_for_offline_device: Optional[bool] = None, + behavior_when_code_cannot_be_shared: Optional[str] = None, + code: Optional[str] = None, + ends_at: Optional[str] = None, + is_external_modification_allowed: Optional[bool] = None, + name: Optional[str] = None, + prefer_native_scheduling: Optional[bool] = None, + preferred_code_length: Optional[float] = None, + starts_at: Optional[str] = None, + use_backup_access_code_pool: Optional[bool] = None, + ) -> List[AccessCode]: """Creates new `access codes `_ that share a common code across multiple devices. - + Users with more than one door lock in a property may want to create groups of linked access codes, all of which have the same code (PIN). For example, a short-term rental host may want to provide guests the same PIN for both a front door lock and a back door lock. - + If you specify a custom code, Seam assigns this custom code to each of the resulting access codes. However, in this case, Seam does not link these access codes together with a ``common_code_key``. That is, ``common_code_key`` remains null for these access codes. - + If you want to change these access codes that are not linked by a ``common_code_key``, you cannot use ``/access_codes/update_multiple``. However, you can update each of these access codes individually, using ``/access_codes/update``. - + See also `Creating and Updating Multiple Linked Access Codes `_. - + For granting a person access to a space, `Access Grants `_ are the default and recommended approach and work across both standalone smart locks and access systems. Use the lower-level Access Codes API directly only when you specifically need to manage individual PIN codes. :param device_ids: IDs of the devices for which you want to create the new access codes. :param allow_external_modification: Indicates whether `external modification `_ of the code is allowed. Default: ``false``. - :param attempt_for_offline_device: + :param attempt_for_offline_device: :param behavior_when_code_cannot_be_shared: Desired behavior if any device cannot share a code. If ``throw`` (default), no access codes will be created if any device cannot share a code. If ``create_random_code``, a random code will be created on devices that cannot share a code. @@ -96,11 +130,11 @@ def create_multiple(self, *, device_ids: List[str], allow_external_modification: :param is_external_modification_allowed: Indicates whether `external modification `_ of the code is allowed. Default: ``false``. :param name: Name of the new access code. Enables administrators and users to identify the access code easily, especially when there are numerous access codes. - + Note that the name provided on Seam is used to identify the code on Seam and is not necessarily the name that will appear in the lock provider's app or on the device. This is because lock providers may have constraints on names, such as length, uniqueness, or characters that can be used. In addition, some lock providers may break down names into components such as ``first_name`` and ``last_name``. - + To provide a consistent experience, Seam identifies the code on Seam by its name but may modify the name that appears on the lock provider's app or on the device. For example, Seam may add additional characters or truncate the name to meet provider constraints. - + To help your users identify codes set by Seam, Seam provides the name exactly as it appears on the lock provider's app or on the device as a separate property called ``appearance``. This is an object with a ``name`` property and, optionally, ``first_name`` and ``last_name`` properties (for providers that break down a name into components). :param prefer_native_scheduling: Indicates whether `native scheduling `_ should be used for time-bound codes when supported by the provider. Default: ``true``. @@ -139,9 +173,15 @@ def generate_code(self, *, device_id: str) -> AccessCode: raise NotImplementedError() @abc.abstractmethod - def get(self, *, access_code_id: Optional[str] = None, code: Optional[str] = None, device_id: Optional[str] = None) -> AccessCode: + def get( + self, + *, + access_code_id: Optional[str] = None, + code: Optional[str] = None, + device_id: Optional[str] = None, + ) -> AccessCode: """Returns a specified `access code `_. - + You must specify either ``access_code_id`` or both ``device_id`` and ``code``. :param access_code_id: ID of the access code that you want to get. You must specify either ``access_code_id`` or both ``device_id`` and ``code``. @@ -156,9 +196,22 @@ def get(self, *, access_code_id: Optional[str] = None, code: Optional[str] = Non raise NotImplementedError() @abc.abstractmethod - def list(self, *, access_code_ids: Optional[List[str]] = None, access_grant_id: Optional[str] = None, access_grant_key: Optional[str] = None, access_method_id: Optional[str] = None, customer_key: Optional[str] = None, device_id: Optional[str] = None, limit: Optional[float] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, user_identifier_key: Optional[str] = None) -> List[AccessCode]: + def list( + self, + *, + access_code_ids: Optional[List[str]] = None, + access_grant_id: Optional[str] = None, + access_grant_key: Optional[str] = None, + access_method_id: Optional[str] = None, + customer_key: Optional[str] = None, + device_id: Optional[str] = None, + limit: Optional[float] = None, + page_cursor: Optional[Union[str, Null]] = None, + search: Optional[str] = None, + user_identifier_key: Optional[str] = None, + ) -> List[AccessCode]: """Returns a list of all `access codes `_. - + Specify ``device_id``, ``access_code_ids``, ``access_method_id``, ``access_grant_id``, or ``access_grant_key``. :param access_code_ids: IDs of the access codes that you want to retrieve. Specify ``device_id``, ``access_code_ids``, ``access_method_id``, ``access_grant_id``, or ``access_grant_key``. @@ -189,13 +242,13 @@ def list(self, *, access_code_ids: Optional[List[str]] = None, access_grant_id: @abc.abstractmethod def pull_backup_access_code(self, *, access_code_id: str) -> AccessCode: """Retrieves a backup access code for an `access code `_. See also `Managing Backup Access Codes `_. - + A backup access code pool is a collection of pre-programmed access codes stored on a device, ready for use. These codes are programmed in addition to the regular access codes on Seam, serving as a safety net for any issues with the primary codes. If there's ever a complication with a primary access code—be it due to intermittent connectivity, manual removal from a device, or provider outages—a backup code can be retrieved. Its end time can then be adjusted to align with the original code, facilitating seamless and uninterrupted access. - + You can pull a backup access code from the pool at any time. These backup codes are guaranteed to work immediately and automatically programmed to be removed from the device after the access code ends. - + You can only pull backup access codes for time-bound access codes. - + Before pulling a backup access code, make sure that the device's ``properties.supports_backup_access_code_pool`` is ``true``. Then, to activate the backup pool, set ``use_backup_access_code_pool`` to ``true`` when creating an access code. :param access_code_id: ID of the access code for which you want to pull a backup access code. @@ -206,9 +259,16 @@ def pull_backup_access_code(self, *, access_code_id: str) -> AccessCode: raise NotImplementedError() @abc.abstractmethod - def report_device_constraints(self, *, device_id: str, max_code_length: Optional[int] = None, min_code_length: Optional[int] = None, supported_code_lengths: Optional[List[float]] = None) -> None: + def report_device_constraints( + self, + *, + device_id: str, + max_code_length: Optional[int] = None, + min_code_length: Optional[int] = None, + supported_code_lengths: Optional[List[float]] = None, + ) -> None: """Enables you to report access code-related constraints for a device. Currently, supports reporting supported code length constraints for SmartThings devices. - + Specify either ``supported_code_lengths`` or ``min_code_length``/``max_code_length``. :param device_id: ID of the device for which you want to report constraints. @@ -223,16 +283,30 @@ def report_device_constraints(self, *, device_id: str, max_code_length: Optional raise NotImplementedError() @abc.abstractmethod - def update(self, *, access_code_id: str, allow_external_modification: Optional[bool] = None, attempt_for_offline_device: Optional[bool] = None, code: Optional[str] = None, device_id: Optional[str] = None, ends_at: Optional[str] = None, is_external_modification_allowed: Optional[bool] = None, is_managed: Optional[bool] = None, name: Optional[str] = None, starts_at: Optional[str] = None, type: Optional[str] = None) -> None: + def update( + self, + *, + access_code_id: str, + allow_external_modification: Optional[bool] = None, + attempt_for_offline_device: Optional[bool] = None, + code: Optional[str] = None, + device_id: Optional[str] = None, + ends_at: Optional[str] = None, + is_external_modification_allowed: Optional[bool] = None, + is_managed: Optional[bool] = None, + name: Optional[str] = None, + starts_at: Optional[str] = None, + type: Optional[str] = None, + ) -> None: """Updates a specified active or upcoming `access code `_. - + See also `Modifying Access Codes `_. :param access_code_id: ID of the access code that you want to update. :param allow_external_modification: Indicates whether `external modification `_ of the code is allowed. Default: ``false``. - :param attempt_for_offline_device: + :param attempt_for_offline_device: :param code: Code to be used for access. @@ -245,11 +319,11 @@ def update(self, *, access_code_id: str, allow_external_modification: Optional[b :param is_managed: Indicates whether the access code is managed through Seam. Note that to convert an unmanaged access code into a managed access code, use ``/access_codes/unmanaged/convert_to_managed``. :param name: Name of the new access code. Enables administrators and users to identify the access code easily, especially when there are numerous access codes. - + Note that the name provided on Seam is used to identify the code on Seam and is not necessarily the name that will appear in the lock provider's app or on the device. This is because lock providers may have constraints on names, such as length, uniqueness, or characters that can be used. In addition, some lock providers may break down names into components such as ``first_name`` and ``last_name``. - + To provide a consistent experience, Seam identifies the code on Seam by its name but may modify the name that appears on the lock provider's app or on the device. For example, Seam may add additional characters or truncate the name to meet provider constraints. - + To help your users identify codes set by Seam, Seam provides the name exactly as it appears on the lock provider's app or on the device as a separate property called ``appearance``. This is an object with a ``name`` property and, optionally, ``first_name`` and ``last_name`` properties (for providers that break down a name into components). :param starts_at: Date and time at which the validity of the new access code starts, in `ISO 8601 `_ format. @@ -260,11 +334,18 @@ def update(self, *, access_code_id: str, allow_external_modification: Optional[b raise NotImplementedError() @abc.abstractmethod - def update_multiple(self, *, common_code_key: str, ends_at: Optional[str] = None, name: Optional[str] = None, starts_at: Optional[str] = None) -> None: + def update_multiple( + self, + *, + common_code_key: str, + ends_at: Optional[str] = None, + name: Optional[str] = None, + starts_at: Optional[str] = None, + ) -> None: """Updates `access codes `_ that share a common code across multiple devices. - + Specify the ``common_code_key`` to identify the set of access codes that you want to update. - + See also `Update Linked Access Codes `_. :param common_code_key: Key that links the group of access codes, assigned on creation by ``/access_codes/create_multiple``. @@ -272,11 +353,11 @@ def update_multiple(self, *, common_code_key: str, ends_at: Optional[str] = None :param ends_at: Date and time at which the validity of the new access code ends, in `ISO 8601 `_ format. Must be a time in the future and after ``starts_at``. :param name: Name of the new access code. Enables administrators and users to identify the access code easily, especially when there are numerous access codes. - + Note that the name provided on Seam is used to identify the code on Seam and is not necessarily the name that will appear in the lock provider's app or on the device. This is because lock providers may have constraints on names, such as length, uniqueness, or characters that can be used. In addition, some lock providers may break down names into components such as ``first_name`` and ``last_name``. - + To provide a consistent experience, Seam identifies the code on Seam by its name but may modify the name that appears on the lock provider's app or on the device. For example, Seam may add additional characters or truncate the name to meet provider constraints. - + To help your users identify codes set by Seam, Seam provides the name exactly as it appears on the lock provider's app or on the device as a separate property called ``appearance``. This is an object with a ``name`` property and, optionally, ``first_name`` and ``last_name`` properties (for providers that break down a name into components). :param starts_at: Date and time at which the validity of the new access code starts, in `ISO 8601 `_ format. @@ -300,15 +381,36 @@ def simulate(self) -> AccessCodesSimulate: def unmanaged(self) -> AccessCodesUnmanaged: return self._unmanaged - @route_metadata(path="/access_codes/create", has_required_parameters=True, has_pagination=False) - def create(self, *, device_id: str, allow_external_modification: Optional[bool] = None, attempt_for_offline_device: Optional[bool] = None, code: Optional[str] = None, common_code_key: Optional[str] = None, ends_at: Optional[str] = None, is_external_modification_allowed: Optional[bool] = None, is_offline_access_code: Optional[bool] = None, is_one_time_use: Optional[bool] = None, max_time_rounding: Optional[str] = None, name: Optional[str] = None, prefer_native_scheduling: Optional[bool] = None, preferred_code_length: Optional[float] = None, starts_at: Optional[str] = None, use_backup_access_code_pool: Optional[bool] = None, use_offline_access_code: Optional[bool] = None) -> AccessCode: + @route_metadata( + path="/access_codes/create", has_required_parameters=True, has_pagination=False + ) + def create( + self, + *, + device_id: str, + allow_external_modification: Optional[bool] = None, + attempt_for_offline_device: Optional[bool] = None, + code: Optional[str] = None, + common_code_key: Optional[str] = None, + ends_at: Optional[str] = None, + is_external_modification_allowed: Optional[bool] = None, + is_offline_access_code: Optional[bool] = None, + is_one_time_use: Optional[bool] = None, + max_time_rounding: Optional[str] = None, + name: Optional[str] = None, + prefer_native_scheduling: Optional[bool] = None, + preferred_code_length: Optional[float] = None, + starts_at: Optional[str] = None, + use_backup_access_code_pool: Optional[bool] = None, + use_offline_access_code: Optional[bool] = None, + ) -> AccessCode: """Creates a new `access code `_. For granting access, we recommend `Access Grants `_ instead: they work across both standalone smart locks and access control systems and manage the underlying codes for you. Use this low-level endpoint only when you need direct control over a code on a single device, such as setting a custom PIN value. :param device_id: ID of the device for which you want to create the new access code. :param allow_external_modification: Indicates whether `external modification `_ of the code is allowed. Default: ``false``. - :param attempt_for_offline_device: + :param attempt_for_offline_device: :param code: Code to be used for access. @@ -325,11 +427,11 @@ def create(self, *, device_id: str, allow_external_modification: Optional[bool] :param max_time_rounding: Maximum rounding adjustment. To create a daily-bound `offline access code `_ for devices that support this feature, set this parameter to ``1d``. :param name: Name of the new access code. Enables administrators and users to identify the access code easily, especially when there are numerous access codes. - + Note that the name provided on Seam is used to identify the code on Seam and is not necessarily the name that will appear in the lock provider's app or on the device. This is because lock providers may have constraints on names, such as length, uniqueness, or characters that can be used. In addition, some lock providers may break down names into components such as ``first_name`` and ``last_name``. - + To provide a consistent experience, Seam identifies the code on Seam by its name but may modify the name that appears on the lock provider's app or on the device. For example, Seam may add additional characters or truncate the name to meet provider constraints. - + To help your users identify codes set by Seam, Seam provides the name exactly as it appears on the lock provider's app or on the device as a separate property called ``appearance``. This is an object with a ``name`` property and, optionally, ``first_name`` and ``last_name`` properties (for providers that break down a name into components). :param prefer_native_scheduling: Indicates whether `native scheduling `_ should be used for time-bound codes when supported by the provider. Default: ``true``. @@ -360,7 +462,9 @@ def create(self, *, device_id: str, allow_external_modification: Optional[bool] if ends_at is not None: json_payload["ends_at"] = ends_at if is_external_modification_allowed is not None: - json_payload["is_external_modification_allowed"] = is_external_modification_allowed + json_payload["is_external_modification_allowed"] = ( + is_external_modification_allowed + ) if is_offline_access_code is not None: json_payload["is_offline_access_code"] = is_offline_access_code if is_one_time_use is not None: @@ -381,31 +485,52 @@ def create(self, *, device_id: str, allow_external_modification: Optional[bool] json_payload["use_offline_access_code"] = use_offline_access_code if not json_payload: - raise ValueError("At least one parameter is required for /access_codes/create") + raise ValueError( + "At least one parameter is required for /access_codes/create" + ) res = self.client.post("/access_codes/create", json=json_payload) return AccessCode.from_dict(res["access_code"]) - @route_metadata(path="/access_codes/create_multiple", has_required_parameters=True, has_pagination=False) - def create_multiple(self, *, device_ids: List[str], allow_external_modification: Optional[bool] = None, attempt_for_offline_device: Optional[bool] = None, behavior_when_code_cannot_be_shared: Optional[str] = None, code: Optional[str] = None, ends_at: Optional[str] = None, is_external_modification_allowed: Optional[bool] = None, name: Optional[str] = None, prefer_native_scheduling: Optional[bool] = None, preferred_code_length: Optional[float] = None, starts_at: Optional[str] = None, use_backup_access_code_pool: Optional[bool] = None) -> List[AccessCode]: + @route_metadata( + path="/access_codes/create_multiple", + has_required_parameters=True, + has_pagination=False, + ) + def create_multiple( + self, + *, + device_ids: List[str], + allow_external_modification: Optional[bool] = None, + attempt_for_offline_device: Optional[bool] = None, + behavior_when_code_cannot_be_shared: Optional[str] = None, + code: Optional[str] = None, + ends_at: Optional[str] = None, + is_external_modification_allowed: Optional[bool] = None, + name: Optional[str] = None, + prefer_native_scheduling: Optional[bool] = None, + preferred_code_length: Optional[float] = None, + starts_at: Optional[str] = None, + use_backup_access_code_pool: Optional[bool] = None, + ) -> List[AccessCode]: """Creates new `access codes `_ that share a common code across multiple devices. - + Users with more than one door lock in a property may want to create groups of linked access codes, all of which have the same code (PIN). For example, a short-term rental host may want to provide guests the same PIN for both a front door lock and a back door lock. - + If you specify a custom code, Seam assigns this custom code to each of the resulting access codes. However, in this case, Seam does not link these access codes together with a ``common_code_key``. That is, ``common_code_key`` remains null for these access codes. - + If you want to change these access codes that are not linked by a ``common_code_key``, you cannot use ``/access_codes/update_multiple``. However, you can update each of these access codes individually, using ``/access_codes/update``. - + See also `Creating and Updating Multiple Linked Access Codes `_. - + For granting a person access to a space, `Access Grants `_ are the default and recommended approach and work across both standalone smart locks and access systems. Use the lower-level Access Codes API directly only when you specifically need to manage individual PIN codes. :param device_ids: IDs of the devices for which you want to create the new access codes. :param allow_external_modification: Indicates whether `external modification `_ of the code is allowed. Default: ``false``. - :param attempt_for_offline_device: + :param attempt_for_offline_device: :param behavior_when_code_cannot_be_shared: Desired behavior if any device cannot share a code. If ``throw`` (default), no access codes will be created if any device cannot share a code. If ``create_random_code``, a random code will be created on devices that cannot share a code. @@ -416,11 +541,11 @@ def create_multiple(self, *, device_ids: List[str], allow_external_modification: :param is_external_modification_allowed: Indicates whether `external modification `_ of the code is allowed. Default: ``false``. :param name: Name of the new access code. Enables administrators and users to identify the access code easily, especially when there are numerous access codes. - + Note that the name provided on Seam is used to identify the code on Seam and is not necessarily the name that will appear in the lock provider's app or on the device. This is because lock providers may have constraints on names, such as length, uniqueness, or characters that can be used. In addition, some lock providers may break down names into components such as ``first_name`` and ``last_name``. - + To provide a consistent experience, Seam identifies the code on Seam by its name but may modify the name that appears on the lock provider's app or on the device. For example, Seam may add additional characters or truncate the name to meet provider constraints. - + To help your users identify codes set by Seam, Seam provides the name exactly as it appears on the lock provider's app or on the device as a separate property called ``appearance``. This is an object with a ``name`` property and, optionally, ``first_name`` and ``last_name`` properties (for providers that break down a name into components). :param prefer_native_scheduling: Indicates whether `native scheduling `_ should be used for time-bound codes when supported by the provider. Default: ``true``. @@ -443,13 +568,17 @@ def create_multiple(self, *, device_ids: List[str], allow_external_modification: if attempt_for_offline_device is not None: json_payload["attempt_for_offline_device"] = attempt_for_offline_device if behavior_when_code_cannot_be_shared is not None: - json_payload["behavior_when_code_cannot_be_shared"] = behavior_when_code_cannot_be_shared + json_payload["behavior_when_code_cannot_be_shared"] = ( + behavior_when_code_cannot_be_shared + ) if code is not None: json_payload["code"] = code if ends_at is not None: json_payload["ends_at"] = ends_at if is_external_modification_allowed is not None: - json_payload["is_external_modification_allowed"] = is_external_modification_allowed + json_payload["is_external_modification_allowed"] = ( + is_external_modification_allowed + ) if name is not None: json_payload["name"] = name if prefer_native_scheduling is not None: @@ -462,13 +591,17 @@ def create_multiple(self, *, device_ids: List[str], allow_external_modification: json_payload["use_backup_access_code_pool"] = use_backup_access_code_pool if not json_payload: - raise ValueError("At least one parameter is required for /access_codes/create_multiple") + raise ValueError( + "At least one parameter is required for /access_codes/create_multiple" + ) res = self.client.put("/access_codes/create_multiple", json=json_payload) return [AccessCode.from_dict(item) for item in res["access_codes"]] - @route_metadata(path="/access_codes/delete", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/access_codes/delete", has_required_parameters=True, has_pagination=False + ) def delete(self, *, access_code_id: str, device_id: Optional[str] = None) -> None: """Deletes an `access code `_. @@ -485,13 +618,19 @@ def delete(self, *, access_code_id: str, device_id: Optional[str] = None) -> Non params["device_id"] = device_id if not params: - raise ValueError("At least one parameter is required for /access_codes/delete") + raise ValueError( + "At least one parameter is required for /access_codes/delete" + ) self.client.delete("/access_codes/delete", params=params) return None - @route_metadata(path="/access_codes/generate_code", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/access_codes/generate_code", + has_required_parameters=True, + has_pagination=False, + ) def generate_code(self, *, device_id: str) -> AccessCode: """Generates a code for an `access code `_, given a device ID. @@ -506,16 +645,26 @@ def generate_code(self, *, device_id: str) -> AccessCode: params["device_id"] = device_id if not params: - raise ValueError("At least one parameter is required for /access_codes/generate_code") + raise ValueError( + "At least one parameter is required for /access_codes/generate_code" + ) res = self.client.get("/access_codes/generate_code", params=params) return AccessCode.from_dict(res["generated_code"]) - @route_metadata(path="/access_codes/get", has_required_parameters=True, has_pagination=False) - def get(self, *, access_code_id: Optional[str] = None, code: Optional[str] = None, device_id: Optional[str] = None) -> AccessCode: + @route_metadata( + path="/access_codes/get", has_required_parameters=True, has_pagination=False + ) + def get( + self, + *, + access_code_id: Optional[str] = None, + code: Optional[str] = None, + device_id: Optional[str] = None, + ) -> AccessCode: """Returns a specified `access code `_. - + You must specify either ``access_code_id`` or both ``device_id`` and ``code``. :param access_code_id: ID of the access code that you want to get. You must specify either ``access_code_id`` or both ``device_id`` and ``code``. @@ -543,10 +692,25 @@ def get(self, *, access_code_id: Optional[str] = None, code: Optional[str] = Non return AccessCode.from_dict(res["access_code"]) - @route_metadata(path="/access_codes/list", has_required_parameters=True, has_pagination=True) - def list(self, *, access_code_ids: Optional[List[str]] = None, access_grant_id: Optional[str] = None, access_grant_key: Optional[str] = None, access_method_id: Optional[str] = None, customer_key: Optional[str] = None, device_id: Optional[str] = None, limit: Optional[float] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, user_identifier_key: Optional[str] = None) -> List[AccessCode]: + @route_metadata( + path="/access_codes/list", has_required_parameters=True, has_pagination=True + ) + def list( + self, + *, + access_code_ids: Optional[List[str]] = None, + access_grant_id: Optional[str] = None, + access_grant_key: Optional[str] = None, + access_method_id: Optional[str] = None, + customer_key: Optional[str] = None, + device_id: Optional[str] = None, + limit: Optional[float] = None, + page_cursor: Optional[Union[str, Null]] = None, + search: Optional[str] = None, + user_identifier_key: Optional[str] = None, + ) -> List[AccessCode]: """Returns a list of all `access codes `_. - + Specify ``device_id``, ``access_code_ids``, ``access_method_id``, ``access_grant_id``, or ``access_grant_key``. :param access_code_ids: IDs of the access codes that you want to retrieve. Specify ``device_id``, ``access_code_ids``, ``access_method_id``, ``access_grant_id``, or ``access_grant_key``. @@ -572,46 +736,52 @@ def list(self, *, access_code_ids: Optional[List[str]] = None, access_grant_id: :returns: OK :raises ValueError: At least one parameter must be provided.""" - json_payload: Dict[str, Any] = {} + params: Dict[str, Any] = {} if access_code_ids is not None: - json_payload["access_code_ids"] = access_code_ids + params["access_code_ids"] = access_code_ids if access_grant_id is not None: - json_payload["access_grant_id"] = access_grant_id + params["access_grant_id"] = access_grant_id if access_grant_key is not None: - json_payload["access_grant_key"] = access_grant_key + params["access_grant_key"] = access_grant_key if access_method_id is not None: - json_payload["access_method_id"] = access_method_id + params["access_method_id"] = access_method_id if customer_key is not None: - json_payload["customer_key"] = customer_key + params["customer_key"] = customer_key if device_id is not None: - json_payload["device_id"] = device_id + params["device_id"] = device_id if limit is not None: - json_payload["limit"] = limit + params["limit"] = limit if page_cursor is not None: - json_payload["page_cursor"] = page_cursor + params["page_cursor"] = page_cursor if search is not None: - json_payload["search"] = search + params["search"] = search if user_identifier_key is not None: - json_payload["user_identifier_key"] = user_identifier_key + params["user_identifier_key"] = user_identifier_key - if not json_payload: - raise ValueError("At least one parameter is required for /access_codes/list") + if not params: + raise ValueError( + "At least one parameter is required for /access_codes/list" + ) - res = self.client.post("/access_codes/list", json=json_payload) + res = self.client.get("/access_codes/list", params=params) return [AccessCode.from_dict(item) for item in res["access_codes"]] - @route_metadata(path="/access_codes/pull_backup_access_code", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/access_codes/pull_backup_access_code", + has_required_parameters=True, + has_pagination=False, + ) def pull_backup_access_code(self, *, access_code_id: str) -> AccessCode: """Retrieves a backup access code for an `access code `_. See also `Managing Backup Access Codes `_. - + A backup access code pool is a collection of pre-programmed access codes stored on a device, ready for use. These codes are programmed in addition to the regular access codes on Seam, serving as a safety net for any issues with the primary codes. If there's ever a complication with a primary access code—be it due to intermittent connectivity, manual removal from a device, or provider outages—a backup code can be retrieved. Its end time can then be adjusted to align with the original code, facilitating seamless and uninterrupted access. - + You can pull a backup access code from the pool at any time. These backup codes are guaranteed to work immediately and automatically programmed to be removed from the device after the access code ends. - + You can only pull backup access codes for time-bound access codes. - + Before pulling a backup access code, make sure that the device's ``properties.supports_backup_access_code_pool`` is ``true``. Then, to activate the backup pool, set ``use_backup_access_code_pool`` to ``true`` when creating an access code. :param access_code_id: ID of the access code for which you want to pull a backup access code. @@ -625,16 +795,31 @@ def pull_backup_access_code(self, *, access_code_id: str) -> AccessCode: json_payload["access_code_id"] = access_code_id if not json_payload: - raise ValueError("At least one parameter is required for /access_codes/pull_backup_access_code") + raise ValueError( + "At least one parameter is required for /access_codes/pull_backup_access_code" + ) - res = self.client.post("/access_codes/pull_backup_access_code", json=json_payload) + res = self.client.post( + "/access_codes/pull_backup_access_code", json=json_payload + ) return AccessCode.from_dict(res["access_code"]) - @route_metadata(path="/access_codes/report_device_constraints", has_required_parameters=True, has_pagination=False) - def report_device_constraints(self, *, device_id: str, max_code_length: Optional[int] = None, min_code_length: Optional[int] = None, supported_code_lengths: Optional[List[float]] = None) -> None: + @route_metadata( + path="/access_codes/report_device_constraints", + has_required_parameters=True, + has_pagination=False, + ) + def report_device_constraints( + self, + *, + device_id: str, + max_code_length: Optional[int] = None, + min_code_length: Optional[int] = None, + supported_code_lengths: Optional[List[float]] = None, + ) -> None: """Enables you to report access code-related constraints for a device. Currently, supports reporting supported code length constraints for SmartThings devices. - + Specify either ``supported_code_lengths`` or ``min_code_length``/``max_code_length``. :param device_id: ID of the device for which you want to report constraints. @@ -658,23 +843,41 @@ def report_device_constraints(self, *, device_id: str, max_code_length: Optional json_payload["supported_code_lengths"] = supported_code_lengths if not json_payload: - raise ValueError("At least one parameter is required for /access_codes/report_device_constraints") + raise ValueError( + "At least one parameter is required for /access_codes/report_device_constraints" + ) self.client.post("/access_codes/report_device_constraints", json=json_payload) return None - @route_metadata(path="/access_codes/update", has_required_parameters=True, has_pagination=False) - def update(self, *, access_code_id: str, allow_external_modification: Optional[bool] = None, attempt_for_offline_device: Optional[bool] = None, code: Optional[str] = None, device_id: Optional[str] = None, ends_at: Optional[str] = None, is_external_modification_allowed: Optional[bool] = None, is_managed: Optional[bool] = None, name: Optional[str] = None, starts_at: Optional[str] = None, type: Optional[str] = None) -> None: + @route_metadata( + path="/access_codes/update", has_required_parameters=True, has_pagination=False + ) + def update( + self, + *, + access_code_id: str, + allow_external_modification: Optional[bool] = None, + attempt_for_offline_device: Optional[bool] = None, + code: Optional[str] = None, + device_id: Optional[str] = None, + ends_at: Optional[str] = None, + is_external_modification_allowed: Optional[bool] = None, + is_managed: Optional[bool] = None, + name: Optional[str] = None, + starts_at: Optional[str] = None, + type: Optional[str] = None, + ) -> None: """Updates a specified active or upcoming `access code `_. - + See also `Modifying Access Codes `_. :param access_code_id: ID of the access code that you want to update. :param allow_external_modification: Indicates whether `external modification `_ of the code is allowed. Default: ``false``. - :param attempt_for_offline_device: + :param attempt_for_offline_device: :param code: Code to be used for access. @@ -687,11 +890,11 @@ def update(self, *, access_code_id: str, allow_external_modification: Optional[b :param is_managed: Indicates whether the access code is managed through Seam. Note that to convert an unmanaged access code into a managed access code, use ``/access_codes/unmanaged/convert_to_managed``. :param name: Name of the new access code. Enables administrators and users to identify the access code easily, especially when there are numerous access codes. - + Note that the name provided on Seam is used to identify the code on Seam and is not necessarily the name that will appear in the lock provider's app or on the device. This is because lock providers may have constraints on names, such as length, uniqueness, or characters that can be used. In addition, some lock providers may break down names into components such as ``first_name`` and ``last_name``. - + To provide a consistent experience, Seam identifies the code on Seam by its name but may modify the name that appears on the lock provider's app or on the device. For example, Seam may add additional characters or truncate the name to meet provider constraints. - + To help your users identify codes set by Seam, Seam provides the name exactly as it appears on the lock provider's app or on the device as a separate property called ``appearance``. This is an object with a ``name`` property and, optionally, ``first_name`` and ``last_name`` properties (for providers that break down a name into components). :param starts_at: Date and time at which the validity of the new access code starts, in `ISO 8601 `_ format. @@ -714,7 +917,9 @@ def update(self, *, access_code_id: str, allow_external_modification: Optional[b if ends_at is not None: json_payload["ends_at"] = ends_at if is_external_modification_allowed is not None: - json_payload["is_external_modification_allowed"] = is_external_modification_allowed + json_payload["is_external_modification_allowed"] = ( + is_external_modification_allowed + ) if is_managed is not None: json_payload["is_managed"] = is_managed if name is not None: @@ -725,18 +930,31 @@ def update(self, *, access_code_id: str, allow_external_modification: Optional[b json_payload["type"] = type if not json_payload: - raise ValueError("At least one parameter is required for /access_codes/update") + raise ValueError( + "At least one parameter is required for /access_codes/update" + ) self.client.put("/access_codes/update", json=json_payload) return None - @route_metadata(path="/access_codes/update_multiple", has_required_parameters=True, has_pagination=False) - def update_multiple(self, *, common_code_key: str, ends_at: Optional[str] = None, name: Optional[str] = None, starts_at: Optional[str] = None) -> None: + @route_metadata( + path="/access_codes/update_multiple", + has_required_parameters=True, + has_pagination=False, + ) + def update_multiple( + self, + *, + common_code_key: str, + ends_at: Optional[str] = None, + name: Optional[str] = None, + starts_at: Optional[str] = None, + ) -> None: """Updates `access codes `_ that share a common code across multiple devices. - + Specify the ``common_code_key`` to identify the set of access codes that you want to update. - + See also `Update Linked Access Codes `_. :param common_code_key: Key that links the group of access codes, assigned on creation by ``/access_codes/create_multiple``. @@ -744,11 +962,11 @@ def update_multiple(self, *, common_code_key: str, ends_at: Optional[str] = None :param ends_at: Date and time at which the validity of the new access code ends, in `ISO 8601 `_ format. Must be a time in the future and after ``starts_at``. :param name: Name of the new access code. Enables administrators and users to identify the access code easily, especially when there are numerous access codes. - + Note that the name provided on Seam is used to identify the code on Seam and is not necessarily the name that will appear in the lock provider's app or on the device. This is because lock providers may have constraints on names, such as length, uniqueness, or characters that can be used. In addition, some lock providers may break down names into components such as ``first_name`` and ``last_name``. - + To provide a consistent experience, Seam identifies the code on Seam by its name but may modify the name that appears on the lock provider's app or on the device. For example, Seam may add additional characters or truncate the name to meet provider constraints. - + To help your users identify codes set by Seam, Seam provides the name exactly as it appears on the lock provider's app or on the device as a separate property called ``appearance``. This is an object with a ``name`` property and, optionally, ``first_name`` and ``last_name`` properties (for providers that break down a name into components). :param starts_at: Date and time at which the validity of the new access code starts, in `ISO 8601 `_ format. @@ -766,7 +984,9 @@ def update_multiple(self, *, common_code_key: str, ends_at: Optional[str] = None json_payload["starts_at"] = starts_at if not json_payload: - raise ValueError("At least one parameter is required for /access_codes/update_multiple") + raise ValueError( + "At least one parameter is required for /access_codes/update_multiple" + ) self.client.patch("/access_codes/update_multiple", json=json_payload) diff --git a/seam/routes/access_codes_simulate.py b/seam/routes/access_codes_simulate.py index cd0592f9..98e486dd 100644 --- a/seam/routes/access_codes_simulate.py +++ b/seam/routes/access_codes_simulate.py @@ -3,13 +3,15 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (UnmanagedAccessCode) +from ..resources import UnmanagedAccessCode class AbstractAccessCodesSimulate(abc.ABC): @abc.abstractmethod - def create_unmanaged_access_code(self, *, code: str, device_id: str, name: str) -> UnmanagedAccessCode: + def create_unmanaged_access_code( + self, *, code: str, device_id: str, name: str + ) -> UnmanagedAccessCode: """Simulates the creation of an `unmanaged access code `_ in a `sandbox workspace `_. :param code: Code of the simulated unmanaged access code. @@ -29,8 +31,14 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/access_codes/simulate/create_unmanaged_access_code", has_required_parameters=True, has_pagination=False) - def create_unmanaged_access_code(self, *, code: str, device_id: str, name: str) -> UnmanagedAccessCode: + @route_metadata( + path="/access_codes/simulate/create_unmanaged_access_code", + has_required_parameters=True, + has_pagination=False, + ) + def create_unmanaged_access_code( + self, *, code: str, device_id: str, name: str + ) -> UnmanagedAccessCode: """Simulates the creation of an `unmanaged access code `_ in a `sandbox workspace `_. :param code: Code of the simulated unmanaged access code. @@ -52,8 +60,12 @@ def create_unmanaged_access_code(self, *, code: str, device_id: str, name: str) json_payload["name"] = name if not json_payload: - raise ValueError("At least one parameter is required for /access_codes/simulate/create_unmanaged_access_code") + raise ValueError( + "At least one parameter is required for /access_codes/simulate/create_unmanaged_access_code" + ) - res = self.client.post("/access_codes/simulate/create_unmanaged_access_code", json=json_payload) + res = self.client.post( + "/access_codes/simulate/create_unmanaged_access_code", json=json_payload + ) return UnmanagedAccessCode.from_dict(res["access_code"]) diff --git a/seam/routes/access_codes_unmanaged.py b/seam/routes/access_codes_unmanaged.py index 7f834113..8a6e67d4 100644 --- a/seam/routes/access_codes_unmanaged.py +++ b/seam/routes/access_codes_unmanaged.py @@ -3,17 +3,24 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (UnmanagedAccessCode) +from ..resources import UnmanagedAccessCode class AbstractAccessCodesUnmanaged(abc.ABC): @abc.abstractmethod - def convert_to_managed(self, *, access_code_id: str, allow_external_modification: Optional[bool] = None, force: Optional[bool] = None, is_external_modification_allowed: Optional[bool] = None) -> None: + def convert_to_managed( + self, + *, + access_code_id: str, + allow_external_modification: Optional[bool] = None, + force: Optional[bool] = None, + is_external_modification_allowed: Optional[bool] = None, + ) -> None: """Converts an `unmanaged access code `_ to an `access code managed through Seam `_. - + An unmanaged access code has a limited set of operations that you can perform on it. Once you convert an unmanaged access code to a managed access code, the full set of access code operations and lifecycle events becomes available for it. - + Note that not all device providers support converting an unmanaged access code to a managed access code. :param access_code_id: ID of the unmanaged access code that you want to convert to a managed access code. @@ -37,9 +44,15 @@ def delete(self, *, access_code_id: str) -> None: raise NotImplementedError() @abc.abstractmethod - def get(self, *, access_code_id: Optional[str] = None, code: Optional[str] = None, device_id: Optional[str] = None) -> UnmanagedAccessCode: + def get( + self, + *, + access_code_id: Optional[str] = None, + code: Optional[str] = None, + device_id: Optional[str] = None, + ) -> UnmanagedAccessCode: """Returns a specified `unmanaged access code `_. - + You must specify either ``access_code_id`` or both ``device_id`` and ``code``. :param access_code_id: ID of the unmanaged access code that you want to get. You must specify either ``access_code_id`` or both ``device_id`` and ``code``. @@ -54,7 +67,15 @@ def get(self, *, access_code_id: Optional[str] = None, code: Optional[str] = Non raise NotImplementedError() @abc.abstractmethod - def list(self, *, device_id: str, limit: Optional[float] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, user_identifier_key: Optional[str] = None) -> List[UnmanagedAccessCode]: + def list( + self, + *, + device_id: str, + limit: Optional[float] = None, + page_cursor: Optional[Union[str, Null]] = None, + search: Optional[str] = None, + user_identifier_key: Optional[str] = None, + ) -> List[UnmanagedAccessCode]: """Returns a list of all `unmanaged access codes `_. :param device_id: ID of the device for which you want to list unmanaged access codes. @@ -73,12 +94,20 @@ def list(self, *, device_id: str, limit: Optional[float] = None, page_cursor: Op raise NotImplementedError() @abc.abstractmethod - def update(self, *, access_code_id: str, is_managed: bool, allow_external_modification: Optional[bool] = None, force: Optional[bool] = None, is_external_modification_allowed: Optional[bool] = None) -> None: + def update( + self, + *, + access_code_id: str, + is_managed: bool, + allow_external_modification: Optional[bool] = None, + force: Optional[bool] = None, + is_external_modification_allowed: Optional[bool] = None, + ) -> None: """Updates a specified `unmanaged access code `_. :param access_code_id: ID of the unmanaged access code that you want to update. - :param is_managed: + :param is_managed: :param allow_external_modification: Indicates whether `external modification `_ of the code is allowed. @@ -95,12 +124,23 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/access_codes/unmanaged/convert_to_managed", has_required_parameters=True, has_pagination=False) - def convert_to_managed(self, *, access_code_id: str, allow_external_modification: Optional[bool] = None, force: Optional[bool] = None, is_external_modification_allowed: Optional[bool] = None) -> None: + @route_metadata( + path="/access_codes/unmanaged/convert_to_managed", + has_required_parameters=True, + has_pagination=False, + ) + def convert_to_managed( + self, + *, + access_code_id: str, + allow_external_modification: Optional[bool] = None, + force: Optional[bool] = None, + is_external_modification_allowed: Optional[bool] = None, + ) -> None: """Converts an `unmanaged access code `_ to an `access code managed through Seam `_. - + An unmanaged access code has a limited set of operations that you can perform on it. Once you convert an unmanaged access code to a managed access code, the full set of access code operations and lifecycle events becomes available for it. - + Note that not all device providers support converting an unmanaged access code to a managed access code. :param access_code_id: ID of the unmanaged access code that you want to convert to a managed access code. @@ -121,16 +161,26 @@ def convert_to_managed(self, *, access_code_id: str, allow_external_modification if force is not None: json_payload["force"] = force if is_external_modification_allowed is not None: - json_payload["is_external_modification_allowed"] = is_external_modification_allowed + json_payload["is_external_modification_allowed"] = ( + is_external_modification_allowed + ) if not json_payload: - raise ValueError("At least one parameter is required for /access_codes/unmanaged/convert_to_managed") + raise ValueError( + "At least one parameter is required for /access_codes/unmanaged/convert_to_managed" + ) - self.client.patch("/access_codes/unmanaged/convert_to_managed", json=json_payload) + self.client.patch( + "/access_codes/unmanaged/convert_to_managed", json=json_payload + ) return None - @route_metadata(path="/access_codes/unmanaged/delete", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/access_codes/unmanaged/delete", + has_required_parameters=True, + has_pagination=False, + ) def delete(self, *, access_code_id: str) -> None: """Deletes an `unmanaged access code `_. @@ -143,16 +193,28 @@ def delete(self, *, access_code_id: str) -> None: params["access_code_id"] = access_code_id if not params: - raise ValueError("At least one parameter is required for /access_codes/unmanaged/delete") + raise ValueError( + "At least one parameter is required for /access_codes/unmanaged/delete" + ) self.client.delete("/access_codes/unmanaged/delete", params=params) return None - @route_metadata(path="/access_codes/unmanaged/get", has_required_parameters=True, has_pagination=False) - def get(self, *, access_code_id: Optional[str] = None, code: Optional[str] = None, device_id: Optional[str] = None) -> UnmanagedAccessCode: + @route_metadata( + path="/access_codes/unmanaged/get", + has_required_parameters=True, + has_pagination=False, + ) + def get( + self, + *, + access_code_id: Optional[str] = None, + code: Optional[str] = None, + device_id: Optional[str] = None, + ) -> UnmanagedAccessCode: """Returns a specified `unmanaged access code `_. - + You must specify either ``access_code_id`` or both ``device_id`` and ``code``. :param access_code_id: ID of the unmanaged access code that you want to get. You must specify either ``access_code_id`` or both ``device_id`` and ``code``. @@ -174,14 +236,28 @@ def get(self, *, access_code_id: Optional[str] = None, code: Optional[str] = Non params["device_id"] = device_id if not params: - raise ValueError("At least one parameter is required for /access_codes/unmanaged/get") + raise ValueError( + "At least one parameter is required for /access_codes/unmanaged/get" + ) res = self.client.get("/access_codes/unmanaged/get", params=params) return UnmanagedAccessCode.from_dict(res["access_code"]) - @route_metadata(path="/access_codes/unmanaged/list", has_required_parameters=True, has_pagination=True) - def list(self, *, device_id: str, limit: Optional[float] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, user_identifier_key: Optional[str] = None) -> List[UnmanagedAccessCode]: + @route_metadata( + path="/access_codes/unmanaged/list", + has_required_parameters=True, + has_pagination=True, + ) + def list( + self, + *, + device_id: str, + limit: Optional[float] = None, + page_cursor: Optional[Union[str, Null]] = None, + search: Optional[str] = None, + user_identifier_key: Optional[str] = None, + ) -> List[UnmanagedAccessCode]: """Returns a list of all `unmanaged access codes `_. :param device_id: ID of the device for which you want to list unmanaged access codes. @@ -211,19 +287,33 @@ def list(self, *, device_id: str, limit: Optional[float] = None, page_cursor: Op params["user_identifier_key"] = user_identifier_key if not params: - raise ValueError("At least one parameter is required for /access_codes/unmanaged/list") + raise ValueError( + "At least one parameter is required for /access_codes/unmanaged/list" + ) res = self.client.get("/access_codes/unmanaged/list", params=params) return [UnmanagedAccessCode.from_dict(item) for item in res["access_codes"]] - @route_metadata(path="/access_codes/unmanaged/update", has_required_parameters=True, has_pagination=False) - def update(self, *, access_code_id: str, is_managed: bool, allow_external_modification: Optional[bool] = None, force: Optional[bool] = None, is_external_modification_allowed: Optional[bool] = None) -> None: + @route_metadata( + path="/access_codes/unmanaged/update", + has_required_parameters=True, + has_pagination=False, + ) + def update( + self, + *, + access_code_id: str, + is_managed: bool, + allow_external_modification: Optional[bool] = None, + force: Optional[bool] = None, + is_external_modification_allowed: Optional[bool] = None, + ) -> None: """Updates a specified `unmanaged access code `_. :param access_code_id: ID of the unmanaged access code that you want to update. - :param is_managed: + :param is_managed: :param allow_external_modification: Indicates whether `external modification `_ of the code is allowed. @@ -243,10 +333,14 @@ def update(self, *, access_code_id: str, is_managed: bool, allow_external_modifi if force is not None: json_payload["force"] = force if is_external_modification_allowed is not None: - json_payload["is_external_modification_allowed"] = is_external_modification_allowed + json_payload["is_external_modification_allowed"] = ( + is_external_modification_allowed + ) if not json_payload: - raise ValueError("At least one parameter is required for /access_codes/unmanaged/update") + raise ValueError( + "At least one parameter is required for /access_codes/unmanaged/update" + ) self.client.patch("/access_codes/unmanaged/update", json=json_payload) diff --git a/seam/routes/access_grants.py b/seam/routes/access_grants.py index 23627942..b8d320df 100644 --- a/seam/routes/access_grants.py +++ b/seam/routes/access_grants.py @@ -3,8 +3,11 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (AccessGrant,Batch) -from .access_grants_unmanaged import AbstractAccessGrantsUnmanaged, AccessGrantsUnmanaged +from ..resources import AccessGrant, Batch +from .access_grants_unmanaged import ( + AbstractAccessGrantsUnmanaged, + AccessGrantsUnmanaged, +) class AbstractAccessGrants(abc.ABC): @@ -15,10 +18,28 @@ def unmanaged(self) -> AbstractAccessGrantsUnmanaged: raise NotImplementedError() @abc.abstractmethod - def create(self, *, requested_access_methods: List[Dict[str, Any]], user_identity_id: Optional[str] = None, user_identity: Optional[Dict[str, Any]] = None, access_grant_key: Optional[str] = None, acs_entrance_ids: Optional[List[str]] = None, customization_profile_id: Optional[str] = None, device_ids: Optional[List[str]] = None, ends_at: Optional[Union[str, Null]] = None, location: Optional[Dict[str, Any]] = None, location_ids: Optional[List[str]] = None, name: Optional[Union[str, Null]] = None, reservation_key: Optional[str] = None, space_ids: Optional[List[str]] = None, space_keys: Optional[List[str]] = None, starts_at: Optional[str] = None) -> AccessGrant: + def create( + self, + *, + requested_access_methods: List[Dict[str, Any]], + user_identity_id: Optional[str] = None, + user_identity: Optional[Dict[str, Any]] = None, + access_grant_key: Optional[str] = None, + acs_entrance_ids: Optional[List[str]] = None, + customization_profile_id: Optional[str] = None, + device_ids: Optional[List[str]] = None, + ends_at: Optional[Union[str, Null]] = None, + location: Optional[Dict[str, Any]] = None, + location_ids: Optional[List[str]] = None, + name: Optional[Union[str, Null]] = None, + reservation_key: Optional[str] = None, + space_ids: Optional[List[str]] = None, + space_keys: Optional[List[str]] = None, + starts_at: Optional[str] = None, + ) -> AccessGrant: """Creates a new `Access Grant `_. Access Grants are the default and recommended way to grant a user access to any physical space, irrespective of the locking hardware. They work with both standalone smart locks (using ``device_ids``) and access control systems (using ``acs_entrance_ids`` or ``space_ids``), and can issue PIN codes, key cards, and mobile keys through a single request. - :param requested_access_methods: + :param requested_access_methods: :param user_identity_id: ID of user identity for whom access is being granted. @@ -63,7 +84,12 @@ def delete(self, *, access_grant_id: str) -> None: raise NotImplementedError() @abc.abstractmethod - def get(self, *, access_grant_id: Optional[str] = None, access_grant_key: Optional[str] = None) -> AccessGrant: + def get( + self, + *, + access_grant_id: Optional[str] = None, + access_grant_key: Optional[str] = None, + ) -> AccessGrant: """Get an Access Grant. :param access_grant_id: ID of Access Grant to get. @@ -76,16 +102,23 @@ def get(self, *, access_grant_id: Optional[str] = None, access_grant_key: Option raise NotImplementedError() @abc.abstractmethod - def get_related(self, *, access_grant_ids: Optional[List[str]] = None, access_grant_keys: Optional[List[str]] = None, exclude: Optional[List[str]] = None, include: Optional[List[str]] = None) -> Batch: + def get_related( + self, + *, + access_grant_ids: Optional[List[str]] = None, + access_grant_keys: Optional[List[str]] = None, + exclude: Optional[List[str]] = None, + include: Optional[List[str]] = None, + ) -> Batch: """Gets all related resources for one or more Access Grants. :param access_grant_ids: IDs of the access grants that you want to get along with their related resources. :param access_grant_keys: Keys of the access grants that you want to get along with their related resources. - :param exclude: + :param exclude: - :param include: + :param include: :returns: OK @@ -93,7 +126,23 @@ def get_related(self, *, access_grant_ids: Optional[List[str]] = None, access_gr raise NotImplementedError() @abc.abstractmethod - def list(self, *, access_code_id: Optional[str] = None, access_grant_ids: Optional[List[str]] = None, access_grant_key: Optional[Union[str, Null]] = None, acs_entrance_id: Optional[str] = None, acs_system_id: Optional[str] = None, customer_key: Optional[str] = None, device_id: Optional[str] = None, limit: Optional[float] = None, location_id: Optional[str] = None, page_cursor: Optional[Union[str, Null]] = None, reservation_key: Optional[str] = None, space_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> List[AccessGrant]: + def list( + self, + *, + access_code_id: Optional[str] = None, + access_grant_ids: Optional[List[str]] = None, + access_grant_key: Optional[Union[str, Null]] = None, + acs_entrance_id: Optional[str] = None, + acs_system_id: Optional[str] = None, + customer_key: Optional[str] = None, + device_id: Optional[str] = None, + limit: Optional[float] = None, + location_id: Optional[str] = None, + page_cursor: Optional[Union[str, Null]] = None, + reservation_key: Optional[str] = None, + space_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> List[AccessGrant]: """Gets an Access Grant. :param access_code_id: ID of the access code by which you want to filter the list of Access Grants. @@ -126,7 +175,9 @@ def list(self, *, access_code_id: Optional[str] = None, access_grant_ids: Option raise NotImplementedError() @abc.abstractmethod - def request_access_methods(self, *, access_grant_id: str, requested_access_methods: List[Dict[str, Any]]) -> AccessGrant: + def request_access_methods( + self, *, access_grant_id: str, requested_access_methods: List[Dict[str, Any]] + ) -> AccessGrant: """Adds additional requested access methods to an existing Access Grant. :param access_grant_id: ID of the Access Grant to add access methods to. @@ -139,7 +190,15 @@ def request_access_methods(self, *, access_grant_id: str, requested_access_metho raise NotImplementedError() @abc.abstractmethod - def update(self, *, access_grant_id: Optional[str] = None, access_grant_key: Optional[str] = None, ends_at: Optional[Union[str, Null]] = None, name: Optional[Union[str, Null]] = None, starts_at: Optional[str] = None) -> None: + def update( + self, + *, + access_grant_id: Optional[str] = None, + access_grant_key: Optional[str] = None, + ends_at: Optional[Union[str, Null]] = None, + name: Optional[Union[str, Null]] = None, + starts_at: Optional[str] = None, + ) -> None: """Updates an existing Access Grant's time window. :param access_grant_id: ID of the Access Grant to update. Provide either ``access_grant_id`` or ``access_grant_key``. @@ -166,11 +225,31 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): def unmanaged(self) -> AccessGrantsUnmanaged: return self._unmanaged - @route_metadata(path="/access_grants/create", has_required_parameters=True, has_pagination=False) - def create(self, *, requested_access_methods: List[Dict[str, Any]], user_identity_id: Optional[str] = None, user_identity: Optional[Dict[str, Any]] = None, access_grant_key: Optional[str] = None, acs_entrance_ids: Optional[List[str]] = None, customization_profile_id: Optional[str] = None, device_ids: Optional[List[str]] = None, ends_at: Optional[Union[str, Null]] = None, location: Optional[Dict[str, Any]] = None, location_ids: Optional[List[str]] = None, name: Optional[Union[str, Null]] = None, reservation_key: Optional[str] = None, space_ids: Optional[List[str]] = None, space_keys: Optional[List[str]] = None, starts_at: Optional[str] = None) -> AccessGrant: + @route_metadata( + path="/access_grants/create", has_required_parameters=True, has_pagination=False + ) + def create( + self, + *, + requested_access_methods: List[Dict[str, Any]], + user_identity_id: Optional[str] = None, + user_identity: Optional[Dict[str, Any]] = None, + access_grant_key: Optional[str] = None, + acs_entrance_ids: Optional[List[str]] = None, + customization_profile_id: Optional[str] = None, + device_ids: Optional[List[str]] = None, + ends_at: Optional[Union[str, Null]] = None, + location: Optional[Dict[str, Any]] = None, + location_ids: Optional[List[str]] = None, + name: Optional[Union[str, Null]] = None, + reservation_key: Optional[str] = None, + space_ids: Optional[List[str]] = None, + space_keys: Optional[List[str]] = None, + starts_at: Optional[str] = None, + ) -> AccessGrant: """Creates a new `Access Grant `_. Access Grants are the default and recommended way to grant a user access to any physical space, irrespective of the locking hardware. They work with both standalone smart locks (using ``device_ids``) and access control systems (using ``acs_entrance_ids`` or ``space_ids``), and can issue PIN codes, key cards, and mobile keys through a single request. - :param requested_access_methods: + :param requested_access_methods: :param user_identity_id: ID of user identity for whom access is being granted. @@ -237,13 +316,17 @@ def create(self, *, requested_access_methods: List[Dict[str, Any]], user_identit json_payload["starts_at"] = starts_at if not json_payload: - raise ValueError("At least one parameter is required for /access_grants/create") + raise ValueError( + "At least one parameter is required for /access_grants/create" + ) res = self.client.post("/access_grants/create", json=json_payload) return AccessGrant.from_dict(res["access_grant"]) - @route_metadata(path="/access_grants/delete", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/access_grants/delete", has_required_parameters=True, has_pagination=False + ) def delete(self, *, access_grant_id: str) -> None: """Delete an Access Grant. @@ -256,14 +339,23 @@ def delete(self, *, access_grant_id: str) -> None: params["access_grant_id"] = access_grant_id if not params: - raise ValueError("At least one parameter is required for /access_grants/delete") + raise ValueError( + "At least one parameter is required for /access_grants/delete" + ) self.client.delete("/access_grants/delete", params=params) return None - @route_metadata(path="/access_grants/get", has_required_parameters=True, has_pagination=False) - def get(self, *, access_grant_id: Optional[str] = None, access_grant_key: Optional[str] = None) -> AccessGrant: + @route_metadata( + path="/access_grants/get", has_required_parameters=True, has_pagination=False + ) + def get( + self, + *, + access_grant_id: Optional[str] = None, + access_grant_key: Optional[str] = None, + ) -> AccessGrant: """Get an Access Grant. :param access_grant_id: ID of Access Grant to get. @@ -281,47 +373,80 @@ def get(self, *, access_grant_id: Optional[str] = None, access_grant_key: Option params["access_grant_key"] = access_grant_key if not params: - raise ValueError("At least one parameter is required for /access_grants/get") + raise ValueError( + "At least one parameter is required for /access_grants/get" + ) res = self.client.get("/access_grants/get", params=params) return AccessGrant.from_dict(res["access_grant"]) - @route_metadata(path="/access_grants/get_related", has_required_parameters=True, has_pagination=False) - def get_related(self, *, access_grant_ids: Optional[List[str]] = None, access_grant_keys: Optional[List[str]] = None, exclude: Optional[List[str]] = None, include: Optional[List[str]] = None) -> Batch: + @route_metadata( + path="/access_grants/get_related", + has_required_parameters=True, + has_pagination=False, + ) + def get_related( + self, + *, + access_grant_ids: Optional[List[str]] = None, + access_grant_keys: Optional[List[str]] = None, + exclude: Optional[List[str]] = None, + include: Optional[List[str]] = None, + ) -> Batch: """Gets all related resources for one or more Access Grants. :param access_grant_ids: IDs of the access grants that you want to get along with their related resources. :param access_grant_keys: Keys of the access grants that you want to get along with their related resources. - :param exclude: + :param exclude: - :param include: + :param include: :returns: OK :raises ValueError: At least one parameter must be provided.""" - json_payload: Dict[str, Any] = {} + params: Dict[str, Any] = {} if access_grant_ids is not None: - json_payload["access_grant_ids"] = access_grant_ids + params["access_grant_ids"] = access_grant_ids if access_grant_keys is not None: - json_payload["access_grant_keys"] = access_grant_keys + params["access_grant_keys"] = access_grant_keys if exclude is not None: - json_payload["exclude"] = exclude + params["exclude"] = exclude if include is not None: - json_payload["include"] = include + params["include"] = include - if not json_payload: - raise ValueError("At least one parameter is required for /access_grants/get_related") + if not params: + raise ValueError( + "At least one parameter is required for /access_grants/get_related" + ) - res = self.client.post("/access_grants/get_related", json=json_payload) + res = self.client.get("/access_grants/get_related", params=params) return Batch.from_dict(res["batch"]) - @route_metadata(path="/access_grants/list", has_required_parameters=False, has_pagination=True) - def list(self, *, access_code_id: Optional[str] = None, access_grant_ids: Optional[List[str]] = None, access_grant_key: Optional[Union[str, Null]] = None, acs_entrance_id: Optional[str] = None, acs_system_id: Optional[str] = None, customer_key: Optional[str] = None, device_id: Optional[str] = None, limit: Optional[float] = None, location_id: Optional[str] = None, page_cursor: Optional[Union[str, Null]] = None, reservation_key: Optional[str] = None, space_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> List[AccessGrant]: + @route_metadata( + path="/access_grants/list", has_required_parameters=False, has_pagination=True + ) + def list( + self, + *, + access_code_id: Optional[str] = None, + access_grant_ids: Optional[List[str]] = None, + access_grant_key: Optional[Union[str, Null]] = None, + acs_entrance_id: Optional[str] = None, + acs_system_id: Optional[str] = None, + customer_key: Optional[str] = None, + device_id: Optional[str] = None, + limit: Optional[float] = None, + location_id: Optional[str] = None, + page_cursor: Optional[Union[str, Null]] = None, + reservation_key: Optional[str] = None, + space_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> List[AccessGrant]: """Gets an Access Grant. :param access_code_id: ID of the access code by which you want to filter the list of Access Grants. @@ -351,41 +476,47 @@ def list(self, *, access_code_id: Optional[str] = None, access_grant_ids: Option :param user_identity_id: ID of user identity by which you want to filter the list of Access Grants. :returns: OK""" - json_payload: Dict[str, Any] = {} + params: Dict[str, Any] = {} if access_code_id is not None: - json_payload["access_code_id"] = access_code_id + params["access_code_id"] = access_code_id if access_grant_ids is not None: - json_payload["access_grant_ids"] = access_grant_ids + params["access_grant_ids"] = access_grant_ids if access_grant_key is not None: - json_payload["access_grant_key"] = access_grant_key + params["access_grant_key"] = access_grant_key if acs_entrance_id is not None: - json_payload["acs_entrance_id"] = acs_entrance_id + params["acs_entrance_id"] = acs_entrance_id if acs_system_id is not None: - json_payload["acs_system_id"] = acs_system_id + params["acs_system_id"] = acs_system_id if customer_key is not None: - json_payload["customer_key"] = customer_key + params["customer_key"] = customer_key if device_id is not None: - json_payload["device_id"] = device_id + params["device_id"] = device_id if limit is not None: - json_payload["limit"] = limit + params["limit"] = limit if location_id is not None: - json_payload["location_id"] = location_id + params["location_id"] = location_id if page_cursor is not None: - json_payload["page_cursor"] = page_cursor + params["page_cursor"] = page_cursor if reservation_key is not None: - json_payload["reservation_key"] = reservation_key + params["reservation_key"] = reservation_key if space_id is not None: - json_payload["space_id"] = space_id + params["space_id"] = space_id if user_identity_id is not None: - json_payload["user_identity_id"] = user_identity_id + params["user_identity_id"] = user_identity_id - res = self.client.post("/access_grants/list", json=json_payload) + res = self.client.get("/access_grants/list", params=params) return [AccessGrant.from_dict(item) for item in res["access_grants"]] - @route_metadata(path="/access_grants/request_access_methods", has_required_parameters=True, has_pagination=False) - def request_access_methods(self, *, access_grant_id: str, requested_access_methods: List[Dict[str, Any]]) -> AccessGrant: + @route_metadata( + path="/access_grants/request_access_methods", + has_required_parameters=True, + has_pagination=False, + ) + def request_access_methods( + self, *, access_grant_id: str, requested_access_methods: List[Dict[str, Any]] + ) -> AccessGrant: """Adds additional requested access methods to an existing Access Grant. :param access_grant_id: ID of the Access Grant to add access methods to. @@ -403,14 +534,28 @@ def request_access_methods(self, *, access_grant_id: str, requested_access_metho json_payload["requested_access_methods"] = requested_access_methods if not json_payload: - raise ValueError("At least one parameter is required for /access_grants/request_access_methods") + raise ValueError( + "At least one parameter is required for /access_grants/request_access_methods" + ) - res = self.client.post("/access_grants/request_access_methods", json=json_payload) + res = self.client.post( + "/access_grants/request_access_methods", json=json_payload + ) return AccessGrant.from_dict(res["access_grant"]) - @route_metadata(path="/access_grants/update", has_required_parameters=True, has_pagination=False) - def update(self, *, access_grant_id: Optional[str] = None, access_grant_key: Optional[str] = None, ends_at: Optional[Union[str, Null]] = None, name: Optional[Union[str, Null]] = None, starts_at: Optional[str] = None) -> None: + @route_metadata( + path="/access_grants/update", has_required_parameters=True, has_pagination=False + ) + def update( + self, + *, + access_grant_id: Optional[str] = None, + access_grant_key: Optional[str] = None, + ends_at: Optional[Union[str, Null]] = None, + name: Optional[Union[str, Null]] = None, + starts_at: Optional[str] = None, + ) -> None: """Updates an existing Access Grant's time window. :param access_grant_id: ID of the Access Grant to update. Provide either ``access_grant_id`` or ``access_grant_key``. @@ -438,7 +583,9 @@ def update(self, *, access_grant_id: Optional[str] = None, access_grant_key: Opt json_payload["starts_at"] = starts_at if not json_payload: - raise ValueError("At least one parameter is required for /access_grants/update") + raise ValueError( + "At least one parameter is required for /access_grants/update" + ) self.client.patch("/access_grants/update", json=json_payload) diff --git a/seam/routes/access_grants_unmanaged.py b/seam/routes/access_grants_unmanaged.py index 2c1b4401..843709e8 100644 --- a/seam/routes/access_grants_unmanaged.py +++ b/seam/routes/access_grants_unmanaged.py @@ -3,7 +3,7 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (UnmanagedAccessGrant) +from ..resources import UnmanagedAccessGrant class AbstractAccessGrantsUnmanaged(abc.ABC): @@ -20,7 +20,16 @@ def get(self, *, access_grant_id: str) -> UnmanagedAccessGrant: raise NotImplementedError() @abc.abstractmethod - def list(self, *, acs_entrance_id: Optional[str] = None, acs_system_id: Optional[str] = None, limit: Optional[float] = None, page_cursor: Optional[Union[str, Null]] = None, reservation_key: Optional[str] = None, user_identity_id: Optional[str] = None) -> List[UnmanagedAccessGrant]: + def list( + self, + *, + acs_entrance_id: Optional[str] = None, + acs_system_id: Optional[str] = None, + limit: Optional[float] = None, + page_cursor: Optional[Union[str, Null]] = None, + reservation_key: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> List[UnmanagedAccessGrant]: """Gets unmanaged Access Grants (where is_managed = false). :param acs_entrance_id: ID of the entrance by which you want to filter the list of unmanaged Access Grants. @@ -39,11 +48,17 @@ def list(self, *, acs_entrance_id: Optional[str] = None, acs_system_id: Optional raise NotImplementedError() @abc.abstractmethod - def update(self, *, access_grant_id: str, is_managed: bool, access_grant_key: Optional[str] = None) -> None: + def update( + self, + *, + access_grant_id: str, + is_managed: bool, + access_grant_key: Optional[str] = None, + ) -> None: """Updates an unmanaged Access Grant to make it managed. - + This endpoint can only be used to convert unmanaged access grants to managed ones by setting ``is_managed`` to ``true``. It cannot be used to convert managed access grants back to unmanaged. - + When converting an unmanaged access grant to managed, all associated access methods will also be converted to managed. :param access_grant_id: ID of the unmanaged Access Grant to update. @@ -61,7 +76,11 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/access_grants/unmanaged/get", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/access_grants/unmanaged/get", + has_required_parameters=True, + has_pagination=False, + ) def get(self, *, access_grant_id: str) -> UnmanagedAccessGrant: """Get an unmanaged Access Grant (where is_managed = false). @@ -76,14 +95,29 @@ def get(self, *, access_grant_id: str) -> UnmanagedAccessGrant: params["access_grant_id"] = access_grant_id if not params: - raise ValueError("At least one parameter is required for /access_grants/unmanaged/get") + raise ValueError( + "At least one parameter is required for /access_grants/unmanaged/get" + ) res = self.client.get("/access_grants/unmanaged/get", params=params) return UnmanagedAccessGrant.from_dict(res["access_grant"]) - @route_metadata(path="/access_grants/unmanaged/list", has_required_parameters=False, has_pagination=True) - def list(self, *, acs_entrance_id: Optional[str] = None, acs_system_id: Optional[str] = None, limit: Optional[float] = None, page_cursor: Optional[Union[str, Null]] = None, reservation_key: Optional[str] = None, user_identity_id: Optional[str] = None) -> List[UnmanagedAccessGrant]: + @route_metadata( + path="/access_grants/unmanaged/list", + has_required_parameters=False, + has_pagination=True, + ) + def list( + self, + *, + acs_entrance_id: Optional[str] = None, + acs_system_id: Optional[str] = None, + limit: Optional[float] = None, + page_cursor: Optional[Union[str, Null]] = None, + reservation_key: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> List[UnmanagedAccessGrant]: """Gets unmanaged Access Grants (where is_managed = false). :param acs_entrance_id: ID of the entrance by which you want to filter the list of unmanaged Access Grants. @@ -118,12 +152,22 @@ def list(self, *, acs_entrance_id: Optional[str] = None, acs_system_id: Optional return [UnmanagedAccessGrant.from_dict(item) for item in res["access_grants"]] - @route_metadata(path="/access_grants/unmanaged/update", has_required_parameters=True, has_pagination=False) - def update(self, *, access_grant_id: str, is_managed: bool, access_grant_key: Optional[str] = None) -> None: + @route_metadata( + path="/access_grants/unmanaged/update", + has_required_parameters=True, + has_pagination=False, + ) + def update( + self, + *, + access_grant_id: str, + is_managed: bool, + access_grant_key: Optional[str] = None, + ) -> None: """Updates an unmanaged Access Grant to make it managed. - + This endpoint can only be used to convert unmanaged access grants to managed ones by setting ``is_managed`` to ``true``. It cannot be used to convert managed access grants back to unmanaged. - + When converting an unmanaged access grant to managed, all associated access methods will also be converted to managed. :param access_grant_id: ID of the unmanaged Access Grant to update. @@ -143,7 +187,9 @@ def update(self, *, access_grant_id: str, is_managed: bool, access_grant_key: Op json_payload["access_grant_key"] = access_grant_key if not json_payload: - raise ValueError("At least one parameter is required for /access_grants/unmanaged/update") + raise ValueError( + "At least one parameter is required for /access_grants/unmanaged/update" + ) self.client.patch("/access_grants/unmanaged/update", json=json_payload) diff --git a/seam/routes/access_methods.py b/seam/routes/access_methods.py index 83d0cf49..483462e6 100644 --- a/seam/routes/access_methods.py +++ b/seam/routes/access_methods.py @@ -3,8 +3,11 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (ActionAttempt,AccessMethod,Batch) -from .access_methods_unmanaged import AbstractAccessMethodsUnmanaged, AccessMethodsUnmanaged +from ..resources import ActionAttempt, AccessMethod, Batch +from .access_methods_unmanaged import ( + AbstractAccessMethodsUnmanaged, + AccessMethodsUnmanaged, +) from ..modules.action_attempts import resolve_action_attempt @@ -16,7 +19,13 @@ def unmanaged(self) -> AbstractAccessMethodsUnmanaged: raise NotImplementedError() @abc.abstractmethod - def assign_card(self, *, access_method_id: str, card_number: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + def assign_card( + self, + *, + access_method_id: str, + card_number: str, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Assigns a pre-registered card credential, identified by ``card_number``, to a card-mode access method. Use this endpoint for access systems that use pre-registered cards, where a physical card must be associated with an access method before it can be used for access. Assigning a card credential also triggers issuance of the access method. :param access_method_id: ID of the ``access_method`` to assign the credential to. @@ -31,7 +40,13 @@ def assign_card(self, *, access_method_id: str, card_number: str, wait_for_actio raise NotImplementedError() @abc.abstractmethod - def delete(self, *, access_method_id: Optional[str] = None, access_grant_id: Optional[str] = None, reservation_key: Optional[str] = None) -> None: + def delete( + self, + *, + access_method_id: Optional[str] = None, + access_grant_id: Optional[str] = None, + reservation_key: Optional[str] = None, + ) -> None: """Deletes an access method. :param access_method_id: ID of access method to delete. @@ -44,7 +59,13 @@ def delete(self, *, access_method_id: Optional[str] = None, access_grant_id: Opt raise NotImplementedError() @abc.abstractmethod - def encode(self, *, access_method_id: str, acs_encoder_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + def encode( + self, + *, + access_method_id: str, + acs_encoder_id: str, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Encodes an existing access method onto a plastic card placed on the specified `encoder `_. :param access_method_id: ID of the ``access_method`` to encode onto a card. @@ -70,14 +91,20 @@ def get(self, *, access_method_id: str) -> AccessMethod: raise NotImplementedError() @abc.abstractmethod - def get_related(self, *, access_method_ids: List[str], exclude: Optional[List[str]] = None, include: Optional[List[str]] = None) -> Batch: + def get_related( + self, + *, + access_method_ids: List[str], + exclude: Optional[List[str]] = None, + include: Optional[List[str]] = None, + ) -> Batch: """Gets all related resources for one or more Access Methods. :param access_method_ids: IDs of the access methods that you want to get along with their related resources. - :param exclude: + :param exclude: - :param include: + :param include: :returns: OK @@ -85,7 +112,18 @@ def get_related(self, *, access_method_ids: List[str], exclude: Optional[List[st raise NotImplementedError() @abc.abstractmethod - def list(self, *, access_code_id: Optional[str] = None, access_grant_id: Optional[str] = None, access_grant_key: Optional[str] = None, acs_entrance_id: Optional[str] = None, device_id: Optional[str] = None, limit: Optional[int] = None, page_cursor: Optional[Union[str, Null]] = None, space_id: Optional[str] = None) -> List[AccessMethod]: + def list( + self, + *, + access_code_id: Optional[str] = None, + access_grant_id: Optional[str] = None, + access_grant_key: Optional[str] = None, + acs_entrance_id: Optional[str] = None, + device_id: Optional[str] = None, + limit: Optional[int] = None, + page_cursor: Optional[Union[str, Null]] = None, + space_id: Optional[str] = None, + ) -> List[AccessMethod]: """Lists all access methods, usually filtered by Access Grant. :param access_code_id: ID of the access code by which to filter the returned access methods. Must be combined with ``access_grant_id``, ``access_grant_key``, or ``acs_entrance_id``. @@ -110,7 +148,13 @@ def list(self, *, access_code_id: Optional[str] = None, access_grant_id: Optiona raise NotImplementedError() @abc.abstractmethod - def unlock_door(self, *, access_method_id: str, acs_entrance_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + def unlock_door( + self, + *, + access_method_id: str, + acs_entrance_id: str, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Remotely unlocks a specified `entrance `_ using the cloud key credential associated with an access method. Returns an action attempt that tracks the progress of the unlock operation. :param access_method_id: ID of the cloud_key ``access_method`` to use for the unlock operation. @@ -135,8 +179,18 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): def unmanaged(self) -> AccessMethodsUnmanaged: return self._unmanaged - @route_metadata(path="/access_methods/assign_card", has_required_parameters=True, has_pagination=False) - def assign_card(self, *, access_method_id: str, card_number: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + @route_metadata( + path="/access_methods/assign_card", + has_required_parameters=True, + has_pagination=False, + ) + def assign_card( + self, + *, + access_method_id: str, + card_number: str, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Assigns a pre-registered card credential, identified by ``card_number``, to a card-mode access method. Use this endpoint for access systems that use pre-registered cards, where a physical card must be associated with an access method before it can be used for access. Assigning a card credential also triggers issuance of the access method. :param access_method_id: ID of the ``access_method`` to assign the credential to. @@ -156,7 +210,9 @@ def assign_card(self, *, access_method_id: str, card_number: str, wait_for_actio json_payload["card_number"] = card_number if not json_payload: - raise ValueError("At least one parameter is required for /access_methods/assign_card") + raise ValueError( + "At least one parameter is required for /access_methods/assign_card" + ) res = self.client.post("/access_methods/assign_card", json=json_payload) @@ -169,11 +225,21 @@ def assign_card(self, *, access_method_id: str, card_number: str, wait_for_actio return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt + wait_for_action_attempt=wait_for_action_attempt, ) - @route_metadata(path="/access_methods/delete", has_required_parameters=True, has_pagination=False) - def delete(self, *, access_method_id: Optional[str] = None, access_grant_id: Optional[str] = None, reservation_key: Optional[str] = None) -> None: + @route_metadata( + path="/access_methods/delete", + has_required_parameters=True, + has_pagination=False, + ) + def delete( + self, + *, + access_method_id: Optional[str] = None, + access_grant_id: Optional[str] = None, + reservation_key: Optional[str] = None, + ) -> None: """Deletes an access method. :param access_method_id: ID of access method to delete. @@ -193,14 +259,26 @@ def delete(self, *, access_method_id: Optional[str] = None, access_grant_id: Opt params["reservation_key"] = reservation_key if not params: - raise ValueError("At least one parameter is required for /access_methods/delete") + raise ValueError( + "At least one parameter is required for /access_methods/delete" + ) self.client.delete("/access_methods/delete", params=params) return None - @route_metadata(path="/access_methods/encode", has_required_parameters=True, has_pagination=False) - def encode(self, *, access_method_id: str, acs_encoder_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + @route_metadata( + path="/access_methods/encode", + has_required_parameters=True, + has_pagination=False, + ) + def encode( + self, + *, + access_method_id: str, + acs_encoder_id: str, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Encodes an existing access method onto a plastic card placed on the specified `encoder `_. :param access_method_id: ID of the ``access_method`` to encode onto a card. @@ -220,7 +298,9 @@ def encode(self, *, access_method_id: str, acs_encoder_id: str, wait_for_action_ json_payload["acs_encoder_id"] = acs_encoder_id if not json_payload: - raise ValueError("At least one parameter is required for /access_methods/encode") + raise ValueError( + "At least one parameter is required for /access_methods/encode" + ) res = self.client.post("/access_methods/encode", json=json_payload) @@ -233,10 +313,12 @@ def encode(self, *, access_method_id: str, acs_encoder_id: str, wait_for_action_ return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt + wait_for_action_attempt=wait_for_action_attempt, ) - @route_metadata(path="/access_methods/get", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/access_methods/get", has_required_parameters=True, has_pagination=False + ) def get(self, *, access_method_id: str) -> AccessMethod: """Gets an access method. @@ -251,43 +333,70 @@ def get(self, *, access_method_id: str) -> AccessMethod: params["access_method_id"] = access_method_id if not params: - raise ValueError("At least one parameter is required for /access_methods/get") + raise ValueError( + "At least one parameter is required for /access_methods/get" + ) res = self.client.get("/access_methods/get", params=params) return AccessMethod.from_dict(res["access_method"]) - @route_metadata(path="/access_methods/get_related", has_required_parameters=True, has_pagination=False) - def get_related(self, *, access_method_ids: List[str], exclude: Optional[List[str]] = None, include: Optional[List[str]] = None) -> Batch: + @route_metadata( + path="/access_methods/get_related", + has_required_parameters=True, + has_pagination=False, + ) + def get_related( + self, + *, + access_method_ids: List[str], + exclude: Optional[List[str]] = None, + include: Optional[List[str]] = None, + ) -> Batch: """Gets all related resources for one or more Access Methods. :param access_method_ids: IDs of the access methods that you want to get along with their related resources. - :param exclude: + :param exclude: - :param include: + :param include: :returns: OK :raises ValueError: At least one parameter must be provided.""" - json_payload: Dict[str, Any] = {} + params: Dict[str, Any] = {} if access_method_ids is not None: - json_payload["access_method_ids"] = access_method_ids + params["access_method_ids"] = access_method_ids if exclude is not None: - json_payload["exclude"] = exclude + params["exclude"] = exclude if include is not None: - json_payload["include"] = include + params["include"] = include - if not json_payload: - raise ValueError("At least one parameter is required for /access_methods/get_related") + if not params: + raise ValueError( + "At least one parameter is required for /access_methods/get_related" + ) - res = self.client.post("/access_methods/get_related", json=json_payload) + res = self.client.get("/access_methods/get_related", params=params) return Batch.from_dict(res["batch"]) - @route_metadata(path="/access_methods/list", has_required_parameters=True, has_pagination=True) - def list(self, *, access_code_id: Optional[str] = None, access_grant_id: Optional[str] = None, access_grant_key: Optional[str] = None, acs_entrance_id: Optional[str] = None, device_id: Optional[str] = None, limit: Optional[int] = None, page_cursor: Optional[Union[str, Null]] = None, space_id: Optional[str] = None) -> List[AccessMethod]: + @route_metadata( + path="/access_methods/list", has_required_parameters=True, has_pagination=True + ) + def list( + self, + *, + access_code_id: Optional[str] = None, + access_grant_id: Optional[str] = None, + access_grant_key: Optional[str] = None, + acs_entrance_id: Optional[str] = None, + device_id: Optional[str] = None, + limit: Optional[int] = None, + page_cursor: Optional[Union[str, Null]] = None, + space_id: Optional[str] = None, + ) -> List[AccessMethod]: """Lists all access methods, usually filtered by Access Grant. :param access_code_id: ID of the access code by which to filter the returned access methods. Must be combined with ``access_grant_id``, ``access_grant_key``, or ``acs_entrance_id``. @@ -329,14 +438,26 @@ def list(self, *, access_code_id: Optional[str] = None, access_grant_id: Optiona params["space_id"] = space_id if not params: - raise ValueError("At least one parameter is required for /access_methods/list") + raise ValueError( + "At least one parameter is required for /access_methods/list" + ) res = self.client.get("/access_methods/list", params=params) return [AccessMethod.from_dict(item) for item in res["access_methods"]] - @route_metadata(path="/access_methods/unlock_door", has_required_parameters=True, has_pagination=False) - def unlock_door(self, *, access_method_id: str, acs_entrance_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + @route_metadata( + path="/access_methods/unlock_door", + has_required_parameters=True, + has_pagination=False, + ) + def unlock_door( + self, + *, + access_method_id: str, + acs_entrance_id: str, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Remotely unlocks a specified `entrance `_ using the cloud key credential associated with an access method. Returns an action attempt that tracks the progress of the unlock operation. :param access_method_id: ID of the cloud_key ``access_method`` to use for the unlock operation. @@ -356,7 +477,9 @@ def unlock_door(self, *, access_method_id: str, acs_entrance_id: str, wait_for_a json_payload["acs_entrance_id"] = acs_entrance_id if not json_payload: - raise ValueError("At least one parameter is required for /access_methods/unlock_door") + raise ValueError( + "At least one parameter is required for /access_methods/unlock_door" + ) res = self.client.post("/access_methods/unlock_door", json=json_payload) @@ -369,5 +492,5 @@ def unlock_door(self, *, access_method_id: str, acs_entrance_id: str, wait_for_a return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt + wait_for_action_attempt=wait_for_action_attempt, ) diff --git a/seam/routes/access_methods_unmanaged.py b/seam/routes/access_methods_unmanaged.py index c2779354..b376eb86 100644 --- a/seam/routes/access_methods_unmanaged.py +++ b/seam/routes/access_methods_unmanaged.py @@ -3,7 +3,7 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (UnmanagedAccessMethod) +from ..resources import UnmanagedAccessMethod class AbstractAccessMethodsUnmanaged(abc.ABC): @@ -20,7 +20,14 @@ def get(self, *, access_method_id: str) -> UnmanagedAccessMethod: raise NotImplementedError() @abc.abstractmethod - def list(self, *, access_grant_id: str, acs_entrance_id: Optional[str] = None, device_id: Optional[str] = None, space_id: Optional[str] = None) -> List[UnmanagedAccessMethod]: + def list( + self, + *, + access_grant_id: str, + acs_entrance_id: Optional[str] = None, + device_id: Optional[str] = None, + space_id: Optional[str] = None, + ) -> List[UnmanagedAccessMethod]: """Lists all unmanaged access methods (where is_managed = false), usually filtered by Access Grant. :param access_grant_id: ID of Access Grant to list unmanaged access methods for. @@ -42,7 +49,11 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/access_methods/unmanaged/get", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/access_methods/unmanaged/get", + has_required_parameters=True, + has_pagination=False, + ) def get(self, *, access_method_id: str) -> UnmanagedAccessMethod: """Gets an unmanaged access method (where is_managed = false). @@ -57,14 +68,27 @@ def get(self, *, access_method_id: str) -> UnmanagedAccessMethod: params["access_method_id"] = access_method_id if not params: - raise ValueError("At least one parameter is required for /access_methods/unmanaged/get") + raise ValueError( + "At least one parameter is required for /access_methods/unmanaged/get" + ) res = self.client.get("/access_methods/unmanaged/get", params=params) return UnmanagedAccessMethod.from_dict(res["access_method"]) - @route_metadata(path="/access_methods/unmanaged/list", has_required_parameters=True, has_pagination=False) - def list(self, *, access_grant_id: str, acs_entrance_id: Optional[str] = None, device_id: Optional[str] = None, space_id: Optional[str] = None) -> List[UnmanagedAccessMethod]: + @route_metadata( + path="/access_methods/unmanaged/list", + has_required_parameters=True, + has_pagination=False, + ) + def list( + self, + *, + access_grant_id: str, + acs_entrance_id: Optional[str] = None, + device_id: Optional[str] = None, + space_id: Optional[str] = None, + ) -> List[UnmanagedAccessMethod]: """Lists all unmanaged access methods (where is_managed = false), usually filtered by Access Grant. :param access_grant_id: ID of Access Grant to list unmanaged access methods for. @@ -90,7 +114,9 @@ def list(self, *, access_grant_id: str, acs_entrance_id: Optional[str] = None, d params["space_id"] = space_id if not params: - raise ValueError("At least one parameter is required for /access_methods/unmanaged/list") + raise ValueError( + "At least one parameter is required for /access_methods/unmanaged/list" + ) res = self.client.get("/access_methods/unmanaged/list", params=params) diff --git a/seam/routes/acs_access_groups.py b/seam/routes/acs_access_groups.py index 58de3651..a01a1826 100644 --- a/seam/routes/acs_access_groups.py +++ b/seam/routes/acs_access_groups.py @@ -3,13 +3,19 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (AcsAccessGroup,AcsEntrance,AcsUser) +from ..resources import AcsAccessGroup, AcsEntrance, AcsUser class AbstractAcsAccessGroups(abc.ABC): @abc.abstractmethod - def add_user(self, *, acs_access_group_id: str, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: + def add_user( + self, + *, + acs_access_group_id: str, + acs_user_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> None: """Adds a specified `access system user `_ to a specified `access group `_. :param acs_access_group_id: ID of the access group to which you want to add an access system user. @@ -42,7 +48,14 @@ def get(self, *, acs_access_group_id: str) -> AcsAccessGroup: raise NotImplementedError() @abc.abstractmethod - def list(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, search: Optional[str] = None, user_identity_id: Optional[str] = None) -> List[AcsAccessGroup]: + def list( + self, + *, + acs_system_id: Optional[str] = None, + acs_user_id: Optional[str] = None, + search: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> List[AcsAccessGroup]: """Returns a list of all `access groups `_. :param acs_system_id: ID of the access system for which you want to retrieve all access groups. @@ -57,7 +70,9 @@ def list(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[str raise NotImplementedError() @abc.abstractmethod - def list_accessible_entrances(self, *, acs_access_group_id: str) -> List[AcsEntrance]: + def list_accessible_entrances( + self, *, acs_access_group_id: str + ) -> List[AcsEntrance]: """Returns a list of all accessible entrances for a specified `access group `_. :param acs_access_group_id: ID of the access group for which you want to retrieve all accessible entrances. @@ -79,7 +94,13 @@ def list_users(self, *, acs_access_group_id: str) -> List[AcsUser]: raise NotImplementedError() @abc.abstractmethod - def remove_user(self, *, acs_access_group_id: str, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: + def remove_user( + self, + *, + acs_access_group_id: str, + acs_user_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> None: """Removes a specified `access system user `_ from a specified `access group `_. :param acs_access_group_id: ID of the access group from which you want to remove an access system user. @@ -97,8 +118,18 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/acs/access_groups/add_user", has_required_parameters=True, has_pagination=False) - def add_user(self, *, acs_access_group_id: str, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: + @route_metadata( + path="/acs/access_groups/add_user", + has_required_parameters=True, + has_pagination=False, + ) + def add_user( + self, + *, + acs_access_group_id: str, + acs_user_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> None: """Adds a specified `access system user `_ to a specified `access group `_. :param acs_access_group_id: ID of the access group to which you want to add an access system user. @@ -118,13 +149,19 @@ def add_user(self, *, acs_access_group_id: str, acs_user_id: Optional[str] = Non json_payload["user_identity_id"] = user_identity_id if not json_payload: - raise ValueError("At least one parameter is required for /acs/access_groups/add_user") + raise ValueError( + "At least one parameter is required for /acs/access_groups/add_user" + ) self.client.put("/acs/access_groups/add_user", json=json_payload) return None - @route_metadata(path="/acs/access_groups/delete", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/acs/access_groups/delete", + has_required_parameters=True, + has_pagination=False, + ) def delete(self, *, acs_access_group_id: str) -> None: """Deletes a specified `access group `_. @@ -137,13 +174,19 @@ def delete(self, *, acs_access_group_id: str) -> None: params["acs_access_group_id"] = acs_access_group_id if not params: - raise ValueError("At least one parameter is required for /acs/access_groups/delete") + raise ValueError( + "At least one parameter is required for /acs/access_groups/delete" + ) self.client.delete("/acs/access_groups/delete", params=params) return None - @route_metadata(path="/acs/access_groups/get", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/acs/access_groups/get", + has_required_parameters=True, + has_pagination=False, + ) def get(self, *, acs_access_group_id: str) -> AcsAccessGroup: """Returns a specified `access group `_. @@ -158,14 +201,27 @@ def get(self, *, acs_access_group_id: str) -> AcsAccessGroup: params["acs_access_group_id"] = acs_access_group_id if not params: - raise ValueError("At least one parameter is required for /acs/access_groups/get") + raise ValueError( + "At least one parameter is required for /acs/access_groups/get" + ) res = self.client.get("/acs/access_groups/get", params=params) return AcsAccessGroup.from_dict(res["acs_access_group"]) - @route_metadata(path="/acs/access_groups/list", has_required_parameters=False, has_pagination=False) - def list(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, search: Optional[str] = None, user_identity_id: Optional[str] = None) -> List[AcsAccessGroup]: + @route_metadata( + path="/acs/access_groups/list", + has_required_parameters=False, + has_pagination=False, + ) + def list( + self, + *, + acs_system_id: Optional[str] = None, + acs_user_id: Optional[str] = None, + search: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> List[AcsAccessGroup]: """Returns a list of all `access groups `_. :param acs_system_id: ID of the access system for which you want to retrieve all access groups. @@ -192,8 +248,14 @@ def list(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[str return [AcsAccessGroup.from_dict(item) for item in res["acs_access_groups"]] - @route_metadata(path="/acs/access_groups/list_accessible_entrances", has_required_parameters=True, has_pagination=False) - def list_accessible_entrances(self, *, acs_access_group_id: str) -> List[AcsEntrance]: + @route_metadata( + path="/acs/access_groups/list_accessible_entrances", + has_required_parameters=True, + has_pagination=False, + ) + def list_accessible_entrances( + self, *, acs_access_group_id: str + ) -> List[AcsEntrance]: """Returns a list of all accessible entrances for a specified `access group `_. :param acs_access_group_id: ID of the access group for which you want to retrieve all accessible entrances. @@ -207,13 +269,21 @@ def list_accessible_entrances(self, *, acs_access_group_id: str) -> List[AcsEntr params["acs_access_group_id"] = acs_access_group_id if not params: - raise ValueError("At least one parameter is required for /acs/access_groups/list_accessible_entrances") + raise ValueError( + "At least one parameter is required for /acs/access_groups/list_accessible_entrances" + ) - res = self.client.get("/acs/access_groups/list_accessible_entrances", params=params) + res = self.client.get( + "/acs/access_groups/list_accessible_entrances", params=params + ) return [AcsEntrance.from_dict(item) for item in res["acs_entrances"]] - @route_metadata(path="/acs/access_groups/list_users", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/acs/access_groups/list_users", + has_required_parameters=True, + has_pagination=False, + ) def list_users(self, *, acs_access_group_id: str) -> List[AcsUser]: """Returns a list of all `access system users `_ in an `access group `_. @@ -228,14 +298,26 @@ def list_users(self, *, acs_access_group_id: str) -> List[AcsUser]: params["acs_access_group_id"] = acs_access_group_id if not params: - raise ValueError("At least one parameter is required for /acs/access_groups/list_users") + raise ValueError( + "At least one parameter is required for /acs/access_groups/list_users" + ) res = self.client.get("/acs/access_groups/list_users", params=params) return [AcsUser.from_dict(item) for item in res["acs_users"]] - @route_metadata(path="/acs/access_groups/remove_user", has_required_parameters=True, has_pagination=False) - def remove_user(self, *, acs_access_group_id: str, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: + @route_metadata( + path="/acs/access_groups/remove_user", + has_required_parameters=True, + has_pagination=False, + ) + def remove_user( + self, + *, + acs_access_group_id: str, + acs_user_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> None: """Removes a specified `access system user `_ from a specified `access group `_. :param acs_access_group_id: ID of the access group from which you want to remove an access system user. @@ -255,7 +337,9 @@ def remove_user(self, *, acs_access_group_id: str, acs_user_id: Optional[str] = params["user_identity_id"] = user_identity_id if not params: - raise ValueError("At least one parameter is required for /acs/access_groups/remove_user") + raise ValueError( + "At least one parameter is required for /acs/access_groups/remove_user" + ) self.client.delete("/acs/access_groups/remove_user", params=params) diff --git a/seam/routes/acs_credentials.py b/seam/routes/acs_credentials.py index c18e4783..ceff9409 100644 --- a/seam/routes/acs_credentials.py +++ b/seam/routes/acs_credentials.py @@ -3,13 +3,19 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (AcsCredential,AcsEntrance) +from ..resources import AcsCredential, AcsEntrance class AbstractAcsCredentials(abc.ABC): @abc.abstractmethod - def assign(self, *, acs_credential_id: str, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: + def assign( + self, + *, + acs_credential_id: str, + acs_user_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> None: """Assigns a specified `credential `_ to a specified `access system user `_. :param acs_credential_id: ID of the credential that you want to assign to an access system user. @@ -22,7 +28,23 @@ def assign(self, *, acs_credential_id: str, acs_user_id: Optional[str] = None, u raise NotImplementedError() @abc.abstractmethod - def create(self, *, access_method: str, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, allowed_acs_entrance_ids: Optional[List[str]] = None, assa_abloy_vostio_metadata: Optional[Dict[str, Any]] = None, code: Optional[str] = None, credential_manager_acs_system_id: Optional[str] = None, ends_at: Optional[str] = None, is_multi_phone_sync_credential: Optional[bool] = None, salto_space_metadata: Optional[Dict[str, Any]] = None, starts_at: Optional[str] = None, user_identity_id: Optional[str] = None, visionline_metadata: Optional[Dict[str, Any]] = None) -> AcsCredential: + def create( + self, + *, + access_method: str, + acs_system_id: Optional[str] = None, + acs_user_id: Optional[str] = None, + allowed_acs_entrance_ids: Optional[List[str]] = None, + assa_abloy_vostio_metadata: Optional[Dict[str, Any]] = None, + code: Optional[str] = None, + credential_manager_acs_system_id: Optional[str] = None, + ends_at: Optional[str] = None, + is_multi_phone_sync_credential: Optional[bool] = None, + salto_space_metadata: Optional[Dict[str, Any]] = None, + starts_at: Optional[str] = None, + user_identity_id: Optional[str] = None, + visionline_metadata: Optional[Dict[str, Any]] = None, + ) -> AcsCredential: """Creates a new `credential `_ for a specified `ACS user `_. For granting access, we recommend `Access Grants `_ instead: they create and manage the underlying credentials for you, across access systems and standalone smart locks alike. Use this low-level endpoint only when you need direct control over an individual ACS credential. :param access_method: Access method for the new credential. Supported values: ``code``, ``card``, ``mobile_key``, ``cloud_key``. @@ -77,7 +99,18 @@ def get(self, *, acs_credential_id: str) -> AcsCredential: raise NotImplementedError() @abc.abstractmethod - def list(self, *, acs_user_id: Optional[str] = None, acs_system_id: Optional[str] = None, user_identity_id: Optional[str] = None, created_before: Optional[str] = None, is_multi_phone_sync_credential: Optional[bool] = None, limit: Optional[float] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None) -> List[AcsCredential]: + def list( + self, + *, + acs_user_id: Optional[str] = None, + acs_system_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + created_before: Optional[str] = None, + is_multi_phone_sync_credential: Optional[bool] = None, + limit: Optional[float] = None, + page_cursor: Optional[Union[str, Null]] = None, + search: Optional[str] = None, + ) -> List[AcsCredential]: """Returns a list of all `credentials `_. :param acs_user_id: ID of the access system user for which you want to retrieve all credentials. @@ -111,7 +144,13 @@ def list_accessible_entrances(self, *, acs_credential_id: str) -> List[AcsEntran raise NotImplementedError() @abc.abstractmethod - def unassign(self, *, acs_credential_id: str, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: + def unassign( + self, + *, + acs_credential_id: str, + acs_user_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> None: """Unassigns a specified `credential `_ from a specified `access system user `_. :param acs_credential_id: ID of the credential that you want to unassign from an access system user. @@ -124,7 +163,13 @@ def unassign(self, *, acs_credential_id: str, acs_user_id: Optional[str] = None, raise NotImplementedError() @abc.abstractmethod - def update(self, *, acs_credential_id: str, code: Optional[str] = None, ends_at: Optional[str] = None) -> None: + def update( + self, + *, + acs_credential_id: str, + code: Optional[str] = None, + ends_at: Optional[str] = None, + ) -> None: """Updates the code and ends at date and time for a specified `credential `_. :param acs_credential_id: ID of the credential that you want to update. @@ -142,8 +187,18 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/acs/credentials/assign", has_required_parameters=True, has_pagination=False) - def assign(self, *, acs_credential_id: str, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: + @route_metadata( + path="/acs/credentials/assign", + has_required_parameters=True, + has_pagination=False, + ) + def assign( + self, + *, + acs_credential_id: str, + acs_user_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> None: """Assigns a specified `credential `_ to a specified `access system user `_. :param acs_credential_id: ID of the credential that you want to assign to an access system user. @@ -163,14 +218,36 @@ def assign(self, *, acs_credential_id: str, acs_user_id: Optional[str] = None, u json_payload["user_identity_id"] = user_identity_id if not json_payload: - raise ValueError("At least one parameter is required for /acs/credentials/assign") + raise ValueError( + "At least one parameter is required for /acs/credentials/assign" + ) self.client.patch("/acs/credentials/assign", json=json_payload) return None - @route_metadata(path="/acs/credentials/create", has_required_parameters=True, has_pagination=False) - def create(self, *, access_method: str, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, allowed_acs_entrance_ids: Optional[List[str]] = None, assa_abloy_vostio_metadata: Optional[Dict[str, Any]] = None, code: Optional[str] = None, credential_manager_acs_system_id: Optional[str] = None, ends_at: Optional[str] = None, is_multi_phone_sync_credential: Optional[bool] = None, salto_space_metadata: Optional[Dict[str, Any]] = None, starts_at: Optional[str] = None, user_identity_id: Optional[str] = None, visionline_metadata: Optional[Dict[str, Any]] = None) -> AcsCredential: + @route_metadata( + path="/acs/credentials/create", + has_required_parameters=True, + has_pagination=False, + ) + def create( + self, + *, + access_method: str, + acs_system_id: Optional[str] = None, + acs_user_id: Optional[str] = None, + allowed_acs_entrance_ids: Optional[List[str]] = None, + assa_abloy_vostio_metadata: Optional[Dict[str, Any]] = None, + code: Optional[str] = None, + credential_manager_acs_system_id: Optional[str] = None, + ends_at: Optional[str] = None, + is_multi_phone_sync_credential: Optional[bool] = None, + salto_space_metadata: Optional[Dict[str, Any]] = None, + starts_at: Optional[str] = None, + user_identity_id: Optional[str] = None, + visionline_metadata: Optional[Dict[str, Any]] = None, + ) -> AcsCredential: """Creates a new `credential `_ for a specified `ACS user `_. For granting access, we recommend `Access Grants `_ instead: they create and manage the underlying credentials for you, across access systems and standalone smart locks alike. Use this low-level endpoint only when you need direct control over an individual ACS credential. :param access_method: Access method for the new credential. Supported values: ``code``, ``card``, ``mobile_key``, ``cloud_key``. @@ -217,11 +294,15 @@ def create(self, *, access_method: str, acs_system_id: Optional[str] = None, acs if code is not None: json_payload["code"] = code if credential_manager_acs_system_id is not None: - json_payload["credential_manager_acs_system_id"] = credential_manager_acs_system_id + json_payload["credential_manager_acs_system_id"] = ( + credential_manager_acs_system_id + ) if ends_at is not None: json_payload["ends_at"] = ends_at if is_multi_phone_sync_credential is not None: - json_payload["is_multi_phone_sync_credential"] = is_multi_phone_sync_credential + json_payload["is_multi_phone_sync_credential"] = ( + is_multi_phone_sync_credential + ) if salto_space_metadata is not None: json_payload["salto_space_metadata"] = salto_space_metadata if starts_at is not None: @@ -232,13 +313,19 @@ def create(self, *, access_method: str, acs_system_id: Optional[str] = None, acs json_payload["visionline_metadata"] = visionline_metadata if not json_payload: - raise ValueError("At least one parameter is required for /acs/credentials/create") + raise ValueError( + "At least one parameter is required for /acs/credentials/create" + ) res = self.client.post("/acs/credentials/create", json=json_payload) return AcsCredential.from_dict(res["acs_credential"]) - @route_metadata(path="/acs/credentials/delete", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/acs/credentials/delete", + has_required_parameters=True, + has_pagination=False, + ) def delete(self, *, acs_credential_id: str) -> None: """Deletes a specified `credential `_. @@ -251,13 +338,17 @@ def delete(self, *, acs_credential_id: str) -> None: params["acs_credential_id"] = acs_credential_id if not params: - raise ValueError("At least one parameter is required for /acs/credentials/delete") + raise ValueError( + "At least one parameter is required for /acs/credentials/delete" + ) self.client.delete("/acs/credentials/delete", params=params) return None - @route_metadata(path="/acs/credentials/get", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/acs/credentials/get", has_required_parameters=True, has_pagination=False + ) def get(self, *, acs_credential_id: str) -> AcsCredential: """Returns a specified `credential `_. @@ -272,14 +363,29 @@ def get(self, *, acs_credential_id: str) -> AcsCredential: params["acs_credential_id"] = acs_credential_id if not params: - raise ValueError("At least one parameter is required for /acs/credentials/get") + raise ValueError( + "At least one parameter is required for /acs/credentials/get" + ) res = self.client.get("/acs/credentials/get", params=params) return AcsCredential.from_dict(res["acs_credential"]) - @route_metadata(path="/acs/credentials/list", has_required_parameters=False, has_pagination=True) - def list(self, *, acs_user_id: Optional[str] = None, acs_system_id: Optional[str] = None, user_identity_id: Optional[str] = None, created_before: Optional[str] = None, is_multi_phone_sync_credential: Optional[bool] = None, limit: Optional[float] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None) -> List[AcsCredential]: + @route_metadata( + path="/acs/credentials/list", has_required_parameters=False, has_pagination=True + ) + def list( + self, + *, + acs_user_id: Optional[str] = None, + acs_system_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + created_before: Optional[str] = None, + is_multi_phone_sync_credential: Optional[bool] = None, + limit: Optional[float] = None, + page_cursor: Optional[Union[str, Null]] = None, + search: Optional[str] = None, + ) -> List[AcsCredential]: """Returns a list of all `credentials `_. :param acs_user_id: ID of the access system user for which you want to retrieve all credentials. @@ -322,7 +428,11 @@ def list(self, *, acs_user_id: Optional[str] = None, acs_system_id: Optional[str return [AcsCredential.from_dict(item) for item in res["acs_credentials"]] - @route_metadata(path="/acs/credentials/list_accessible_entrances", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/acs/credentials/list_accessible_entrances", + has_required_parameters=True, + has_pagination=False, + ) def list_accessible_entrances(self, *, acs_credential_id: str) -> List[AcsEntrance]: """Returns a list of all `entrances `_ to which a `credential `_ grants access. @@ -337,14 +447,28 @@ def list_accessible_entrances(self, *, acs_credential_id: str) -> List[AcsEntran params["acs_credential_id"] = acs_credential_id if not params: - raise ValueError("At least one parameter is required for /acs/credentials/list_accessible_entrances") + raise ValueError( + "At least one parameter is required for /acs/credentials/list_accessible_entrances" + ) - res = self.client.get("/acs/credentials/list_accessible_entrances", params=params) + res = self.client.get( + "/acs/credentials/list_accessible_entrances", params=params + ) return [AcsEntrance.from_dict(item) for item in res["acs_entrances"]] - @route_metadata(path="/acs/credentials/unassign", has_required_parameters=True, has_pagination=False) - def unassign(self, *, acs_credential_id: str, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: + @route_metadata( + path="/acs/credentials/unassign", + has_required_parameters=True, + has_pagination=False, + ) + def unassign( + self, + *, + acs_credential_id: str, + acs_user_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> None: """Unassigns a specified `credential `_ from a specified `access system user `_. :param acs_credential_id: ID of the credential that you want to unassign from an access system user. @@ -364,14 +488,26 @@ def unassign(self, *, acs_credential_id: str, acs_user_id: Optional[str] = None, json_payload["user_identity_id"] = user_identity_id if not json_payload: - raise ValueError("At least one parameter is required for /acs/credentials/unassign") + raise ValueError( + "At least one parameter is required for /acs/credentials/unassign" + ) self.client.patch("/acs/credentials/unassign", json=json_payload) return None - @route_metadata(path="/acs/credentials/update", has_required_parameters=True, has_pagination=False) - def update(self, *, acs_credential_id: str, code: Optional[str] = None, ends_at: Optional[str] = None) -> None: + @route_metadata( + path="/acs/credentials/update", + has_required_parameters=True, + has_pagination=False, + ) + def update( + self, + *, + acs_credential_id: str, + code: Optional[str] = None, + ends_at: Optional[str] = None, + ) -> None: """Updates the code and ends at date and time for a specified `credential `_. :param acs_credential_id: ID of the credential that you want to update. @@ -391,7 +527,9 @@ def update(self, *, acs_credential_id: str, code: Optional[str] = None, ends_at: json_payload["ends_at"] = ends_at if not json_payload: - raise ValueError("At least one parameter is required for /acs/credentials/update") + raise ValueError( + "At least one parameter is required for /acs/credentials/update" + ) self.client.patch("/acs/credentials/update", json=json_payload) diff --git a/seam/routes/acs_encoders.py b/seam/routes/acs_encoders.py index db148a98..be2eb326 100644 --- a/seam/routes/acs_encoders.py +++ b/seam/routes/acs_encoders.py @@ -3,7 +3,7 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (ActionAttempt,AcsEncoder) +from ..resources import ActionAttempt, AcsEncoder from .acs_encoders_simulate import AbstractAcsEncodersSimulate, AcsEncodersSimulate from ..modules.action_attempts import resolve_action_attempt @@ -16,7 +16,14 @@ def simulate(self) -> AbstractAcsEncodersSimulate: raise NotImplementedError() @abc.abstractmethod - def encode_credential(self, *, acs_encoder_id: str, access_method_id: Optional[str] = None, acs_credential_id: Optional[str] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + def encode_credential( + self, + *, + acs_encoder_id: str, + access_method_id: Optional[str] = None, + acs_credential_id: Optional[str] = None, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Encodes an existing `credential `_ onto a plastic card placed on the specified `encoder `_. Either provide an ``acs_credential_id`` or an ``access_method_id`` :param acs_encoder_id: ID of the ``acs_encoder`` to use to encode the ``acs_credential``. @@ -44,7 +51,15 @@ def get(self, *, acs_encoder_id: str) -> AcsEncoder: raise NotImplementedError() @abc.abstractmethod - def list(self, *, acs_system_id: Optional[str] = None, acs_system_ids: Optional[List[str]] = None, acs_encoder_ids: Optional[List[str]] = None, limit: Optional[float] = None, page_cursor: Optional[Union[str, Null]] = None) -> List[AcsEncoder]: + def list( + self, + *, + acs_system_id: Optional[str] = None, + acs_system_ids: Optional[List[str]] = None, + acs_encoder_ids: Optional[List[str]] = None, + limit: Optional[float] = None, + page_cursor: Optional[Union[str, Null]] = None, + ) -> List[AcsEncoder]: """Returns a list of all `encoders `_. :param acs_system_id: ID of the access system for which you want to retrieve all encoders. @@ -61,7 +76,13 @@ def list(self, *, acs_system_id: Optional[str] = None, acs_system_ids: Optional[ raise NotImplementedError() @abc.abstractmethod - def scan_credential(self, *, acs_encoder_id: str, salto_ks_metadata: Optional[Dict[str, Any]] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + def scan_credential( + self, + *, + acs_encoder_id: str, + salto_ks_metadata: Optional[Dict[str, Any]] = None, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Scans an encoded `acs_credential `_ from a plastic card placed on the specified `encoder `_. :param acs_encoder_id: ID of the encoder to use for the scan. @@ -76,7 +97,15 @@ def scan_credential(self, *, acs_encoder_id: str, salto_ks_metadata: Optional[Di raise NotImplementedError() @abc.abstractmethod - def scan_to_assign_credential(self, *, acs_encoder_id: str, acs_user_id: Optional[str] = None, salto_ks_metadata: Optional[Dict[str, Any]] = None, user_identity_id: Optional[str] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + def scan_to_assign_credential( + self, + *, + acs_encoder_id: str, + acs_user_id: Optional[str] = None, + salto_ks_metadata: Optional[Dict[str, Any]] = None, + user_identity_id: Optional[str] = None, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Scans a physical card placed on the specified `encoder `_ and assigns the scanned credential to an ACS user. Provide either an ``acs_user_id`` or a ``user_identity_id``. :param acs_encoder_id: ID of the ``acs_encoder`` to use to scan the credential. @@ -105,8 +134,19 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): def simulate(self) -> AcsEncodersSimulate: return self._simulate - @route_metadata(path="/acs/encoders/encode_credential", has_required_parameters=True, has_pagination=False) - def encode_credential(self, *, acs_encoder_id: str, access_method_id: Optional[str] = None, acs_credential_id: Optional[str] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + @route_metadata( + path="/acs/encoders/encode_credential", + has_required_parameters=True, + has_pagination=False, + ) + def encode_credential( + self, + *, + acs_encoder_id: str, + access_method_id: Optional[str] = None, + acs_credential_id: Optional[str] = None, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Encodes an existing `credential `_ onto a plastic card placed on the specified `encoder `_. Either provide an ``acs_credential_id`` or an ``access_method_id`` :param acs_encoder_id: ID of the ``acs_encoder`` to use to encode the ``acs_credential``. @@ -130,7 +170,9 @@ def encode_credential(self, *, acs_encoder_id: str, access_method_id: Optional[s json_payload["acs_credential_id"] = acs_credential_id if not json_payload: - raise ValueError("At least one parameter is required for /acs/encoders/encode_credential") + raise ValueError( + "At least one parameter is required for /acs/encoders/encode_credential" + ) res = self.client.post("/acs/encoders/encode_credential", json=json_payload) @@ -143,10 +185,12 @@ def encode_credential(self, *, acs_encoder_id: str, access_method_id: Optional[s return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt + wait_for_action_attempt=wait_for_action_attempt, ) - @route_metadata(path="/acs/encoders/get", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/acs/encoders/get", has_required_parameters=True, has_pagination=False + ) def get(self, *, acs_encoder_id: str) -> AcsEncoder: """Returns a specified `encoder `_. @@ -167,8 +211,18 @@ def get(self, *, acs_encoder_id: str) -> AcsEncoder: return AcsEncoder.from_dict(res["acs_encoder"]) - @route_metadata(path="/acs/encoders/list", has_required_parameters=False, has_pagination=True) - def list(self, *, acs_system_id: Optional[str] = None, acs_system_ids: Optional[List[str]] = None, acs_encoder_ids: Optional[List[str]] = None, limit: Optional[float] = None, page_cursor: Optional[Union[str, Null]] = None) -> List[AcsEncoder]: + @route_metadata( + path="/acs/encoders/list", has_required_parameters=False, has_pagination=True + ) + def list( + self, + *, + acs_system_id: Optional[str] = None, + acs_system_ids: Optional[List[str]] = None, + acs_encoder_ids: Optional[List[str]] = None, + limit: Optional[float] = None, + page_cursor: Optional[Union[str, Null]] = None, + ) -> List[AcsEncoder]: """Returns a list of all `encoders `_. :param acs_system_id: ID of the access system for which you want to retrieve all encoders. @@ -182,25 +236,35 @@ def list(self, *, acs_system_id: Optional[str] = None, acs_system_ids: Optional[ :param page_cursor: Identifies the specific page of results to return, obtained from the previous page's ``next_page_cursor``. :returns: OK""" - json_payload: Dict[str, Any] = {} + params: Dict[str, Any] = {} if acs_system_id is not None: - json_payload["acs_system_id"] = acs_system_id + params["acs_system_id"] = acs_system_id if acs_system_ids is not None: - json_payload["acs_system_ids"] = acs_system_ids + params["acs_system_ids"] = acs_system_ids if acs_encoder_ids is not None: - json_payload["acs_encoder_ids"] = acs_encoder_ids + params["acs_encoder_ids"] = acs_encoder_ids if limit is not None: - json_payload["limit"] = limit + params["limit"] = limit if page_cursor is not None: - json_payload["page_cursor"] = page_cursor + params["page_cursor"] = page_cursor - res = self.client.post("/acs/encoders/list", json=json_payload) + res = self.client.get("/acs/encoders/list", params=params) return [AcsEncoder.from_dict(item) for item in res["acs_encoders"]] - @route_metadata(path="/acs/encoders/scan_credential", has_required_parameters=True, has_pagination=False) - def scan_credential(self, *, acs_encoder_id: str, salto_ks_metadata: Optional[Dict[str, Any]] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + @route_metadata( + path="/acs/encoders/scan_credential", + has_required_parameters=True, + has_pagination=False, + ) + def scan_credential( + self, + *, + acs_encoder_id: str, + salto_ks_metadata: Optional[Dict[str, Any]] = None, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Scans an encoded `acs_credential `_ from a plastic card placed on the specified `encoder `_. :param acs_encoder_id: ID of the encoder to use for the scan. @@ -220,7 +284,9 @@ def scan_credential(self, *, acs_encoder_id: str, salto_ks_metadata: Optional[Di json_payload["salto_ks_metadata"] = salto_ks_metadata if not json_payload: - raise ValueError("At least one parameter is required for /acs/encoders/scan_credential") + raise ValueError( + "At least one parameter is required for /acs/encoders/scan_credential" + ) res = self.client.post("/acs/encoders/scan_credential", json=json_payload) @@ -233,11 +299,23 @@ def scan_credential(self, *, acs_encoder_id: str, salto_ks_metadata: Optional[Di return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt + wait_for_action_attempt=wait_for_action_attempt, ) - @route_metadata(path="/acs/encoders/scan_to_assign_credential", has_required_parameters=True, has_pagination=False) - def scan_to_assign_credential(self, *, acs_encoder_id: str, acs_user_id: Optional[str] = None, salto_ks_metadata: Optional[Dict[str, Any]] = None, user_identity_id: Optional[str] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + @route_metadata( + path="/acs/encoders/scan_to_assign_credential", + has_required_parameters=True, + has_pagination=False, + ) + def scan_to_assign_credential( + self, + *, + acs_encoder_id: str, + acs_user_id: Optional[str] = None, + salto_ks_metadata: Optional[Dict[str, Any]] = None, + user_identity_id: Optional[str] = None, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Scans a physical card placed on the specified `encoder `_ and assigns the scanned credential to an ACS user. Provide either an ``acs_user_id`` or a ``user_identity_id``. :param acs_encoder_id: ID of the ``acs_encoder`` to use to scan the credential. @@ -265,9 +343,13 @@ def scan_to_assign_credential(self, *, acs_encoder_id: str, acs_user_id: Optiona json_payload["user_identity_id"] = user_identity_id if not json_payload: - raise ValueError("At least one parameter is required for /acs/encoders/scan_to_assign_credential") + raise ValueError( + "At least one parameter is required for /acs/encoders/scan_to_assign_credential" + ) - res = self.client.post("/acs/encoders/scan_to_assign_credential", json=json_payload) + res = self.client.post( + "/acs/encoders/scan_to_assign_credential", json=json_payload + ) wait_for_action_attempt = ( self.defaults.get("wait_for_action_attempt") @@ -278,5 +360,5 @@ def scan_to_assign_credential(self, *, acs_encoder_id: str, acs_user_id: Optiona return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt + wait_for_action_attempt=wait_for_action_attempt, ) diff --git a/seam/routes/acs_encoders_simulate.py b/seam/routes/acs_encoders_simulate.py index 62b0d6ee..099e8e4f 100644 --- a/seam/routes/acs_encoders_simulate.py +++ b/seam/routes/acs_encoders_simulate.py @@ -8,7 +8,13 @@ class AbstractAcsEncodersSimulate(abc.ABC): @abc.abstractmethod - def next_credential_encode_will_fail(self, *, acs_encoder_id: str, error_code: Optional[str] = None, acs_credential_id: Optional[str] = None) -> None: + def next_credential_encode_will_fail( + self, + *, + acs_encoder_id: str, + error_code: Optional[str] = None, + acs_credential_id: Optional[str] = None, + ) -> None: """Simulates that the next attempt to encode a `credential `_ using the specified `encoder `_ will fail. You can only perform this action within a `sandbox workspace `_. :param acs_encoder_id: ID of the ``acs_encoder`` that will be used in the next request to encode the ``acs_credential``. @@ -21,7 +27,9 @@ def next_credential_encode_will_fail(self, *, acs_encoder_id: str, error_code: O raise NotImplementedError() @abc.abstractmethod - def next_credential_encode_will_succeed(self, *, acs_encoder_id: str, scenario: Optional[str] = None) -> None: + def next_credential_encode_will_succeed( + self, *, acs_encoder_id: str, scenario: Optional[str] = None + ) -> None: """Simulates that the next attempt to encode a `credential `_ using the specified `encoder `_ will succeed. You can only perform this action within a `sandbox workspace `_. :param acs_encoder_id: ID of the ``acs_encoder`` that will be used in the next request to encode the ``acs_credential``. @@ -32,20 +40,32 @@ def next_credential_encode_will_succeed(self, *, acs_encoder_id: str, scenario: raise NotImplementedError() @abc.abstractmethod - def next_credential_scan_will_fail(self, *, acs_encoder_id: str, error_code: Optional[str] = None, acs_credential_id_on_seam: Optional[str] = None) -> None: + def next_credential_scan_will_fail( + self, + *, + acs_encoder_id: str, + error_code: Optional[str] = None, + acs_credential_id_on_seam: Optional[str] = None, + ) -> None: """Simulates that the next attempt to scan a `credential `_ using the specified `encoder `_ will fail. You can only perform this action within a `sandbox workspace `_. :param acs_encoder_id: ID of the ``acs_encoder`` that will fail to scan the ``acs_credential`` in the next request. - :param error_code: + :param error_code: - :param acs_credential_id_on_seam: + :param acs_credential_id_on_seam: :raises ValueError: At least one parameter must be provided.""" raise NotImplementedError() @abc.abstractmethod - def next_credential_scan_will_succeed(self, *, acs_encoder_id: str, acs_credential_id_on_seam: Optional[str] = None, scenario: Optional[str] = None) -> None: + def next_credential_scan_will_succeed( + self, + *, + acs_encoder_id: str, + acs_credential_id_on_seam: Optional[str] = None, + scenario: Optional[str] = None, + ) -> None: """Simulates that the next attempt to scan a `credential `_ using the specified `encoder `_ will succeed. You can only perform this action within a `sandbox workspace `_. :param acs_encoder_id: ID of the ``acs_encoder`` that will be used in the next request to scan the ``acs_credential``. @@ -63,8 +83,18 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/acs/encoders/simulate/next_credential_encode_will_fail", has_required_parameters=True, has_pagination=False) - def next_credential_encode_will_fail(self, *, acs_encoder_id: str, error_code: Optional[str] = None, acs_credential_id: Optional[str] = None) -> None: + @route_metadata( + path="/acs/encoders/simulate/next_credential_encode_will_fail", + has_required_parameters=True, + has_pagination=False, + ) + def next_credential_encode_will_fail( + self, + *, + acs_encoder_id: str, + error_code: Optional[str] = None, + acs_credential_id: Optional[str] = None, + ) -> None: """Simulates that the next attempt to encode a `credential `_ using the specified `encoder `_ will fail. You can only perform this action within a `sandbox workspace `_. :param acs_encoder_id: ID of the ``acs_encoder`` that will be used in the next request to encode the ``acs_credential``. @@ -84,14 +114,24 @@ def next_credential_encode_will_fail(self, *, acs_encoder_id: str, error_code: O json_payload["acs_credential_id"] = acs_credential_id if not json_payload: - raise ValueError("At least one parameter is required for /acs/encoders/simulate/next_credential_encode_will_fail") + raise ValueError( + "At least one parameter is required for /acs/encoders/simulate/next_credential_encode_will_fail" + ) - self.client.post("/acs/encoders/simulate/next_credential_encode_will_fail", json=json_payload) + self.client.post( + "/acs/encoders/simulate/next_credential_encode_will_fail", json=json_payload + ) return None - @route_metadata(path="/acs/encoders/simulate/next_credential_encode_will_succeed", has_required_parameters=True, has_pagination=False) - def next_credential_encode_will_succeed(self, *, acs_encoder_id: str, scenario: Optional[str] = None) -> None: + @route_metadata( + path="/acs/encoders/simulate/next_credential_encode_will_succeed", + has_required_parameters=True, + has_pagination=False, + ) + def next_credential_encode_will_succeed( + self, *, acs_encoder_id: str, scenario: Optional[str] = None + ) -> None: """Simulates that the next attempt to encode a `credential `_ using the specified `encoder `_ will succeed. You can only perform this action within a `sandbox workspace `_. :param acs_encoder_id: ID of the ``acs_encoder`` that will be used in the next request to encode the ``acs_credential``. @@ -107,21 +147,36 @@ def next_credential_encode_will_succeed(self, *, acs_encoder_id: str, scenario: json_payload["scenario"] = scenario if not json_payload: - raise ValueError("At least one parameter is required for /acs/encoders/simulate/next_credential_encode_will_succeed") + raise ValueError( + "At least one parameter is required for /acs/encoders/simulate/next_credential_encode_will_succeed" + ) - self.client.post("/acs/encoders/simulate/next_credential_encode_will_succeed", json=json_payload) + self.client.post( + "/acs/encoders/simulate/next_credential_encode_will_succeed", + json=json_payload, + ) return None - @route_metadata(path="/acs/encoders/simulate/next_credential_scan_will_fail", has_required_parameters=True, has_pagination=False) - def next_credential_scan_will_fail(self, *, acs_encoder_id: str, error_code: Optional[str] = None, acs_credential_id_on_seam: Optional[str] = None) -> None: + @route_metadata( + path="/acs/encoders/simulate/next_credential_scan_will_fail", + has_required_parameters=True, + has_pagination=False, + ) + def next_credential_scan_will_fail( + self, + *, + acs_encoder_id: str, + error_code: Optional[str] = None, + acs_credential_id_on_seam: Optional[str] = None, + ) -> None: """Simulates that the next attempt to scan a `credential `_ using the specified `encoder `_ will fail. You can only perform this action within a `sandbox workspace `_. :param acs_encoder_id: ID of the ``acs_encoder`` that will fail to scan the ``acs_credential`` in the next request. - :param error_code: + :param error_code: - :param acs_credential_id_on_seam: + :param acs_credential_id_on_seam: :raises ValueError: At least one parameter must be provided.""" json_payload: Dict[str, Any] = {} @@ -134,14 +189,28 @@ def next_credential_scan_will_fail(self, *, acs_encoder_id: str, error_code: Opt json_payload["acs_credential_id_on_seam"] = acs_credential_id_on_seam if not json_payload: - raise ValueError("At least one parameter is required for /acs/encoders/simulate/next_credential_scan_will_fail") + raise ValueError( + "At least one parameter is required for /acs/encoders/simulate/next_credential_scan_will_fail" + ) - self.client.post("/acs/encoders/simulate/next_credential_scan_will_fail", json=json_payload) + self.client.post( + "/acs/encoders/simulate/next_credential_scan_will_fail", json=json_payload + ) return None - @route_metadata(path="/acs/encoders/simulate/next_credential_scan_will_succeed", has_required_parameters=True, has_pagination=False) - def next_credential_scan_will_succeed(self, *, acs_encoder_id: str, acs_credential_id_on_seam: Optional[str] = None, scenario: Optional[str] = None) -> None: + @route_metadata( + path="/acs/encoders/simulate/next_credential_scan_will_succeed", + has_required_parameters=True, + has_pagination=False, + ) + def next_credential_scan_will_succeed( + self, + *, + acs_encoder_id: str, + acs_credential_id_on_seam: Optional[str] = None, + scenario: Optional[str] = None, + ) -> None: """Simulates that the next attempt to scan a `credential `_ using the specified `encoder `_ will succeed. You can only perform this action within a `sandbox workspace `_. :param acs_encoder_id: ID of the ``acs_encoder`` that will be used in the next request to scan the ``acs_credential``. @@ -161,8 +230,13 @@ def next_credential_scan_will_succeed(self, *, acs_encoder_id: str, acs_credenti json_payload["scenario"] = scenario if not json_payload: - raise ValueError("At least one parameter is required for /acs/encoders/simulate/next_credential_scan_will_succeed") - - self.client.post("/acs/encoders/simulate/next_credential_scan_will_succeed", json=json_payload) + raise ValueError( + "At least one parameter is required for /acs/encoders/simulate/next_credential_scan_will_succeed" + ) + + self.client.post( + "/acs/encoders/simulate/next_credential_scan_will_succeed", + json=json_payload, + ) return None diff --git a/seam/routes/acs_entrances.py b/seam/routes/acs_entrances.py index 043a91c4..92fd2bc1 100644 --- a/seam/routes/acs_entrances.py +++ b/seam/routes/acs_entrances.py @@ -3,7 +3,7 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (AcsEntrance,AcsCredential,ActionAttempt) +from ..resources import AcsEntrance, AcsCredential, ActionAttempt from ..modules.action_attempts import resolve_action_attempt @@ -21,7 +21,13 @@ def get(self, *, acs_entrance_id: str) -> AcsEntrance: raise NotImplementedError() @abc.abstractmethod - def grant_access(self, *, acs_entrance_id: str, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: + def grant_access( + self, + *, + acs_entrance_id: str, + acs_user_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> None: """Grants a specified `access system user `_ access to a specified `access system entrance `_. :param acs_entrance_id: ID of the entrance to which you want to grant an access system user access. @@ -34,7 +40,21 @@ def grant_access(self, *, acs_entrance_id: str, acs_user_id: Optional[str] = Non raise NotImplementedError() @abc.abstractmethod - def list(self, *, access_method_id: Optional[str] = None, acs_credential_id: Optional[str] = None, acs_entrance_ids: Optional[List[str]] = None, acs_system_id: Optional[str] = None, connected_account_id: Optional[str] = None, customer_key: Optional[str] = None, limit: Optional[int] = None, location_id: Optional[Union[str, Null]] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, space_id: Optional[str] = None) -> List[AcsEntrance]: + def list( + self, + *, + access_method_id: Optional[str] = None, + acs_credential_id: Optional[str] = None, + acs_entrance_ids: Optional[List[str]] = None, + acs_system_id: Optional[str] = None, + connected_account_id: Optional[str] = None, + customer_key: Optional[str] = None, + limit: Optional[int] = None, + location_id: Optional[Union[str, Null]] = None, + page_cursor: Optional[Union[str, Null]] = None, + search: Optional[str] = None, + space_id: Optional[str] = None, + ) -> List[AcsEntrance]: """Returns a list of all `access system entrances `_. :param access_method_id: ID of the access method for which you want to retrieve all entrances to which it grants access. @@ -63,7 +83,9 @@ def list(self, *, access_method_id: Optional[str] = None, acs_credential_id: Opt raise NotImplementedError() @abc.abstractmethod - def list_credentials_with_access(self, *, acs_entrance_id: str, include_if: Optional[List[str]] = None) -> List[AcsCredential]: + def list_credentials_with_access( + self, *, acs_entrance_id: str, include_if: Optional[List[str]] = None + ) -> List[AcsCredential]: """Returns a list of all `credentials `_ with access to a specified `entrance `_. :param acs_entrance_id: ID of the entrance for which you want to list all credentials that grant access. @@ -76,7 +98,13 @@ def list_credentials_with_access(self, *, acs_entrance_id: str, include_if: Opti raise NotImplementedError() @abc.abstractmethod - def unlock(self, *, acs_credential_id: str, acs_entrance_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + def unlock( + self, + *, + acs_credential_id: str, + acs_entrance_id: str, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Remotely unlocks a specified `entrance `_ using a cloud_key credential. Returns an action attempt that tracks the progress of the unlock operation. :param acs_credential_id: ID of the cloud_key credential to use for the unlock operation. @@ -96,7 +124,9 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/acs/entrances/get", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/acs/entrances/get", has_required_parameters=True, has_pagination=False + ) def get(self, *, acs_entrance_id: str) -> AcsEntrance: """Returns a specified `access system entrance `_. @@ -111,14 +141,26 @@ def get(self, *, acs_entrance_id: str) -> AcsEntrance: params["acs_entrance_id"] = acs_entrance_id if not params: - raise ValueError("At least one parameter is required for /acs/entrances/get") + raise ValueError( + "At least one parameter is required for /acs/entrances/get" + ) res = self.client.get("/acs/entrances/get", params=params) return AcsEntrance.from_dict(res["acs_entrance"]) - @route_metadata(path="/acs/entrances/grant_access", has_required_parameters=True, has_pagination=False) - def grant_access(self, *, acs_entrance_id: str, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: + @route_metadata( + path="/acs/entrances/grant_access", + has_required_parameters=True, + has_pagination=False, + ) + def grant_access( + self, + *, + acs_entrance_id: str, + acs_user_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> None: """Grants a specified `access system user `_ access to a specified `access system entrance `_. :param acs_entrance_id: ID of the entrance to which you want to grant an access system user access. @@ -138,14 +180,32 @@ def grant_access(self, *, acs_entrance_id: str, acs_user_id: Optional[str] = Non json_payload["user_identity_id"] = user_identity_id if not json_payload: - raise ValueError("At least one parameter is required for /acs/entrances/grant_access") + raise ValueError( + "At least one parameter is required for /acs/entrances/grant_access" + ) self.client.post("/acs/entrances/grant_access", json=json_payload) return None - @route_metadata(path="/acs/entrances/list", has_required_parameters=False, has_pagination=True) - def list(self, *, access_method_id: Optional[str] = None, acs_credential_id: Optional[str] = None, acs_entrance_ids: Optional[List[str]] = None, acs_system_id: Optional[str] = None, connected_account_id: Optional[str] = None, customer_key: Optional[str] = None, limit: Optional[int] = None, location_id: Optional[Union[str, Null]] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, space_id: Optional[str] = None) -> List[AcsEntrance]: + @route_metadata( + path="/acs/entrances/list", has_required_parameters=False, has_pagination=True + ) + def list( + self, + *, + access_method_id: Optional[str] = None, + acs_credential_id: Optional[str] = None, + acs_entrance_ids: Optional[List[str]] = None, + acs_system_id: Optional[str] = None, + connected_account_id: Optional[str] = None, + customer_key: Optional[str] = None, + limit: Optional[int] = None, + location_id: Optional[Union[str, Null]] = None, + page_cursor: Optional[Union[str, Null]] = None, + search: Optional[str] = None, + space_id: Optional[str] = None, + ) -> List[AcsEntrance]: """Returns a list of all `access system entrances `_. :param access_method_id: ID of the access method for which you want to retrieve all entrances to which it grants access. @@ -171,37 +231,43 @@ def list(self, *, access_method_id: Optional[str] = None, acs_credential_id: Opt :param space_id: ID of the space for which you want to list entrances. :returns: OK""" - json_payload: Dict[str, Any] = {} + params: Dict[str, Any] = {} if access_method_id is not None: - json_payload["access_method_id"] = access_method_id + params["access_method_id"] = access_method_id if acs_credential_id is not None: - json_payload["acs_credential_id"] = acs_credential_id + params["acs_credential_id"] = acs_credential_id if acs_entrance_ids is not None: - json_payload["acs_entrance_ids"] = acs_entrance_ids + params["acs_entrance_ids"] = acs_entrance_ids if acs_system_id is not None: - json_payload["acs_system_id"] = acs_system_id + params["acs_system_id"] = acs_system_id if connected_account_id is not None: - json_payload["connected_account_id"] = connected_account_id + params["connected_account_id"] = connected_account_id if customer_key is not None: - json_payload["customer_key"] = customer_key + params["customer_key"] = customer_key if limit is not None: - json_payload["limit"] = limit + params["limit"] = limit if location_id is not None: - json_payload["location_id"] = location_id + params["location_id"] = location_id if page_cursor is not None: - json_payload["page_cursor"] = page_cursor + params["page_cursor"] = page_cursor if search is not None: - json_payload["search"] = search + params["search"] = search if space_id is not None: - json_payload["space_id"] = space_id + params["space_id"] = space_id - res = self.client.post("/acs/entrances/list", json=json_payload) + res = self.client.get("/acs/entrances/list", params=params) return [AcsEntrance.from_dict(item) for item in res["acs_entrances"]] - @route_metadata(path="/acs/entrances/list_credentials_with_access", has_required_parameters=True, has_pagination=False) - def list_credentials_with_access(self, *, acs_entrance_id: str, include_if: Optional[List[str]] = None) -> List[AcsCredential]: + @route_metadata( + path="/acs/entrances/list_credentials_with_access", + has_required_parameters=True, + has_pagination=False, + ) + def list_credentials_with_access( + self, *, acs_entrance_id: str, include_if: Optional[List[str]] = None + ) -> List[AcsCredential]: """Returns a list of all `credentials `_ with access to a specified `entrance `_. :param acs_entrance_id: ID of the entrance for which you want to list all credentials that grant access. @@ -211,22 +277,34 @@ def list_credentials_with_access(self, *, acs_entrance_id: str, include_if: Opti :returns: OK :raises ValueError: At least one parameter must be provided.""" - json_payload: Dict[str, Any] = {} + params: Dict[str, Any] = {} if acs_entrance_id is not None: - json_payload["acs_entrance_id"] = acs_entrance_id + params["acs_entrance_id"] = acs_entrance_id if include_if is not None: - json_payload["include_if"] = include_if + params["include_if"] = include_if - if not json_payload: - raise ValueError("At least one parameter is required for /acs/entrances/list_credentials_with_access") + if not params: + raise ValueError( + "At least one parameter is required for /acs/entrances/list_credentials_with_access" + ) - res = self.client.post("/acs/entrances/list_credentials_with_access", json=json_payload) + res = self.client.get( + "/acs/entrances/list_credentials_with_access", params=params + ) return [AcsCredential.from_dict(item) for item in res["acs_credentials"]] - @route_metadata(path="/acs/entrances/unlock", has_required_parameters=True, has_pagination=False) - def unlock(self, *, acs_credential_id: str, acs_entrance_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + @route_metadata( + path="/acs/entrances/unlock", has_required_parameters=True, has_pagination=False + ) + def unlock( + self, + *, + acs_credential_id: str, + acs_entrance_id: str, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Remotely unlocks a specified `entrance `_ using a cloud_key credential. Returns an action attempt that tracks the progress of the unlock operation. :param acs_credential_id: ID of the cloud_key credential to use for the unlock operation. @@ -246,7 +324,9 @@ def unlock(self, *, acs_credential_id: str, acs_entrance_id: str, wait_for_actio json_payload["acs_entrance_id"] = acs_entrance_id if not json_payload: - raise ValueError("At least one parameter is required for /acs/entrances/unlock") + raise ValueError( + "At least one parameter is required for /acs/entrances/unlock" + ) res = self.client.post("/acs/entrances/unlock", json=json_payload) @@ -259,5 +339,5 @@ def unlock(self, *, acs_credential_id: str, acs_entrance_id: str, wait_for_actio return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt + wait_for_action_attempt=wait_for_action_attempt, ) diff --git a/seam/routes/acs_systems.py b/seam/routes/acs_systems.py index a1a6cea4..767c062f 100644 --- a/seam/routes/acs_systems.py +++ b/seam/routes/acs_systems.py @@ -3,7 +3,7 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (AcsSystem) +from ..resources import AcsSystem class AbstractAcsSystems(abc.ABC): @@ -20,9 +20,15 @@ def get(self, *, acs_system_id: str) -> AcsSystem: raise NotImplementedError() @abc.abstractmethod - def list(self, *, connected_account_id: Optional[str] = None, customer_key: Optional[str] = None, search: Optional[str] = None) -> List[AcsSystem]: + def list( + self, + *, + connected_account_id: Optional[str] = None, + customer_key: Optional[str] = None, + search: Optional[str] = None, + ) -> List[AcsSystem]: """Returns a list of all `access systems `_. - + To filter the list of returned access systems by a specific connected account ID, include the ``connected_account_id`` in the request body. If you omit the ``connected_account_id`` parameter, the response includes all access systems connected to your workspace. :param connected_account_id: ID of the connected account by which you want to filter the list of access systems. @@ -35,9 +41,11 @@ def list(self, *, connected_account_id: Optional[str] = None, customer_key: Opti raise NotImplementedError() @abc.abstractmethod - def list_compatible_credential_manager_acs_systems(self, *, acs_system_id: str) -> List[AcsSystem]: + def list_compatible_credential_manager_acs_systems( + self, *, acs_system_id: str + ) -> List[AcsSystem]: """Returns a list of all credential manager systems that are compatible with a specified `access system `_. - + Specify the access system for which you want to retrieve all compatible credential manager systems by including the corresponding ``acs_system_id`` in the request body. :param acs_system_id: ID of the access system for which you want to retrieve all compatible credential manager systems. @@ -48,7 +56,13 @@ def list_compatible_credential_manager_acs_systems(self, *, acs_system_id: str) raise NotImplementedError() @abc.abstractmethod - def report_devices(self, *, acs_system_id: str, acs_encoders: Optional[List[Dict[str, Any]]] = None, acs_entrances: Optional[List[Dict[str, Any]]] = None) -> None: + def report_devices( + self, + *, + acs_system_id: str, + acs_encoders: Optional[List[Dict[str, Any]]] = None, + acs_entrances: Optional[List[Dict[str, Any]]] = None, + ) -> None: """Reports ACS system device status including encoders and entrances. :param acs_system_id: ID of the ACS system to report resources for @@ -66,7 +80,9 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/acs/systems/get", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/acs/systems/get", has_required_parameters=True, has_pagination=False + ) def get(self, *, acs_system_id: str) -> AcsSystem: """Returns a specified `access system `_. @@ -87,10 +103,18 @@ def get(self, *, acs_system_id: str) -> AcsSystem: return AcsSystem.from_dict(res["acs_system"]) - @route_metadata(path="/acs/systems/list", has_required_parameters=False, has_pagination=False) - def list(self, *, connected_account_id: Optional[str] = None, customer_key: Optional[str] = None, search: Optional[str] = None) -> List[AcsSystem]: + @route_metadata( + path="/acs/systems/list", has_required_parameters=False, has_pagination=False + ) + def list( + self, + *, + connected_account_id: Optional[str] = None, + customer_key: Optional[str] = None, + search: Optional[str] = None, + ) -> List[AcsSystem]: """Returns a list of all `access systems `_. - + To filter the list of returned access systems by a specific connected account ID, include the ``connected_account_id`` in the request body. If you omit the ``connected_account_id`` parameter, the response includes all access systems connected to your workspace. :param connected_account_id: ID of the connected account by which you want to filter the list of access systems. @@ -113,10 +137,16 @@ def list(self, *, connected_account_id: Optional[str] = None, customer_key: Opti return [AcsSystem.from_dict(item) for item in res["acs_systems"]] - @route_metadata(path="/acs/systems/list_compatible_credential_manager_acs_systems", has_required_parameters=True, has_pagination=False) - def list_compatible_credential_manager_acs_systems(self, *, acs_system_id: str) -> List[AcsSystem]: + @route_metadata( + path="/acs/systems/list_compatible_credential_manager_acs_systems", + has_required_parameters=True, + has_pagination=False, + ) + def list_compatible_credential_manager_acs_systems( + self, *, acs_system_id: str + ) -> List[AcsSystem]: """Returns a list of all credential manager systems that are compatible with a specified `access system `_. - + Specify the access system for which you want to retrieve all compatible credential manager systems by including the corresponding ``acs_system_id`` in the request body. :param acs_system_id: ID of the access system for which you want to retrieve all compatible credential manager systems. @@ -130,14 +160,28 @@ def list_compatible_credential_manager_acs_systems(self, *, acs_system_id: str) params["acs_system_id"] = acs_system_id if not params: - raise ValueError("At least one parameter is required for /acs/systems/list_compatible_credential_manager_acs_systems") + raise ValueError( + "At least one parameter is required for /acs/systems/list_compatible_credential_manager_acs_systems" + ) - res = self.client.get("/acs/systems/list_compatible_credential_manager_acs_systems", params=params) + res = self.client.get( + "/acs/systems/list_compatible_credential_manager_acs_systems", params=params + ) return [AcsSystem.from_dict(item) for item in res["acs_systems"]] - @route_metadata(path="/acs/systems/report_devices", has_required_parameters=True, has_pagination=False) - def report_devices(self, *, acs_system_id: str, acs_encoders: Optional[List[Dict[str, Any]]] = None, acs_entrances: Optional[List[Dict[str, Any]]] = None) -> None: + @route_metadata( + path="/acs/systems/report_devices", + has_required_parameters=True, + has_pagination=False, + ) + def report_devices( + self, + *, + acs_system_id: str, + acs_encoders: Optional[List[Dict[str, Any]]] = None, + acs_entrances: Optional[List[Dict[str, Any]]] = None, + ) -> None: """Reports ACS system device status including encoders and entrances. :param acs_system_id: ID of the ACS system to report resources for @@ -157,7 +201,9 @@ def report_devices(self, *, acs_system_id: str, acs_encoders: Optional[List[Dict json_payload["acs_entrances"] = acs_entrances if not json_payload: - raise ValueError("At least one parameter is required for /acs/systems/report_devices") + raise ValueError( + "At least one parameter is required for /acs/systems/report_devices" + ) self.client.post("/acs/systems/report_devices", json=json_payload) diff --git a/seam/routes/acs_users.py b/seam/routes/acs_users.py index f918c800..1da621eb 100644 --- a/seam/routes/acs_users.py +++ b/seam/routes/acs_users.py @@ -3,13 +3,15 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (AcsUser,AcsEntrance) +from ..resources import AcsUser, AcsEntrance class AbstractAcsUsers(abc.ABC): @abc.abstractmethod - def add_to_access_group(self, *, acs_access_group_id: str, acs_user_id: str) -> None: + def add_to_access_group( + self, *, acs_access_group_id: str, acs_user_id: str + ) -> None: """Adds a specified `access system user `_ to a specified `access group `_. :param acs_access_group_id: ID of the access group to which you want to add an access system user. @@ -20,7 +22,18 @@ def add_to_access_group(self, *, acs_access_group_id: str, acs_user_id: str) -> raise NotImplementedError() @abc.abstractmethod - def create(self, *, acs_system_id: str, full_name: str, access_schedule: Optional[Dict[str, Any]] = None, acs_access_group_ids: Optional[List[str]] = None, email: Optional[str] = None, email_address: Optional[str] = None, phone_number: Optional[str] = None, user_identity_id: Optional[str] = None) -> AcsUser: + def create( + self, + *, + acs_system_id: str, + full_name: str, + access_schedule: Optional[Dict[str, Any]] = None, + acs_access_group_ids: Optional[List[str]] = None, + email: Optional[str] = None, + email_address: Optional[str] = None, + phone_number: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> AcsUser: """Creates a new `access system user `_. :param acs_system_id: ID of the access system to which you want to add the new access system user. @@ -45,7 +58,13 @@ def create(self, *, acs_system_id: str, full_name: str, access_schedule: Optiona raise NotImplementedError() @abc.abstractmethod - def delete(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: + def delete( + self, + *, + acs_system_id: Optional[str] = None, + acs_user_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> None: """Deletes a specified `access system user `_ and invalidates the access system user's `credentials `_. :param acs_system_id: ID of the access system that you want to delete. You must provide acs_system_id with user_identity_id. @@ -58,7 +77,13 @@ def delete(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[s raise NotImplementedError() @abc.abstractmethod - def get(self, *, acs_user_id: Optional[str] = None, acs_system_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> AcsUser: + def get( + self, + *, + acs_user_id: Optional[str] = None, + acs_system_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> AcsUser: """Returns a specified `access system user `_. :param acs_user_id: ID of the access system user that you want to get. You can only provide acs_user_id or user_identity_id. @@ -73,7 +98,18 @@ def get(self, *, acs_user_id: Optional[str] = None, acs_system_id: Optional[str] raise NotImplementedError() @abc.abstractmethod - def list(self, *, acs_system_id: Optional[str] = None, created_before: Optional[str] = None, limit: Optional[int] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, user_identity_email_address: Optional[str] = None, user_identity_id: Optional[str] = None, user_identity_phone_number: Optional[str] = None) -> List[AcsUser]: + def list( + self, + *, + acs_system_id: Optional[str] = None, + created_before: Optional[str] = None, + limit: Optional[int] = None, + page_cursor: Optional[Union[str, Null]] = None, + search: Optional[str] = None, + user_identity_email_address: Optional[str] = None, + user_identity_id: Optional[str] = None, + user_identity_phone_number: Optional[str] = None, + ) -> List[AcsUser]: """Returns a list of all `access system users `_. :param acs_system_id: ID of the ``acs_system`` for which you want to retrieve all access system users. @@ -96,7 +132,13 @@ def list(self, *, acs_system_id: Optional[str] = None, created_before: Optional[ raise NotImplementedError() @abc.abstractmethod - def list_accessible_entrances(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> List[AcsEntrance]: + def list_accessible_entrances( + self, + *, + acs_system_id: Optional[str] = None, + acs_user_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> List[AcsEntrance]: """Lists the `entrances `_ to which a specified `access system user `_ has access. :param acs_system_id: ID of the access system for which you want to list accessible entrances. You can only provide acs_system_id with user_identity_id. @@ -111,7 +153,13 @@ def list_accessible_entrances(self, *, acs_system_id: Optional[str] = None, acs_ raise NotImplementedError() @abc.abstractmethod - def remove_from_access_group(self, *, acs_access_group_id: str, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: + def remove_from_access_group( + self, + *, + acs_access_group_id: str, + acs_user_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> None: """Removes a specified `access system user `_ from a specified `access group `_. :param acs_access_group_id: ID of the access group from which you want to remove an access system user. @@ -124,7 +172,13 @@ def remove_from_access_group(self, *, acs_access_group_id: str, acs_user_id: Opt raise NotImplementedError() @abc.abstractmethod - def revoke_access_to_all_entrances(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: + def revoke_access_to_all_entrances( + self, + *, + acs_system_id: Optional[str] = None, + acs_user_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> None: """Revokes access to all `entrances `_ for a specified `access system user `_. :param acs_system_id: ID of the access system for which you want to revoke access. You can only provide acs_system_id with user_identity_id. @@ -137,7 +191,13 @@ def revoke_access_to_all_entrances(self, *, acs_system_id: Optional[str] = None, raise NotImplementedError() @abc.abstractmethod - def suspend(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: + def suspend( + self, + *, + acs_system_id: Optional[str] = None, + acs_user_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> None: """`Suspends `_ a specified `access system user `_. Suspending an access system user revokes their access temporarily. To restore an access system user's access, you can `unsuspend `_ them. :param acs_system_id: ID of the access system that you want to suspend. You can only provide acs_user_id or the combination of acs_system_id and user_identity_id. @@ -150,7 +210,13 @@ def suspend(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[ raise NotImplementedError() @abc.abstractmethod - def unsuspend(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: + def unsuspend( + self, + *, + acs_system_id: Optional[str] = None, + acs_user_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> None: """`Unsuspends `_ a specified suspended `access system user `_. While `suspending an access system user `_ revokes their access temporarily, unsuspending the access system user restores their access. :param acs_system_id: ID of the access system of the user that you want to unsuspend. You can only provide acs_system_id with user_identity_id. @@ -163,7 +229,19 @@ def unsuspend(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optiona raise NotImplementedError() @abc.abstractmethod - def update(self, *, access_schedule: Optional[Union[Dict[str, Any], Null]] = None, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, email: Optional[str] = None, email_address: Optional[str] = None, full_name: Optional[str] = None, hid_acs_system_id: Optional[str] = None, phone_number: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: + def update( + self, + *, + access_schedule: Optional[Union[Dict[str, Any], Null]] = None, + acs_system_id: Optional[str] = None, + acs_user_id: Optional[str] = None, + email: Optional[str] = None, + email_address: Optional[str] = None, + full_name: Optional[str] = None, + hid_acs_system_id: Optional[str] = None, + phone_number: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> None: """Updates the properties of a specified `access system user `_. :param access_schedule: ``starts_at`` and ``ends_at`` timestamps for the access system user's access. If you specify an ``access_schedule``, you may include both ``starts_at`` and ``ends_at``. If you omit ``starts_at``, it defaults to the current time. ``ends_at`` is optional and must be a time in the future and after ``starts_at``. @@ -193,8 +271,14 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/acs/users/add_to_access_group", has_required_parameters=True, has_pagination=False) - def add_to_access_group(self, *, acs_access_group_id: str, acs_user_id: str) -> None: + @route_metadata( + path="/acs/users/add_to_access_group", + has_required_parameters=True, + has_pagination=False, + ) + def add_to_access_group( + self, *, acs_access_group_id: str, acs_user_id: str + ) -> None: """Adds a specified `access system user `_ to a specified `access group `_. :param acs_access_group_id: ID of the access group to which you want to add an access system user. @@ -210,14 +294,29 @@ def add_to_access_group(self, *, acs_access_group_id: str, acs_user_id: str) -> json_payload["acs_user_id"] = acs_user_id if not json_payload: - raise ValueError("At least one parameter is required for /acs/users/add_to_access_group") + raise ValueError( + "At least one parameter is required for /acs/users/add_to_access_group" + ) self.client.put("/acs/users/add_to_access_group", json=json_payload) return None - @route_metadata(path="/acs/users/create", has_required_parameters=True, has_pagination=False) - def create(self, *, acs_system_id: str, full_name: str, access_schedule: Optional[Dict[str, Any]] = None, acs_access_group_ids: Optional[List[str]] = None, email: Optional[str] = None, email_address: Optional[str] = None, phone_number: Optional[str] = None, user_identity_id: Optional[str] = None) -> AcsUser: + @route_metadata( + path="/acs/users/create", has_required_parameters=True, has_pagination=False + ) + def create( + self, + *, + acs_system_id: str, + full_name: str, + access_schedule: Optional[Dict[str, Any]] = None, + acs_access_group_ids: Optional[List[str]] = None, + email: Optional[str] = None, + email_address: Optional[str] = None, + phone_number: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> AcsUser: """Creates a new `access system user `_. :param acs_system_id: ID of the access system to which you want to add the new access system user. @@ -265,8 +364,16 @@ def create(self, *, acs_system_id: str, full_name: str, access_schedule: Optiona return AcsUser.from_dict(res["acs_user"]) - @route_metadata(path="/acs/users/delete", has_required_parameters=True, has_pagination=False) - def delete(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: + @route_metadata( + path="/acs/users/delete", has_required_parameters=True, has_pagination=False + ) + def delete( + self, + *, + acs_system_id: Optional[str] = None, + acs_user_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> None: """Deletes a specified `access system user `_ and invalidates the access system user's `credentials `_. :param acs_system_id: ID of the access system that you want to delete. You must provide acs_system_id with user_identity_id. @@ -292,8 +399,16 @@ def delete(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[s return None - @route_metadata(path="/acs/users/get", has_required_parameters=True, has_pagination=False) - def get(self, *, acs_user_id: Optional[str] = None, acs_system_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> AcsUser: + @route_metadata( + path="/acs/users/get", has_required_parameters=True, has_pagination=False + ) + def get( + self, + *, + acs_user_id: Optional[str] = None, + acs_system_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> AcsUser: """Returns a specified `access system user `_. :param acs_user_id: ID of the access system user that you want to get. You can only provide acs_user_id or user_identity_id. @@ -321,8 +436,21 @@ def get(self, *, acs_user_id: Optional[str] = None, acs_system_id: Optional[str] return AcsUser.from_dict(res["acs_user"]) - @route_metadata(path="/acs/users/list", has_required_parameters=False, has_pagination=True) - def list(self, *, acs_system_id: Optional[str] = None, created_before: Optional[str] = None, limit: Optional[int] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, user_identity_email_address: Optional[str] = None, user_identity_id: Optional[str] = None, user_identity_phone_number: Optional[str] = None) -> List[AcsUser]: + @route_metadata( + path="/acs/users/list", has_required_parameters=False, has_pagination=True + ) + def list( + self, + *, + acs_system_id: Optional[str] = None, + created_before: Optional[str] = None, + limit: Optional[int] = None, + page_cursor: Optional[Union[str, Null]] = None, + search: Optional[str] = None, + user_identity_email_address: Optional[str] = None, + user_identity_id: Optional[str] = None, + user_identity_phone_number: Optional[str] = None, + ) -> List[AcsUser]: """Returns a list of all `access system users `_. :param acs_system_id: ID of the ``acs_system`` for which you want to retrieve all access system users. @@ -365,8 +493,18 @@ def list(self, *, acs_system_id: Optional[str] = None, created_before: Optional[ return [AcsUser.from_dict(item) for item in res["acs_users"]] - @route_metadata(path="/acs/users/list_accessible_entrances", has_required_parameters=True, has_pagination=False) - def list_accessible_entrances(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> List[AcsEntrance]: + @route_metadata( + path="/acs/users/list_accessible_entrances", + has_required_parameters=True, + has_pagination=False, + ) + def list_accessible_entrances( + self, + *, + acs_system_id: Optional[str] = None, + acs_user_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> List[AcsEntrance]: """Lists the `entrances `_ to which a specified `access system user `_ has access. :param acs_system_id: ID of the access system for which you want to list accessible entrances. You can only provide acs_system_id with user_identity_id. @@ -388,14 +526,26 @@ def list_accessible_entrances(self, *, acs_system_id: Optional[str] = None, acs_ params["user_identity_id"] = user_identity_id if not params: - raise ValueError("At least one parameter is required for /acs/users/list_accessible_entrances") + raise ValueError( + "At least one parameter is required for /acs/users/list_accessible_entrances" + ) res = self.client.get("/acs/users/list_accessible_entrances", params=params) return [AcsEntrance.from_dict(item) for item in res["acs_entrances"]] - @route_metadata(path="/acs/users/remove_from_access_group", has_required_parameters=True, has_pagination=False) - def remove_from_access_group(self, *, acs_access_group_id: str, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: + @route_metadata( + path="/acs/users/remove_from_access_group", + has_required_parameters=True, + has_pagination=False, + ) + def remove_from_access_group( + self, + *, + acs_access_group_id: str, + acs_user_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> None: """Removes a specified `access system user `_ from a specified `access group `_. :param acs_access_group_id: ID of the access group from which you want to remove an access system user. @@ -415,14 +565,26 @@ def remove_from_access_group(self, *, acs_access_group_id: str, acs_user_id: Opt params["user_identity_id"] = user_identity_id if not params: - raise ValueError("At least one parameter is required for /acs/users/remove_from_access_group") + raise ValueError( + "At least one parameter is required for /acs/users/remove_from_access_group" + ) self.client.delete("/acs/users/remove_from_access_group", params=params) return None - @route_metadata(path="/acs/users/revoke_access_to_all_entrances", has_required_parameters=True, has_pagination=False) - def revoke_access_to_all_entrances(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: + @route_metadata( + path="/acs/users/revoke_access_to_all_entrances", + has_required_parameters=True, + has_pagination=False, + ) + def revoke_access_to_all_entrances( + self, + *, + acs_system_id: Optional[str] = None, + acs_user_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> None: """Revokes access to all `entrances `_ for a specified `access system user `_. :param acs_system_id: ID of the access system for which you want to revoke access. You can only provide acs_system_id with user_identity_id. @@ -442,14 +604,24 @@ def revoke_access_to_all_entrances(self, *, acs_system_id: Optional[str] = None, json_payload["user_identity_id"] = user_identity_id if not json_payload: - raise ValueError("At least one parameter is required for /acs/users/revoke_access_to_all_entrances") + raise ValueError( + "At least one parameter is required for /acs/users/revoke_access_to_all_entrances" + ) self.client.post("/acs/users/revoke_access_to_all_entrances", json=json_payload) return None - @route_metadata(path="/acs/users/suspend", has_required_parameters=True, has_pagination=False) - def suspend(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: + @route_metadata( + path="/acs/users/suspend", has_required_parameters=True, has_pagination=False + ) + def suspend( + self, + *, + acs_system_id: Optional[str] = None, + acs_user_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> None: """`Suspends `_ a specified `access system user `_. Suspending an access system user revokes their access temporarily. To restore an access system user's access, you can `unsuspend `_ them. :param acs_system_id: ID of the access system that you want to suspend. You can only provide acs_user_id or the combination of acs_system_id and user_identity_id. @@ -469,14 +641,24 @@ def suspend(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[ json_payload["user_identity_id"] = user_identity_id if not json_payload: - raise ValueError("At least one parameter is required for /acs/users/suspend") + raise ValueError( + "At least one parameter is required for /acs/users/suspend" + ) self.client.post("/acs/users/suspend", json=json_payload) return None - @route_metadata(path="/acs/users/unsuspend", has_required_parameters=True, has_pagination=False) - def unsuspend(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: + @route_metadata( + path="/acs/users/unsuspend", has_required_parameters=True, has_pagination=False + ) + def unsuspend( + self, + *, + acs_system_id: Optional[str] = None, + acs_user_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> None: """`Unsuspends `_ a specified suspended `access system user `_. While `suspending an access system user `_ revokes their access temporarily, unsuspending the access system user restores their access. :param acs_system_id: ID of the access system of the user that you want to unsuspend. You can only provide acs_system_id with user_identity_id. @@ -496,14 +678,30 @@ def unsuspend(self, *, acs_system_id: Optional[str] = None, acs_user_id: Optiona json_payload["user_identity_id"] = user_identity_id if not json_payload: - raise ValueError("At least one parameter is required for /acs/users/unsuspend") + raise ValueError( + "At least one parameter is required for /acs/users/unsuspend" + ) self.client.post("/acs/users/unsuspend", json=json_payload) return None - @route_metadata(path="/acs/users/update", has_required_parameters=True, has_pagination=False) - def update(self, *, access_schedule: Optional[Union[Dict[str, Any], Null]] = None, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None, email: Optional[str] = None, email_address: Optional[str] = None, full_name: Optional[str] = None, hid_acs_system_id: Optional[str] = None, phone_number: Optional[str] = None, user_identity_id: Optional[str] = None) -> None: + @route_metadata( + path="/acs/users/update", has_required_parameters=True, has_pagination=False + ) + def update( + self, + *, + access_schedule: Optional[Union[Dict[str, Any], Null]] = None, + acs_system_id: Optional[str] = None, + acs_user_id: Optional[str] = None, + email: Optional[str] = None, + email_address: Optional[str] = None, + full_name: Optional[str] = None, + hid_acs_system_id: Optional[str] = None, + phone_number: Optional[str] = None, + user_identity_id: Optional[str] = None, + ) -> None: """Updates the properties of a specified `access system user `_. :param access_schedule: ``starts_at`` and ``ends_at`` timestamps for the access system user's access. If you specify an ``access_schedule``, you may include both ``starts_at`` and ``ends_at``. If you omit ``starts_at``, it defaults to the current time. ``ends_at`` is optional and must be a time in the future and after ``starts_at``. diff --git a/seam/routes/action_attempts.py b/seam/routes/action_attempts.py index f01698ee..b4cfd7f7 100644 --- a/seam/routes/action_attempts.py +++ b/seam/routes/action_attempts.py @@ -3,14 +3,19 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (ActionAttempt) +from ..resources import ActionAttempt from ..modules.action_attempts import resolve_action_attempt class AbstractActionAttempts(abc.ABC): @abc.abstractmethod - def get(self, *, action_attempt_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + def get( + self, + *, + action_attempt_id: str, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Returns a specified `action attempt `_. :param action_attempt_id: ID of the action attempt that you want to get. @@ -23,7 +28,14 @@ def get(self, *, action_attempt_id: str, wait_for_action_attempt: Optional[Union raise NotImplementedError() @abc.abstractmethod - def list(self, *, action_attempt_ids: Optional[List[str]] = None, device_id: Optional[str] = None, limit: Optional[int] = None, page_cursor: Optional[Union[str, Null]] = None) -> List[ActionAttempt]: + def list( + self, + *, + action_attempt_ids: Optional[List[str]] = None, + device_id: Optional[str] = None, + limit: Optional[int] = None, + page_cursor: Optional[Union[str, Null]] = None, + ) -> List[ActionAttempt]: """Returns a list of the `action attempts `_ that you specify as an array of ``action_attempt_id``s. :param action_attempt_ids: IDs of the action attempts that you want to retrieve. @@ -43,8 +55,15 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/action_attempts/get", has_required_parameters=True, has_pagination=False) - def get(self, *, action_attempt_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + @route_metadata( + path="/action_attempts/get", has_required_parameters=True, has_pagination=False + ) + def get( + self, + *, + action_attempt_id: str, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Returns a specified `action attempt `_. :param action_attempt_id: ID of the action attempt that you want to get. @@ -60,7 +79,9 @@ def get(self, *, action_attempt_id: str, wait_for_action_attempt: Optional[Union params["action_attempt_id"] = action_attempt_id if not params: - raise ValueError("At least one parameter is required for /action_attempts/get") + raise ValueError( + "At least one parameter is required for /action_attempts/get" + ) res = self.client.get("/action_attempts/get", params=params) @@ -73,11 +94,20 @@ def get(self, *, action_attempt_id: str, wait_for_action_attempt: Optional[Union return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt + wait_for_action_attempt=wait_for_action_attempt, ) - @route_metadata(path="/action_attempts/list", has_required_parameters=False, has_pagination=True) - def list(self, *, action_attempt_ids: Optional[List[str]] = None, device_id: Optional[str] = None, limit: Optional[int] = None, page_cursor: Optional[Union[str, Null]] = None) -> List[ActionAttempt]: + @route_metadata( + path="/action_attempts/list", has_required_parameters=False, has_pagination=True + ) + def list( + self, + *, + action_attempt_ids: Optional[List[str]] = None, + device_id: Optional[str] = None, + limit: Optional[int] = None, + page_cursor: Optional[Union[str, Null]] = None, + ) -> List[ActionAttempt]: """Returns a list of the `action attempts `_ that you specify as an array of ``action_attempt_id``s. :param action_attempt_ids: IDs of the action attempts that you want to retrieve. @@ -89,17 +119,17 @@ def list(self, *, action_attempt_ids: Optional[List[str]] = None, device_id: Opt :param page_cursor: Identifies the specific page of results to return, obtained from the previous page's ``next_page_cursor``. :returns: OK""" - json_payload: Dict[str, Any] = {} + params: Dict[str, Any] = {} if action_attempt_ids is not None: - json_payload["action_attempt_ids"] = action_attempt_ids + params["action_attempt_ids"] = action_attempt_ids if device_id is not None: - json_payload["device_id"] = device_id + params["device_id"] = device_id if limit is not None: - json_payload["limit"] = limit + params["limit"] = limit if page_cursor is not None: - json_payload["page_cursor"] = page_cursor + params["page_cursor"] = page_cursor - res = self.client.post("/action_attempts/list", json=json_payload) + res = self.client.get("/action_attempts/list", params=params) return [ActionAttempt.from_dict(item) for item in res["action_attempts"]] diff --git a/seam/routes/client_sessions.py b/seam/routes/client_sessions.py index d34ef84d..83c3ca30 100644 --- a/seam/routes/client_sessions.py +++ b/seam/routes/client_sessions.py @@ -3,13 +3,24 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (ClientSession) +from ..resources import ClientSession class AbstractClientSessions(abc.ABC): @abc.abstractmethod - def create(self, *, connect_webview_ids: Optional[List[str]] = None, connected_account_ids: Optional[List[str]] = None, customer_id: Optional[str] = None, customer_key: Optional[str] = None, expires_at: Optional[str] = None, user_identifier_key: Optional[str] = None, user_identity_id: Optional[str] = None, user_identity_ids: Optional[List[str]] = None) -> ClientSession: + def create( + self, + *, + connect_webview_ids: Optional[List[str]] = None, + connected_account_ids: Optional[List[str]] = None, + customer_id: Optional[str] = None, + customer_key: Optional[str] = None, + expires_at: Optional[str] = None, + user_identifier_key: Optional[str] = None, + user_identity_id: Optional[str] = None, + user_identity_ids: Optional[List[str]] = None, + ) -> ClientSession: """Creates a new `client session `_. :param connect_webview_ids: IDs of the `Connect Webviews `_ for which you want to create a client session. @@ -41,7 +52,12 @@ def delete(self, *, client_session_id: str) -> None: raise NotImplementedError() @abc.abstractmethod - def get(self, *, client_session_id: Optional[str] = None, user_identifier_key: Optional[str] = None) -> ClientSession: + def get( + self, + *, + client_session_id: Optional[str] = None, + user_identifier_key: Optional[str] = None, + ) -> ClientSession: """Returns a specified `client session `_. :param client_session_id: ID of the client session that you want to get. @@ -52,7 +68,16 @@ def get(self, *, client_session_id: Optional[str] = None, user_identifier_key: O raise NotImplementedError() @abc.abstractmethod - def get_or_create(self, *, connect_webview_ids: Optional[List[str]] = None, connected_account_ids: Optional[List[str]] = None, expires_at: Optional[str] = None, user_identifier_key: Optional[str] = None, user_identity_id: Optional[str] = None, user_identity_ids: Optional[List[str]] = None) -> ClientSession: + def get_or_create( + self, + *, + connect_webview_ids: Optional[List[str]] = None, + connected_account_ids: Optional[List[str]] = None, + expires_at: Optional[str] = None, + user_identifier_key: Optional[str] = None, + user_identity_id: Optional[str] = None, + user_identity_ids: Optional[List[str]] = None, + ) -> ClientSession: """Returns a `client session `_ with specific characteristics or creates a new client session with these characteristics if it does not yet exist. :param connect_webview_ids: IDs of the `Connect Webviews `_ that you want to associate with the client session (or that are already associated with the existing client session). @@ -71,7 +96,16 @@ def get_or_create(self, *, connect_webview_ids: Optional[List[str]] = None, conn raise NotImplementedError() @abc.abstractmethod - def grant_access(self, *, client_session_id: Optional[str] = None, connect_webview_ids: Optional[List[str]] = None, connected_account_ids: Optional[List[str]] = None, user_identifier_key: Optional[str] = None, user_identity_id: Optional[str] = None, user_identity_ids: Optional[List[str]] = None) -> None: + def grant_access( + self, + *, + client_session_id: Optional[str] = None, + connect_webview_ids: Optional[List[str]] = None, + connected_account_ids: Optional[List[str]] = None, + user_identifier_key: Optional[str] = None, + user_identity_id: Optional[str] = None, + user_identity_ids: Optional[List[str]] = None, + ) -> None: """Grants a `client session `_ access to one or more resources, such as `Connect Webviews `_, `user identities `_, and so on. :param client_session_id: ID of the client session to which you want to grant access to resources. @@ -90,7 +124,15 @@ def grant_access(self, *, client_session_id: Optional[str] = None, connect_webvi raise NotImplementedError() @abc.abstractmethod - def list(self, *, client_session_id: Optional[str] = None, connect_webview_id: Optional[str] = None, user_identifier_key: Optional[str] = None, user_identity_id: Optional[str] = None, without_user_identifier_key: Optional[bool] = None) -> List[ClientSession]: + def list( + self, + *, + client_session_id: Optional[str] = None, + connect_webview_id: Optional[str] = None, + user_identifier_key: Optional[str] = None, + user_identity_id: Optional[str] = None, + without_user_identifier_key: Optional[bool] = None, + ) -> List[ClientSession]: """Returns a list of all `client sessions `_. :param client_session_id: ID of the client session that you want to retrieve. @@ -109,7 +151,7 @@ def list(self, *, client_session_id: Optional[str] = None, connect_webview_id: O @abc.abstractmethod def revoke(self, *, client_session_id: str) -> None: """Revokes a `client session `_. - + Note that `deleting a client session `_ is a separate action. :param client_session_id: ID of the client session that you want to revoke. @@ -123,8 +165,23 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/client_sessions/create", has_required_parameters=False, has_pagination=False) - def create(self, *, connect_webview_ids: Optional[List[str]] = None, connected_account_ids: Optional[List[str]] = None, customer_id: Optional[str] = None, customer_key: Optional[str] = None, expires_at: Optional[str] = None, user_identifier_key: Optional[str] = None, user_identity_id: Optional[str] = None, user_identity_ids: Optional[List[str]] = None) -> ClientSession: + @route_metadata( + path="/client_sessions/create", + has_required_parameters=False, + has_pagination=False, + ) + def create( + self, + *, + connect_webview_ids: Optional[List[str]] = None, + connected_account_ids: Optional[List[str]] = None, + customer_id: Optional[str] = None, + customer_key: Optional[str] = None, + expires_at: Optional[str] = None, + user_identifier_key: Optional[str] = None, + user_identity_id: Optional[str] = None, + user_identity_ids: Optional[List[str]] = None, + ) -> ClientSession: """Creates a new `client session `_. :param connect_webview_ids: IDs of the `Connect Webviews `_ for which you want to create a client session. @@ -167,7 +224,11 @@ def create(self, *, connect_webview_ids: Optional[List[str]] = None, connected_a return ClientSession.from_dict(res["client_session"]) - @route_metadata(path="/client_sessions/delete", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/client_sessions/delete", + has_required_parameters=True, + has_pagination=False, + ) def delete(self, *, client_session_id: str) -> None: """Deletes a `client session `_. @@ -180,14 +241,23 @@ def delete(self, *, client_session_id: str) -> None: params["client_session_id"] = client_session_id if not params: - raise ValueError("At least one parameter is required for /client_sessions/delete") + raise ValueError( + "At least one parameter is required for /client_sessions/delete" + ) self.client.delete("/client_sessions/delete", params=params) return None - @route_metadata(path="/client_sessions/get", has_required_parameters=False, has_pagination=False) - def get(self, *, client_session_id: Optional[str] = None, user_identifier_key: Optional[str] = None) -> ClientSession: + @route_metadata( + path="/client_sessions/get", has_required_parameters=False, has_pagination=False + ) + def get( + self, + *, + client_session_id: Optional[str] = None, + user_identifier_key: Optional[str] = None, + ) -> ClientSession: """Returns a specified `client session `_. :param client_session_id: ID of the client session that you want to get. @@ -206,8 +276,21 @@ def get(self, *, client_session_id: Optional[str] = None, user_identifier_key: O return ClientSession.from_dict(res["client_session"]) - @route_metadata(path="/client_sessions/get_or_create", has_required_parameters=False, has_pagination=False) - def get_or_create(self, *, connect_webview_ids: Optional[List[str]] = None, connected_account_ids: Optional[List[str]] = None, expires_at: Optional[str] = None, user_identifier_key: Optional[str] = None, user_identity_id: Optional[str] = None, user_identity_ids: Optional[List[str]] = None) -> ClientSession: + @route_metadata( + path="/client_sessions/get_or_create", + has_required_parameters=False, + has_pagination=False, + ) + def get_or_create( + self, + *, + connect_webview_ids: Optional[List[str]] = None, + connected_account_ids: Optional[List[str]] = None, + expires_at: Optional[str] = None, + user_identifier_key: Optional[str] = None, + user_identity_id: Optional[str] = None, + user_identity_ids: Optional[List[str]] = None, + ) -> ClientSession: """Returns a `client session `_ with specific characteristics or creates a new client session with these characteristics if it does not yet exist. :param connect_webview_ids: IDs of the `Connect Webviews `_ that you want to associate with the client session (or that are already associated with the existing client session). @@ -242,8 +325,21 @@ def get_or_create(self, *, connect_webview_ids: Optional[List[str]] = None, conn return ClientSession.from_dict(res["client_session"]) - @route_metadata(path="/client_sessions/grant_access", has_required_parameters=True, has_pagination=False) - def grant_access(self, *, client_session_id: Optional[str] = None, connect_webview_ids: Optional[List[str]] = None, connected_account_ids: Optional[List[str]] = None, user_identifier_key: Optional[str] = None, user_identity_id: Optional[str] = None, user_identity_ids: Optional[List[str]] = None) -> None: + @route_metadata( + path="/client_sessions/grant_access", + has_required_parameters=True, + has_pagination=False, + ) + def grant_access( + self, + *, + client_session_id: Optional[str] = None, + connect_webview_ids: Optional[List[str]] = None, + connected_account_ids: Optional[List[str]] = None, + user_identifier_key: Optional[str] = None, + user_identity_id: Optional[str] = None, + user_identity_ids: Optional[List[str]] = None, + ) -> None: """Grants a `client session `_ access to one or more resources, such as `Connect Webviews `_, `user identities `_, and so on. :param client_session_id: ID of the client session to which you want to grant access to resources. @@ -275,14 +371,28 @@ def grant_access(self, *, client_session_id: Optional[str] = None, connect_webvi json_payload["user_identity_ids"] = user_identity_ids if not json_payload: - raise ValueError("At least one parameter is required for /client_sessions/grant_access") + raise ValueError( + "At least one parameter is required for /client_sessions/grant_access" + ) self.client.patch("/client_sessions/grant_access", json=json_payload) return None - @route_metadata(path="/client_sessions/list", has_required_parameters=False, has_pagination=False) - def list(self, *, client_session_id: Optional[str] = None, connect_webview_id: Optional[str] = None, user_identifier_key: Optional[str] = None, user_identity_id: Optional[str] = None, without_user_identifier_key: Optional[bool] = None) -> List[ClientSession]: + @route_metadata( + path="/client_sessions/list", + has_required_parameters=False, + has_pagination=False, + ) + def list( + self, + *, + client_session_id: Optional[str] = None, + connect_webview_id: Optional[str] = None, + user_identifier_key: Optional[str] = None, + user_identity_id: Optional[str] = None, + without_user_identifier_key: Optional[bool] = None, + ) -> List[ClientSession]: """Returns a list of all `client sessions `_. :param client_session_id: ID of the client session that you want to retrieve. @@ -313,10 +423,14 @@ def list(self, *, client_session_id: Optional[str] = None, connect_webview_id: O return [ClientSession.from_dict(item) for item in res["client_sessions"]] - @route_metadata(path="/client_sessions/revoke", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/client_sessions/revoke", + has_required_parameters=True, + has_pagination=False, + ) def revoke(self, *, client_session_id: str) -> None: """Revokes a `client session `_. - + Note that `deleting a client session `_ is a separate action. :param client_session_id: ID of the client session that you want to revoke. @@ -328,7 +442,9 @@ def revoke(self, *, client_session_id: str) -> None: json_payload["client_session_id"] = client_session_id if not json_payload: - raise ValueError("At least one parameter is required for /client_sessions/revoke") + raise ValueError( + "At least one parameter is required for /client_sessions/revoke" + ) self.client.post("/client_sessions/revoke", json=json_payload) diff --git a/seam/routes/connect_webviews.py b/seam/routes/connect_webviews.py index 37b3ef22..e7785711 100644 --- a/seam/routes/connect_webviews.py +++ b/seam/routes/connect_webviews.py @@ -3,19 +3,32 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (ConnectWebview) +from ..resources import ConnectWebview class AbstractConnectWebviews(abc.ABC): @abc.abstractmethod - def create(self, *, accepted_capabilities: Optional[List[str]] = None, accepted_providers: Optional[List[str]] = None, automatically_manage_new_devices: Optional[bool] = None, custom_metadata: Optional[Dict[str, Any]] = None, custom_redirect_failure_url: Optional[str] = None, custom_redirect_url: Optional[str] = None, customer_key: Optional[str] = None, excluded_providers: Optional[List[str]] = None, provider_category: Optional[str] = None, wait_for_device_creation: Optional[bool] = None) -> ConnectWebview: + def create( + self, + *, + accepted_capabilities: Optional[List[str]] = None, + accepted_providers: Optional[List[str]] = None, + automatically_manage_new_devices: Optional[bool] = None, + custom_metadata: Optional[Dict[str, Any]] = None, + custom_redirect_failure_url: Optional[str] = None, + custom_redirect_url: Optional[str] = None, + customer_key: Optional[str] = None, + excluded_providers: Optional[List[str]] = None, + provider_category: Optional[str] = None, + wait_for_device_creation: Optional[bool] = None, + ) -> ConnectWebview: """Creates a new `Connect Webview `_. - + To enable a user to connect their devices or systems to Seam, they must sign in to their device or system account. To enable a user to sign in, you create a ``connect_webview``. After creating the Connect Webview, you receive a URL that you can use to display the visual component of this Connect Webview for your user. You can open an iframe or new window to display the Connect Webview. - + You should make a new ``connect_webview`` for each unique login request. Each ``connect_webview`` tracks the user that signed in with it. You receive an error if you reuse a Connect Webview for the same user twice or if you use the same Connect Webview for multiple users. - + See also: `Connect Webview Process `_. :param accepted_capabilities: List of accepted device capabilities that restrict the types of devices that can be connected through the Connect Webview. If not provided, defaults will be determined based on the accepted providers. @@ -44,7 +57,7 @@ def create(self, *, accepted_capabilities: Optional[List[str]] = None, accepted_ @abc.abstractmethod def delete(self, *, connect_webview_id: str) -> None: """Deletes a `Connect Webview `_. - + You do not need to delete a Connect Webview once a user completes it. Instead, you can simply ignore completed Connect Webviews. :param connect_webview_id: ID of the Connect Webview that you want to delete. @@ -55,7 +68,7 @@ def delete(self, *, connect_webview_id: str) -> None: @abc.abstractmethod def get(self, *, connect_webview_id: str) -> ConnectWebview: """Returns a specified `Connect Webview `_. - + Unless you're using a ``custom_redirect_url``, you should poll a newly-created ``connect_webview`` to find out if the user has signed in or to get details about what devices they've connected. :param connect_webview_id: ID of the Connect Webview that you want to get. @@ -66,7 +79,16 @@ def get(self, *, connect_webview_id: str) -> ConnectWebview: raise NotImplementedError() @abc.abstractmethod - def list(self, *, custom_metadata_has: Optional[Dict[str, Any]] = None, customer_key: Optional[str] = None, limit: Optional[float] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, user_identifier_key: Optional[str] = None) -> List[ConnectWebview]: + def list( + self, + *, + custom_metadata_has: Optional[Dict[str, Any]] = None, + customer_key: Optional[str] = None, + limit: Optional[float] = None, + page_cursor: Optional[Union[str, Null]] = None, + search: Optional[str] = None, + user_identifier_key: Optional[str] = None, + ) -> List[ConnectWebview]: """Returns a list of all `Connect Webviews `_. :param custom_metadata_has: Custom metadata pairs by which you want to `filter Connect Webviews `_. Returns Connect Webviews with ``custom_metadata`` that contains all of the provided key:value pairs. @@ -90,14 +112,31 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/connect_webviews/create", has_required_parameters=False, has_pagination=False) - def create(self, *, accepted_capabilities: Optional[List[str]] = None, accepted_providers: Optional[List[str]] = None, automatically_manage_new_devices: Optional[bool] = None, custom_metadata: Optional[Dict[str, Any]] = None, custom_redirect_failure_url: Optional[str] = None, custom_redirect_url: Optional[str] = None, customer_key: Optional[str] = None, excluded_providers: Optional[List[str]] = None, provider_category: Optional[str] = None, wait_for_device_creation: Optional[bool] = None) -> ConnectWebview: + @route_metadata( + path="/connect_webviews/create", + has_required_parameters=False, + has_pagination=False, + ) + def create( + self, + *, + accepted_capabilities: Optional[List[str]] = None, + accepted_providers: Optional[List[str]] = None, + automatically_manage_new_devices: Optional[bool] = None, + custom_metadata: Optional[Dict[str, Any]] = None, + custom_redirect_failure_url: Optional[str] = None, + custom_redirect_url: Optional[str] = None, + customer_key: Optional[str] = None, + excluded_providers: Optional[List[str]] = None, + provider_category: Optional[str] = None, + wait_for_device_creation: Optional[bool] = None, + ) -> ConnectWebview: """Creates a new `Connect Webview `_. - + To enable a user to connect their devices or systems to Seam, they must sign in to their device or system account. To enable a user to sign in, you create a ``connect_webview``. After creating the Connect Webview, you receive a URL that you can use to display the visual component of this Connect Webview for your user. You can open an iframe or new window to display the Connect Webview. - + You should make a new ``connect_webview`` for each unique login request. Each ``connect_webview`` tracks the user that signed in with it. You receive an error if you reuse a Connect Webview for the same user twice or if you use the same Connect Webview for multiple users. - + See also: `Connect Webview Process `_. :param accepted_capabilities: List of accepted device capabilities that restrict the types of devices that can be connected through the Connect Webview. If not provided, defaults will be determined based on the accepted providers. @@ -128,7 +167,9 @@ def create(self, *, accepted_capabilities: Optional[List[str]] = None, accepted_ if accepted_providers is not None: json_payload["accepted_providers"] = accepted_providers if automatically_manage_new_devices is not None: - json_payload["automatically_manage_new_devices"] = automatically_manage_new_devices + json_payload["automatically_manage_new_devices"] = ( + automatically_manage_new_devices + ) if custom_metadata is not None: json_payload["custom_metadata"] = custom_metadata if custom_redirect_failure_url is not None: @@ -148,10 +189,14 @@ def create(self, *, accepted_capabilities: Optional[List[str]] = None, accepted_ return ConnectWebview.from_dict(res["connect_webview"]) - @route_metadata(path="/connect_webviews/delete", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/connect_webviews/delete", + has_required_parameters=True, + has_pagination=False, + ) def delete(self, *, connect_webview_id: str) -> None: """Deletes a `Connect Webview `_. - + You do not need to delete a Connect Webview once a user completes it. Instead, you can simply ignore completed Connect Webviews. :param connect_webview_id: ID of the Connect Webview that you want to delete. @@ -163,16 +208,20 @@ def delete(self, *, connect_webview_id: str) -> None: params["connect_webview_id"] = connect_webview_id if not params: - raise ValueError("At least one parameter is required for /connect_webviews/delete") + raise ValueError( + "At least one parameter is required for /connect_webviews/delete" + ) self.client.delete("/connect_webviews/delete", params=params) return None - @route_metadata(path="/connect_webviews/get", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/connect_webviews/get", has_required_parameters=True, has_pagination=False + ) def get(self, *, connect_webview_id: str) -> ConnectWebview: """Returns a specified `Connect Webview `_. - + Unless you're using a ``custom_redirect_url``, you should poll a newly-created ``connect_webview`` to find out if the user has signed in or to get details about what devices they've connected. :param connect_webview_id: ID of the Connect Webview that you want to get. @@ -186,14 +235,29 @@ def get(self, *, connect_webview_id: str) -> ConnectWebview: params["connect_webview_id"] = connect_webview_id if not params: - raise ValueError("At least one parameter is required for /connect_webviews/get") + raise ValueError( + "At least one parameter is required for /connect_webviews/get" + ) res = self.client.get("/connect_webviews/get", params=params) return ConnectWebview.from_dict(res["connect_webview"]) - @route_metadata(path="/connect_webviews/list", has_required_parameters=False, has_pagination=True) - def list(self, *, custom_metadata_has: Optional[Dict[str, Any]] = None, customer_key: Optional[str] = None, limit: Optional[float] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, user_identifier_key: Optional[str] = None) -> List[ConnectWebview]: + @route_metadata( + path="/connect_webviews/list", + has_required_parameters=False, + has_pagination=True, + ) + def list( + self, + *, + custom_metadata_has: Optional[Dict[str, Any]] = None, + customer_key: Optional[str] = None, + limit: Optional[float] = None, + page_cursor: Optional[Union[str, Null]] = None, + search: Optional[str] = None, + user_identifier_key: Optional[str] = None, + ) -> List[ConnectWebview]: """Returns a list of all `Connect Webviews `_. :param custom_metadata_has: Custom metadata pairs by which you want to `filter Connect Webviews `_. Returns Connect Webviews with ``custom_metadata`` that contains all of the provided key:value pairs. @@ -209,21 +273,21 @@ def list(self, *, custom_metadata_has: Optional[Dict[str, Any]] = None, customer :param user_identifier_key: Your user ID for the user by which you want to filter Connect Webviews. :returns: OK""" - json_payload: Dict[str, Any] = {} + params: Dict[str, Any] = {} if custom_metadata_has is not None: - json_payload["custom_metadata_has"] = custom_metadata_has + params["custom_metadata_has"] = custom_metadata_has if customer_key is not None: - json_payload["customer_key"] = customer_key + params["customer_key"] = customer_key if limit is not None: - json_payload["limit"] = limit + params["limit"] = limit if page_cursor is not None: - json_payload["page_cursor"] = page_cursor + params["page_cursor"] = page_cursor if search is not None: - json_payload["search"] = search + params["search"] = search if user_identifier_key is not None: - json_payload["user_identifier_key"] = user_identifier_key + params["user_identifier_key"] = user_identifier_key - res = self.client.post("/connect_webviews/list", json=json_payload) + res = self.client.get("/connect_webviews/list", params=params) return [ConnectWebview.from_dict(item) for item in res["connect_webviews"]] diff --git a/seam/routes/connected_accounts.py b/seam/routes/connected_accounts.py index 9657fa91..4ab88a7d 100644 --- a/seam/routes/connected_accounts.py +++ b/seam/routes/connected_accounts.py @@ -3,8 +3,11 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (ConnectedAccount) -from .connected_accounts_simulate import AbstractConnectedAccountsSimulate, ConnectedAccountsSimulate +from ..resources import ConnectedAccount +from .connected_accounts_simulate import ( + AbstractConnectedAccountsSimulate, + ConnectedAccountsSimulate, +) class AbstractConnectedAccounts(abc.ABC): @@ -17,9 +20,9 @@ def simulate(self) -> AbstractConnectedAccountsSimulate: @abc.abstractmethod def delete(self, *, connected_account_id: str) -> None: """Deletes a specified `connected account `_. - + Deleting a connected account triggers a ``connected_account.deleted`` event and removes the connected account and all data associated with the connected account from Seam, including devices, events, access codes, and so on. For every deleted resource, Seam sends a corresponding deleted event, but the resource is not deleted from the provider. - + For example, if you delete a connected account with a device that has an access code, Seam sends a ``connected_account.deleted`` event, a ``device.deleted`` event, and an ``access_code.deleted`` event, but Seam does not remove the access code from the device. :param connected_account_id: ID of the connected account that you want to delete. @@ -28,7 +31,9 @@ def delete(self, *, connected_account_id: str) -> None: raise NotImplementedError() @abc.abstractmethod - def get(self, *, connected_account_id: Optional[str] = None, email: Optional[str] = None) -> ConnectedAccount: + def get( + self, *, connected_account_id: Optional[str] = None, email: Optional[str] = None + ) -> ConnectedAccount: """Returns a specified `connected account `_. :param connected_account_id: ID of the connected account that you want to get. @@ -41,7 +46,17 @@ def get(self, *, connected_account_id: Optional[str] = None, email: Optional[str raise NotImplementedError() @abc.abstractmethod - def list(self, *, custom_metadata_has: Optional[Dict[str, Any]] = None, customer_key: Optional[str] = None, limit: Optional[int] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, space_id: Optional[str] = None, user_identifier_key: Optional[str] = None) -> List[ConnectedAccount]: + def list( + self, + *, + custom_metadata_has: Optional[Dict[str, Any]] = None, + customer_key: Optional[str] = None, + limit: Optional[int] = None, + page_cursor: Optional[Union[str, Null]] = None, + search: Optional[str] = None, + space_id: Optional[str] = None, + user_identifier_key: Optional[str] = None, + ) -> List[ConnectedAccount]: """Returns a list of all `connected accounts `_. :param custom_metadata_has: Custom metadata pairs by which you want to filter connected accounts. Returns connected accounts with ``custom_metadata`` that contains all of the provided key:value pairs. @@ -71,7 +86,16 @@ def sync(self, *, connected_account_id: str) -> None: raise NotImplementedError() @abc.abstractmethod - def update(self, *, connected_account_id: str, accepted_capabilities: Optional[List[str]] = None, automatically_manage_new_devices: Optional[bool] = None, custom_metadata: Optional[Dict[str, Any]] = None, customer_key: Optional[str] = None, display_name: Optional[str] = None) -> None: + def update( + self, + *, + connected_account_id: str, + accepted_capabilities: Optional[List[str]] = None, + automatically_manage_new_devices: Optional[bool] = None, + custom_metadata: Optional[Dict[str, Any]] = None, + customer_key: Optional[str] = None, + display_name: Optional[str] = None, + ) -> None: """Updates a `connected account `_. :param connected_account_id: ID of the connected account that you want to update. @@ -100,12 +124,16 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): def simulate(self) -> ConnectedAccountsSimulate: return self._simulate - @route_metadata(path="/connected_accounts/delete", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/connected_accounts/delete", + has_required_parameters=True, + has_pagination=False, + ) def delete(self, *, connected_account_id: str) -> None: """Deletes a specified `connected account `_. - + Deleting a connected account triggers a ``connected_account.deleted`` event and removes the connected account and all data associated with the connected account from Seam, including devices, events, access codes, and so on. For every deleted resource, Seam sends a corresponding deleted event, but the resource is not deleted from the provider. - + For example, if you delete a connected account with a device that has an access code, Seam sends a ``connected_account.deleted`` event, a ``device.deleted`` event, and an ``access_code.deleted`` event, but Seam does not remove the access code from the device. :param connected_account_id: ID of the connected account that you want to delete. @@ -117,14 +145,22 @@ def delete(self, *, connected_account_id: str) -> None: params["connected_account_id"] = connected_account_id if not params: - raise ValueError("At least one parameter is required for /connected_accounts/delete") + raise ValueError( + "At least one parameter is required for /connected_accounts/delete" + ) self.client.delete("/connected_accounts/delete", params=params) return None - @route_metadata(path="/connected_accounts/get", has_required_parameters=True, has_pagination=False) - def get(self, *, connected_account_id: Optional[str] = None, email: Optional[str] = None) -> ConnectedAccount: + @route_metadata( + path="/connected_accounts/get", + has_required_parameters=True, + has_pagination=False, + ) + def get( + self, *, connected_account_id: Optional[str] = None, email: Optional[str] = None + ) -> ConnectedAccount: """Returns a specified `connected account `_. :param connected_account_id: ID of the connected account that you want to get. @@ -142,14 +178,30 @@ def get(self, *, connected_account_id: Optional[str] = None, email: Optional[str params["email"] = email if not params: - raise ValueError("At least one parameter is required for /connected_accounts/get") + raise ValueError( + "At least one parameter is required for /connected_accounts/get" + ) res = self.client.get("/connected_accounts/get", params=params) return ConnectedAccount.from_dict(res["connected_account"]) - @route_metadata(path="/connected_accounts/list", has_required_parameters=False, has_pagination=True) - def list(self, *, custom_metadata_has: Optional[Dict[str, Any]] = None, customer_key: Optional[str] = None, limit: Optional[int] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, space_id: Optional[str] = None, user_identifier_key: Optional[str] = None) -> List[ConnectedAccount]: + @route_metadata( + path="/connected_accounts/list", + has_required_parameters=False, + has_pagination=True, + ) + def list( + self, + *, + custom_metadata_has: Optional[Dict[str, Any]] = None, + customer_key: Optional[str] = None, + limit: Optional[int] = None, + page_cursor: Optional[Union[str, Null]] = None, + search: Optional[str] = None, + space_id: Optional[str] = None, + user_identifier_key: Optional[str] = None, + ) -> List[ConnectedAccount]: """Returns a list of all `connected accounts `_. :param custom_metadata_has: Custom metadata pairs by which you want to filter connected accounts. Returns connected accounts with ``custom_metadata`` that contains all of the provided key:value pairs. @@ -167,28 +219,32 @@ def list(self, *, custom_metadata_has: Optional[Dict[str, Any]] = None, customer :param user_identifier_key: Your user ID for the user by which you want to filter connected accounts. :returns: OK""" - json_payload: Dict[str, Any] = {} + params: Dict[str, Any] = {} if custom_metadata_has is not None: - json_payload["custom_metadata_has"] = custom_metadata_has + params["custom_metadata_has"] = custom_metadata_has if customer_key is not None: - json_payload["customer_key"] = customer_key + params["customer_key"] = customer_key if limit is not None: - json_payload["limit"] = limit + params["limit"] = limit if page_cursor is not None: - json_payload["page_cursor"] = page_cursor + params["page_cursor"] = page_cursor if search is not None: - json_payload["search"] = search + params["search"] = search if space_id is not None: - json_payload["space_id"] = space_id + params["space_id"] = space_id if user_identifier_key is not None: - json_payload["user_identifier_key"] = user_identifier_key + params["user_identifier_key"] = user_identifier_key - res = self.client.post("/connected_accounts/list", json=json_payload) + res = self.client.get("/connected_accounts/list", params=params) return [ConnectedAccount.from_dict(item) for item in res["connected_accounts"]] - @route_metadata(path="/connected_accounts/sync", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/connected_accounts/sync", + has_required_parameters=True, + has_pagination=False, + ) def sync(self, *, connected_account_id: str) -> None: """Request a `connected account `_ sync attempt for the specified ``connected_account_id``. @@ -201,14 +257,29 @@ def sync(self, *, connected_account_id: str) -> None: json_payload["connected_account_id"] = connected_account_id if not json_payload: - raise ValueError("At least one parameter is required for /connected_accounts/sync") + raise ValueError( + "At least one parameter is required for /connected_accounts/sync" + ) self.client.post("/connected_accounts/sync", json=json_payload) return None - @route_metadata(path="/connected_accounts/update", has_required_parameters=True, has_pagination=False) - def update(self, *, connected_account_id: str, accepted_capabilities: Optional[List[str]] = None, automatically_manage_new_devices: Optional[bool] = None, custom_metadata: Optional[Dict[str, Any]] = None, customer_key: Optional[str] = None, display_name: Optional[str] = None) -> None: + @route_metadata( + path="/connected_accounts/update", + has_required_parameters=True, + has_pagination=False, + ) + def update( + self, + *, + connected_account_id: str, + accepted_capabilities: Optional[List[str]] = None, + automatically_manage_new_devices: Optional[bool] = None, + custom_metadata: Optional[Dict[str, Any]] = None, + customer_key: Optional[str] = None, + display_name: Optional[str] = None, + ) -> None: """Updates a `connected account `_. :param connected_account_id: ID of the connected account that you want to update. @@ -231,7 +302,9 @@ def update(self, *, connected_account_id: str, accepted_capabilities: Optional[L if accepted_capabilities is not None: json_payload["accepted_capabilities"] = accepted_capabilities if automatically_manage_new_devices is not None: - json_payload["automatically_manage_new_devices"] = automatically_manage_new_devices + json_payload["automatically_manage_new_devices"] = ( + automatically_manage_new_devices + ) if custom_metadata is not None: json_payload["custom_metadata"] = custom_metadata if customer_key is not None: @@ -240,7 +313,9 @@ def update(self, *, connected_account_id: str, accepted_capabilities: Optional[L json_payload["display_name"] = display_name if not json_payload: - raise ValueError("At least one parameter is required for /connected_accounts/update") + raise ValueError( + "At least one parameter is required for /connected_accounts/update" + ) self.client.patch("/connected_accounts/update", json=json_payload) diff --git a/seam/routes/connected_accounts_simulate.py b/seam/routes/connected_accounts_simulate.py index 19a69fb5..a92f0cf1 100644 --- a/seam/routes/connected_accounts_simulate.py +++ b/seam/routes/connected_accounts_simulate.py @@ -22,7 +22,11 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/connected_accounts/simulate/disconnect", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/connected_accounts/simulate/disconnect", + has_required_parameters=True, + has_pagination=False, + ) def disconnect(self, *, connected_account_id: str) -> None: """Simulates a connected account becoming disconnected from Seam. Only applicable for `sandbox workspaces `_. @@ -35,7 +39,9 @@ def disconnect(self, *, connected_account_id: str) -> None: json_payload["connected_account_id"] = connected_account_id if not json_payload: - raise ValueError("At least one parameter is required for /connected_accounts/simulate/disconnect") + raise ValueError( + "At least one parameter is required for /connected_accounts/simulate/disconnect" + ) self.client.post("/connected_accounts/simulate/disconnect", json=json_payload) diff --git a/seam/routes/customers.py b/seam/routes/customers.py index 2f06537c..45cc87f6 100644 --- a/seam/routes/customers.py +++ b/seam/routes/customers.py @@ -3,13 +3,27 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (CustomerPortal) +from ..resources import CustomerPortal class AbstractCustomers(abc.ABC): @abc.abstractmethod - def create_portal(self, *, customer_resources_filters: Optional[List[Dict[str, Any]]] = None, customization_profile_id: Optional[str] = None, deep_link: Optional[Dict[str, Any]] = None, exclude_locale_picker: Optional[bool] = None, features: Optional[Dict[str, Any]] = None, is_embedded: Optional[bool] = None, landing_page: Optional[Dict[str, Any]] = None, locale: Optional[str] = None, navigation_mode: Optional[str] = None, read_only: Optional[bool] = None, customer_data: Optional[Dict[str, Any]] = None) -> CustomerPortal: + def create_portal( + self, + *, + customer_resources_filters: Optional[List[Dict[str, Any]]] = None, + customization_profile_id: Optional[str] = None, + deep_link: Optional[Dict[str, Any]] = None, + exclude_locale_picker: Optional[bool] = None, + features: Optional[Dict[str, Any]] = None, + is_embedded: Optional[bool] = None, + landing_page: Optional[Dict[str, Any]] = None, + locale: Optional[str] = None, + navigation_mode: Optional[str] = None, + read_only: Optional[bool] = None, + customer_data: Optional[Dict[str, Any]] = None, + ) -> CustomerPortal: """Creates a new customer portal magic link with configurable features. :param customer_resources_filters: Filter configuration for resources based on their custom_metadata. Each filter specifies a field, operation, and value to match against resource custom_metadata. @@ -20,7 +34,7 @@ def create_portal(self, *, customer_resources_filters: Optional[List[Dict[str, A :param exclude_locale_picker: Whether to exclude the option to select a locale within the portal UI. - :param features: + :param features: :param is_embedded: Whether the portal is embedded in another application. @@ -32,13 +46,35 @@ def create_portal(self, *, customer_resources_filters: Optional[List[Dict[str, A :param read_only: Whether the portal is read-only. When true, the customer can browse the portal but cannot perform any mutating action; write requests made with the portal's client session are rejected. - :param customer_data: + :param customer_data: :returns: OK""" raise NotImplementedError() @abc.abstractmethod - def delete_data(self, *, access_grant_keys: Optional[List[str]] = None, booking_keys: Optional[List[str]] = None, building_keys: Optional[List[str]] = None, common_area_keys: Optional[List[str]] = None, customer_keys: Optional[List[str]] = None, facility_keys: Optional[List[str]] = None, guest_keys: Optional[List[str]] = None, listing_keys: Optional[List[str]] = None, property_keys: Optional[List[str]] = None, property_listing_keys: Optional[List[str]] = None, reservation_keys: Optional[List[str]] = None, resident_keys: Optional[List[str]] = None, room_keys: Optional[List[str]] = None, space_keys: Optional[List[str]] = None, staff_member_keys: Optional[List[str]] = None, tenant_keys: Optional[List[str]] = None, unit_keys: Optional[List[str]] = None, user_identity_keys: Optional[List[str]] = None, user_keys: Optional[List[str]] = None) -> None: + def delete_data( + self, + *, + access_grant_keys: Optional[List[str]] = None, + booking_keys: Optional[List[str]] = None, + building_keys: Optional[List[str]] = None, + common_area_keys: Optional[List[str]] = None, + customer_keys: Optional[List[str]] = None, + facility_keys: Optional[List[str]] = None, + guest_keys: Optional[List[str]] = None, + listing_keys: Optional[List[str]] = None, + property_keys: Optional[List[str]] = None, + property_listing_keys: Optional[List[str]] = None, + reservation_keys: Optional[List[str]] = None, + resident_keys: Optional[List[str]] = None, + room_keys: Optional[List[str]] = None, + space_keys: Optional[List[str]] = None, + staff_member_keys: Optional[List[str]] = None, + tenant_keys: Optional[List[str]] = None, + unit_keys: Optional[List[str]] = None, + user_identity_keys: Optional[List[str]] = None, + user_keys: Optional[List[str]] = None, + ) -> None: """Deletes customer data including resources like spaces, properties, rooms, users, etc. This will delete the partner resources and any related Seam resources (user identities, access grants, spaces). @@ -82,7 +118,30 @@ def delete_data(self, *, access_grant_keys: Optional[List[str]] = None, booking_ raise NotImplementedError() @abc.abstractmethod - def push_data(self, *, customer_key: str, access_grants: Optional[List[Dict[str, Any]]] = None, bookings: Optional[List[Dict[str, Any]]] = None, buildings: Optional[List[Dict[str, Any]]] = None, common_areas: Optional[List[Dict[str, Any]]] = None, facilities: Optional[List[Dict[str, Any]]] = None, guests: Optional[List[Dict[str, Any]]] = None, listings: Optional[List[Dict[str, Any]]] = None, properties: Optional[List[Dict[str, Any]]] = None, property_listings: Optional[List[Dict[str, Any]]] = None, reservations: Optional[List[Dict[str, Any]]] = None, residents: Optional[List[Dict[str, Any]]] = None, rooms: Optional[List[Dict[str, Any]]] = None, sites: Optional[List[Dict[str, Any]]] = None, spaces: Optional[List[Dict[str, Any]]] = None, staff_members: Optional[List[Dict[str, Any]]] = None, tenants: Optional[List[Dict[str, Any]]] = None, units: Optional[List[Dict[str, Any]]] = None, user_identities: Optional[List[Dict[str, Any]]] = None, users: Optional[List[Dict[str, Any]]] = None) -> None: + def push_data( + self, + *, + customer_key: str, + access_grants: Optional[List[Dict[str, Any]]] = None, + bookings: Optional[List[Dict[str, Any]]] = None, + buildings: Optional[List[Dict[str, Any]]] = None, + common_areas: Optional[List[Dict[str, Any]]] = None, + facilities: Optional[List[Dict[str, Any]]] = None, + guests: Optional[List[Dict[str, Any]]] = None, + listings: Optional[List[Dict[str, Any]]] = None, + properties: Optional[List[Dict[str, Any]]] = None, + property_listings: Optional[List[Dict[str, Any]]] = None, + reservations: Optional[List[Dict[str, Any]]] = None, + residents: Optional[List[Dict[str, Any]]] = None, + rooms: Optional[List[Dict[str, Any]]] = None, + sites: Optional[List[Dict[str, Any]]] = None, + spaces: Optional[List[Dict[str, Any]]] = None, + staff_members: Optional[List[Dict[str, Any]]] = None, + tenants: Optional[List[Dict[str, Any]]] = None, + units: Optional[List[Dict[str, Any]]] = None, + user_identities: Optional[List[Dict[str, Any]]] = None, + users: Optional[List[Dict[str, Any]]] = None, + ) -> None: """Pushes customer data including resources like spaces, properties, rooms, users, etc. :param customer_key: Your unique identifier for the customer. @@ -134,8 +193,26 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/customers/create_portal", has_required_parameters=False, has_pagination=False) - def create_portal(self, *, customer_resources_filters: Optional[List[Dict[str, Any]]] = None, customization_profile_id: Optional[str] = None, deep_link: Optional[Dict[str, Any]] = None, exclude_locale_picker: Optional[bool] = None, features: Optional[Dict[str, Any]] = None, is_embedded: Optional[bool] = None, landing_page: Optional[Dict[str, Any]] = None, locale: Optional[str] = None, navigation_mode: Optional[str] = None, read_only: Optional[bool] = None, customer_data: Optional[Dict[str, Any]] = None) -> CustomerPortal: + @route_metadata( + path="/customers/create_portal", + has_required_parameters=False, + has_pagination=False, + ) + def create_portal( + self, + *, + customer_resources_filters: Optional[List[Dict[str, Any]]] = None, + customization_profile_id: Optional[str] = None, + deep_link: Optional[Dict[str, Any]] = None, + exclude_locale_picker: Optional[bool] = None, + features: Optional[Dict[str, Any]] = None, + is_embedded: Optional[bool] = None, + landing_page: Optional[Dict[str, Any]] = None, + locale: Optional[str] = None, + navigation_mode: Optional[str] = None, + read_only: Optional[bool] = None, + customer_data: Optional[Dict[str, Any]] = None, + ) -> CustomerPortal: """Creates a new customer portal magic link with configurable features. :param customer_resources_filters: Filter configuration for resources based on their custom_metadata. Each filter specifies a field, operation, and value to match against resource custom_metadata. @@ -146,7 +223,7 @@ def create_portal(self, *, customer_resources_filters: Optional[List[Dict[str, A :param exclude_locale_picker: Whether to exclude the option to select a locale within the portal UI. - :param features: + :param features: :param is_embedded: Whether the portal is embedded in another application. @@ -158,7 +235,7 @@ def create_portal(self, *, customer_resources_filters: Optional[List[Dict[str, A :param read_only: Whether the portal is read-only. When true, the customer can browse the portal but cannot perform any mutating action; write requests made with the portal's client session are rejected. - :param customer_data: + :param customer_data: :returns: OK""" json_payload: Dict[str, Any] = {} @@ -190,8 +267,34 @@ def create_portal(self, *, customer_resources_filters: Optional[List[Dict[str, A return CustomerPortal.from_dict(res["customer_portal"]) - @route_metadata(path="/customers/delete_data", has_required_parameters=False, has_pagination=False) - def delete_data(self, *, access_grant_keys: Optional[List[str]] = None, booking_keys: Optional[List[str]] = None, building_keys: Optional[List[str]] = None, common_area_keys: Optional[List[str]] = None, customer_keys: Optional[List[str]] = None, facility_keys: Optional[List[str]] = None, guest_keys: Optional[List[str]] = None, listing_keys: Optional[List[str]] = None, property_keys: Optional[List[str]] = None, property_listing_keys: Optional[List[str]] = None, reservation_keys: Optional[List[str]] = None, resident_keys: Optional[List[str]] = None, room_keys: Optional[List[str]] = None, space_keys: Optional[List[str]] = None, staff_member_keys: Optional[List[str]] = None, tenant_keys: Optional[List[str]] = None, unit_keys: Optional[List[str]] = None, user_identity_keys: Optional[List[str]] = None, user_keys: Optional[List[str]] = None) -> None: + @route_metadata( + path="/customers/delete_data", + has_required_parameters=False, + has_pagination=False, + ) + def delete_data( + self, + *, + access_grant_keys: Optional[List[str]] = None, + booking_keys: Optional[List[str]] = None, + building_keys: Optional[List[str]] = None, + common_area_keys: Optional[List[str]] = None, + customer_keys: Optional[List[str]] = None, + facility_keys: Optional[List[str]] = None, + guest_keys: Optional[List[str]] = None, + listing_keys: Optional[List[str]] = None, + property_keys: Optional[List[str]] = None, + property_listing_keys: Optional[List[str]] = None, + reservation_keys: Optional[List[str]] = None, + resident_keys: Optional[List[str]] = None, + room_keys: Optional[List[str]] = None, + space_keys: Optional[List[str]] = None, + staff_member_keys: Optional[List[str]] = None, + tenant_keys: Optional[List[str]] = None, + unit_keys: Optional[List[str]] = None, + user_identity_keys: Optional[List[str]] = None, + user_keys: Optional[List[str]] = None, + ) -> None: """Deletes customer data including resources like spaces, properties, rooms, users, etc. This will delete the partner resources and any related Seam resources (user identities, access grants, spaces). @@ -232,53 +335,78 @@ def delete_data(self, *, access_grant_keys: Optional[List[str]] = None, booking_ :param user_identity_keys: List of user identity keys to delete. :param user_keys: List of user keys to delete.""" - json_payload: Dict[str, Any] = {} + params: Dict[str, Any] = {} if access_grant_keys is not None: - json_payload["access_grant_keys"] = access_grant_keys + params["access_grant_keys"] = access_grant_keys if booking_keys is not None: - json_payload["booking_keys"] = booking_keys + params["booking_keys"] = booking_keys if building_keys is not None: - json_payload["building_keys"] = building_keys + params["building_keys"] = building_keys if common_area_keys is not None: - json_payload["common_area_keys"] = common_area_keys + params["common_area_keys"] = common_area_keys if customer_keys is not None: - json_payload["customer_keys"] = customer_keys + params["customer_keys"] = customer_keys if facility_keys is not None: - json_payload["facility_keys"] = facility_keys + params["facility_keys"] = facility_keys if guest_keys is not None: - json_payload["guest_keys"] = guest_keys + params["guest_keys"] = guest_keys if listing_keys is not None: - json_payload["listing_keys"] = listing_keys + params["listing_keys"] = listing_keys if property_keys is not None: - json_payload["property_keys"] = property_keys + params["property_keys"] = property_keys if property_listing_keys is not None: - json_payload["property_listing_keys"] = property_listing_keys + params["property_listing_keys"] = property_listing_keys if reservation_keys is not None: - json_payload["reservation_keys"] = reservation_keys + params["reservation_keys"] = reservation_keys if resident_keys is not None: - json_payload["resident_keys"] = resident_keys + params["resident_keys"] = resident_keys if room_keys is not None: - json_payload["room_keys"] = room_keys + params["room_keys"] = room_keys if space_keys is not None: - json_payload["space_keys"] = space_keys + params["space_keys"] = space_keys if staff_member_keys is not None: - json_payload["staff_member_keys"] = staff_member_keys + params["staff_member_keys"] = staff_member_keys if tenant_keys is not None: - json_payload["tenant_keys"] = tenant_keys + params["tenant_keys"] = tenant_keys if unit_keys is not None: - json_payload["unit_keys"] = unit_keys + params["unit_keys"] = unit_keys if user_identity_keys is not None: - json_payload["user_identity_keys"] = user_identity_keys + params["user_identity_keys"] = user_identity_keys if user_keys is not None: - json_payload["user_keys"] = user_keys + params["user_keys"] = user_keys - self.client.post("/customers/delete_data", json=json_payload) + self.client.delete("/customers/delete_data", params=params) return None - @route_metadata(path="/customers/push_data", has_required_parameters=True, has_pagination=False) - def push_data(self, *, customer_key: str, access_grants: Optional[List[Dict[str, Any]]] = None, bookings: Optional[List[Dict[str, Any]]] = None, buildings: Optional[List[Dict[str, Any]]] = None, common_areas: Optional[List[Dict[str, Any]]] = None, facilities: Optional[List[Dict[str, Any]]] = None, guests: Optional[List[Dict[str, Any]]] = None, listings: Optional[List[Dict[str, Any]]] = None, properties: Optional[List[Dict[str, Any]]] = None, property_listings: Optional[List[Dict[str, Any]]] = None, reservations: Optional[List[Dict[str, Any]]] = None, residents: Optional[List[Dict[str, Any]]] = None, rooms: Optional[List[Dict[str, Any]]] = None, sites: Optional[List[Dict[str, Any]]] = None, spaces: Optional[List[Dict[str, Any]]] = None, staff_members: Optional[List[Dict[str, Any]]] = None, tenants: Optional[List[Dict[str, Any]]] = None, units: Optional[List[Dict[str, Any]]] = None, user_identities: Optional[List[Dict[str, Any]]] = None, users: Optional[List[Dict[str, Any]]] = None) -> None: + @route_metadata( + path="/customers/push_data", has_required_parameters=True, has_pagination=False + ) + def push_data( + self, + *, + customer_key: str, + access_grants: Optional[List[Dict[str, Any]]] = None, + bookings: Optional[List[Dict[str, Any]]] = None, + buildings: Optional[List[Dict[str, Any]]] = None, + common_areas: Optional[List[Dict[str, Any]]] = None, + facilities: Optional[List[Dict[str, Any]]] = None, + guests: Optional[List[Dict[str, Any]]] = None, + listings: Optional[List[Dict[str, Any]]] = None, + properties: Optional[List[Dict[str, Any]]] = None, + property_listings: Optional[List[Dict[str, Any]]] = None, + reservations: Optional[List[Dict[str, Any]]] = None, + residents: Optional[List[Dict[str, Any]]] = None, + rooms: Optional[List[Dict[str, Any]]] = None, + sites: Optional[List[Dict[str, Any]]] = None, + spaces: Optional[List[Dict[str, Any]]] = None, + staff_members: Optional[List[Dict[str, Any]]] = None, + tenants: Optional[List[Dict[str, Any]]] = None, + units: Optional[List[Dict[str, Any]]] = None, + user_identities: Optional[List[Dict[str, Any]]] = None, + users: Optional[List[Dict[str, Any]]] = None, + ) -> None: """Pushes customer data including resources like spaces, properties, rooms, users, etc. :param customer_key: Your unique identifier for the customer. @@ -366,7 +494,9 @@ def push_data(self, *, customer_key: str, access_grants: Optional[List[Dict[str, json_payload["users"] = users if not json_payload: - raise ValueError("At least one parameter is required for /customers/push_data") + raise ValueError( + "At least one parameter is required for /customers/push_data" + ) self.client.post("/customers/push_data", json=json_payload) diff --git a/seam/routes/devices.py b/seam/routes/devices.py index dc95c503..519d619b 100644 --- a/seam/routes/devices.py +++ b/seam/routes/devices.py @@ -3,7 +3,7 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (Device,DeviceProvider) +from ..resources import Device, DeviceProvider from .devices_simulate import AbstractDevicesSimulate, DevicesSimulate from .devices_unmanaged import AbstractDevicesUnmanaged, DevicesUnmanaged @@ -21,9 +21,11 @@ def unmanaged(self) -> AbstractDevicesUnmanaged: raise NotImplementedError() @abc.abstractmethod - def get(self, *, device_id: Optional[str] = None, name: Optional[str] = None) -> Device: + def get( + self, *, device_id: Optional[str] = None, name: Optional[str] = None + ) -> Device: """Returns a specified `device `_. - + You must specify either ``device_id`` or ``name``. :param device_id: ID of the device that you want to get. @@ -36,7 +38,26 @@ def get(self, *, device_id: Optional[str] = None, name: Optional[str] = None) -> raise NotImplementedError() @abc.abstractmethod - def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id: Optional[str] = None, connected_account_ids: Optional[List[str]] = None, created_before: Optional[str] = None, custom_metadata_has: Optional[Dict[str, Any]] = None, customer_key: Optional[str] = None, device_ids: Optional[List[str]] = None, device_type: Optional[str] = None, device_types: Optional[List[str]] = None, limit: Optional[float] = None, manufacturer: Optional[str] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, space_id: Optional[str] = None, unstable_location_id: Optional[Union[str, Null]] = None, user_identifier_key: Optional[str] = None) -> List[Device]: + def list( + self, + *, + connect_webview_id: Optional[str] = None, + connected_account_id: Optional[str] = None, + connected_account_ids: Optional[List[str]] = None, + created_before: Optional[str] = None, + custom_metadata_has: Optional[Dict[str, Any]] = None, + customer_key: Optional[str] = None, + device_ids: Optional[List[str]] = None, + device_type: Optional[str] = None, + device_types: Optional[List[str]] = None, + limit: Optional[float] = None, + manufacturer: Optional[str] = None, + page_cursor: Optional[Union[str, Null]] = None, + search: Optional[str] = None, + space_id: Optional[str] = None, + unstable_location_id: Optional[Union[str, Null]] = None, + user_identifier_key: Optional[str] = None, + ) -> List[Device]: """Returns a list of all `devices `_. :param connect_webview_id: ID of the Connect Webview for which you want to list devices. @@ -75,11 +96,13 @@ def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id raise NotImplementedError() @abc.abstractmethod - def list_device_providers(self, *, provider_category: Optional[str] = None) -> List[DeviceProvider]: + def list_device_providers( + self, *, provider_category: Optional[str] = None + ) -> List[DeviceProvider]: """Returns a list of all device providers. - + The information that this endpoint returns for each provider includes a set of `capability flags `_, such as ``device_provider.can_remotely_unlock``. If at least one supported device from a provider has a specific capability, the corresponding capability flag is ``true``. - + When you create a `Connect Webview `_, you can customize the providers—that is, the brands—that it displays. In the ``/connect_webviews/create`` request, include the desired set of device provider keys in the ``accepted_providers`` parameter. See also `Customize the Brands to Display in Your Connect Webviews `_. :param provider_category: Category for which you want to list providers. @@ -97,9 +120,18 @@ def report_provider_metadata(self, *, devices: List[Dict[str, Any]]) -> None: raise NotImplementedError() @abc.abstractmethod - def update(self, *, device_id: str, backup_access_code_pool_enabled: Optional[bool] = None, custom_metadata: Optional[Dict[str, Any]] = None, is_managed: Optional[bool] = None, name: Optional[Union[str, Null]] = None, properties: Optional[Dict[str, Any]] = None) -> None: + def update( + self, + *, + device_id: str, + backup_access_code_pool_enabled: Optional[bool] = None, + custom_metadata: Optional[Dict[str, Any]] = None, + is_managed: Optional[bool] = None, + name: Optional[Union[str, Null]] = None, + properties: Optional[Dict[str, Any]] = None, + ) -> None: """Updates a specified `device `_. - + You can add or change `custom metadata `_ for a device, change the device's name, or `convert a managed device to unmanaged `_. :param device_id: ID of the device that you want to update. @@ -112,7 +144,7 @@ def update(self, *, device_id: str, backup_access_code_pool_enabled: Optional[bo :param name: Name for the device. - :param properties: + :param properties: :raises ValueError: At least one parameter must be provided.""" raise NotImplementedError() @@ -133,10 +165,14 @@ def simulate(self) -> DevicesSimulate: def unmanaged(self) -> DevicesUnmanaged: return self._unmanaged - @route_metadata(path="/devices/get", has_required_parameters=True, has_pagination=False) - def get(self, *, device_id: Optional[str] = None, name: Optional[str] = None) -> Device: + @route_metadata( + path="/devices/get", has_required_parameters=True, has_pagination=False + ) + def get( + self, *, device_id: Optional[str] = None, name: Optional[str] = None + ) -> Device: """Returns a specified `device `_. - + You must specify either ``device_id`` or ``name``. :param device_id: ID of the device that you want to get. @@ -160,8 +196,29 @@ def get(self, *, device_id: Optional[str] = None, name: Optional[str] = None) -> return Device.from_dict(res["device"]) - @route_metadata(path="/devices/list", has_required_parameters=False, has_pagination=True) - def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id: Optional[str] = None, connected_account_ids: Optional[List[str]] = None, created_before: Optional[str] = None, custom_metadata_has: Optional[Dict[str, Any]] = None, customer_key: Optional[str] = None, device_ids: Optional[List[str]] = None, device_type: Optional[str] = None, device_types: Optional[List[str]] = None, limit: Optional[float] = None, manufacturer: Optional[str] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, space_id: Optional[str] = None, unstable_location_id: Optional[Union[str, Null]] = None, user_identifier_key: Optional[str] = None) -> List[Device]: + @route_metadata( + path="/devices/list", has_required_parameters=False, has_pagination=True + ) + def list( + self, + *, + connect_webview_id: Optional[str] = None, + connected_account_id: Optional[str] = None, + connected_account_ids: Optional[List[str]] = None, + created_before: Optional[str] = None, + custom_metadata_has: Optional[Dict[str, Any]] = None, + customer_key: Optional[str] = None, + device_ids: Optional[List[str]] = None, + device_type: Optional[str] = None, + device_types: Optional[List[str]] = None, + limit: Optional[float] = None, + manufacturer: Optional[str] = None, + page_cursor: Optional[Union[str, Null]] = None, + search: Optional[str] = None, + space_id: Optional[str] = None, + unstable_location_id: Optional[Union[str, Null]] = None, + user_identifier_key: Optional[str] = None, + ) -> List[Device]: """Returns a list of all `devices `_. :param connect_webview_id: ID of the Connect Webview for which you want to list devices. @@ -197,51 +254,57 @@ def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id :param user_identifier_key: Your own internal user ID for the user for which you want to list devices. :returns: OK""" - json_payload: Dict[str, Any] = {} + params: Dict[str, Any] = {} if connect_webview_id is not None: - json_payload["connect_webview_id"] = connect_webview_id + params["connect_webview_id"] = connect_webview_id if connected_account_id is not None: - json_payload["connected_account_id"] = connected_account_id + params["connected_account_id"] = connected_account_id if connected_account_ids is not None: - json_payload["connected_account_ids"] = connected_account_ids + params["connected_account_ids"] = connected_account_ids if created_before is not None: - json_payload["created_before"] = created_before + params["created_before"] = created_before if custom_metadata_has is not None: - json_payload["custom_metadata_has"] = custom_metadata_has + params["custom_metadata_has"] = custom_metadata_has if customer_key is not None: - json_payload["customer_key"] = customer_key + params["customer_key"] = customer_key if device_ids is not None: - json_payload["device_ids"] = device_ids + params["device_ids"] = device_ids if device_type is not None: - json_payload["device_type"] = device_type + params["device_type"] = device_type if device_types is not None: - json_payload["device_types"] = device_types + params["device_types"] = device_types if limit is not None: - json_payload["limit"] = limit + params["limit"] = limit if manufacturer is not None: - json_payload["manufacturer"] = manufacturer + params["manufacturer"] = manufacturer if page_cursor is not None: - json_payload["page_cursor"] = page_cursor + params["page_cursor"] = page_cursor if search is not None: - json_payload["search"] = search + params["search"] = search if space_id is not None: - json_payload["space_id"] = space_id + params["space_id"] = space_id if unstable_location_id is not None: - json_payload["unstable_location_id"] = unstable_location_id + params["unstable_location_id"] = unstable_location_id if user_identifier_key is not None: - json_payload["user_identifier_key"] = user_identifier_key + params["user_identifier_key"] = user_identifier_key - res = self.client.post("/devices/list", json=json_payload) + res = self.client.get("/devices/list", params=params) return [Device.from_dict(item) for item in res["devices"]] - @route_metadata(path="/devices/list_device_providers", has_required_parameters=False, has_pagination=False) - def list_device_providers(self, *, provider_category: Optional[str] = None) -> List[DeviceProvider]: + @route_metadata( + path="/devices/list_device_providers", + has_required_parameters=False, + has_pagination=False, + ) + def list_device_providers( + self, *, provider_category: Optional[str] = None + ) -> List[DeviceProvider]: """Returns a list of all device providers. - + The information that this endpoint returns for each provider includes a set of `capability flags `_, such as ``device_provider.can_remotely_unlock``. If at least one supported device from a provider has a specific capability, the corresponding capability flag is ``true``. - + When you create a `Connect Webview `_, you can customize the providers—that is, the brands—that it displays. In the ``/connect_webviews/create`` request, include the desired set of device provider keys in the ``accepted_providers`` parameter. See also `Customize the Brands to Display in Your Connect Webviews `_. :param provider_category: Category for which you want to list providers. @@ -256,7 +319,11 @@ def list_device_providers(self, *, provider_category: Optional[str] = None) -> L return [DeviceProvider.from_dict(item) for item in res["device_providers"]] - @route_metadata(path="/devices/report_provider_metadata", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/devices/report_provider_metadata", + has_required_parameters=True, + has_pagination=False, + ) def report_provider_metadata(self, *, devices: List[Dict[str, Any]]) -> None: """Updates provider-specific metadata for devices. @@ -269,16 +336,29 @@ def report_provider_metadata(self, *, devices: List[Dict[str, Any]]) -> None: json_payload["devices"] = devices if not json_payload: - raise ValueError("At least one parameter is required for /devices/report_provider_metadata") + raise ValueError( + "At least one parameter is required for /devices/report_provider_metadata" + ) self.client.post("/devices/report_provider_metadata", json=json_payload) return None - @route_metadata(path="/devices/update", has_required_parameters=True, has_pagination=False) - def update(self, *, device_id: str, backup_access_code_pool_enabled: Optional[bool] = None, custom_metadata: Optional[Dict[str, Any]] = None, is_managed: Optional[bool] = None, name: Optional[Union[str, Null]] = None, properties: Optional[Dict[str, Any]] = None) -> None: + @route_metadata( + path="/devices/update", has_required_parameters=True, has_pagination=False + ) + def update( + self, + *, + device_id: str, + backup_access_code_pool_enabled: Optional[bool] = None, + custom_metadata: Optional[Dict[str, Any]] = None, + is_managed: Optional[bool] = None, + name: Optional[Union[str, Null]] = None, + properties: Optional[Dict[str, Any]] = None, + ) -> None: """Updates a specified `device `_. - + You can add or change `custom metadata `_ for a device, change the device's name, or `convert a managed device to unmanaged `_. :param device_id: ID of the device that you want to update. @@ -291,7 +371,7 @@ def update(self, *, device_id: str, backup_access_code_pool_enabled: Optional[bo :param name: Name for the device. - :param properties: + :param properties: :raises ValueError: At least one parameter must be provided.""" json_payload: Dict[str, Any] = {} @@ -299,7 +379,9 @@ def update(self, *, device_id: str, backup_access_code_pool_enabled: Optional[bo if device_id is not None: json_payload["device_id"] = device_id if backup_access_code_pool_enabled is not None: - json_payload["backup_access_code_pool_enabled"] = backup_access_code_pool_enabled + json_payload["backup_access_code_pool_enabled"] = ( + backup_access_code_pool_enabled + ) if custom_metadata is not None: json_payload["custom_metadata"] = custom_metadata if is_managed is not None: diff --git a/seam/routes/devices_simulate.py b/seam/routes/devices_simulate.py index 3aab8490..57b2d418 100644 --- a/seam/routes/devices_simulate.py +++ b/seam/routes/devices_simulate.py @@ -56,9 +56,9 @@ def paid_subscription(self, *, device_id: str, is_expired: bool) -> None: Send ``is_expired: true`` to simulate an expired subscription, or ``false`` to simulate an active subscription. The actual device error is created/cleared by the poller after this state change. - :param device_id: + :param device_id: - :param is_expired: + :param is_expired: :raises ValueError: At least one parameter must be provided.""" raise NotImplementedError() @@ -78,7 +78,11 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/devices/simulate/connect", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/devices/simulate/connect", + has_required_parameters=True, + has_pagination=False, + ) def connect(self, *, device_id: str) -> None: """Simulates connecting a device to Seam. Only applicable for `sandbox devices `_. See also `Testing Your App Against Device Disconnection and Removal `_. @@ -91,13 +95,19 @@ def connect(self, *, device_id: str) -> None: json_payload["device_id"] = device_id if not json_payload: - raise ValueError("At least one parameter is required for /devices/simulate/connect") + raise ValueError( + "At least one parameter is required for /devices/simulate/connect" + ) self.client.post("/devices/simulate/connect", json=json_payload) return None - @route_metadata(path="/devices/simulate/connect_to_hub", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/devices/simulate/connect_to_hub", + has_required_parameters=True, + has_pagination=False, + ) def connect_to_hub(self, *, device_id: str) -> None: """Simulates bringing the Wi‑Fi hub (bridge) back online for a device. Only applicable for sandbox workspaces and currently @@ -113,13 +123,19 @@ def connect_to_hub(self, *, device_id: str) -> None: json_payload["device_id"] = device_id if not json_payload: - raise ValueError("At least one parameter is required for /devices/simulate/connect_to_hub") + raise ValueError( + "At least one parameter is required for /devices/simulate/connect_to_hub" + ) self.client.post("/devices/simulate/connect_to_hub", json=json_payload) return None - @route_metadata(path="/devices/simulate/disconnect", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/devices/simulate/disconnect", + has_required_parameters=True, + has_pagination=False, + ) def disconnect(self, *, device_id: str) -> None: """Simulates disconnecting a device from Seam. Only applicable for `sandbox devices `_. See also `Testing Your App Against Device Disconnection and Removal `_. @@ -132,13 +148,19 @@ def disconnect(self, *, device_id: str) -> None: json_payload["device_id"] = device_id if not json_payload: - raise ValueError("At least one parameter is required for /devices/simulate/disconnect") + raise ValueError( + "At least one parameter is required for /devices/simulate/disconnect" + ) self.client.post("/devices/simulate/disconnect", json=json_payload) return None - @route_metadata(path="/devices/simulate/disconnect_from_hub", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/devices/simulate/disconnect_from_hub", + has_required_parameters=True, + has_pagination=False, + ) def disconnect_from_hub(self, *, device_id: str) -> None: """Simulates taking the Wi‑Fi hub (bridge) offline for a device. Only applicable for sandbox workspaces and currently @@ -155,21 +177,27 @@ def disconnect_from_hub(self, *, device_id: str) -> None: json_payload["device_id"] = device_id if not json_payload: - raise ValueError("At least one parameter is required for /devices/simulate/disconnect_from_hub") + raise ValueError( + "At least one parameter is required for /devices/simulate/disconnect_from_hub" + ) self.client.post("/devices/simulate/disconnect_from_hub", json=json_payload) return None - @route_metadata(path="/devices/simulate/paid_subscription", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/devices/simulate/paid_subscription", + has_required_parameters=True, + has_pagination=False, + ) def paid_subscription(self, *, device_id: str, is_expired: bool) -> None: """Toggle the simulated Nuki Smart Hosting subscription for a device (sandbox only). Send ``is_expired: true`` to simulate an expired subscription, or ``false`` to simulate an active subscription. The actual device error is created/cleared by the poller after this state change. - :param device_id: + :param device_id: - :param is_expired: + :param is_expired: :raises ValueError: At least one parameter must be provided.""" json_payload: Dict[str, Any] = {} @@ -180,13 +208,19 @@ def paid_subscription(self, *, device_id: str, is_expired: bool) -> None: json_payload["is_expired"] = is_expired if not json_payload: - raise ValueError("At least one parameter is required for /devices/simulate/paid_subscription") + raise ValueError( + "At least one parameter is required for /devices/simulate/paid_subscription" + ) self.client.post("/devices/simulate/paid_subscription", json=json_payload) return None - @route_metadata(path="/devices/simulate/remove", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/devices/simulate/remove", + has_required_parameters=True, + has_pagination=False, + ) def remove(self, *, device_id: str) -> None: """Simulates removing a device from Seam. Only applicable for `sandbox devices `_. See also `Testing Your App Against Device Disconnection and Removal `_. @@ -199,7 +233,9 @@ def remove(self, *, device_id: str) -> None: json_payload["device_id"] = device_id if not json_payload: - raise ValueError("At least one parameter is required for /devices/simulate/remove") + raise ValueError( + "At least one parameter is required for /devices/simulate/remove" + ) self.client.post("/devices/simulate/remove", json=json_payload) diff --git a/seam/routes/devices_unmanaged.py b/seam/routes/devices_unmanaged.py index 6643a8e1..dbe81f49 100644 --- a/seam/routes/devices_unmanaged.py +++ b/seam/routes/devices_unmanaged.py @@ -3,17 +3,19 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (UnmanagedDevice) +from ..resources import UnmanagedDevice class AbstractDevicesUnmanaged(abc.ABC): @abc.abstractmethod - def get(self, *, device_id: Optional[str] = None, name: Optional[str] = None) -> UnmanagedDevice: + def get( + self, *, device_id: Optional[str] = None, name: Optional[str] = None + ) -> UnmanagedDevice: """Returns a specified `unmanaged device `_. - + An unmanaged device has a limited set of visible properties and a subset of supported events. You cannot control an unmanaged device. Any `access codes `_ on an unmanaged device are unmanaged. To control an unmanaged device with Seam, `convert it to a managed device `_. - + You must specify either ``device_id`` or ``name``. :param device_id: ID of the unmanaged device that you want to get. @@ -26,9 +28,24 @@ def get(self, *, device_id: Optional[str] = None, name: Optional[str] = None) -> raise NotImplementedError() @abc.abstractmethod - def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id: Optional[str] = None, connected_account_ids: Optional[List[str]] = None, created_before: Optional[str] = None, customer_key: Optional[str] = None, device_ids: Optional[List[str]] = None, device_type: Optional[str] = None, device_types: Optional[List[str]] = None, limit: Optional[float] = None, manufacturer: Optional[str] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None) -> List[UnmanagedDevice]: + def list( + self, + *, + connect_webview_id: Optional[str] = None, + connected_account_id: Optional[str] = None, + connected_account_ids: Optional[List[str]] = None, + created_before: Optional[str] = None, + customer_key: Optional[str] = None, + device_ids: Optional[List[str]] = None, + device_type: Optional[str] = None, + device_types: Optional[List[str]] = None, + limit: Optional[float] = None, + manufacturer: Optional[str] = None, + page_cursor: Optional[Union[str, Null]] = None, + search: Optional[str] = None, + ) -> List[UnmanagedDevice]: """Returns a list of all `unmanaged devices `_. - + An unmanaged device has a limited set of visible properties and a subset of supported events. You cannot control an unmanaged device. Any `access codes `_ on an unmanaged device are unmanaged. To control an unmanaged device with Seam, `convert it to a managed device `_. :param connect_webview_id: ID of the Connect Webview for which you want to list devices. @@ -59,9 +76,15 @@ def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id raise NotImplementedError() @abc.abstractmethod - def update(self, *, device_id: str, custom_metadata: Optional[Dict[str, Any]] = None, is_managed: Optional[bool] = None) -> None: + def update( + self, + *, + device_id: str, + custom_metadata: Optional[Dict[str, Any]] = None, + is_managed: Optional[bool] = None, + ) -> None: """Updates a specified `unmanaged device `_. To convert an unmanaged device to managed, set ``is_managed`` to ``true``. - + An unmanaged device has a limited set of visible properties and a subset of supported events. You cannot control an unmanaged device. Any `access codes `_ on an unmanaged device are unmanaged. To control an unmanaged device with Seam, `convert it to a managed device `_. :param device_id: ID of the unmanaged device that you want to update. @@ -79,12 +102,18 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/devices/unmanaged/get", has_required_parameters=True, has_pagination=False) - def get(self, *, device_id: Optional[str] = None, name: Optional[str] = None) -> UnmanagedDevice: + @route_metadata( + path="/devices/unmanaged/get", + has_required_parameters=True, + has_pagination=False, + ) + def get( + self, *, device_id: Optional[str] = None, name: Optional[str] = None + ) -> UnmanagedDevice: """Returns a specified `unmanaged device `_. - + An unmanaged device has a limited set of visible properties and a subset of supported events. You cannot control an unmanaged device. Any `access codes `_ on an unmanaged device are unmanaged. To control an unmanaged device with Seam, `convert it to a managed device `_. - + You must specify either ``device_id`` or ``name``. :param device_id: ID of the unmanaged device that you want to get. @@ -102,16 +131,37 @@ def get(self, *, device_id: Optional[str] = None, name: Optional[str] = None) -> params["name"] = name if not params: - raise ValueError("At least one parameter is required for /devices/unmanaged/get") + raise ValueError( + "At least one parameter is required for /devices/unmanaged/get" + ) res = self.client.get("/devices/unmanaged/get", params=params) return UnmanagedDevice.from_dict(res["device"]) - @route_metadata(path="/devices/unmanaged/list", has_required_parameters=False, has_pagination=True) - def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id: Optional[str] = None, connected_account_ids: Optional[List[str]] = None, created_before: Optional[str] = None, customer_key: Optional[str] = None, device_ids: Optional[List[str]] = None, device_type: Optional[str] = None, device_types: Optional[List[str]] = None, limit: Optional[float] = None, manufacturer: Optional[str] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None) -> List[UnmanagedDevice]: + @route_metadata( + path="/devices/unmanaged/list", + has_required_parameters=False, + has_pagination=True, + ) + def list( + self, + *, + connect_webview_id: Optional[str] = None, + connected_account_id: Optional[str] = None, + connected_account_ids: Optional[List[str]] = None, + created_before: Optional[str] = None, + customer_key: Optional[str] = None, + device_ids: Optional[List[str]] = None, + device_type: Optional[str] = None, + device_types: Optional[List[str]] = None, + limit: Optional[float] = None, + manufacturer: Optional[str] = None, + page_cursor: Optional[Union[str, Null]] = None, + search: Optional[str] = None, + ) -> List[UnmanagedDevice]: """Returns a list of all `unmanaged devices `_. - + An unmanaged device has a limited set of visible properties and a subset of supported events. You cannot control an unmanaged device. Any `access codes `_ on an unmanaged device are unmanaged. To control an unmanaged device with Seam, `convert it to a managed device `_. :param connect_webview_id: ID of the Connect Webview for which you want to list devices. @@ -139,41 +189,51 @@ def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id :param search: String for which to search. Filters returned devices to include all records that satisfy a partial match using ``device_id`` (full or partial UUID prefix, minimum 4 characters), ``connected_account_id``, ``display_name``, ``custom_metadata`` or ``location.location_name``. :returns: OK""" - json_payload: Dict[str, Any] = {} + params: Dict[str, Any] = {} if connect_webview_id is not None: - json_payload["connect_webview_id"] = connect_webview_id + params["connect_webview_id"] = connect_webview_id if connected_account_id is not None: - json_payload["connected_account_id"] = connected_account_id + params["connected_account_id"] = connected_account_id if connected_account_ids is not None: - json_payload["connected_account_ids"] = connected_account_ids + params["connected_account_ids"] = connected_account_ids if created_before is not None: - json_payload["created_before"] = created_before + params["created_before"] = created_before if customer_key is not None: - json_payload["customer_key"] = customer_key + params["customer_key"] = customer_key if device_ids is not None: - json_payload["device_ids"] = device_ids + params["device_ids"] = device_ids if device_type is not None: - json_payload["device_type"] = device_type + params["device_type"] = device_type if device_types is not None: - json_payload["device_types"] = device_types + params["device_types"] = device_types if limit is not None: - json_payload["limit"] = limit + params["limit"] = limit if manufacturer is not None: - json_payload["manufacturer"] = manufacturer + params["manufacturer"] = manufacturer if page_cursor is not None: - json_payload["page_cursor"] = page_cursor + params["page_cursor"] = page_cursor if search is not None: - json_payload["search"] = search + params["search"] = search - res = self.client.post("/devices/unmanaged/list", json=json_payload) + res = self.client.get("/devices/unmanaged/list", params=params) return [UnmanagedDevice.from_dict(item) for item in res["devices"]] - @route_metadata(path="/devices/unmanaged/update", has_required_parameters=True, has_pagination=False) - def update(self, *, device_id: str, custom_metadata: Optional[Dict[str, Any]] = None, is_managed: Optional[bool] = None) -> None: + @route_metadata( + path="/devices/unmanaged/update", + has_required_parameters=True, + has_pagination=False, + ) + def update( + self, + *, + device_id: str, + custom_metadata: Optional[Dict[str, Any]] = None, + is_managed: Optional[bool] = None, + ) -> None: """Updates a specified `unmanaged device `_. To convert an unmanaged device to managed, set ``is_managed`` to ``true``. - + An unmanaged device has a limited set of visible properties and a subset of supported events. You cannot control an unmanaged device. Any `access codes `_ on an unmanaged device are unmanaged. To control an unmanaged device with Seam, `convert it to a managed device `_. :param device_id: ID of the unmanaged device that you want to update. @@ -193,7 +253,9 @@ def update(self, *, device_id: str, custom_metadata: Optional[Dict[str, Any]] = json_payload["is_managed"] = is_managed if not json_payload: - raise ValueError("At least one parameter is required for /devices/unmanaged/update") + raise ValueError( + "At least one parameter is required for /devices/unmanaged/update" + ) self.client.patch("/devices/unmanaged/update", json=json_payload) diff --git a/seam/routes/events.py b/seam/routes/events.py index 938c269d..f889ae49 100644 --- a/seam/routes/events.py +++ b/seam/routes/events.py @@ -3,13 +3,19 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (SeamEvent) +from ..resources import SeamEvent class AbstractEvents(abc.ABC): @abc.abstractmethod - def get(self, *, event_id: Optional[str] = None, device_id: Optional[str] = None, event_type: Optional[str] = None) -> SeamEvent: + def get( + self, + *, + event_id: Optional[str] = None, + device_id: Optional[str] = None, + event_type: Optional[str] = None, + ) -> SeamEvent: """Returns a specified event. This endpoint returns the same event that would be sent to a `webhook `_, but it enables you to retrieve an event that already took place. :param event_id: Unique identifier for the event that you want to get. @@ -24,7 +30,38 @@ def get(self, *, event_id: Optional[str] = None, device_id: Optional[str] = None raise NotImplementedError() @abc.abstractmethod - def list(self, *, access_code_id: Optional[str] = None, access_code_ids: Optional[List[str]] = None, access_grant_id: Optional[str] = None, access_grant_ids: Optional[List[str]] = None, access_method_id: Optional[str] = None, access_method_ids: Optional[List[str]] = None, acs_access_group_id: Optional[str] = None, acs_credential_id: Optional[str] = None, acs_encoder_id: Optional[str] = None, acs_entrance_id: Optional[str] = None, acs_system_id: Optional[str] = None, acs_system_ids: Optional[List[str]] = None, acs_user_id: Optional[str] = None, between: Optional[List[str]] = None, connect_webview_id: Optional[str] = None, connected_account_id: Optional[str] = None, customer_key: Optional[str] = None, device_id: Optional[str] = None, device_ids: Optional[List[str]] = None, event_ids: Optional[List[str]] = None, event_type: Optional[str] = None, event_types: Optional[List[str]] = None, limit: Optional[float] = None, since: Optional[str] = None, space_id: Optional[str] = None, space_ids: Optional[List[str]] = None, unstable_offset: Optional[float] = None, user_identity_id: Optional[str] = None) -> List[SeamEvent]: + def list( + self, + *, + access_code_id: Optional[str] = None, + access_code_ids: Optional[List[str]] = None, + access_grant_id: Optional[str] = None, + access_grant_ids: Optional[List[str]] = None, + access_method_id: Optional[str] = None, + access_method_ids: Optional[List[str]] = None, + acs_access_group_id: Optional[str] = None, + acs_credential_id: Optional[str] = None, + acs_encoder_id: Optional[str] = None, + acs_entrance_id: Optional[str] = None, + acs_system_id: Optional[str] = None, + acs_system_ids: Optional[List[str]] = None, + acs_user_id: Optional[str] = None, + between: Optional[List[str]] = None, + connect_webview_id: Optional[str] = None, + connected_account_id: Optional[str] = None, + customer_key: Optional[str] = None, + device_id: Optional[str] = None, + device_ids: Optional[List[str]] = None, + event_ids: Optional[List[str]] = None, + event_type: Optional[str] = None, + event_types: Optional[List[str]] = None, + limit: Optional[float] = None, + since: Optional[str] = None, + space_id: Optional[str] = None, + space_ids: Optional[List[str]] = None, + unstable_offset: Optional[float] = None, + user_identity_id: Optional[str] = None, + ) -> List[SeamEvent]: """Returns a list of all events. This endpoint returns the same events that would be sent to a `webhook `_, but it enables you to filter or see events that already took place. :param access_code_id: ID of the access code for which you want to list events. @@ -94,8 +131,16 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/events/get", has_required_parameters=True, has_pagination=False) - def get(self, *, event_id: Optional[str] = None, device_id: Optional[str] = None, event_type: Optional[str] = None) -> SeamEvent: + @route_metadata( + path="/events/get", has_required_parameters=True, has_pagination=False + ) + def get( + self, + *, + event_id: Optional[str] = None, + device_id: Optional[str] = None, + event_type: Optional[str] = None, + ) -> SeamEvent: """Returns a specified event. This endpoint returns the same event that would be sent to a `webhook `_, but it enables you to retrieve an event that already took place. :param event_id: Unique identifier for the event that you want to get. @@ -123,8 +168,41 @@ def get(self, *, event_id: Optional[str] = None, device_id: Optional[str] = None return SeamEvent.from_dict(res["event"]) - @route_metadata(path="/events/list", has_required_parameters=True, has_pagination=False) - def list(self, *, access_code_id: Optional[str] = None, access_code_ids: Optional[List[str]] = None, access_grant_id: Optional[str] = None, access_grant_ids: Optional[List[str]] = None, access_method_id: Optional[str] = None, access_method_ids: Optional[List[str]] = None, acs_access_group_id: Optional[str] = None, acs_credential_id: Optional[str] = None, acs_encoder_id: Optional[str] = None, acs_entrance_id: Optional[str] = None, acs_system_id: Optional[str] = None, acs_system_ids: Optional[List[str]] = None, acs_user_id: Optional[str] = None, between: Optional[List[str]] = None, connect_webview_id: Optional[str] = None, connected_account_id: Optional[str] = None, customer_key: Optional[str] = None, device_id: Optional[str] = None, device_ids: Optional[List[str]] = None, event_ids: Optional[List[str]] = None, event_type: Optional[str] = None, event_types: Optional[List[str]] = None, limit: Optional[float] = None, since: Optional[str] = None, space_id: Optional[str] = None, space_ids: Optional[List[str]] = None, unstable_offset: Optional[float] = None, user_identity_id: Optional[str] = None) -> List[SeamEvent]: + @route_metadata( + path="/events/list", has_required_parameters=True, has_pagination=False + ) + def list( + self, + *, + access_code_id: Optional[str] = None, + access_code_ids: Optional[List[str]] = None, + access_grant_id: Optional[str] = None, + access_grant_ids: Optional[List[str]] = None, + access_method_id: Optional[str] = None, + access_method_ids: Optional[List[str]] = None, + acs_access_group_id: Optional[str] = None, + acs_credential_id: Optional[str] = None, + acs_encoder_id: Optional[str] = None, + acs_entrance_id: Optional[str] = None, + acs_system_id: Optional[str] = None, + acs_system_ids: Optional[List[str]] = None, + acs_user_id: Optional[str] = None, + between: Optional[List[str]] = None, + connect_webview_id: Optional[str] = None, + connected_account_id: Optional[str] = None, + customer_key: Optional[str] = None, + device_id: Optional[str] = None, + device_ids: Optional[List[str]] = None, + event_ids: Optional[List[str]] = None, + event_type: Optional[str] = None, + event_types: Optional[List[str]] = None, + limit: Optional[float] = None, + since: Optional[str] = None, + space_id: Optional[str] = None, + space_ids: Optional[List[str]] = None, + unstable_offset: Optional[float] = None, + user_identity_id: Optional[str] = None, + ) -> List[SeamEvent]: """Returns a list of all events. This endpoint returns the same events that would be sent to a `webhook `_, but it enables you to filter or see events that already took place. :param access_code_id: ID of the access code for which you want to list events. @@ -186,68 +264,68 @@ def list(self, *, access_code_id: Optional[str] = None, access_code_ids: Optiona :returns: OK :raises ValueError: At least one parameter must be provided.""" - json_payload: Dict[str, Any] = {} + params: Dict[str, Any] = {} if access_code_id is not None: - json_payload["access_code_id"] = access_code_id + params["access_code_id"] = access_code_id if access_code_ids is not None: - json_payload["access_code_ids"] = access_code_ids + params["access_code_ids"] = access_code_ids if access_grant_id is not None: - json_payload["access_grant_id"] = access_grant_id + params["access_grant_id"] = access_grant_id if access_grant_ids is not None: - json_payload["access_grant_ids"] = access_grant_ids + params["access_grant_ids"] = access_grant_ids if access_method_id is not None: - json_payload["access_method_id"] = access_method_id + params["access_method_id"] = access_method_id if access_method_ids is not None: - json_payload["access_method_ids"] = access_method_ids + params["access_method_ids"] = access_method_ids if acs_access_group_id is not None: - json_payload["acs_access_group_id"] = acs_access_group_id + params["acs_access_group_id"] = acs_access_group_id if acs_credential_id is not None: - json_payload["acs_credential_id"] = acs_credential_id + params["acs_credential_id"] = acs_credential_id if acs_encoder_id is not None: - json_payload["acs_encoder_id"] = acs_encoder_id + params["acs_encoder_id"] = acs_encoder_id if acs_entrance_id is not None: - json_payload["acs_entrance_id"] = acs_entrance_id + params["acs_entrance_id"] = acs_entrance_id if acs_system_id is not None: - json_payload["acs_system_id"] = acs_system_id + params["acs_system_id"] = acs_system_id if acs_system_ids is not None: - json_payload["acs_system_ids"] = acs_system_ids + params["acs_system_ids"] = acs_system_ids if acs_user_id is not None: - json_payload["acs_user_id"] = acs_user_id + params["acs_user_id"] = acs_user_id if between is not None: - json_payload["between"] = between + params["between"] = between if connect_webview_id is not None: - json_payload["connect_webview_id"] = connect_webview_id + params["connect_webview_id"] = connect_webview_id if connected_account_id is not None: - json_payload["connected_account_id"] = connected_account_id + params["connected_account_id"] = connected_account_id if customer_key is not None: - json_payload["customer_key"] = customer_key + params["customer_key"] = customer_key if device_id is not None: - json_payload["device_id"] = device_id + params["device_id"] = device_id if device_ids is not None: - json_payload["device_ids"] = device_ids + params["device_ids"] = device_ids if event_ids is not None: - json_payload["event_ids"] = event_ids + params["event_ids"] = event_ids if event_type is not None: - json_payload["event_type"] = event_type + params["event_type"] = event_type if event_types is not None: - json_payload["event_types"] = event_types + params["event_types"] = event_types if limit is not None: - json_payload["limit"] = limit + params["limit"] = limit if since is not None: - json_payload["since"] = since + params["since"] = since if space_id is not None: - json_payload["space_id"] = space_id + params["space_id"] = space_id if space_ids is not None: - json_payload["space_ids"] = space_ids + params["space_ids"] = space_ids if unstable_offset is not None: - json_payload["unstable_offset"] = unstable_offset + params["unstable_offset"] = unstable_offset if user_identity_id is not None: - json_payload["user_identity_id"] = user_identity_id + params["user_identity_id"] = user_identity_id - if not json_payload: + if not params: raise ValueError("At least one parameter is required for /events/list") - res = self.client.post("/events/list", json=json_payload) + res = self.client.get("/events/list", params=params) return [SeamEvent.from_dict(item) for item in res["events"]] diff --git a/seam/routes/instant_keys.py b/seam/routes/instant_keys.py index 4afea042..eb15278d 100644 --- a/seam/routes/instant_keys.py +++ b/seam/routes/instant_keys.py @@ -3,7 +3,7 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (InstantKey) +from ..resources import InstantKey class AbstractInstantKeys(abc.ABC): @@ -18,7 +18,12 @@ def delete(self, *, instant_key_id: str) -> None: raise NotImplementedError() @abc.abstractmethod - def get(self, *, instant_key_id: Optional[str] = None, instant_key_url: Optional[str] = None) -> InstantKey: + def get( + self, + *, + instant_key_id: Optional[str] = None, + instant_key_url: Optional[str] = None, + ) -> InstantKey: """Gets an `instant key `_. :param instant_key_id: ID of the instant key to get. @@ -45,7 +50,9 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/instant_keys/delete", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/instant_keys/delete", has_required_parameters=True, has_pagination=False + ) def delete(self, *, instant_key_id: str) -> None: """Deletes a specified `Instant Key `_. @@ -58,14 +65,23 @@ def delete(self, *, instant_key_id: str) -> None: params["instant_key_id"] = instant_key_id if not params: - raise ValueError("At least one parameter is required for /instant_keys/delete") + raise ValueError( + "At least one parameter is required for /instant_keys/delete" + ) self.client.delete("/instant_keys/delete", params=params) return None - @route_metadata(path="/instant_keys/get", has_required_parameters=True, has_pagination=False) - def get(self, *, instant_key_id: Optional[str] = None, instant_key_url: Optional[str] = None) -> InstantKey: + @route_metadata( + path="/instant_keys/get", has_required_parameters=True, has_pagination=False + ) + def get( + self, + *, + instant_key_id: Optional[str] = None, + instant_key_url: Optional[str] = None, + ) -> InstantKey: """Gets an `instant key `_. :param instant_key_id: ID of the instant key to get. @@ -89,7 +105,9 @@ def get(self, *, instant_key_id: Optional[str] = None, instant_key_url: Optional return InstantKey.from_dict(res["instant_key"]) - @route_metadata(path="/instant_keys/list", has_required_parameters=False, has_pagination=False) + @route_metadata( + path="/instant_keys/list", has_required_parameters=False, has_pagination=False + ) def list(self, *, user_identity_id: Optional[str] = None) -> List[InstantKey]: """Returns a list of all `instant keys `_. diff --git a/seam/routes/locks.py b/seam/routes/locks.py index 8803f722..45a7577c 100644 --- a/seam/routes/locks.py +++ b/seam/routes/locks.py @@ -3,7 +3,7 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (ActionAttempt,Device) +from ..resources import ActionAttempt, Device from .locks_simulate import AbstractLocksSimulate, LocksSimulate from ..modules.action_attempts import resolve_action_attempt @@ -16,7 +16,14 @@ def simulate(self) -> AbstractLocksSimulate: raise NotImplementedError() @abc.abstractmethod - def configure_auto_lock(self, *, auto_lock_enabled: bool, device_id: str, auto_lock_delay_seconds: Optional[float] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + def configure_auto_lock( + self, + *, + auto_lock_enabled: bool, + device_id: str, + auto_lock_delay_seconds: Optional[float] = None, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Configures the auto-lock setting for a specified `lock `_. :param auto_lock_enabled: Whether to enable or disable auto-lock. @@ -33,7 +40,9 @@ def configure_auto_lock(self, *, auto_lock_enabled: bool, device_id: str, auto_l raise NotImplementedError() @abc.abstractmethod - def get(self, *, device_id: Optional[str] = None, name: Optional[str] = None) -> Device: + def get( + self, *, device_id: Optional[str] = None, name: Optional[str] = None + ) -> Device: """Returns a specified `lock `_. :param device_id: ID of the lock that you want to get. @@ -49,7 +58,16 @@ def get(self, *, device_id: Optional[str] = None, name: Optional[str] = None) -> raise NotImplementedError() @abc.abstractmethod - def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id: Optional[str] = None, customer_key: Optional[str] = None, device_type: Optional[str] = None, device_types: Optional[List[str]] = None, manufacturer: Optional[str] = None) -> List[Device]: + def list( + self, + *, + connect_webview_id: Optional[str] = None, + connected_account_id: Optional[str] = None, + customer_key: Optional[str] = None, + device_type: Optional[str] = None, + device_types: Optional[List[str]] = None, + manufacturer: Optional[str] = None, + ) -> List[Device]: """Returns a list of all `locks `_. :param connect_webview_id: ID of the Connect Webview for which you want to list devices. @@ -68,7 +86,12 @@ def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id raise NotImplementedError() @abc.abstractmethod - def lock_door(self, *, device_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + def lock_door( + self, + *, + device_id: str, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Locks a `lock `_. See also `Locking and Unlocking Smart Locks `_. :param device_id: ID of the lock that you want to lock. @@ -81,7 +104,12 @@ def lock_door(self, *, device_id: str, wait_for_action_attempt: Optional[Union[b raise NotImplementedError() @abc.abstractmethod - def unlock_door(self, *, device_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + def unlock_door( + self, + *, + device_id: str, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Unlocks a `lock `_. See also `Locking and Unlocking Smart Locks `_. :param device_id: ID of the lock that you want to unlock. @@ -104,8 +132,19 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): def simulate(self) -> LocksSimulate: return self._simulate - @route_metadata(path="/locks/configure_auto_lock", has_required_parameters=True, has_pagination=False) - def configure_auto_lock(self, *, auto_lock_enabled: bool, device_id: str, auto_lock_delay_seconds: Optional[float] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + @route_metadata( + path="/locks/configure_auto_lock", + has_required_parameters=True, + has_pagination=False, + ) + def configure_auto_lock( + self, + *, + auto_lock_enabled: bool, + device_id: str, + auto_lock_delay_seconds: Optional[float] = None, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Configures the auto-lock setting for a specified `lock `_. :param auto_lock_enabled: Whether to enable or disable auto-lock. @@ -129,7 +168,9 @@ def configure_auto_lock(self, *, auto_lock_enabled: bool, device_id: str, auto_l json_payload["auto_lock_delay_seconds"] = auto_lock_delay_seconds if not json_payload: - raise ValueError("At least one parameter is required for /locks/configure_auto_lock") + raise ValueError( + "At least one parameter is required for /locks/configure_auto_lock" + ) res = self.client.post("/locks/configure_auto_lock", json=json_payload) @@ -142,11 +183,15 @@ def configure_auto_lock(self, *, auto_lock_enabled: bool, device_id: str, auto_l return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt + wait_for_action_attempt=wait_for_action_attempt, ) - @route_metadata(path="/locks/get", has_required_parameters=True, has_pagination=False) - def get(self, *, device_id: Optional[str] = None, name: Optional[str] = None) -> Device: + @route_metadata( + path="/locks/get", has_required_parameters=True, has_pagination=False + ) + def get( + self, *, device_id: Optional[str] = None, name: Optional[str] = None + ) -> Device: """Returns a specified `lock `_. :param device_id: ID of the lock that you want to get. @@ -173,8 +218,19 @@ def get(self, *, device_id: Optional[str] = None, name: Optional[str] = None) -> return Device.from_dict(res["device"]) - @route_metadata(path="/locks/list", has_required_parameters=False, has_pagination=False) - def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id: Optional[str] = None, customer_key: Optional[str] = None, device_type: Optional[str] = None, device_types: Optional[List[str]] = None, manufacturer: Optional[str] = None) -> List[Device]: + @route_metadata( + path="/locks/list", has_required_parameters=False, has_pagination=False + ) + def list( + self, + *, + connect_webview_id: Optional[str] = None, + connected_account_id: Optional[str] = None, + customer_key: Optional[str] = None, + device_type: Optional[str] = None, + device_types: Optional[List[str]] = None, + manufacturer: Optional[str] = None, + ) -> List[Device]: """Returns a list of all `locks `_. :param connect_webview_id: ID of the Connect Webview for which you want to list devices. @@ -190,27 +246,34 @@ def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id :param manufacturer: Manufacturer of the locks that you want to list. :returns: OK""" - json_payload: Dict[str, Any] = {} + params: Dict[str, Any] = {} if connect_webview_id is not None: - json_payload["connect_webview_id"] = connect_webview_id + params["connect_webview_id"] = connect_webview_id if connected_account_id is not None: - json_payload["connected_account_id"] = connected_account_id + params["connected_account_id"] = connected_account_id if customer_key is not None: - json_payload["customer_key"] = customer_key + params["customer_key"] = customer_key if device_type is not None: - json_payload["device_type"] = device_type + params["device_type"] = device_type if device_types is not None: - json_payload["device_types"] = device_types + params["device_types"] = device_types if manufacturer is not None: - json_payload["manufacturer"] = manufacturer + params["manufacturer"] = manufacturer - res = self.client.post("/locks/list", json=json_payload) + res = self.client.get("/locks/list", params=params) return [Device.from_dict(item) for item in res["devices"]] - @route_metadata(path="/locks/lock_door", has_required_parameters=True, has_pagination=False) - def lock_door(self, *, device_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + @route_metadata( + path="/locks/lock_door", has_required_parameters=True, has_pagination=False + ) + def lock_door( + self, + *, + device_id: str, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Locks a `lock `_. See also `Locking and Unlocking Smart Locks `_. :param device_id: ID of the lock that you want to lock. @@ -239,11 +302,18 @@ def lock_door(self, *, device_id: str, wait_for_action_attempt: Optional[Union[b return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt + wait_for_action_attempt=wait_for_action_attempt, ) - @route_metadata(path="/locks/unlock_door", has_required_parameters=True, has_pagination=False) - def unlock_door(self, *, device_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + @route_metadata( + path="/locks/unlock_door", has_required_parameters=True, has_pagination=False + ) + def unlock_door( + self, + *, + device_id: str, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Unlocks a `lock `_. See also `Locking and Unlocking Smart Locks `_. :param device_id: ID of the lock that you want to unlock. @@ -259,7 +329,9 @@ def unlock_door(self, *, device_id: str, wait_for_action_attempt: Optional[Union json_payload["device_id"] = device_id if not json_payload: - raise ValueError("At least one parameter is required for /locks/unlock_door") + raise ValueError( + "At least one parameter is required for /locks/unlock_door" + ) res = self.client.post("/locks/unlock_door", json=json_payload) @@ -272,5 +344,5 @@ def unlock_door(self, *, device_id: str, wait_for_action_attempt: Optional[Union return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt + wait_for_action_attempt=wait_for_action_attempt, ) diff --git a/seam/routes/locks_simulate.py b/seam/routes/locks_simulate.py index c8df2ddc..932c68fb 100644 --- a/seam/routes/locks_simulate.py +++ b/seam/routes/locks_simulate.py @@ -3,14 +3,20 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (ActionAttempt) +from ..resources import ActionAttempt from ..modules.action_attempts import resolve_action_attempt class AbstractLocksSimulate(abc.ABC): @abc.abstractmethod - def keypad_code_entry(self, *, code: str, device_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + def keypad_code_entry( + self, + *, + code: str, + device_id: str, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Simulates the entry of a code on a keypad. You can only perform this action for `August `_ devices within `sandbox workspaces `_. :param code: Code that you want to simulate entering on a keypad. @@ -25,7 +31,12 @@ def keypad_code_entry(self, *, code: str, device_id: str, wait_for_action_attemp raise NotImplementedError() @abc.abstractmethod - def manual_lock_via_keypad(self, *, device_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + def manual_lock_via_keypad( + self, + *, + device_id: str, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Simulates a manual lock action using a keypad. You can only perform this action for `August `_ devices within `sandbox workspaces `_. :param device_id: ID of the device for which you want to simulate a manual lock action using a keypad. @@ -43,8 +54,18 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/locks/simulate/keypad_code_entry", has_required_parameters=True, has_pagination=False) - def keypad_code_entry(self, *, code: str, device_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + @route_metadata( + path="/locks/simulate/keypad_code_entry", + has_required_parameters=True, + has_pagination=False, + ) + def keypad_code_entry( + self, + *, + code: str, + device_id: str, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Simulates the entry of a code on a keypad. You can only perform this action for `August `_ devices within `sandbox workspaces `_. :param code: Code that you want to simulate entering on a keypad. @@ -64,7 +85,9 @@ def keypad_code_entry(self, *, code: str, device_id: str, wait_for_action_attemp json_payload["device_id"] = device_id if not json_payload: - raise ValueError("At least one parameter is required for /locks/simulate/keypad_code_entry") + raise ValueError( + "At least one parameter is required for /locks/simulate/keypad_code_entry" + ) res = self.client.post("/locks/simulate/keypad_code_entry", json=json_payload) @@ -77,11 +100,20 @@ def keypad_code_entry(self, *, code: str, device_id: str, wait_for_action_attemp return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt + wait_for_action_attempt=wait_for_action_attempt, ) - @route_metadata(path="/locks/simulate/manual_lock_via_keypad", has_required_parameters=True, has_pagination=False) - def manual_lock_via_keypad(self, *, device_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + @route_metadata( + path="/locks/simulate/manual_lock_via_keypad", + has_required_parameters=True, + has_pagination=False, + ) + def manual_lock_via_keypad( + self, + *, + device_id: str, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Simulates a manual lock action using a keypad. You can only perform this action for `August `_ devices within `sandbox workspaces `_. :param device_id: ID of the device for which you want to simulate a manual lock action using a keypad. @@ -97,9 +129,13 @@ def manual_lock_via_keypad(self, *, device_id: str, wait_for_action_attempt: Opt json_payload["device_id"] = device_id if not json_payload: - raise ValueError("At least one parameter is required for /locks/simulate/manual_lock_via_keypad") + raise ValueError( + "At least one parameter is required for /locks/simulate/manual_lock_via_keypad" + ) - res = self.client.post("/locks/simulate/manual_lock_via_keypad", json=json_payload) + res = self.client.post( + "/locks/simulate/manual_lock_via_keypad", json=json_payload + ) wait_for_action_attempt = ( self.defaults.get("wait_for_action_attempt") @@ -110,5 +146,5 @@ def manual_lock_via_keypad(self, *, device_id: str, wait_for_action_attempt: Opt return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt + wait_for_action_attempt=wait_for_action_attempt, ) diff --git a/seam/routes/noise_sensors.py b/seam/routes/noise_sensors.py index d1320f1a..f3db9f02 100644 --- a/seam/routes/noise_sensors.py +++ b/seam/routes/noise_sensors.py @@ -3,8 +3,11 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (Device) -from .noise_sensors_noise_thresholds import AbstractNoiseSensorsNoiseThresholds, NoiseSensorsNoiseThresholds +from ..resources import Device +from .noise_sensors_noise_thresholds import ( + AbstractNoiseSensorsNoiseThresholds, + NoiseSensorsNoiseThresholds, +) from .noise_sensors_simulate import AbstractNoiseSensorsSimulate, NoiseSensorsSimulate @@ -21,7 +24,16 @@ def simulate(self) -> AbstractNoiseSensorsSimulate: raise NotImplementedError() @abc.abstractmethod - def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id: Optional[str] = None, customer_key: Optional[str] = None, device_type: Optional[str] = None, device_types: Optional[List[str]] = None, manufacturer: Optional[str] = None) -> List[Device]: + def list( + self, + *, + connect_webview_id: Optional[str] = None, + connected_account_id: Optional[str] = None, + customer_key: Optional[str] = None, + device_type: Optional[str] = None, + device_types: Optional[List[str]] = None, + manufacturer: Optional[str] = None, + ) -> List[Device]: """Returns a list of all `noise sensors `_. :param connect_webview_id: ID of the Connect Webview for which you want to list devices. @@ -44,7 +56,9 @@ class NoiseSensors(AbstractNoiseSensors): def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - self._noise_thresholds = NoiseSensorsNoiseThresholds(client=client, defaults=defaults) + self._noise_thresholds = NoiseSensorsNoiseThresholds( + client=client, defaults=defaults + ) self._simulate = NoiseSensorsSimulate(client=client, defaults=defaults) @property @@ -55,8 +69,19 @@ def noise_thresholds(self) -> NoiseSensorsNoiseThresholds: def simulate(self) -> NoiseSensorsSimulate: return self._simulate - @route_metadata(path="/noise_sensors/list", has_required_parameters=False, has_pagination=False) - def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id: Optional[str] = None, customer_key: Optional[str] = None, device_type: Optional[str] = None, device_types: Optional[List[str]] = None, manufacturer: Optional[str] = None) -> List[Device]: + @route_metadata( + path="/noise_sensors/list", has_required_parameters=False, has_pagination=False + ) + def list( + self, + *, + connect_webview_id: Optional[str] = None, + connected_account_id: Optional[str] = None, + customer_key: Optional[str] = None, + device_type: Optional[str] = None, + device_types: Optional[List[str]] = None, + manufacturer: Optional[str] = None, + ) -> List[Device]: """Returns a list of all `noise sensors `_. :param connect_webview_id: ID of the Connect Webview for which you want to list devices. @@ -72,21 +97,21 @@ def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id :param manufacturer: Manufacturers of the noise sensors that you want to list. :returns: OK""" - json_payload: Dict[str, Any] = {} + params: Dict[str, Any] = {} if connect_webview_id is not None: - json_payload["connect_webview_id"] = connect_webview_id + params["connect_webview_id"] = connect_webview_id if connected_account_id is not None: - json_payload["connected_account_id"] = connected_account_id + params["connected_account_id"] = connected_account_id if customer_key is not None: - json_payload["customer_key"] = customer_key + params["customer_key"] = customer_key if device_type is not None: - json_payload["device_type"] = device_type + params["device_type"] = device_type if device_types is not None: - json_payload["device_types"] = device_types + params["device_types"] = device_types if manufacturer is not None: - json_payload["manufacturer"] = manufacturer + params["manufacturer"] = manufacturer - res = self.client.post("/noise_sensors/list", json=json_payload) + res = self.client.get("/noise_sensors/list", params=params) return [Device.from_dict(item) for item in res["devices"]] diff --git a/seam/routes/noise_sensors_noise_thresholds.py b/seam/routes/noise_sensors_noise_thresholds.py index 94550dcc..0a076ada 100644 --- a/seam/routes/noise_sensors_noise_thresholds.py +++ b/seam/routes/noise_sensors_noise_thresholds.py @@ -3,13 +3,22 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (NoiseThreshold) +from ..resources import NoiseThreshold class AbstractNoiseSensorsNoiseThresholds(abc.ABC): @abc.abstractmethod - def create(self, *, device_id: str, ends_daily_at: str, starts_daily_at: str, name: Optional[str] = None, noise_threshold_decibels: Optional[float] = None, noise_threshold_nrs: Optional[float] = None) -> NoiseThreshold: + def create( + self, + *, + device_id: str, + ends_daily_at: str, + starts_daily_at: str, + name: Optional[str] = None, + noise_threshold_decibels: Optional[float] = None, + noise_threshold_nrs: Optional[float] = None, + ) -> NoiseThreshold: """Creates a new `noise threshold `_ for a `noise sensor `_. Thresholds represent the limits of noise tolerated at a property, which can be customized for each hour of the day. Each device has its own default thresholds, but you can use the Seam API to modify them. :param device_id: ID of the device for which you want to create a noise threshold. @@ -63,7 +72,17 @@ def list(self, *, device_id: str) -> List[NoiseThreshold]: raise NotImplementedError() @abc.abstractmethod - def update(self, *, device_id: str, noise_threshold_id: str, ends_daily_at: Optional[str] = None, name: Optional[str] = None, noise_threshold_decibels: Optional[float] = None, noise_threshold_nrs: Optional[float] = None, starts_daily_at: Optional[str] = None) -> None: + def update( + self, + *, + device_id: str, + noise_threshold_id: str, + ends_daily_at: Optional[str] = None, + name: Optional[str] = None, + noise_threshold_decibels: Optional[float] = None, + noise_threshold_nrs: Optional[float] = None, + starts_daily_at: Optional[str] = None, + ) -> None: """Updates a `noise threshold `_ for a `noise sensor `_. :param device_id: ID of the device that contains the noise threshold that you want to update. @@ -89,8 +108,21 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/noise_sensors/noise_thresholds/create", has_required_parameters=True, has_pagination=False) - def create(self, *, device_id: str, ends_daily_at: str, starts_daily_at: str, name: Optional[str] = None, noise_threshold_decibels: Optional[float] = None, noise_threshold_nrs: Optional[float] = None) -> NoiseThreshold: + @route_metadata( + path="/noise_sensors/noise_thresholds/create", + has_required_parameters=True, + has_pagination=False, + ) + def create( + self, + *, + device_id: str, + ends_daily_at: str, + starts_daily_at: str, + name: Optional[str] = None, + noise_threshold_decibels: Optional[float] = None, + noise_threshold_nrs: Optional[float] = None, + ) -> NoiseThreshold: """Creates a new `noise threshold `_ for a `noise sensor `_. Thresholds represent the limits of noise tolerated at a property, which can be customized for each hour of the day. Each device has its own default thresholds, but you can use the Seam API to modify them. :param device_id: ID of the device for which you want to create a noise threshold. @@ -124,13 +156,21 @@ def create(self, *, device_id: str, ends_daily_at: str, starts_daily_at: str, na json_payload["noise_threshold_nrs"] = noise_threshold_nrs if not json_payload: - raise ValueError("At least one parameter is required for /noise_sensors/noise_thresholds/create") + raise ValueError( + "At least one parameter is required for /noise_sensors/noise_thresholds/create" + ) - res = self.client.post("/noise_sensors/noise_thresholds/create", json=json_payload) + res = self.client.post( + "/noise_sensors/noise_thresholds/create", json=json_payload + ) return NoiseThreshold.from_dict(res["noise_threshold"]) - @route_metadata(path="/noise_sensors/noise_thresholds/delete", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/noise_sensors/noise_thresholds/delete", + has_required_parameters=True, + has_pagination=False, + ) def delete(self, *, device_id: str, noise_threshold_id: str) -> None: """Deletes a `noise threshold `_ from a `noise sensor `_. @@ -147,13 +187,19 @@ def delete(self, *, device_id: str, noise_threshold_id: str) -> None: params["noise_threshold_id"] = noise_threshold_id if not params: - raise ValueError("At least one parameter is required for /noise_sensors/noise_thresholds/delete") + raise ValueError( + "At least one parameter is required for /noise_sensors/noise_thresholds/delete" + ) self.client.delete("/noise_sensors/noise_thresholds/delete", params=params) return None - @route_metadata(path="/noise_sensors/noise_thresholds/get", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/noise_sensors/noise_thresholds/get", + has_required_parameters=True, + has_pagination=False, + ) def get(self, *, noise_threshold_id: str) -> NoiseThreshold: """Returns a specified `noise threshold `_ for a `noise sensor `_. @@ -168,13 +214,19 @@ def get(self, *, noise_threshold_id: str) -> NoiseThreshold: params["noise_threshold_id"] = noise_threshold_id if not params: - raise ValueError("At least one parameter is required for /noise_sensors/noise_thresholds/get") + raise ValueError( + "At least one parameter is required for /noise_sensors/noise_thresholds/get" + ) res = self.client.get("/noise_sensors/noise_thresholds/get", params=params) return NoiseThreshold.from_dict(res["noise_threshold"]) - @route_metadata(path="/noise_sensors/noise_thresholds/list", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/noise_sensors/noise_thresholds/list", + has_required_parameters=True, + has_pagination=False, + ) def list(self, *, device_id: str) -> List[NoiseThreshold]: """Returns a list of all `noise thresholds `_ for a `noise sensor `_. @@ -189,14 +241,30 @@ def list(self, *, device_id: str) -> List[NoiseThreshold]: params["device_id"] = device_id if not params: - raise ValueError("At least one parameter is required for /noise_sensors/noise_thresholds/list") + raise ValueError( + "At least one parameter is required for /noise_sensors/noise_thresholds/list" + ) res = self.client.get("/noise_sensors/noise_thresholds/list", params=params) return [NoiseThreshold.from_dict(item) for item in res["noise_thresholds"]] - @route_metadata(path="/noise_sensors/noise_thresholds/update", has_required_parameters=True, has_pagination=False) - def update(self, *, device_id: str, noise_threshold_id: str, ends_daily_at: Optional[str] = None, name: Optional[str] = None, noise_threshold_decibels: Optional[float] = None, noise_threshold_nrs: Optional[float] = None, starts_daily_at: Optional[str] = None) -> None: + @route_metadata( + path="/noise_sensors/noise_thresholds/update", + has_required_parameters=True, + has_pagination=False, + ) + def update( + self, + *, + device_id: str, + noise_threshold_id: str, + ends_daily_at: Optional[str] = None, + name: Optional[str] = None, + noise_threshold_decibels: Optional[float] = None, + noise_threshold_nrs: Optional[float] = None, + starts_daily_at: Optional[str] = None, + ) -> None: """Updates a `noise threshold `_ for a `noise sensor `_. :param device_id: ID of the device that contains the noise threshold that you want to update. @@ -232,7 +300,9 @@ def update(self, *, device_id: str, noise_threshold_id: str, ends_daily_at: Opti json_payload["starts_daily_at"] = starts_daily_at if not json_payload: - raise ValueError("At least one parameter is required for /noise_sensors/noise_thresholds/update") + raise ValueError( + "At least one parameter is required for /noise_sensors/noise_thresholds/update" + ) self.client.put("/noise_sensors/noise_thresholds/update", json=json_payload) diff --git a/seam/routes/noise_sensors_simulate.py b/seam/routes/noise_sensors_simulate.py index 1d4e7b11..edf047f1 100644 --- a/seam/routes/noise_sensors_simulate.py +++ b/seam/routes/noise_sensors_simulate.py @@ -22,7 +22,11 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/noise_sensors/simulate/trigger_noise_threshold", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/noise_sensors/simulate/trigger_noise_threshold", + has_required_parameters=True, + has_pagination=False, + ) def trigger_noise_threshold(self, *, device_id: str) -> None: """Simulates the triggering of a `noise threshold `_ for a `noise sensor `_ in a `sandbox workspace `_. @@ -35,8 +39,12 @@ def trigger_noise_threshold(self, *, device_id: str) -> None: json_payload["device_id"] = device_id if not json_payload: - raise ValueError("At least one parameter is required for /noise_sensors/simulate/trigger_noise_threshold") + raise ValueError( + "At least one parameter is required for /noise_sensors/simulate/trigger_noise_threshold" + ) - self.client.post("/noise_sensors/simulate/trigger_noise_threshold", json=json_payload) + self.client.post( + "/noise_sensors/simulate/trigger_noise_threshold", json=json_payload + ) return None diff --git a/seam/routes/phones.py b/seam/routes/phones.py index 4f623969..cabca85b 100644 --- a/seam/routes/phones.py +++ b/seam/routes/phones.py @@ -3,7 +3,7 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (Phone) +from ..resources import Phone from .phones_simulate import AbstractPhonesSimulate, PhonesSimulate @@ -35,7 +35,12 @@ def get(self, *, device_id: str) -> Phone: raise NotImplementedError() @abc.abstractmethod - def list(self, *, acs_credential_id: Optional[str] = None, owner_user_identity_id: Optional[str] = None) -> List[Phone]: + def list( + self, + *, + acs_credential_id: Optional[str] = None, + owner_user_identity_id: Optional[str] = None, + ) -> List[Phone]: """Returns a list of all `phones `_. To filter the list of returned phones by a specific owner user identity or credential, include the ``owner_user_identity_id`` or ``acs_credential_id``, respectively, in the request body. :param acs_credential_id: ID of the `credential `_ by which you want to filter the list of returned phones. @@ -56,7 +61,9 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): def simulate(self) -> PhonesSimulate: return self._simulate - @route_metadata(path="/phones/deactivate", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/phones/deactivate", has_required_parameters=True, has_pagination=False + ) def deactivate(self, *, device_id: str) -> None: """Deactivates a phone, which is useful, for example, if a user has lost their phone. For more information, see `App User Lost Phone Process `_. @@ -69,13 +76,17 @@ def deactivate(self, *, device_id: str) -> None: params["device_id"] = device_id if not params: - raise ValueError("At least one parameter is required for /phones/deactivate") + raise ValueError( + "At least one parameter is required for /phones/deactivate" + ) self.client.delete("/phones/deactivate", params=params) return None - @route_metadata(path="/phones/get", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/phones/get", has_required_parameters=True, has_pagination=False + ) def get(self, *, device_id: str) -> Phone: """Returns a specified `phone `_. @@ -96,8 +107,15 @@ def get(self, *, device_id: str) -> Phone: return Phone.from_dict(res["phone"]) - @route_metadata(path="/phones/list", has_required_parameters=False, has_pagination=False) - def list(self, *, acs_credential_id: Optional[str] = None, owner_user_identity_id: Optional[str] = None) -> List[Phone]: + @route_metadata( + path="/phones/list", has_required_parameters=False, has_pagination=False + ) + def list( + self, + *, + acs_credential_id: Optional[str] = None, + owner_user_identity_id: Optional[str] = None, + ) -> List[Phone]: """Returns a list of all `phones `_. To filter the list of returned phones by a specific owner user identity or credential, include the ``owner_user_identity_id`` or ``acs_credential_id``, respectively, in the request body. :param acs_credential_id: ID of the `credential `_ by which you want to filter the list of returned phones. diff --git a/seam/routes/phones_simulate.py b/seam/routes/phones_simulate.py index 8b79cf0e..183f5870 100644 --- a/seam/routes/phones_simulate.py +++ b/seam/routes/phones_simulate.py @@ -3,13 +3,20 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (Phone) +from ..resources import Phone class AbstractPhonesSimulate(abc.ABC): @abc.abstractmethod - def create_sandbox_phone(self, *, user_identity_id: str, assa_abloy_metadata: Optional[Dict[str, Any]] = None, custom_sdk_installation_id: Optional[str] = None, phone_metadata: Optional[Dict[str, Any]] = None) -> Phone: + def create_sandbox_phone( + self, + *, + user_identity_id: str, + assa_abloy_metadata: Optional[Dict[str, Any]] = None, + custom_sdk_installation_id: Optional[str] = None, + phone_metadata: Optional[Dict[str, Any]] = None, + ) -> Phone: """Creates a new simulated phone in a `sandbox workspace `_. See also `Creating a Simulated Phone for a User Identity `_. :param user_identity_id: ID of the user identity that you want to associate with the simulated phone. @@ -31,8 +38,19 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/phones/simulate/create_sandbox_phone", has_required_parameters=True, has_pagination=False) - def create_sandbox_phone(self, *, user_identity_id: str, assa_abloy_metadata: Optional[Dict[str, Any]] = None, custom_sdk_installation_id: Optional[str] = None, phone_metadata: Optional[Dict[str, Any]] = None) -> Phone: + @route_metadata( + path="/phones/simulate/create_sandbox_phone", + has_required_parameters=True, + has_pagination=False, + ) + def create_sandbox_phone( + self, + *, + user_identity_id: str, + assa_abloy_metadata: Optional[Dict[str, Any]] = None, + custom_sdk_installation_id: Optional[str] = None, + phone_metadata: Optional[Dict[str, Any]] = None, + ) -> Phone: """Creates a new simulated phone in a `sandbox workspace `_. See also `Creating a Simulated Phone for a User Identity `_. :param user_identity_id: ID of the user identity that you want to associate with the simulated phone. @@ -58,8 +76,12 @@ def create_sandbox_phone(self, *, user_identity_id: str, assa_abloy_metadata: Op json_payload["phone_metadata"] = phone_metadata if not json_payload: - raise ValueError("At least one parameter is required for /phones/simulate/create_sandbox_phone") + raise ValueError( + "At least one parameter is required for /phones/simulate/create_sandbox_phone" + ) - res = self.client.post("/phones/simulate/create_sandbox_phone", json=json_payload) + res = self.client.post( + "/phones/simulate/create_sandbox_phone", json=json_payload + ) return Phone.from_dict(res["phone"]) diff --git a/seam/routes/spaces.py b/seam/routes/spaces.py index 9262eea1..109639a2 100644 --- a/seam/routes/spaces.py +++ b/seam/routes/spaces.py @@ -3,7 +3,7 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (Space,Batch) +from ..resources import Space, Batch class AbstractSpaces(abc.ABC): @@ -20,7 +20,9 @@ def add_acs_entrances(self, *, acs_entrance_ids: List[str], space_id: str) -> No raise NotImplementedError() @abc.abstractmethod - def add_connected_account(self, *, connected_account_id: str, space_id: str) -> None: + def add_connected_account( + self, *, connected_account_id: str, space_id: str + ) -> None: """Adds a `connected account `_ to a specific space. :param connected_account_id: ID of the connected account that you want to add to the space. @@ -42,7 +44,17 @@ def add_devices(self, *, device_ids: List[str], space_id: str) -> None: raise NotImplementedError() @abc.abstractmethod - def create(self, *, name: str, acs_entrance_ids: Optional[List[str]] = None, connected_account_ids: Optional[List[str]] = None, customer_data: Optional[Dict[str, Any]] = None, customer_key: Optional[str] = None, device_ids: Optional[List[str]] = None, space_key: Optional[str] = None) -> Space: + def create( + self, + *, + name: str, + acs_entrance_ids: Optional[List[str]] = None, + connected_account_ids: Optional[List[str]] = None, + customer_data: Optional[Dict[str, Any]] = None, + customer_key: Optional[str] = None, + device_ids: Optional[List[str]] = None, + space_key: Optional[str] = None, + ) -> Space: """Creates a new space. :param name: Name of the space that you want to create. @@ -74,7 +86,9 @@ def delete(self, *, space_id: str) -> None: raise NotImplementedError() @abc.abstractmethod - def get(self, *, space_id: Optional[str] = None, space_key: Optional[str] = None) -> Space: + def get( + self, *, space_id: Optional[str] = None, space_key: Optional[str] = None + ) -> Space: """Gets a space. :param space_id: ID of the space that you want to get. @@ -87,12 +101,19 @@ def get(self, *, space_id: Optional[str] = None, space_key: Optional[str] = None raise NotImplementedError() @abc.abstractmethod - def get_related(self, *, exclude: Optional[List[str]] = None, include: Optional[List[str]] = None, space_ids: Optional[List[str]] = None, space_keys: Optional[List[str]] = None) -> Batch: + def get_related( + self, + *, + exclude: Optional[List[str]] = None, + include: Optional[List[str]] = None, + space_ids: Optional[List[str]] = None, + space_keys: Optional[List[str]] = None, + ) -> Batch: """Gets all related resources for one or more Spaces. - :param exclude: + :param exclude: - :param include: + :param include: :param space_ids: IDs of the spaces that you want to get along with their related resources. @@ -104,7 +125,15 @@ def get_related(self, *, exclude: Optional[List[str]] = None, include: Optional[ raise NotImplementedError() @abc.abstractmethod - def list(self, *, customer_key: Optional[str] = None, limit: Optional[float] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, space_key: Optional[str] = None) -> List[Space]: + def list( + self, + *, + customer_key: Optional[str] = None, + limit: Optional[float] = None, + page_cursor: Optional[Union[str, Null]] = None, + search: Optional[str] = None, + space_key: Optional[str] = None, + ) -> List[Space]: """Returns a list of all spaces. :param customer_key: Customer key for which you want to list spaces. @@ -121,7 +150,9 @@ def list(self, *, customer_key: Optional[str] = None, limit: Optional[float] = N raise NotImplementedError() @abc.abstractmethod - def remove_acs_entrances(self, *, acs_entrance_ids: List[str], space_id: str) -> None: + def remove_acs_entrances( + self, *, acs_entrance_ids: List[str], space_id: str + ) -> None: """Removes `entrances `_ from a specific space. :param acs_entrance_ids: IDs of the entrances that you want to remove from the space. @@ -132,7 +163,9 @@ def remove_acs_entrances(self, *, acs_entrance_ids: List[str], space_id: str) -> raise NotImplementedError() @abc.abstractmethod - def remove_connected_account(self, *, connected_account_id: str, space_id: str) -> None: + def remove_connected_account( + self, *, connected_account_id: str, space_id: str + ) -> None: """Removes a `connected account `_ from a specific space. :param connected_account_id: ID of the connected account that you want to remove from the space. @@ -154,7 +187,16 @@ def remove_devices(self, *, device_ids: List[str], space_id: str) -> None: raise NotImplementedError() @abc.abstractmethod - def update(self, *, acs_entrance_ids: Optional[List[str]] = None, customer_data: Optional[Dict[str, Any]] = None, device_ids: Optional[List[str]] = None, name: Optional[str] = None, space_id: Optional[str] = None, space_key: Optional[str] = None) -> Space: + def update( + self, + *, + acs_entrance_ids: Optional[List[str]] = None, + customer_data: Optional[Dict[str, Any]] = None, + device_ids: Optional[List[str]] = None, + name: Optional[str] = None, + space_id: Optional[str] = None, + space_key: Optional[str] = None, + ) -> Space: """Updates an existing space. :param acs_entrance_ids: IDs of the entrances that you want to set for the space. If specified, this will replace all existing entrances. @@ -178,7 +220,11 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/spaces/add_acs_entrances", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/spaces/add_acs_entrances", + has_required_parameters=True, + has_pagination=False, + ) def add_acs_entrances(self, *, acs_entrance_ids: List[str], space_id: str) -> None: """Adds `entrances `_ to a specific space. @@ -195,14 +241,22 @@ def add_acs_entrances(self, *, acs_entrance_ids: List[str], space_id: str) -> No json_payload["space_id"] = space_id if not json_payload: - raise ValueError("At least one parameter is required for /spaces/add_acs_entrances") + raise ValueError( + "At least one parameter is required for /spaces/add_acs_entrances" + ) self.client.put("/spaces/add_acs_entrances", json=json_payload) return None - @route_metadata(path="/spaces/add_connected_account", has_required_parameters=True, has_pagination=False) - def add_connected_account(self, *, connected_account_id: str, space_id: str) -> None: + @route_metadata( + path="/spaces/add_connected_account", + has_required_parameters=True, + has_pagination=False, + ) + def add_connected_account( + self, *, connected_account_id: str, space_id: str + ) -> None: """Adds a `connected account `_ to a specific space. :param connected_account_id: ID of the connected account that you want to add to the space. @@ -218,13 +272,17 @@ def add_connected_account(self, *, connected_account_id: str, space_id: str) -> json_payload["space_id"] = space_id if not json_payload: - raise ValueError("At least one parameter is required for /spaces/add_connected_account") + raise ValueError( + "At least one parameter is required for /spaces/add_connected_account" + ) self.client.put("/spaces/add_connected_account", json=json_payload) return None - @route_metadata(path="/spaces/add_devices", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/spaces/add_devices", has_required_parameters=True, has_pagination=False + ) def add_devices(self, *, device_ids: List[str], space_id: str) -> None: """Adds devices to a specific space. @@ -241,14 +299,28 @@ def add_devices(self, *, device_ids: List[str], space_id: str) -> None: json_payload["space_id"] = space_id if not json_payload: - raise ValueError("At least one parameter is required for /spaces/add_devices") + raise ValueError( + "At least one parameter is required for /spaces/add_devices" + ) self.client.put("/spaces/add_devices", json=json_payload) return None - @route_metadata(path="/spaces/create", has_required_parameters=True, has_pagination=False) - def create(self, *, name: str, acs_entrance_ids: Optional[List[str]] = None, connected_account_ids: Optional[List[str]] = None, customer_data: Optional[Dict[str, Any]] = None, customer_key: Optional[str] = None, device_ids: Optional[List[str]] = None, space_key: Optional[str] = None) -> Space: + @route_metadata( + path="/spaces/create", has_required_parameters=True, has_pagination=False + ) + def create( + self, + *, + name: str, + acs_entrance_ids: Optional[List[str]] = None, + connected_account_ids: Optional[List[str]] = None, + customer_data: Optional[Dict[str, Any]] = None, + customer_key: Optional[str] = None, + device_ids: Optional[List[str]] = None, + space_key: Optional[str] = None, + ) -> Space: """Creates a new space. :param name: Name of the space that you want to create. @@ -292,7 +364,9 @@ def create(self, *, name: str, acs_entrance_ids: Optional[List[str]] = None, con return Space.from_dict(res["space"]) - @route_metadata(path="/spaces/delete", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/spaces/delete", has_required_parameters=True, has_pagination=False + ) def delete(self, *, space_id: str) -> None: """Deletes a space. @@ -311,8 +385,12 @@ def delete(self, *, space_id: str) -> None: return None - @route_metadata(path="/spaces/get", has_required_parameters=True, has_pagination=False) - def get(self, *, space_id: Optional[str] = None, space_key: Optional[str] = None) -> Space: + @route_metadata( + path="/spaces/get", has_required_parameters=True, has_pagination=False + ) + def get( + self, *, space_id: Optional[str] = None, space_key: Optional[str] = None + ) -> Space: """Gets a space. :param space_id: ID of the space that you want to get. @@ -336,13 +414,22 @@ def get(self, *, space_id: Optional[str] = None, space_key: Optional[str] = None return Space.from_dict(res["space"]) - @route_metadata(path="/spaces/get_related", has_required_parameters=True, has_pagination=False) - def get_related(self, *, exclude: Optional[List[str]] = None, include: Optional[List[str]] = None, space_ids: Optional[List[str]] = None, space_keys: Optional[List[str]] = None) -> Batch: + @route_metadata( + path="/spaces/get_related", has_required_parameters=True, has_pagination=False + ) + def get_related( + self, + *, + exclude: Optional[List[str]] = None, + include: Optional[List[str]] = None, + space_ids: Optional[List[str]] = None, + space_keys: Optional[List[str]] = None, + ) -> Batch: """Gets all related resources for one or more Spaces. - :param exclude: + :param exclude: - :param include: + :param include: :param space_ids: IDs of the spaces that you want to get along with their related resources. @@ -351,26 +438,38 @@ def get_related(self, *, exclude: Optional[List[str]] = None, include: Optional[ :returns: OK :raises ValueError: At least one parameter must be provided.""" - json_payload: Dict[str, Any] = {} + params: Dict[str, Any] = {} if exclude is not None: - json_payload["exclude"] = exclude + params["exclude"] = exclude if include is not None: - json_payload["include"] = include + params["include"] = include if space_ids is not None: - json_payload["space_ids"] = space_ids + params["space_ids"] = space_ids if space_keys is not None: - json_payload["space_keys"] = space_keys + params["space_keys"] = space_keys - if not json_payload: - raise ValueError("At least one parameter is required for /spaces/get_related") + if not params: + raise ValueError( + "At least one parameter is required for /spaces/get_related" + ) - res = self.client.post("/spaces/get_related", json=json_payload) + res = self.client.get("/spaces/get_related", params=params) return Batch.from_dict(res["batch"]) - @route_metadata(path="/spaces/list", has_required_parameters=False, has_pagination=True) - def list(self, *, customer_key: Optional[str] = None, limit: Optional[float] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, space_key: Optional[str] = None) -> List[Space]: + @route_metadata( + path="/spaces/list", has_required_parameters=False, has_pagination=True + ) + def list( + self, + *, + customer_key: Optional[str] = None, + limit: Optional[float] = None, + page_cursor: Optional[Union[str, Null]] = None, + search: Optional[str] = None, + space_key: Optional[str] = None, + ) -> List[Space]: """Returns a list of all spaces. :param customer_key: Customer key for which you want to list spaces. @@ -401,8 +500,14 @@ def list(self, *, customer_key: Optional[str] = None, limit: Optional[float] = N return [Space.from_dict(item) for item in res["spaces"]] - @route_metadata(path="/spaces/remove_acs_entrances", has_required_parameters=True, has_pagination=False) - def remove_acs_entrances(self, *, acs_entrance_ids: List[str], space_id: str) -> None: + @route_metadata( + path="/spaces/remove_acs_entrances", + has_required_parameters=True, + has_pagination=False, + ) + def remove_acs_entrances( + self, *, acs_entrance_ids: List[str], space_id: str + ) -> None: """Removes `entrances `_ from a specific space. :param acs_entrance_ids: IDs of the entrances that you want to remove from the space. @@ -410,22 +515,30 @@ def remove_acs_entrances(self, *, acs_entrance_ids: List[str], space_id: str) -> :param space_id: ID of the space from which you want to remove entrances. :raises ValueError: At least one parameter must be provided.""" - json_payload: Dict[str, Any] = {} + params: Dict[str, Any] = {} if acs_entrance_ids is not None: - json_payload["acs_entrance_ids"] = acs_entrance_ids + params["acs_entrance_ids"] = acs_entrance_ids if space_id is not None: - json_payload["space_id"] = space_id + params["space_id"] = space_id - if not json_payload: - raise ValueError("At least one parameter is required for /spaces/remove_acs_entrances") + if not params: + raise ValueError( + "At least one parameter is required for /spaces/remove_acs_entrances" + ) - self.client.post("/spaces/remove_acs_entrances", json=json_payload) + self.client.delete("/spaces/remove_acs_entrances", params=params) return None - @route_metadata(path="/spaces/remove_connected_account", has_required_parameters=True, has_pagination=False) - def remove_connected_account(self, *, connected_account_id: str, space_id: str) -> None: + @route_metadata( + path="/spaces/remove_connected_account", + has_required_parameters=True, + has_pagination=False, + ) + def remove_connected_account( + self, *, connected_account_id: str, space_id: str + ) -> None: """Removes a `connected account `_ from a specific space. :param connected_account_id: ID of the connected account that you want to remove from the space. @@ -441,13 +554,19 @@ def remove_connected_account(self, *, connected_account_id: str, space_id: str) params["space_id"] = space_id if not params: - raise ValueError("At least one parameter is required for /spaces/remove_connected_account") + raise ValueError( + "At least one parameter is required for /spaces/remove_connected_account" + ) self.client.delete("/spaces/remove_connected_account", params=params) return None - @route_metadata(path="/spaces/remove_devices", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/spaces/remove_devices", + has_required_parameters=True, + has_pagination=False, + ) def remove_devices(self, *, device_ids: List[str], space_id: str) -> None: """Removes devices from a specific space. @@ -456,22 +575,35 @@ def remove_devices(self, *, device_ids: List[str], space_id: str) -> None: :param space_id: ID of the space from which you want to remove devices. :raises ValueError: At least one parameter must be provided.""" - json_payload: Dict[str, Any] = {} + params: Dict[str, Any] = {} if device_ids is not None: - json_payload["device_ids"] = device_ids + params["device_ids"] = device_ids if space_id is not None: - json_payload["space_id"] = space_id + params["space_id"] = space_id - if not json_payload: - raise ValueError("At least one parameter is required for /spaces/remove_devices") + if not params: + raise ValueError( + "At least one parameter is required for /spaces/remove_devices" + ) - self.client.post("/spaces/remove_devices", json=json_payload) + self.client.delete("/spaces/remove_devices", params=params) return None - @route_metadata(path="/spaces/update", has_required_parameters=False, has_pagination=False) - def update(self, *, acs_entrance_ids: Optional[List[str]] = None, customer_data: Optional[Dict[str, Any]] = None, device_ids: Optional[List[str]] = None, name: Optional[str] = None, space_id: Optional[str] = None, space_key: Optional[str] = None) -> Space: + @route_metadata( + path="/spaces/update", has_required_parameters=False, has_pagination=False + ) + def update( + self, + *, + acs_entrance_ids: Optional[List[str]] = None, + customer_data: Optional[Dict[str, Any]] = None, + device_ids: Optional[List[str]] = None, + name: Optional[str] = None, + space_id: Optional[str] = None, + space_key: Optional[str] = None, + ) -> Space: """Updates an existing space. :param acs_entrance_ids: IDs of the entrances that you want to set for the space. If specified, this will replace all existing entrances. diff --git a/seam/routes/thermostats.py b/seam/routes/thermostats.py index b3f8dd33..71bc8e57 100644 --- a/seam/routes/thermostats.py +++ b/seam/routes/thermostats.py @@ -3,8 +3,11 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (ActionAttempt,Device) -from .thermostats_daily_programs import AbstractThermostatsDailyPrograms, ThermostatsDailyPrograms +from ..resources import ActionAttempt, Device +from .thermostats_daily_programs import ( + AbstractThermostatsDailyPrograms, + ThermostatsDailyPrograms, +) from .thermostats_schedules import AbstractThermostatsSchedules, ThermostatsSchedules from .thermostats_simulate import AbstractThermostatsSimulate, ThermostatsSimulate from ..modules.action_attempts import resolve_action_attempt @@ -28,7 +31,13 @@ def simulate(self) -> AbstractThermostatsSimulate: raise NotImplementedError() @abc.abstractmethod - def activate_climate_preset(self, *, climate_preset_key: str, device_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + def activate_climate_preset( + self, + *, + climate_preset_key: str, + device_id: str, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Activates a specified `climate preset `_ for a specified `thermostat `_. :param climate_preset_key: Climate preset key of the climate preset that you want to activate. @@ -43,7 +52,14 @@ def activate_climate_preset(self, *, climate_preset_key: str, device_id: str, wa raise NotImplementedError() @abc.abstractmethod - def cool(self, *, device_id: str, cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + def cool( + self, + *, + device_id: str, + cooling_set_point_celsius: Optional[float] = None, + cooling_set_point_fahrenheit: Optional[float] = None, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Sets a specified `thermostat `_ to `cool mode `_. :param device_id: ID of the thermostat device that you want to set to cool mode. @@ -60,7 +76,22 @@ def cool(self, *, device_id: str, cooling_set_point_celsius: Optional[float] = N raise NotImplementedError() @abc.abstractmethod - def create_climate_preset(self, *, climate_preset_key: str, device_id: str, climate_preset_mode: Optional[str] = None, cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, ecobee_metadata: Optional[Dict[str, Any]] = None, fan_mode_setting: Optional[str] = None, heating_set_point_celsius: Optional[float] = None, heating_set_point_fahrenheit: Optional[float] = None, hvac_mode_setting: Optional[str] = None, manual_override_allowed: Optional[bool] = None, name: Optional[Union[str, Null]] = None) -> None: + def create_climate_preset( + self, + *, + climate_preset_key: str, + device_id: str, + climate_preset_mode: Optional[str] = None, + cooling_set_point_celsius: Optional[float] = None, + cooling_set_point_fahrenheit: Optional[float] = None, + ecobee_metadata: Optional[Dict[str, Any]] = None, + fan_mode_setting: Optional[str] = None, + heating_set_point_celsius: Optional[float] = None, + heating_set_point_fahrenheit: Optional[float] = None, + hvac_mode_setting: Optional[str] = None, + manual_override_allowed: Optional[bool] = None, + name: Optional[Union[str, Null]] = None, + ) -> None: """Creates a `climate preset `_ for a specified `thermostat `_. :param climate_preset_key: Unique key to identify the `climate preset `_. @@ -102,7 +133,14 @@ def delete_climate_preset(self, *, climate_preset_key: str, device_id: str) -> N raise NotImplementedError() @abc.abstractmethod - def heat(self, *, device_id: str, heating_set_point_celsius: Optional[float] = None, heating_set_point_fahrenheit: Optional[float] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + def heat( + self, + *, + device_id: str, + heating_set_point_celsius: Optional[float] = None, + heating_set_point_fahrenheit: Optional[float] = None, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Sets a specified `thermostat `_ to `heat mode `_. :param device_id: ID of the thermostat device that you want to set to heat mode. @@ -119,7 +157,16 @@ def heat(self, *, device_id: str, heating_set_point_celsius: Optional[float] = N raise NotImplementedError() @abc.abstractmethod - def heat_cool(self, *, device_id: str, cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, heating_set_point_celsius: Optional[float] = None, heating_set_point_fahrenheit: Optional[float] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + def heat_cool( + self, + *, + device_id: str, + cooling_set_point_celsius: Optional[float] = None, + cooling_set_point_fahrenheit: Optional[float] = None, + heating_set_point_celsius: Optional[float] = None, + heating_set_point_fahrenheit: Optional[float] = None, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Sets a specified `thermostat `_ to `heat-cool ("auto") mode `_. :param device_id: ID of the thermostat device that you want to set to heat-cool mode. @@ -140,7 +187,16 @@ def heat_cool(self, *, device_id: str, cooling_set_point_celsius: Optional[float raise NotImplementedError() @abc.abstractmethod - def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id: Optional[str] = None, customer_key: Optional[str] = None, device_type: Optional[str] = None, device_types: Optional[List[str]] = None, manufacturer: Optional[str] = None) -> List[Device]: + def list( + self, + *, + connect_webview_id: Optional[str] = None, + connected_account_id: Optional[str] = None, + customer_key: Optional[str] = None, + device_type: Optional[str] = None, + device_types: Optional[List[str]] = None, + manufacturer: Optional[str] = None, + ) -> List[Device]: """Returns a list of all `thermostats `_. :param connect_webview_id: ID of the Connect Webview for which you want to list devices. @@ -159,7 +215,12 @@ def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id raise NotImplementedError() @abc.abstractmethod - def off(self, *, device_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + def off( + self, + *, + device_id: str, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Sets a specified `thermostat `_ to `"off" mode `_. :param device_id: ID of the thermostat device that you want to set to off mode. @@ -172,7 +233,9 @@ def off(self, *, device_id: str, wait_for_action_attempt: Optional[Union[bool, D raise NotImplementedError() @abc.abstractmethod - def set_fallback_climate_preset(self, *, climate_preset_key: str, device_id: str) -> None: + def set_fallback_climate_preset( + self, *, climate_preset_key: str, device_id: str + ) -> None: """Sets a specified `climate preset `_ as the `"fallback" `_ preset for a specified `thermostat `_. :param climate_preset_key: Climate preset key of the climate preset that you want to set as the fallback climate preset. @@ -183,7 +246,14 @@ def set_fallback_climate_preset(self, *, climate_preset_key: str, device_id: str raise NotImplementedError() @abc.abstractmethod - def set_fan_mode(self, *, device_id: str, fan_mode: Optional[str] = None, fan_mode_setting: Optional[str] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + def set_fan_mode( + self, + *, + device_id: str, + fan_mode: Optional[str] = None, + fan_mode_setting: Optional[str] = None, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Sets the `fan mode setting `_ for a specified `thermostat `_. :param device_id: ID of the thermostat device for which you want to set the fan mode. @@ -200,12 +270,22 @@ def set_fan_mode(self, *, device_id: str, fan_mode: Optional[str] = None, fan_mo raise NotImplementedError() @abc.abstractmethod - def set_hvac_mode(self, *, device_id: str, hvac_mode_setting: str, cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, heating_set_point_celsius: Optional[float] = None, heating_set_point_fahrenheit: Optional[float] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + def set_hvac_mode( + self, + *, + device_id: str, + hvac_mode_setting: str, + cooling_set_point_celsius: Optional[float] = None, + cooling_set_point_fahrenheit: Optional[float] = None, + heating_set_point_celsius: Optional[float] = None, + heating_set_point_fahrenheit: Optional[float] = None, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Sets the `HVAC mode `_ for a specified `thermostat `_. :param device_id: ID of the thermostat device for which you want to set the HVAC mode. - :param hvac_mode_setting: + :param hvac_mode_setting: :param cooling_set_point_celsius: `Cooling set point `_ in °C that you want to set for the thermostat. You must set one of the ``cooling_set_point`` parameters. @@ -223,7 +303,15 @@ def set_hvac_mode(self, *, device_id: str, hvac_mode_setting: str, cooling_set_p raise NotImplementedError() @abc.abstractmethod - def set_temperature_threshold(self, *, device_id: str, lower_limit_celsius: Optional[Union[float, Null]] = None, lower_limit_fahrenheit: Optional[Union[float, Null]] = None, upper_limit_celsius: Optional[Union[float, Null]] = None, upper_limit_fahrenheit: Optional[Union[float, Null]] = None) -> None: + def set_temperature_threshold( + self, + *, + device_id: str, + lower_limit_celsius: Optional[Union[float, Null]] = None, + lower_limit_fahrenheit: Optional[Union[float, Null]] = None, + upper_limit_celsius: Optional[Union[float, Null]] = None, + upper_limit_fahrenheit: Optional[Union[float, Null]] = None, + ) -> None: """Sets a `temperature threshold `_ for a specified thermostat. Seam emits a ``thermostat.temperature_threshold_exceeded`` event and adds a warning on a thermostat if it reports a temperature outside the threshold range. :param device_id: ID of the thermostat device for which you want to set a temperature threshold. @@ -240,7 +328,22 @@ def set_temperature_threshold(self, *, device_id: str, lower_limit_celsius: Opti raise NotImplementedError() @abc.abstractmethod - def update_climate_preset(self, *, climate_preset_key: str, device_id: str, climate_preset_mode: Optional[str] = None, cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, ecobee_metadata: Optional[Dict[str, Any]] = None, fan_mode_setting: Optional[str] = None, heating_set_point_celsius: Optional[float] = None, heating_set_point_fahrenheit: Optional[float] = None, hvac_mode_setting: Optional[str] = None, manual_override_allowed: Optional[bool] = None, name: Optional[Union[str, Null]] = None) -> None: + def update_climate_preset( + self, + *, + climate_preset_key: str, + device_id: str, + climate_preset_mode: Optional[str] = None, + cooling_set_point_celsius: Optional[float] = None, + cooling_set_point_fahrenheit: Optional[float] = None, + ecobee_metadata: Optional[Dict[str, Any]] = None, + fan_mode_setting: Optional[str] = None, + heating_set_point_celsius: Optional[float] = None, + heating_set_point_fahrenheit: Optional[float] = None, + hvac_mode_setting: Optional[str] = None, + manual_override_allowed: Optional[bool] = None, + name: Optional[Union[str, Null]] = None, + ) -> None: """Updates a specified `climate preset `_ for a specified `thermostat `_. :param climate_preset_key: Unique key to identify the `climate preset `_. @@ -271,7 +374,19 @@ def update_climate_preset(self, *, climate_preset_key: str, device_id: str, clim raise NotImplementedError() @abc.abstractmethod - def update_weekly_program(self, *, device_id: str, friday_program_id: Optional[Union[str, Null]] = None, monday_program_id: Optional[Union[str, Null]] = None, saturday_program_id: Optional[Union[str, Null]] = None, sunday_program_id: Optional[Union[str, Null]] = None, thursday_program_id: Optional[Union[str, Null]] = None, tuesday_program_id: Optional[Union[str, Null]] = None, wednesday_program_id: Optional[Union[str, Null]] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + def update_weekly_program( + self, + *, + device_id: str, + friday_program_id: Optional[Union[str, Null]] = None, + monday_program_id: Optional[Union[str, Null]] = None, + saturday_program_id: Optional[Union[str, Null]] = None, + sunday_program_id: Optional[Union[str, Null]] = None, + thursday_program_id: Optional[Union[str, Null]] = None, + tuesday_program_id: Optional[Union[str, Null]] = None, + wednesday_program_id: Optional[Union[str, Null]] = None, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Updates the thermostat weekly program for a thermostat device. To configure a weekly program, specify the ID of the daily program that you want to use for each day of the week. When you update a weekly program, the set of programs that you specify overwrites any previous weekly program for the thermostat. :param device_id: ID of the thermostat device for which you want to update the weekly program. @@ -302,7 +417,9 @@ class Thermostats(AbstractThermostats): def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - self._daily_programs = ThermostatsDailyPrograms(client=client, defaults=defaults) + self._daily_programs = ThermostatsDailyPrograms( + client=client, defaults=defaults + ) self._schedules = ThermostatsSchedules(client=client, defaults=defaults) self._simulate = ThermostatsSimulate(client=client, defaults=defaults) @@ -318,8 +435,18 @@ def schedules(self) -> ThermostatsSchedules: def simulate(self) -> ThermostatsSimulate: return self._simulate - @route_metadata(path="/thermostats/activate_climate_preset", has_required_parameters=True, has_pagination=False) - def activate_climate_preset(self, *, climate_preset_key: str, device_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + @route_metadata( + path="/thermostats/activate_climate_preset", + has_required_parameters=True, + has_pagination=False, + ) + def activate_climate_preset( + self, + *, + climate_preset_key: str, + device_id: str, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Activates a specified `climate preset `_ for a specified `thermostat `_. :param climate_preset_key: Climate preset key of the climate preset that you want to activate. @@ -339,9 +466,13 @@ def activate_climate_preset(self, *, climate_preset_key: str, device_id: str, wa json_payload["device_id"] = device_id if not json_payload: - raise ValueError("At least one parameter is required for /thermostats/activate_climate_preset") + raise ValueError( + "At least one parameter is required for /thermostats/activate_climate_preset" + ) - res = self.client.post("/thermostats/activate_climate_preset", json=json_payload) + res = self.client.post( + "/thermostats/activate_climate_preset", json=json_payload + ) wait_for_action_attempt = ( self.defaults.get("wait_for_action_attempt") @@ -352,11 +483,20 @@ def activate_climate_preset(self, *, climate_preset_key: str, device_id: str, wa return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt + wait_for_action_attempt=wait_for_action_attempt, ) - @route_metadata(path="/thermostats/cool", has_required_parameters=True, has_pagination=False) - def cool(self, *, device_id: str, cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + @route_metadata( + path="/thermostats/cool", has_required_parameters=True, has_pagination=False + ) + def cool( + self, + *, + device_id: str, + cooling_set_point_celsius: Optional[float] = None, + cooling_set_point_fahrenheit: Optional[float] = None, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Sets a specified `thermostat `_ to `cool mode `_. :param device_id: ID of the thermostat device that you want to set to cool mode. @@ -393,11 +533,30 @@ def cool(self, *, device_id: str, cooling_set_point_celsius: Optional[float] = N return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt + wait_for_action_attempt=wait_for_action_attempt, ) - @route_metadata(path="/thermostats/create_climate_preset", has_required_parameters=True, has_pagination=False) - def create_climate_preset(self, *, climate_preset_key: str, device_id: str, climate_preset_mode: Optional[str] = None, cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, ecobee_metadata: Optional[Dict[str, Any]] = None, fan_mode_setting: Optional[str] = None, heating_set_point_celsius: Optional[float] = None, heating_set_point_fahrenheit: Optional[float] = None, hvac_mode_setting: Optional[str] = None, manual_override_allowed: Optional[bool] = None, name: Optional[Union[str, Null]] = None) -> None: + @route_metadata( + path="/thermostats/create_climate_preset", + has_required_parameters=True, + has_pagination=False, + ) + def create_climate_preset( + self, + *, + climate_preset_key: str, + device_id: str, + climate_preset_mode: Optional[str] = None, + cooling_set_point_celsius: Optional[float] = None, + cooling_set_point_fahrenheit: Optional[float] = None, + ecobee_metadata: Optional[Dict[str, Any]] = None, + fan_mode_setting: Optional[str] = None, + heating_set_point_celsius: Optional[float] = None, + heating_set_point_fahrenheit: Optional[float] = None, + hvac_mode_setting: Optional[str] = None, + manual_override_allowed: Optional[bool] = None, + name: Optional[Union[str, Null]] = None, + ) -> None: """Creates a `climate preset `_ for a specified `thermostat `_. :param climate_preset_key: Unique key to identify the `climate preset `_. @@ -453,13 +612,19 @@ def create_climate_preset(self, *, climate_preset_key: str, device_id: str, clim json_payload["name"] = name if not json_payload: - raise ValueError("At least one parameter is required for /thermostats/create_climate_preset") + raise ValueError( + "At least one parameter is required for /thermostats/create_climate_preset" + ) self.client.post("/thermostats/create_climate_preset", json=json_payload) return None - @route_metadata(path="/thermostats/delete_climate_preset", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/thermostats/delete_climate_preset", + has_required_parameters=True, + has_pagination=False, + ) def delete_climate_preset(self, *, climate_preset_key: str, device_id: str) -> None: """Deletes a specified `climate preset `_ for a specified `thermostat `_. @@ -476,14 +641,25 @@ def delete_climate_preset(self, *, climate_preset_key: str, device_id: str) -> N params["device_id"] = device_id if not params: - raise ValueError("At least one parameter is required for /thermostats/delete_climate_preset") + raise ValueError( + "At least one parameter is required for /thermostats/delete_climate_preset" + ) self.client.delete("/thermostats/delete_climate_preset", params=params) return None - @route_metadata(path="/thermostats/heat", has_required_parameters=True, has_pagination=False) - def heat(self, *, device_id: str, heating_set_point_celsius: Optional[float] = None, heating_set_point_fahrenheit: Optional[float] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + @route_metadata( + path="/thermostats/heat", has_required_parameters=True, has_pagination=False + ) + def heat( + self, + *, + device_id: str, + heating_set_point_celsius: Optional[float] = None, + heating_set_point_fahrenheit: Optional[float] = None, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Sets a specified `thermostat `_ to `heat mode `_. :param device_id: ID of the thermostat device that you want to set to heat mode. @@ -520,11 +696,24 @@ def heat(self, *, device_id: str, heating_set_point_celsius: Optional[float] = N return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt + wait_for_action_attempt=wait_for_action_attempt, ) - @route_metadata(path="/thermostats/heat_cool", has_required_parameters=True, has_pagination=False) - def heat_cool(self, *, device_id: str, cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, heating_set_point_celsius: Optional[float] = None, heating_set_point_fahrenheit: Optional[float] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + @route_metadata( + path="/thermostats/heat_cool", + has_required_parameters=True, + has_pagination=False, + ) + def heat_cool( + self, + *, + device_id: str, + cooling_set_point_celsius: Optional[float] = None, + cooling_set_point_fahrenheit: Optional[float] = None, + heating_set_point_celsius: Optional[float] = None, + heating_set_point_fahrenheit: Optional[float] = None, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Sets a specified `thermostat `_ to `heat-cool ("auto") mode `_. :param device_id: ID of the thermostat device that you want to set to heat-cool mode. @@ -556,7 +745,9 @@ def heat_cool(self, *, device_id: str, cooling_set_point_celsius: Optional[float json_payload["heating_set_point_fahrenheit"] = heating_set_point_fahrenheit if not json_payload: - raise ValueError("At least one parameter is required for /thermostats/heat_cool") + raise ValueError( + "At least one parameter is required for /thermostats/heat_cool" + ) res = self.client.post("/thermostats/heat_cool", json=json_payload) @@ -569,11 +760,22 @@ def heat_cool(self, *, device_id: str, cooling_set_point_celsius: Optional[float return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt + wait_for_action_attempt=wait_for_action_attempt, ) - @route_metadata(path="/thermostats/list", has_required_parameters=False, has_pagination=False) - def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id: Optional[str] = None, customer_key: Optional[str] = None, device_type: Optional[str] = None, device_types: Optional[List[str]] = None, manufacturer: Optional[str] = None) -> List[Device]: + @route_metadata( + path="/thermostats/list", has_required_parameters=False, has_pagination=False + ) + def list( + self, + *, + connect_webview_id: Optional[str] = None, + connected_account_id: Optional[str] = None, + customer_key: Optional[str] = None, + device_type: Optional[str] = None, + device_types: Optional[List[str]] = None, + manufacturer: Optional[str] = None, + ) -> List[Device]: """Returns a list of all `thermostats `_. :param connect_webview_id: ID of the Connect Webview for which you want to list devices. @@ -589,27 +791,34 @@ def list(self, *, connect_webview_id: Optional[str] = None, connected_account_id :param manufacturer: Manufacturer by which you want to filter thermostat devices. :returns: OK""" - json_payload: Dict[str, Any] = {} + params: Dict[str, Any] = {} if connect_webview_id is not None: - json_payload["connect_webview_id"] = connect_webview_id + params["connect_webview_id"] = connect_webview_id if connected_account_id is not None: - json_payload["connected_account_id"] = connected_account_id + params["connected_account_id"] = connected_account_id if customer_key is not None: - json_payload["customer_key"] = customer_key + params["customer_key"] = customer_key if device_type is not None: - json_payload["device_type"] = device_type + params["device_type"] = device_type if device_types is not None: - json_payload["device_types"] = device_types + params["device_types"] = device_types if manufacturer is not None: - json_payload["manufacturer"] = manufacturer + params["manufacturer"] = manufacturer - res = self.client.post("/thermostats/list", json=json_payload) + res = self.client.get("/thermostats/list", params=params) return [Device.from_dict(item) for item in res["devices"]] - @route_metadata(path="/thermostats/off", has_required_parameters=True, has_pagination=False) - def off(self, *, device_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + @route_metadata( + path="/thermostats/off", has_required_parameters=True, has_pagination=False + ) + def off( + self, + *, + device_id: str, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Sets a specified `thermostat `_ to `"off" mode `_. :param device_id: ID of the thermostat device that you want to set to off mode. @@ -638,11 +847,17 @@ def off(self, *, device_id: str, wait_for_action_attempt: Optional[Union[bool, D return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt + wait_for_action_attempt=wait_for_action_attempt, ) - @route_metadata(path="/thermostats/set_fallback_climate_preset", has_required_parameters=True, has_pagination=False) - def set_fallback_climate_preset(self, *, climate_preset_key: str, device_id: str) -> None: + @route_metadata( + path="/thermostats/set_fallback_climate_preset", + has_required_parameters=True, + has_pagination=False, + ) + def set_fallback_climate_preset( + self, *, climate_preset_key: str, device_id: str + ) -> None: """Sets a specified `climate preset `_ as the `"fallback" `_ preset for a specified `thermostat `_. :param climate_preset_key: Climate preset key of the climate preset that you want to set as the fallback climate preset. @@ -658,14 +873,27 @@ def set_fallback_climate_preset(self, *, climate_preset_key: str, device_id: str json_payload["device_id"] = device_id if not json_payload: - raise ValueError("At least one parameter is required for /thermostats/set_fallback_climate_preset") + raise ValueError( + "At least one parameter is required for /thermostats/set_fallback_climate_preset" + ) self.client.post("/thermostats/set_fallback_climate_preset", json=json_payload) return None - @route_metadata(path="/thermostats/set_fan_mode", has_required_parameters=True, has_pagination=False) - def set_fan_mode(self, *, device_id: str, fan_mode: Optional[str] = None, fan_mode_setting: Optional[str] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + @route_metadata( + path="/thermostats/set_fan_mode", + has_required_parameters=True, + has_pagination=False, + ) + def set_fan_mode( + self, + *, + device_id: str, + fan_mode: Optional[str] = None, + fan_mode_setting: Optional[str] = None, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Sets the `fan mode setting `_ for a specified `thermostat `_. :param device_id: ID of the thermostat device for which you want to set the fan mode. @@ -689,7 +917,9 @@ def set_fan_mode(self, *, device_id: str, fan_mode: Optional[str] = None, fan_mo json_payload["fan_mode_setting"] = fan_mode_setting if not json_payload: - raise ValueError("At least one parameter is required for /thermostats/set_fan_mode") + raise ValueError( + "At least one parameter is required for /thermostats/set_fan_mode" + ) res = self.client.post("/thermostats/set_fan_mode", json=json_payload) @@ -702,16 +932,30 @@ def set_fan_mode(self, *, device_id: str, fan_mode: Optional[str] = None, fan_mo return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt + wait_for_action_attempt=wait_for_action_attempt, ) - @route_metadata(path="/thermostats/set_hvac_mode", has_required_parameters=True, has_pagination=False) - def set_hvac_mode(self, *, device_id: str, hvac_mode_setting: str, cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, heating_set_point_celsius: Optional[float] = None, heating_set_point_fahrenheit: Optional[float] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + @route_metadata( + path="/thermostats/set_hvac_mode", + has_required_parameters=True, + has_pagination=False, + ) + def set_hvac_mode( + self, + *, + device_id: str, + hvac_mode_setting: str, + cooling_set_point_celsius: Optional[float] = None, + cooling_set_point_fahrenheit: Optional[float] = None, + heating_set_point_celsius: Optional[float] = None, + heating_set_point_fahrenheit: Optional[float] = None, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Sets the `HVAC mode `_ for a specified `thermostat `_. :param device_id: ID of the thermostat device for which you want to set the HVAC mode. - :param hvac_mode_setting: + :param hvac_mode_setting: :param cooling_set_point_celsius: `Cooling set point `_ in °C that you want to set for the thermostat. You must set one of the ``cooling_set_point`` parameters. @@ -742,7 +986,9 @@ def set_hvac_mode(self, *, device_id: str, hvac_mode_setting: str, cooling_set_p json_payload["heating_set_point_fahrenheit"] = heating_set_point_fahrenheit if not json_payload: - raise ValueError("At least one parameter is required for /thermostats/set_hvac_mode") + raise ValueError( + "At least one parameter is required for /thermostats/set_hvac_mode" + ) res = self.client.post("/thermostats/set_hvac_mode", json=json_payload) @@ -755,11 +1001,23 @@ def set_hvac_mode(self, *, device_id: str, hvac_mode_setting: str, cooling_set_p return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt + wait_for_action_attempt=wait_for_action_attempt, ) - @route_metadata(path="/thermostats/set_temperature_threshold", has_required_parameters=True, has_pagination=False) - def set_temperature_threshold(self, *, device_id: str, lower_limit_celsius: Optional[Union[float, Null]] = None, lower_limit_fahrenheit: Optional[Union[float, Null]] = None, upper_limit_celsius: Optional[Union[float, Null]] = None, upper_limit_fahrenheit: Optional[Union[float, Null]] = None) -> None: + @route_metadata( + path="/thermostats/set_temperature_threshold", + has_required_parameters=True, + has_pagination=False, + ) + def set_temperature_threshold( + self, + *, + device_id: str, + lower_limit_celsius: Optional[Union[float, Null]] = None, + lower_limit_fahrenheit: Optional[Union[float, Null]] = None, + upper_limit_celsius: Optional[Union[float, Null]] = None, + upper_limit_fahrenheit: Optional[Union[float, Null]] = None, + ) -> None: """Sets a `temperature threshold `_ for a specified thermostat. Seam emits a ``thermostat.temperature_threshold_exceeded`` event and adds a warning on a thermostat if it reports a temperature outside the threshold range. :param device_id: ID of the thermostat device for which you want to set a temperature threshold. @@ -787,14 +1045,35 @@ def set_temperature_threshold(self, *, device_id: str, lower_limit_celsius: Opti json_payload["upper_limit_fahrenheit"] = upper_limit_fahrenheit if not json_payload: - raise ValueError("At least one parameter is required for /thermostats/set_temperature_threshold") + raise ValueError( + "At least one parameter is required for /thermostats/set_temperature_threshold" + ) self.client.patch("/thermostats/set_temperature_threshold", json=json_payload) return None - @route_metadata(path="/thermostats/update_climate_preset", has_required_parameters=True, has_pagination=False) - def update_climate_preset(self, *, climate_preset_key: str, device_id: str, climate_preset_mode: Optional[str] = None, cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, ecobee_metadata: Optional[Dict[str, Any]] = None, fan_mode_setting: Optional[str] = None, heating_set_point_celsius: Optional[float] = None, heating_set_point_fahrenheit: Optional[float] = None, hvac_mode_setting: Optional[str] = None, manual_override_allowed: Optional[bool] = None, name: Optional[Union[str, Null]] = None) -> None: + @route_metadata( + path="/thermostats/update_climate_preset", + has_required_parameters=True, + has_pagination=False, + ) + def update_climate_preset( + self, + *, + climate_preset_key: str, + device_id: str, + climate_preset_mode: Optional[str] = None, + cooling_set_point_celsius: Optional[float] = None, + cooling_set_point_fahrenheit: Optional[float] = None, + ecobee_metadata: Optional[Dict[str, Any]] = None, + fan_mode_setting: Optional[str] = None, + heating_set_point_celsius: Optional[float] = None, + heating_set_point_fahrenheit: Optional[float] = None, + hvac_mode_setting: Optional[str] = None, + manual_override_allowed: Optional[bool] = None, + name: Optional[Union[str, Null]] = None, + ) -> None: """Updates a specified `climate preset `_ for a specified `thermostat `_. :param climate_preset_key: Unique key to identify the `climate preset `_. @@ -850,14 +1129,32 @@ def update_climate_preset(self, *, climate_preset_key: str, device_id: str, clim json_payload["name"] = name if not json_payload: - raise ValueError("At least one parameter is required for /thermostats/update_climate_preset") + raise ValueError( + "At least one parameter is required for /thermostats/update_climate_preset" + ) self.client.patch("/thermostats/update_climate_preset", json=json_payload) return None - @route_metadata(path="/thermostats/update_weekly_program", has_required_parameters=True, has_pagination=False) - def update_weekly_program(self, *, device_id: str, friday_program_id: Optional[Union[str, Null]] = None, monday_program_id: Optional[Union[str, Null]] = None, saturday_program_id: Optional[Union[str, Null]] = None, sunday_program_id: Optional[Union[str, Null]] = None, thursday_program_id: Optional[Union[str, Null]] = None, tuesday_program_id: Optional[Union[str, Null]] = None, wednesday_program_id: Optional[Union[str, Null]] = None, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + @route_metadata( + path="/thermostats/update_weekly_program", + has_required_parameters=True, + has_pagination=False, + ) + def update_weekly_program( + self, + *, + device_id: str, + friday_program_id: Optional[Union[str, Null]] = None, + monday_program_id: Optional[Union[str, Null]] = None, + saturday_program_id: Optional[Union[str, Null]] = None, + sunday_program_id: Optional[Union[str, Null]] = None, + thursday_program_id: Optional[Union[str, Null]] = None, + tuesday_program_id: Optional[Union[str, Null]] = None, + wednesday_program_id: Optional[Union[str, Null]] = None, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Updates the thermostat weekly program for a thermostat device. To configure a weekly program, specify the ID of the daily program that you want to use for each day of the week. When you update a weekly program, the set of programs that you specify overwrites any previous weekly program for the thermostat. :param device_id: ID of the thermostat device for which you want to update the weekly program. @@ -901,7 +1198,9 @@ def update_weekly_program(self, *, device_id: str, friday_program_id: Optional[U json_payload["wednesday_program_id"] = wednesday_program_id if not json_payload: - raise ValueError("At least one parameter is required for /thermostats/update_weekly_program") + raise ValueError( + "At least one parameter is required for /thermostats/update_weekly_program" + ) res = self.client.post("/thermostats/update_weekly_program", json=json_payload) @@ -914,5 +1213,5 @@ def update_weekly_program(self, *, device_id: str, friday_program_id: Optional[U return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt + wait_for_action_attempt=wait_for_action_attempt, ) diff --git a/seam/routes/thermostats_daily_programs.py b/seam/routes/thermostats_daily_programs.py index 9245322b..f72073e1 100644 --- a/seam/routes/thermostats_daily_programs.py +++ b/seam/routes/thermostats_daily_programs.py @@ -3,14 +3,16 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (ThermostatDailyProgram,ActionAttempt) +from ..resources import ThermostatDailyProgram, ActionAttempt from ..modules.action_attempts import resolve_action_attempt class AbstractThermostatsDailyPrograms(abc.ABC): @abc.abstractmethod - def create(self, *, device_id: str, name: str, periods: List[Dict[str, Any]]) -> ThermostatDailyProgram: + def create( + self, *, device_id: str, name: str, periods: List[Dict[str, Any]] + ) -> ThermostatDailyProgram: """Creates a new thermostat daily program. A daily program consists of a set of periods, where each period includes a start time and the key of a configured climate preset. Once you have defined a daily program, you can assign it to one or more days within a weekly program. :param device_id: ID of the thermostat device for which you want to create a daily program. @@ -34,7 +36,14 @@ def delete(self, *, thermostat_daily_program_id: str) -> None: raise NotImplementedError() @abc.abstractmethod - def update(self, *, name: str, periods: List[Dict[str, Any]], thermostat_daily_program_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + def update( + self, + *, + name: str, + periods: List[Dict[str, Any]], + thermostat_daily_program_id: str, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Updates a specified thermostat daily program. The periods that you specify overwrite any existing periods for the daily program. :param name: Name of the thermostat daily program that you want to update. @@ -56,8 +65,14 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/thermostats/daily_programs/create", has_required_parameters=True, has_pagination=False) - def create(self, *, device_id: str, name: str, periods: List[Dict[str, Any]]) -> ThermostatDailyProgram: + @route_metadata( + path="/thermostats/daily_programs/create", + has_required_parameters=True, + has_pagination=False, + ) + def create( + self, *, device_id: str, name: str, periods: List[Dict[str, Any]] + ) -> ThermostatDailyProgram: """Creates a new thermostat daily program. A daily program consists of a set of periods, where each period includes a start time and the key of a configured climate preset. Once you have defined a daily program, you can assign it to one or more days within a weekly program. :param device_id: ID of the thermostat device for which you want to create a daily program. @@ -79,13 +94,19 @@ def create(self, *, device_id: str, name: str, periods: List[Dict[str, Any]]) -> json_payload["periods"] = periods if not json_payload: - raise ValueError("At least one parameter is required for /thermostats/daily_programs/create") + raise ValueError( + "At least one parameter is required for /thermostats/daily_programs/create" + ) res = self.client.post("/thermostats/daily_programs/create", json=json_payload) return ThermostatDailyProgram.from_dict(res["thermostat_daily_program"]) - @route_metadata(path="/thermostats/daily_programs/delete", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/thermostats/daily_programs/delete", + has_required_parameters=True, + has_pagination=False, + ) def delete(self, *, thermostat_daily_program_id: str) -> None: """Deletes a thermostat daily program. @@ -98,14 +119,27 @@ def delete(self, *, thermostat_daily_program_id: str) -> None: params["thermostat_daily_program_id"] = thermostat_daily_program_id if not params: - raise ValueError("At least one parameter is required for /thermostats/daily_programs/delete") + raise ValueError( + "At least one parameter is required for /thermostats/daily_programs/delete" + ) self.client.delete("/thermostats/daily_programs/delete", params=params) return None - @route_metadata(path="/thermostats/daily_programs/update", has_required_parameters=True, has_pagination=False) - def update(self, *, name: str, periods: List[Dict[str, Any]], thermostat_daily_program_id: str, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + @route_metadata( + path="/thermostats/daily_programs/update", + has_required_parameters=True, + has_pagination=False, + ) + def update( + self, + *, + name: str, + periods: List[Dict[str, Any]], + thermostat_daily_program_id: str, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: """Updates a specified thermostat daily program. The periods that you specify overwrite any existing periods for the daily program. :param name: Name of the thermostat daily program that you want to update. @@ -129,7 +163,9 @@ def update(self, *, name: str, periods: List[Dict[str, Any]], thermostat_daily_p json_payload["thermostat_daily_program_id"] = thermostat_daily_program_id if not json_payload: - raise ValueError("At least one parameter is required for /thermostats/daily_programs/update") + raise ValueError( + "At least one parameter is required for /thermostats/daily_programs/update" + ) res = self.client.patch("/thermostats/daily_programs/update", json=json_payload) @@ -142,5 +178,5 @@ def update(self, *, name: str, periods: List[Dict[str, Any]], thermostat_daily_p return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt + wait_for_action_attempt=wait_for_action_attempt, ) diff --git a/seam/routes/thermostats_schedules.py b/seam/routes/thermostats_schedules.py index d806918f..7c4787ad 100644 --- a/seam/routes/thermostats_schedules.py +++ b/seam/routes/thermostats_schedules.py @@ -3,13 +3,23 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (ThermostatSchedule) +from ..resources import ThermostatSchedule class AbstractThermostatsSchedules(abc.ABC): @abc.abstractmethod - def create(self, *, climate_preset_key: str, device_id: str, ends_at: str, starts_at: str, is_override_allowed: Optional[bool] = None, max_override_period_minutes: Optional[Union[int, Null]] = None, name: Optional[str] = None) -> ThermostatSchedule: + def create( + self, + *, + climate_preset_key: str, + device_id: str, + ends_at: str, + starts_at: str, + is_override_allowed: Optional[bool] = None, + max_override_period_minutes: Optional[Union[int, Null]] = None, + name: Optional[str] = None, + ) -> ThermostatSchedule: """Creates a new `thermostat schedule `_ for a specified `thermostat `_. :param climate_preset_key: Key of the `climate preset `_ to use for the new thermostat schedule. @@ -52,7 +62,9 @@ def get(self, *, thermostat_schedule_id: str) -> ThermostatSchedule: raise NotImplementedError() @abc.abstractmethod - def list(self, *, device_id: str, user_identifier_key: Optional[str] = None) -> List[ThermostatSchedule]: + def list( + self, *, device_id: str, user_identifier_key: Optional[str] = None + ) -> List[ThermostatSchedule]: """Returns a list of all `thermostat schedules `_ for a specified `thermostat `_. :param device_id: ID of the thermostat device for which you want to list schedules. @@ -65,7 +77,17 @@ def list(self, *, device_id: str, user_identifier_key: Optional[str] = None) -> raise NotImplementedError() @abc.abstractmethod - def update(self, *, thermostat_schedule_id: str, climate_preset_key: Optional[str] = None, ends_at: Optional[str] = None, is_override_allowed: Optional[bool] = None, max_override_period_minutes: Optional[Union[int, Null]] = None, name: Optional[str] = None, starts_at: Optional[str] = None) -> None: + def update( + self, + *, + thermostat_schedule_id: str, + climate_preset_key: Optional[str] = None, + ends_at: Optional[str] = None, + is_override_allowed: Optional[bool] = None, + max_override_period_minutes: Optional[Union[int, Null]] = None, + name: Optional[str] = None, + starts_at: Optional[str] = None, + ) -> None: """Updates a specified `thermostat schedule `_. :param thermostat_schedule_id: ID of the thermostat schedule that you want to update. @@ -91,8 +113,22 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/thermostats/schedules/create", has_required_parameters=True, has_pagination=False) - def create(self, *, climate_preset_key: str, device_id: str, ends_at: str, starts_at: str, is_override_allowed: Optional[bool] = None, max_override_period_minutes: Optional[Union[int, Null]] = None, name: Optional[str] = None) -> ThermostatSchedule: + @route_metadata( + path="/thermostats/schedules/create", + has_required_parameters=True, + has_pagination=False, + ) + def create( + self, + *, + climate_preset_key: str, + device_id: str, + ends_at: str, + starts_at: str, + is_override_allowed: Optional[bool] = None, + max_override_period_minutes: Optional[Union[int, Null]] = None, + name: Optional[str] = None, + ) -> ThermostatSchedule: """Creates a new `thermostat schedule `_ for a specified `thermostat `_. :param climate_preset_key: Key of the `climate preset `_ to use for the new thermostat schedule. @@ -130,13 +166,19 @@ def create(self, *, climate_preset_key: str, device_id: str, ends_at: str, start json_payload["name"] = name if not json_payload: - raise ValueError("At least one parameter is required for /thermostats/schedules/create") + raise ValueError( + "At least one parameter is required for /thermostats/schedules/create" + ) res = self.client.post("/thermostats/schedules/create", json=json_payload) return ThermostatSchedule.from_dict(res["thermostat_schedule"]) - @route_metadata(path="/thermostats/schedules/delete", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/thermostats/schedules/delete", + has_required_parameters=True, + has_pagination=False, + ) def delete(self, *, thermostat_schedule_id: str) -> None: """Deletes a `thermostat schedule `_ for a specified `thermostat `_. @@ -149,13 +191,19 @@ def delete(self, *, thermostat_schedule_id: str) -> None: params["thermostat_schedule_id"] = thermostat_schedule_id if not params: - raise ValueError("At least one parameter is required for /thermostats/schedules/delete") + raise ValueError( + "At least one parameter is required for /thermostats/schedules/delete" + ) self.client.delete("/thermostats/schedules/delete", params=params) return None - @route_metadata(path="/thermostats/schedules/get", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/thermostats/schedules/get", + has_required_parameters=True, + has_pagination=False, + ) def get(self, *, thermostat_schedule_id: str) -> ThermostatSchedule: """Returns a specified `thermostat schedule `_. @@ -170,14 +218,22 @@ def get(self, *, thermostat_schedule_id: str) -> ThermostatSchedule: params["thermostat_schedule_id"] = thermostat_schedule_id if not params: - raise ValueError("At least one parameter is required for /thermostats/schedules/get") + raise ValueError( + "At least one parameter is required for /thermostats/schedules/get" + ) res = self.client.get("/thermostats/schedules/get", params=params) return ThermostatSchedule.from_dict(res["thermostat_schedule"]) - @route_metadata(path="/thermostats/schedules/list", has_required_parameters=True, has_pagination=False) - def list(self, *, device_id: str, user_identifier_key: Optional[str] = None) -> List[ThermostatSchedule]: + @route_metadata( + path="/thermostats/schedules/list", + has_required_parameters=True, + has_pagination=False, + ) + def list( + self, *, device_id: str, user_identifier_key: Optional[str] = None + ) -> List[ThermostatSchedule]: """Returns a list of all `thermostat schedules `_ for a specified `thermostat `_. :param device_id: ID of the thermostat device for which you want to list schedules. @@ -195,14 +251,32 @@ def list(self, *, device_id: str, user_identifier_key: Optional[str] = None) -> params["user_identifier_key"] = user_identifier_key if not params: - raise ValueError("At least one parameter is required for /thermostats/schedules/list") + raise ValueError( + "At least one parameter is required for /thermostats/schedules/list" + ) res = self.client.get("/thermostats/schedules/list", params=params) - return [ThermostatSchedule.from_dict(item) for item in res["thermostat_schedules"]] - - @route_metadata(path="/thermostats/schedules/update", has_required_parameters=True, has_pagination=False) - def update(self, *, thermostat_schedule_id: str, climate_preset_key: Optional[str] = None, ends_at: Optional[str] = None, is_override_allowed: Optional[bool] = None, max_override_period_minutes: Optional[Union[int, Null]] = None, name: Optional[str] = None, starts_at: Optional[str] = None) -> None: + return [ + ThermostatSchedule.from_dict(item) for item in res["thermostat_schedules"] + ] + + @route_metadata( + path="/thermostats/schedules/update", + has_required_parameters=True, + has_pagination=False, + ) + def update( + self, + *, + thermostat_schedule_id: str, + climate_preset_key: Optional[str] = None, + ends_at: Optional[str] = None, + is_override_allowed: Optional[bool] = None, + max_override_period_minutes: Optional[Union[int, Null]] = None, + name: Optional[str] = None, + starts_at: Optional[str] = None, + ) -> None: """Updates a specified `thermostat schedule `_. :param thermostat_schedule_id: ID of the thermostat schedule that you want to update. @@ -238,7 +312,9 @@ def update(self, *, thermostat_schedule_id: str, climate_preset_key: Optional[st json_payload["starts_at"] = starts_at if not json_payload: - raise ValueError("At least one parameter is required for /thermostats/schedules/update") + raise ValueError( + "At least one parameter is required for /thermostats/schedules/update" + ) self.client.patch("/thermostats/schedules/update", json=json_payload) diff --git a/seam/routes/thermostats_simulate.py b/seam/routes/thermostats_simulate.py index f0ea820e..78352237 100644 --- a/seam/routes/thermostats_simulate.py +++ b/seam/routes/thermostats_simulate.py @@ -8,7 +8,16 @@ class AbstractThermostatsSimulate(abc.ABC): @abc.abstractmethod - def hvac_mode_adjusted(self, *, device_id: str, hvac_mode: str, cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, heating_set_point_celsius: Optional[float] = None, heating_set_point_fahrenheit: Optional[float] = None) -> None: + def hvac_mode_adjusted( + self, + *, + device_id: str, + hvac_mode: str, + cooling_set_point_celsius: Optional[float] = None, + cooling_set_point_fahrenheit: Optional[float] = None, + heating_set_point_celsius: Optional[float] = None, + heating_set_point_fahrenheit: Optional[float] = None, + ) -> None: """Simulates having adjusted the `HVAC mode `_ for a `thermostat `_. Only applicable for `sandbox devices `_. See also `Testing Your Thermostat App with Simulate Endpoints `_. :param device_id: ID of the thermostat device for which you want to simulate having adjusted the HVAC mode. @@ -27,7 +36,13 @@ def hvac_mode_adjusted(self, *, device_id: str, hvac_mode: str, cooling_set_poin raise NotImplementedError() @abc.abstractmethod - def temperature_reached(self, *, device_id: str, temperature_celsius: Optional[float] = None, temperature_fahrenheit: Optional[float] = None) -> None: + def temperature_reached( + self, + *, + device_id: str, + temperature_celsius: Optional[float] = None, + temperature_fahrenheit: Optional[float] = None, + ) -> None: """Simulates a `thermostat `_ reaching a specified temperature. Only applicable for `sandbox devices `_. See also `Testing Your Thermostat App with Simulate Endpoints `_. :param device_id: ID of the thermostat device that you want to simulate reaching a specified temperature. @@ -45,8 +60,21 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/thermostats/simulate/hvac_mode_adjusted", has_required_parameters=True, has_pagination=False) - def hvac_mode_adjusted(self, *, device_id: str, hvac_mode: str, cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, heating_set_point_celsius: Optional[float] = None, heating_set_point_fahrenheit: Optional[float] = None) -> None: + @route_metadata( + path="/thermostats/simulate/hvac_mode_adjusted", + has_required_parameters=True, + has_pagination=False, + ) + def hvac_mode_adjusted( + self, + *, + device_id: str, + hvac_mode: str, + cooling_set_point_celsius: Optional[float] = None, + cooling_set_point_fahrenheit: Optional[float] = None, + heating_set_point_celsius: Optional[float] = None, + heating_set_point_fahrenheit: Optional[float] = None, + ) -> None: """Simulates having adjusted the `HVAC mode `_ for a `thermostat `_. Only applicable for `sandbox devices `_. See also `Testing Your Thermostat App with Simulate Endpoints `_. :param device_id: ID of the thermostat device for which you want to simulate having adjusted the HVAC mode. @@ -78,14 +106,26 @@ def hvac_mode_adjusted(self, *, device_id: str, hvac_mode: str, cooling_set_poin json_payload["heating_set_point_fahrenheit"] = heating_set_point_fahrenheit if not json_payload: - raise ValueError("At least one parameter is required for /thermostats/simulate/hvac_mode_adjusted") + raise ValueError( + "At least one parameter is required for /thermostats/simulate/hvac_mode_adjusted" + ) self.client.post("/thermostats/simulate/hvac_mode_adjusted", json=json_payload) return None - @route_metadata(path="/thermostats/simulate/temperature_reached", has_required_parameters=True, has_pagination=False) - def temperature_reached(self, *, device_id: str, temperature_celsius: Optional[float] = None, temperature_fahrenheit: Optional[float] = None) -> None: + @route_metadata( + path="/thermostats/simulate/temperature_reached", + has_required_parameters=True, + has_pagination=False, + ) + def temperature_reached( + self, + *, + device_id: str, + temperature_celsius: Optional[float] = None, + temperature_fahrenheit: Optional[float] = None, + ) -> None: """Simulates a `thermostat `_ reaching a specified temperature. Only applicable for `sandbox devices `_. See also `Testing Your Thermostat App with Simulate Endpoints `_. :param device_id: ID of the thermostat device that you want to simulate reaching a specified temperature. @@ -105,7 +145,9 @@ def temperature_reached(self, *, device_id: str, temperature_celsius: Optional[f json_payload["temperature_fahrenheit"] = temperature_fahrenheit if not json_payload: - raise ValueError("At least one parameter is required for /thermostats/simulate/temperature_reached") + raise ValueError( + "At least one parameter is required for /thermostats/simulate/temperature_reached" + ) self.client.post("/thermostats/simulate/temperature_reached", json=json_payload) diff --git a/seam/routes/user_identities.py b/seam/routes/user_identities.py index ac481ca0..c1cd04e0 100644 --- a/seam/routes/user_identities.py +++ b/seam/routes/user_identities.py @@ -3,8 +3,18 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (UserIdentity,InstantKey,Device,AcsEntrance,AcsSystem,AcsUser) -from .user_identities_unmanaged import AbstractUserIdentitiesUnmanaged, UserIdentitiesUnmanaged +from ..resources import ( + UserIdentity, + InstantKey, + Device, + AcsEntrance, + AcsSystem, + AcsUser, +) +from .user_identities_unmanaged import ( + AbstractUserIdentitiesUnmanaged, + UserIdentitiesUnmanaged, +) class AbstractUserIdentities(abc.ABC): @@ -15,11 +25,17 @@ def unmanaged(self) -> AbstractUserIdentitiesUnmanaged: raise NotImplementedError() @abc.abstractmethod - def add_acs_user(self, *, acs_user_id: str, user_identity_id: Optional[str] = None, user_identity_key: Optional[str] = None) -> None: + def add_acs_user( + self, + *, + acs_user_id: str, + user_identity_id: Optional[str] = None, + user_identity_key: Optional[str] = None, + ) -> None: """Adds a specified `access system user `_ to a specified `user identity `_. - + You must specify either ``user_identity_id`` or ``user_identity_key`` to identify the user identity. - + If ``user_identity_key`` is provided, but the user identity doesn't exist, a new user identity will be created automatically using information from the ACS user. :param acs_user_id: ID of the access system user that you want to add to the user identity. @@ -32,7 +48,15 @@ def add_acs_user(self, *, acs_user_id: str, user_identity_id: Optional[str] = No raise NotImplementedError() @abc.abstractmethod - def create(self, *, acs_system_ids: Optional[List[str]] = None, email_address: Optional[Union[str, Null]] = None, full_name: Optional[Union[str, Null]] = None, phone_number: Optional[Union[str, Null]] = None, user_identity_key: Optional[Union[str, Null]] = None) -> UserIdentity: + def create( + self, + *, + acs_system_ids: Optional[List[str]] = None, + email_address: Optional[Union[str, Null]] = None, + full_name: Optional[Union[str, Null]] = None, + phone_number: Optional[Union[str, Null]] = None, + user_identity_key: Optional[Union[str, Null]] = None, + ) -> UserIdentity: """Creates a new `user identity `_. :param acs_system_ids: List of access system IDs to associate with the new user identity through access system users. If there's no user with the same email address or phone number in the specified access systems, a new access system user is created. If there is an existing user with the same email or phone number in the specified access systems, the user is linked to the user identity. @@ -58,12 +82,18 @@ def delete(self, *, user_identity_id: str) -> None: raise NotImplementedError() @abc.abstractmethod - def generate_instant_key(self, *, user_identity_id: str, customization_profile_id: Optional[str] = None, max_use_count: Optional[float] = None) -> InstantKey: + def generate_instant_key( + self, + *, + user_identity_id: str, + customization_profile_id: Optional[str] = None, + max_use_count: Optional[float] = None, + ) -> InstantKey: """Generates a new `instant key `_ for a specified `user identity `_. :param user_identity_id: ID of the user identity for which you want to generate an instant key. - :param customization_profile_id: + :param customization_profile_id: :param max_use_count: Maximum number of times the instant key can be used. Default: 1. @@ -73,12 +103,17 @@ def generate_instant_key(self, *, user_identity_id: str, customization_profile_i raise NotImplementedError() @abc.abstractmethod - def get(self, *, user_identity_id: Optional[str] = None, user_identity_key: Optional[str] = None) -> UserIdentity: + def get( + self, + *, + user_identity_id: Optional[str] = None, + user_identity_key: Optional[str] = None, + ) -> UserIdentity: """Returns a specified `user identity `_. :param user_identity_id: ID of the user identity that you want to get. - :param user_identity_key: + :param user_identity_key: :returns: OK @@ -97,7 +132,16 @@ def grant_access_to_device(self, *, device_id: str, user_identity_id: str) -> No raise NotImplementedError() @abc.abstractmethod - def list(self, *, created_before: Optional[str] = None, credential_manager_acs_system_id: Optional[str] = None, limit: Optional[int] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, user_identity_ids: Optional[List[str]] = None) -> List[UserIdentity]: + def list( + self, + *, + created_before: Optional[str] = None, + credential_manager_acs_system_id: Optional[str] = None, + limit: Optional[int] = None, + page_cursor: Optional[Union[str, Null]] = None, + search: Optional[str] = None, + user_identity_ids: Optional[List[str]] = None, + ) -> List[UserIdentity]: """Returns a list of all `user identities `_. :param created_before: Timestamp by which to limit returned user identities. Returns user identities created before this timestamp. @@ -182,7 +226,15 @@ def revoke_access_to_device(self, *, device_id: str, user_identity_id: str) -> N raise NotImplementedError() @abc.abstractmethod - def update(self, *, user_identity_id: str, email_address: Optional[Union[str, Null]] = None, full_name: Optional[Union[str, Null]] = None, phone_number: Optional[Union[str, Null]] = None, user_identity_key: Optional[Union[str, Null]] = None) -> None: + def update( + self, + *, + user_identity_id: str, + email_address: Optional[Union[str, Null]] = None, + full_name: Optional[Union[str, Null]] = None, + phone_number: Optional[Union[str, Null]] = None, + user_identity_key: Optional[Union[str, Null]] = None, + ) -> None: """Updates a specified `user identity `_. :param user_identity_id: ID of the user identity that you want to update. @@ -209,12 +261,22 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): def unmanaged(self) -> UserIdentitiesUnmanaged: return self._unmanaged - @route_metadata(path="/user_identities/add_acs_user", has_required_parameters=True, has_pagination=False) - def add_acs_user(self, *, acs_user_id: str, user_identity_id: Optional[str] = None, user_identity_key: Optional[str] = None) -> None: + @route_metadata( + path="/user_identities/add_acs_user", + has_required_parameters=True, + has_pagination=False, + ) + def add_acs_user( + self, + *, + acs_user_id: str, + user_identity_id: Optional[str] = None, + user_identity_key: Optional[str] = None, + ) -> None: """Adds a specified `access system user `_ to a specified `user identity `_. - + You must specify either ``user_identity_id`` or ``user_identity_key`` to identify the user identity. - + If ``user_identity_key`` is provided, but the user identity doesn't exist, a new user identity will be created automatically using information from the ACS user. :param acs_user_id: ID of the access system user that you want to add to the user identity. @@ -234,14 +296,28 @@ def add_acs_user(self, *, acs_user_id: str, user_identity_id: Optional[str] = No json_payload["user_identity_key"] = user_identity_key if not json_payload: - raise ValueError("At least one parameter is required for /user_identities/add_acs_user") + raise ValueError( + "At least one parameter is required for /user_identities/add_acs_user" + ) self.client.put("/user_identities/add_acs_user", json=json_payload) return None - @route_metadata(path="/user_identities/create", has_required_parameters=False, has_pagination=False) - def create(self, *, acs_system_ids: Optional[List[str]] = None, email_address: Optional[Union[str, Null]] = None, full_name: Optional[Union[str, Null]] = None, phone_number: Optional[Union[str, Null]] = None, user_identity_key: Optional[Union[str, Null]] = None) -> UserIdentity: + @route_metadata( + path="/user_identities/create", + has_required_parameters=False, + has_pagination=False, + ) + def create( + self, + *, + acs_system_ids: Optional[List[str]] = None, + email_address: Optional[Union[str, Null]] = None, + full_name: Optional[Union[str, Null]] = None, + phone_number: Optional[Union[str, Null]] = None, + user_identity_key: Optional[Union[str, Null]] = None, + ) -> UserIdentity: """Creates a new `user identity `_. :param acs_system_ids: List of access system IDs to associate with the new user identity through access system users. If there's no user with the same email address or phone number in the specified access systems, a new access system user is created. If there is an existing user with the same email or phone number in the specified access systems, the user is linked to the user identity. @@ -272,7 +348,11 @@ def create(self, *, acs_system_ids: Optional[List[str]] = None, email_address: O return UserIdentity.from_dict(res["user_identity"]) - @route_metadata(path="/user_identities/delete", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/user_identities/delete", + has_required_parameters=True, + has_pagination=False, + ) def delete(self, *, user_identity_id: str) -> None: """Deletes a specified `user identity `_. This deletes the user identity and all associated resources, including any `credentials `_, `acs users `_ and `client sessions `_. @@ -285,19 +365,31 @@ def delete(self, *, user_identity_id: str) -> None: params["user_identity_id"] = user_identity_id if not params: - raise ValueError("At least one parameter is required for /user_identities/delete") + raise ValueError( + "At least one parameter is required for /user_identities/delete" + ) self.client.delete("/user_identities/delete", params=params) return None - @route_metadata(path="/user_identities/generate_instant_key", has_required_parameters=True, has_pagination=False) - def generate_instant_key(self, *, user_identity_id: str, customization_profile_id: Optional[str] = None, max_use_count: Optional[float] = None) -> InstantKey: + @route_metadata( + path="/user_identities/generate_instant_key", + has_required_parameters=True, + has_pagination=False, + ) + def generate_instant_key( + self, + *, + user_identity_id: str, + customization_profile_id: Optional[str] = None, + max_use_count: Optional[float] = None, + ) -> InstantKey: """Generates a new `instant key `_ for a specified `user identity `_. :param user_identity_id: ID of the user identity for which you want to generate an instant key. - :param customization_profile_id: + :param customization_profile_id: :param max_use_count: Maximum number of times the instant key can be used. Default: 1. @@ -314,19 +406,30 @@ def generate_instant_key(self, *, user_identity_id: str, customization_profile_i json_payload["max_use_count"] = max_use_count if not json_payload: - raise ValueError("At least one parameter is required for /user_identities/generate_instant_key") + raise ValueError( + "At least one parameter is required for /user_identities/generate_instant_key" + ) - res = self.client.post("/user_identities/generate_instant_key", json=json_payload) + res = self.client.post( + "/user_identities/generate_instant_key", json=json_payload + ) return InstantKey.from_dict(res["instant_key"]) - @route_metadata(path="/user_identities/get", has_required_parameters=True, has_pagination=False) - def get(self, *, user_identity_id: Optional[str] = None, user_identity_key: Optional[str] = None) -> UserIdentity: + @route_metadata( + path="/user_identities/get", has_required_parameters=True, has_pagination=False + ) + def get( + self, + *, + user_identity_id: Optional[str] = None, + user_identity_key: Optional[str] = None, + ) -> UserIdentity: """Returns a specified `user identity `_. :param user_identity_id: ID of the user identity that you want to get. - :param user_identity_key: + :param user_identity_key: :returns: OK @@ -339,13 +442,19 @@ def get(self, *, user_identity_id: Optional[str] = None, user_identity_key: Opti params["user_identity_key"] = user_identity_key if not params: - raise ValueError("At least one parameter is required for /user_identities/get") + raise ValueError( + "At least one parameter is required for /user_identities/get" + ) res = self.client.get("/user_identities/get", params=params) return UserIdentity.from_dict(res["user_identity"]) - @route_metadata(path="/user_identities/grant_access_to_device", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/user_identities/grant_access_to_device", + has_required_parameters=True, + has_pagination=False, + ) def grant_access_to_device(self, *, device_id: str, user_identity_id: str) -> None: """Grants a specified `user identity `_ access to a specified `device `_. @@ -362,14 +471,27 @@ def grant_access_to_device(self, *, device_id: str, user_identity_id: str) -> No json_payload["user_identity_id"] = user_identity_id if not json_payload: - raise ValueError("At least one parameter is required for /user_identities/grant_access_to_device") + raise ValueError( + "At least one parameter is required for /user_identities/grant_access_to_device" + ) self.client.put("/user_identities/grant_access_to_device", json=json_payload) return None - @route_metadata(path="/user_identities/list", has_required_parameters=False, has_pagination=True) - def list(self, *, created_before: Optional[str] = None, credential_manager_acs_system_id: Optional[str] = None, limit: Optional[int] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None, user_identity_ids: Optional[List[str]] = None) -> List[UserIdentity]: + @route_metadata( + path="/user_identities/list", has_required_parameters=False, has_pagination=True + ) + def list( + self, + *, + created_before: Optional[str] = None, + credential_manager_acs_system_id: Optional[str] = None, + limit: Optional[int] = None, + page_cursor: Optional[Union[str, Null]] = None, + search: Optional[str] = None, + user_identity_ids: Optional[List[str]] = None, + ) -> List[UserIdentity]: """Returns a list of all `user identities `_. :param created_before: Timestamp by which to limit returned user identities. Returns user identities created before this timestamp. @@ -385,26 +507,32 @@ def list(self, *, created_before: Optional[str] = None, credential_manager_acs_s :param user_identity_ids: Array of user identity IDs by which to filter the list of user identities. :returns: OK""" - json_payload: Dict[str, Any] = {} + params: Dict[str, Any] = {} if created_before is not None: - json_payload["created_before"] = created_before + params["created_before"] = created_before if credential_manager_acs_system_id is not None: - json_payload["credential_manager_acs_system_id"] = credential_manager_acs_system_id + params["credential_manager_acs_system_id"] = ( + credential_manager_acs_system_id + ) if limit is not None: - json_payload["limit"] = limit + params["limit"] = limit if page_cursor is not None: - json_payload["page_cursor"] = page_cursor + params["page_cursor"] = page_cursor if search is not None: - json_payload["search"] = search + params["search"] = search if user_identity_ids is not None: - json_payload["user_identity_ids"] = user_identity_ids + params["user_identity_ids"] = user_identity_ids - res = self.client.post("/user_identities/list", json=json_payload) + res = self.client.get("/user_identities/list", params=params) return [UserIdentity.from_dict(item) for item in res["user_identities"]] - @route_metadata(path="/user_identities/list_accessible_devices", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/user_identities/list_accessible_devices", + has_required_parameters=True, + has_pagination=False, + ) def list_accessible_devices(self, *, user_identity_id: str) -> List[Device]: """Returns a list of all `devices `_ associated with a specified `user identity `_. This includes devices derived from the access grants assigned to the user identity and devices directly linked to the user identity. @@ -419,13 +547,19 @@ def list_accessible_devices(self, *, user_identity_id: str) -> List[Device]: params["user_identity_id"] = user_identity_id if not params: - raise ValueError("At least one parameter is required for /user_identities/list_accessible_devices") + raise ValueError( + "At least one parameter is required for /user_identities/list_accessible_devices" + ) res = self.client.get("/user_identities/list_accessible_devices", params=params) return [Device.from_dict(item) for item in res["devices"]] - @route_metadata(path="/user_identities/list_accessible_entrances", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/user_identities/list_accessible_entrances", + has_required_parameters=True, + has_pagination=False, + ) def list_accessible_entrances(self, *, user_identity_id: str) -> List[AcsEntrance]: """Returns a list of all `ACS entrances `_ accessible to a specified `user identity `_. This includes entrances derived from the access grants assigned to the user identity and entrances accessible through ACS users linked to the user identity. @@ -440,13 +574,21 @@ def list_accessible_entrances(self, *, user_identity_id: str) -> List[AcsEntranc params["user_identity_id"] = user_identity_id if not params: - raise ValueError("At least one parameter is required for /user_identities/list_accessible_entrances") + raise ValueError( + "At least one parameter is required for /user_identities/list_accessible_entrances" + ) - res = self.client.get("/user_identities/list_accessible_entrances", params=params) + res = self.client.get( + "/user_identities/list_accessible_entrances", params=params + ) return [AcsEntrance.from_dict(item) for item in res["acs_entrances"]] - @route_metadata(path="/user_identities/list_acs_systems", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/user_identities/list_acs_systems", + has_required_parameters=True, + has_pagination=False, + ) def list_acs_systems(self, *, user_identity_id: str) -> List[AcsSystem]: """Returns a list of all `access systems `_ associated with a specified `user identity `_. @@ -461,13 +603,19 @@ def list_acs_systems(self, *, user_identity_id: str) -> List[AcsSystem]: params["user_identity_id"] = user_identity_id if not params: - raise ValueError("At least one parameter is required for /user_identities/list_acs_systems") + raise ValueError( + "At least one parameter is required for /user_identities/list_acs_systems" + ) res = self.client.get("/user_identities/list_acs_systems", params=params) return [AcsSystem.from_dict(item) for item in res["acs_systems"]] - @route_metadata(path="/user_identities/list_acs_users", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/user_identities/list_acs_users", + has_required_parameters=True, + has_pagination=False, + ) def list_acs_users(self, *, user_identity_id: str) -> List[AcsUser]: """Returns a list of all `access system users `_ assigned to a specified `user identity `_. @@ -482,13 +630,19 @@ def list_acs_users(self, *, user_identity_id: str) -> List[AcsUser]: params["user_identity_id"] = user_identity_id if not params: - raise ValueError("At least one parameter is required for /user_identities/list_acs_users") + raise ValueError( + "At least one parameter is required for /user_identities/list_acs_users" + ) res = self.client.get("/user_identities/list_acs_users", params=params) return [AcsUser.from_dict(item) for item in res["acs_users"]] - @route_metadata(path="/user_identities/remove_acs_user", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/user_identities/remove_acs_user", + has_required_parameters=True, + has_pagination=False, + ) def remove_acs_user(self, *, acs_user_id: str, user_identity_id: str) -> None: """Removes a specified `access system user `_ from a specified `user identity `_. @@ -505,13 +659,19 @@ def remove_acs_user(self, *, acs_user_id: str, user_identity_id: str) -> None: params["user_identity_id"] = user_identity_id if not params: - raise ValueError("At least one parameter is required for /user_identities/remove_acs_user") + raise ValueError( + "At least one parameter is required for /user_identities/remove_acs_user" + ) self.client.delete("/user_identities/remove_acs_user", params=params) return None - @route_metadata(path="/user_identities/revoke_access_to_device", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/user_identities/revoke_access_to_device", + has_required_parameters=True, + has_pagination=False, + ) def revoke_access_to_device(self, *, device_id: str, user_identity_id: str) -> None: """Revokes access to a specified `device `_ from a specified `user identity `_. @@ -528,14 +688,28 @@ def revoke_access_to_device(self, *, device_id: str, user_identity_id: str) -> N params["user_identity_id"] = user_identity_id if not params: - raise ValueError("At least one parameter is required for /user_identities/revoke_access_to_device") + raise ValueError( + "At least one parameter is required for /user_identities/revoke_access_to_device" + ) self.client.delete("/user_identities/revoke_access_to_device", params=params) return None - @route_metadata(path="/user_identities/update", has_required_parameters=True, has_pagination=False) - def update(self, *, user_identity_id: str, email_address: Optional[Union[str, Null]] = None, full_name: Optional[Union[str, Null]] = None, phone_number: Optional[Union[str, Null]] = None, user_identity_key: Optional[Union[str, Null]] = None) -> None: + @route_metadata( + path="/user_identities/update", + has_required_parameters=True, + has_pagination=False, + ) + def update( + self, + *, + user_identity_id: str, + email_address: Optional[Union[str, Null]] = None, + full_name: Optional[Union[str, Null]] = None, + phone_number: Optional[Union[str, Null]] = None, + user_identity_key: Optional[Union[str, Null]] = None, + ) -> None: """Updates a specified `user identity `_. :param user_identity_id: ID of the user identity that you want to update. @@ -563,7 +737,9 @@ def update(self, *, user_identity_id: str, email_address: Optional[Union[str, Nu json_payload["user_identity_key"] = user_identity_key if not json_payload: - raise ValueError("At least one parameter is required for /user_identities/update") + raise ValueError( + "At least one parameter is required for /user_identities/update" + ) self.client.patch("/user_identities/update", json=json_payload) diff --git a/seam/routes/user_identities_unmanaged.py b/seam/routes/user_identities_unmanaged.py index 91a71518..b177177a 100644 --- a/seam/routes/user_identities_unmanaged.py +++ b/seam/routes/user_identities_unmanaged.py @@ -3,7 +3,7 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (UnmanagedUserIdentity) +from ..resources import UnmanagedUserIdentity class AbstractUserIdentitiesUnmanaged(abc.ABC): @@ -20,7 +20,14 @@ def get(self, *, user_identity_id: str) -> UnmanagedUserIdentity: raise NotImplementedError() @abc.abstractmethod - def list(self, *, created_before: Optional[str] = None, limit: Optional[int] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None) -> List[UnmanagedUserIdentity]: + def list( + self, + *, + created_before: Optional[str] = None, + limit: Optional[int] = None, + page_cursor: Optional[Union[str, Null]] = None, + search: Optional[str] = None, + ) -> List[UnmanagedUserIdentity]: """Returns a list of all unmanaged `user identities `_ (where is_managed = false). :param created_before: Timestamp by which to limit returned unmanaged user identities. Returns user identities created before this timestamp. @@ -35,9 +42,15 @@ def list(self, *, created_before: Optional[str] = None, limit: Optional[int] = N raise NotImplementedError() @abc.abstractmethod - def update(self, *, is_managed: bool, user_identity_id: str, user_identity_key: Optional[str] = None) -> None: + def update( + self, + *, + is_managed: bool, + user_identity_id: str, + user_identity_key: Optional[str] = None, + ) -> None: """Updates an unmanaged `user identity `_ to make it managed. - + This endpoint can only be used to convert unmanaged user identities to managed ones by setting ``is_managed`` to ``true``. It cannot be used to convert managed user identities back to unmanaged. :param is_managed: Must be set to true to convert the unmanaged user identity to managed. @@ -55,7 +68,11 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/user_identities/unmanaged/get", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/user_identities/unmanaged/get", + has_required_parameters=True, + has_pagination=False, + ) def get(self, *, user_identity_id: str) -> UnmanagedUserIdentity: """Returns a specified unmanaged `user identity `_ (where is_managed = false). @@ -70,14 +87,27 @@ def get(self, *, user_identity_id: str) -> UnmanagedUserIdentity: params["user_identity_id"] = user_identity_id if not params: - raise ValueError("At least one parameter is required for /user_identities/unmanaged/get") + raise ValueError( + "At least one parameter is required for /user_identities/unmanaged/get" + ) res = self.client.get("/user_identities/unmanaged/get", params=params) return UnmanagedUserIdentity.from_dict(res["user_identity"]) - @route_metadata(path="/user_identities/unmanaged/list", has_required_parameters=False, has_pagination=True) - def list(self, *, created_before: Optional[str] = None, limit: Optional[int] = None, page_cursor: Optional[Union[str, Null]] = None, search: Optional[str] = None) -> List[UnmanagedUserIdentity]: + @route_metadata( + path="/user_identities/unmanaged/list", + has_required_parameters=False, + has_pagination=True, + ) + def list( + self, + *, + created_before: Optional[str] = None, + limit: Optional[int] = None, + page_cursor: Optional[Union[str, Null]] = None, + search: Optional[str] = None, + ) -> List[UnmanagedUserIdentity]: """Returns a list of all unmanaged `user identities `_ (where is_managed = false). :param created_before: Timestamp by which to limit returned unmanaged user identities. Returns user identities created before this timestamp. @@ -102,12 +132,24 @@ def list(self, *, created_before: Optional[str] = None, limit: Optional[int] = N res = self.client.get("/user_identities/unmanaged/list", params=params) - return [UnmanagedUserIdentity.from_dict(item) for item in res["user_identities"]] - - @route_metadata(path="/user_identities/unmanaged/update", has_required_parameters=True, has_pagination=False) - def update(self, *, is_managed: bool, user_identity_id: str, user_identity_key: Optional[str] = None) -> None: + return [ + UnmanagedUserIdentity.from_dict(item) for item in res["user_identities"] + ] + + @route_metadata( + path="/user_identities/unmanaged/update", + has_required_parameters=True, + has_pagination=False, + ) + def update( + self, + *, + is_managed: bool, + user_identity_id: str, + user_identity_key: Optional[str] = None, + ) -> None: """Updates an unmanaged `user identity `_ to make it managed. - + This endpoint can only be used to convert unmanaged user identities to managed ones by setting ``is_managed`` to ``true``. It cannot be used to convert managed user identities back to unmanaged. :param is_managed: Must be set to true to convert the unmanaged user identity to managed. @@ -127,7 +169,9 @@ def update(self, *, is_managed: bool, user_identity_id: str, user_identity_key: json_payload["user_identity_key"] = user_identity_key if not json_payload: - raise ValueError("At least one parameter is required for /user_identities/unmanaged/update") + raise ValueError( + "At least one parameter is required for /user_identities/unmanaged/update" + ) self.client.patch("/user_identities/unmanaged/update", json=json_payload) diff --git a/seam/routes/webhooks.py b/seam/routes/webhooks.py index 99da3a4d..bf407c41 100644 --- a/seam/routes/webhooks.py +++ b/seam/routes/webhooks.py @@ -3,7 +3,7 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (Webhook) +from ..resources import Webhook class AbstractWebhooks(abc.ABC): @@ -65,7 +65,9 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/webhooks/create", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/webhooks/create", has_required_parameters=True, has_pagination=False + ) def create(self, *, url: str, event_types: Optional[List[str]] = None) -> Webhook: """Creates a new `webhook `_. @@ -90,7 +92,9 @@ def create(self, *, url: str, event_types: Optional[List[str]] = None) -> Webhoo return Webhook.from_dict(res["webhook"]) - @route_metadata(path="/webhooks/delete", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/webhooks/delete", has_required_parameters=True, has_pagination=False + ) def delete(self, *, webhook_id: str) -> None: """Deletes a specified `webhook `_. @@ -109,7 +113,9 @@ def delete(self, *, webhook_id: str) -> None: return None - @route_metadata(path="/webhooks/get", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/webhooks/get", has_required_parameters=True, has_pagination=False + ) def get(self, *, webhook_id: str) -> Webhook: """Gets a specified `webhook `_. @@ -130,19 +136,22 @@ def get(self, *, webhook_id: str) -> Webhook: return Webhook.from_dict(res["webhook"]) - @route_metadata(path="/webhooks/list", has_required_parameters=False, has_pagination=False) + @route_metadata( + path="/webhooks/list", has_required_parameters=False, has_pagination=False + ) def list(self) -> List[Webhook]: """Returns a list of all `webhooks `_. :returns: OK""" params: Dict[str, Any] = {} - res = self.client.get("/webhooks/list", params=params) return [Webhook.from_dict(item) for item in res["webhooks"]] - @route_metadata(path="/webhooks/update", has_required_parameters=True, has_pagination=False) + @route_metadata( + path="/webhooks/update", has_required_parameters=True, has_pagination=False + ) def update(self, *, event_types: List[str], webhook_id: str) -> None: """Updates a specified `webhook `_. diff --git a/seam/routes/workspaces.py b/seam/routes/workspaces.py index 407df2c1..01ee6655 100644 --- a/seam/routes/workspaces.py +++ b/seam/routes/workspaces.py @@ -3,14 +3,27 @@ from ..client import SeamHttpClient from ..route import route_metadata from ..null import Null -from ..resources import (Workspace,ActionAttempt) +from ..resources import Workspace, ActionAttempt from ..modules.action_attempts import resolve_action_attempt class AbstractWorkspaces(abc.ABC): @abc.abstractmethod - def create(self, *, name: str, company_name: Optional[str] = None, connect_partner_name: Optional[Union[str, Null]] = None, connect_webview_customization: Optional[Dict[str, Any]] = None, is_sandbox: Optional[bool] = None, organization_id: Optional[str] = None, webview_logo_shape: Optional[str] = None, webview_primary_button_color: Optional[str] = None, webview_primary_button_text_color: Optional[str] = None, webview_success_message: Optional[str] = None) -> Workspace: + def create( + self, + *, + name: str, + company_name: Optional[str] = None, + connect_partner_name: Optional[Union[str, Null]] = None, + connect_webview_customization: Optional[Dict[str, Any]] = None, + is_sandbox: Optional[bool] = None, + organization_id: Optional[str] = None, + webview_logo_shape: Optional[str] = None, + webview_primary_button_color: Optional[str] = None, + webview_primary_button_text_color: Optional[str] = None, + webview_success_message: Optional[str] = None, + ) -> Workspace: """Creates a new `workspace `_. :param name: Name of the new workspace. @@ -53,7 +66,9 @@ def list(self) -> List[Workspace]: raise NotImplementedError() @abc.abstractmethod - def reset_sandbox(self, *, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + def reset_sandbox( + self, *, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None + ) -> ActionAttempt: """Resets the `sandbox workspace `_ associated with the authentication value. Note that this endpoint is only available for sandbox workspaces. :param wait_for_action_attempt: Whether, and for how long, to wait for the action attempt to finish. @@ -62,7 +77,16 @@ def reset_sandbox(self, *, wait_for_action_attempt: Optional[Union[bool, Dict[st raise NotImplementedError() @abc.abstractmethod - def update(self, *, connect_partner_name: Optional[str] = None, connect_webview_customization: Optional[Dict[str, Any]] = None, is_publishable_key_auth_enabled: Optional[bool] = None, is_suspended: Optional[bool] = None, name: Optional[str] = None, organization_id: Optional[str] = None) -> None: + def update( + self, + *, + connect_partner_name: Optional[str] = None, + connect_webview_customization: Optional[Dict[str, Any]] = None, + is_publishable_key_auth_enabled: Optional[bool] = None, + is_suspended: Optional[bool] = None, + name: Optional[str] = None, + organization_id: Optional[str] = None, + ) -> None: """Updates the `workspace `_ associated with the authentication value. :param connect_partner_name: Connect partner name for the workspace. @@ -75,7 +99,8 @@ def update(self, *, connect_partner_name: Optional[str] = None, connect_webview_ :param name: Name of the workspace. - :param organization_id: ID of the organization to assign the workspace to. The authenticated user must be the owner of the workspace and an admin of the target organization.""" + :param organization_id: ID of the organization to assign the workspace to. The authenticated user must be the owner of the workspace and an admin of the target organization. + """ raise NotImplementedError() @@ -84,8 +109,23 @@ def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - @route_metadata(path="/workspaces/create", has_required_parameters=True, has_pagination=False) - def create(self, *, name: str, company_name: Optional[str] = None, connect_partner_name: Optional[Union[str, Null]] = None, connect_webview_customization: Optional[Dict[str, Any]] = None, is_sandbox: Optional[bool] = None, organization_id: Optional[str] = None, webview_logo_shape: Optional[str] = None, webview_primary_button_color: Optional[str] = None, webview_primary_button_text_color: Optional[str] = None, webview_success_message: Optional[str] = None) -> Workspace: + @route_metadata( + path="/workspaces/create", has_required_parameters=True, has_pagination=False + ) + def create( + self, + *, + name: str, + company_name: Optional[str] = None, + connect_partner_name: Optional[Union[str, Null]] = None, + connect_webview_customization: Optional[Dict[str, Any]] = None, + is_sandbox: Optional[bool] = None, + organization_id: Optional[str] = None, + webview_logo_shape: Optional[str] = None, + webview_primary_button_color: Optional[str] = None, + webview_primary_button_text_color: Optional[str] = None, + webview_success_message: Optional[str] = None, + ) -> Workspace: """Creates a new `workspace `_. :param name: Name of the new workspace. @@ -120,7 +160,9 @@ def create(self, *, name: str, company_name: Optional[str] = None, connect_partn if connect_partner_name is not None: json_payload["connect_partner_name"] = connect_partner_name if connect_webview_customization is not None: - json_payload["connect_webview_customization"] = connect_webview_customization + json_payload["connect_webview_customization"] = ( + connect_webview_customization + ) if is_sandbox is not None: json_payload["is_sandbox"] = is_sandbox if organization_id is not None: @@ -130,43 +172,55 @@ def create(self, *, name: str, company_name: Optional[str] = None, connect_partn if webview_primary_button_color is not None: json_payload["webview_primary_button_color"] = webview_primary_button_color if webview_primary_button_text_color is not None: - json_payload["webview_primary_button_text_color"] = webview_primary_button_text_color + json_payload["webview_primary_button_text_color"] = ( + webview_primary_button_text_color + ) if webview_success_message is not None: json_payload["webview_success_message"] = webview_success_message if not json_payload: - raise ValueError("At least one parameter is required for /workspaces/create") + raise ValueError( + "At least one parameter is required for /workspaces/create" + ) res = self.client.post("/workspaces/create", json=json_payload) return Workspace.from_dict(res["workspace"]) - @route_metadata(path="/workspaces/get", has_required_parameters=False, has_pagination=False) + @route_metadata( + path="/workspaces/get", has_required_parameters=False, has_pagination=False + ) def get(self) -> Workspace: """Returns the `workspace `_ associated with the authentication value. :returns: OK""" params: Dict[str, Any] = {} - res = self.client.get("/workspaces/get", params=params) return Workspace.from_dict(res["workspace"]) - @route_metadata(path="/workspaces/list", has_required_parameters=False, has_pagination=False) + @route_metadata( + path="/workspaces/list", has_required_parameters=False, has_pagination=False + ) def list(self) -> List[Workspace]: """Returns a list of `workspaces `_ associated with the authentication value. :returns: OK""" params: Dict[str, Any] = {} - res = self.client.get("/workspaces/list", params=params) return [Workspace.from_dict(item) for item in res["workspaces"]] - @route_metadata(path="/workspaces/reset_sandbox", has_required_parameters=False, has_pagination=False) - def reset_sandbox(self, *, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None) -> ActionAttempt: + @route_metadata( + path="/workspaces/reset_sandbox", + has_required_parameters=False, + has_pagination=False, + ) + def reset_sandbox( + self, *, wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None + ) -> ActionAttempt: """Resets the `sandbox workspace `_ associated with the authentication value. Note that this endpoint is only available for sandbox workspaces. :param wait_for_action_attempt: Whether, and for how long, to wait for the action attempt to finish. @@ -174,7 +228,6 @@ def reset_sandbox(self, *, wait_for_action_attempt: Optional[Union[bool, Dict[st :returns: OK""" json_payload: Dict[str, Any] = {} - res = self.client.post("/workspaces/reset_sandbox", json=json_payload) wait_for_action_attempt = ( @@ -186,11 +239,22 @@ def reset_sandbox(self, *, wait_for_action_attempt: Optional[Union[bool, Dict[st return resolve_action_attempt( client=self.client, action_attempt=ActionAttempt.from_dict(res["action_attempt"]), - wait_for_action_attempt=wait_for_action_attempt + wait_for_action_attempt=wait_for_action_attempt, ) - @route_metadata(path="/workspaces/update", has_required_parameters=False, has_pagination=False) - def update(self, *, connect_partner_name: Optional[str] = None, connect_webview_customization: Optional[Dict[str, Any]] = None, is_publishable_key_auth_enabled: Optional[bool] = None, is_suspended: Optional[bool] = None, name: Optional[str] = None, organization_id: Optional[str] = None) -> None: + @route_metadata( + path="/workspaces/update", has_required_parameters=False, has_pagination=False + ) + def update( + self, + *, + connect_partner_name: Optional[str] = None, + connect_webview_customization: Optional[Dict[str, Any]] = None, + is_publishable_key_auth_enabled: Optional[bool] = None, + is_suspended: Optional[bool] = None, + name: Optional[str] = None, + organization_id: Optional[str] = None, + ) -> None: """Updates the `workspace `_ associated with the authentication value. :param connect_partner_name: Connect partner name for the workspace. @@ -203,15 +267,20 @@ def update(self, *, connect_partner_name: Optional[str] = None, connect_webview_ :param name: Name of the workspace. - :param organization_id: ID of the organization to assign the workspace to. The authenticated user must be the owner of the workspace and an admin of the target organization.""" + :param organization_id: ID of the organization to assign the workspace to. The authenticated user must be the owner of the workspace and an admin of the target organization. + """ json_payload: Dict[str, Any] = {} if connect_partner_name is not None: json_payload["connect_partner_name"] = connect_partner_name if connect_webview_customization is not None: - json_payload["connect_webview_customization"] = connect_webview_customization + json_payload["connect_webview_customization"] = ( + connect_webview_customization + ) if is_publishable_key_auth_enabled is not None: - json_payload["is_publishable_key_auth_enabled"] = is_publishable_key_auth_enabled + json_payload["is_publishable_key_auth_enabled"] = ( + is_publishable_key_auth_enabled + ) if is_suspended is not None: json_payload["is_suspended"] = is_suspended if name is not None: diff --git a/test/client_test.py b/test/client_test.py index e80bd266..360de5fc 100644 --- a/test/client_test.py +++ b/test/client_test.py @@ -4,8 +4,8 @@ def test_seam_exposes_a_client_that_can_make_requests(seam: Seam, server): _, seed = server - response = seam.client.post( - "/devices/get", json={"device_id": seed["august_device_1"]} + response = seam.client.get( + "/devices/get", params={"device_id": seed["august_device_1"]} ) assert response["device"]["workspace_id"] == seed["seed_workspace_1"] diff --git a/test/conftest.py b/test/conftest.py index 39a47693..4c867470 100755 --- a/test/conftest.py +++ b/test/conftest.py @@ -70,21 +70,16 @@ def recording_server(responses): class Handler(BaseHTTPRequestHandler): protocol_version = "HTTP/1.1" - # pylint: disable-next=invalid-name - def do_GET(self): - self._handle_request() - - # pylint: disable-next=invalid-name - def do_POST(self): - self._handle_request() - def _handle_request(self): content_length = int(self.headers.get("content-length", 0)) raw_body = self.rfile.read(content_length) + path, _, query = self.path.partition("?") requests.append( { - "path": self.path, + "method": self.command, + "path": path, + "query": query, "headers": {k.lower(): v for k, v in self.headers.items()}, "body": json.loads(raw_body) if raw_body else None, } @@ -109,6 +104,15 @@ def _handle_request(self): self.end_headers() self.wfile.write(body) + # Endpoints are served over their semantic method, so record them all. + # BaseHTTPRequestHandler dispatches on these names. + # pylint: disable=invalid-name + do_GET = _handle_request + do_POST = _handle_request + do_PUT = _handle_request + do_PATCH = _handle_request + do_DELETE = _handle_request + def log_message(self, *args): pass diff --git a/test/headers_test.py b/test/headers_test.py index dfb040e0..3e6d7667 100644 --- a/test/headers_test.py +++ b/test/headers_test.py @@ -17,7 +17,9 @@ def test_seam_sends_default_headers(recording_server): assert len(requests) == 1 [request] = requests - assert request["path"] == f"/devices/get?device_id={device_id}" + assert request["method"] == "GET" + assert request["path"] == "/devices/get" + assert request["query"] == f"device_id={device_id}" assert request["body"] is None assert request["headers"]["seam-sdk-name"] == "seamapi/python" diff --git a/test/http_error_test.py b/test/http_error_test.py index dd30329b..b09b83df 100644 --- a/test/http_error_test.py +++ b/test/http_error_test.py @@ -39,6 +39,7 @@ def test_seam_http_throws_invalid_input_error(server): seam = Seam(api_key=seed["seam_apikey1_token"], endpoint=endpoint) + # /devices/get requires either device_id or name. with pytest.raises(SeamHttpInvalidInputError) as exc_info: seam.devices.list(device_ids=4242) err = exc_info.value diff --git a/test/null_test.py b/test/null_test.py index 36b1a452..1b0b8e35 100644 --- a/test/null_test.py +++ b/test/null_test.py @@ -91,20 +91,20 @@ def request(self, method, url, *args, **kwargs): def test_client_sends_null_params_as_json_null(sent_payloads): client = SeamHttpClient(base_url="https://example.com", auth_headers={}) - client.post("/devices/update", json={"device_id": "a", "name": NULL}) + client.patch("/devices/update", json={"device_id": "a", "name": NULL}) assert sent_payloads == [{"device_id": "a", "name": None}] def test_client_sends_nested_null_params_as_json_null(sent_payloads): client = SeamHttpClient(base_url="https://example.com", auth_headers={}) - client.post("/spaces/update", json={"customer_data": {"check_in": NULL}}) + client.patch("/spaces/update", json={"customer_data": {"check_in": NULL}}) assert sent_payloads == [{"customer_data": {"check_in": None}}] def test_client_passes_through_payloads_without_null_params(sent_payloads): client = SeamHttpClient(base_url="https://example.com", auth_headers={}) - client.post("/devices/update", json={"device_id": "a", "name": "Front Door"}) + client.patch("/devices/update", json={"device_id": "a", "name": "Front Door"}) assert sent_payloads == [{"device_id": "a", "name": "Front Door"}] diff --git a/test/serialization_test.py b/test/serialization_test.py index 40d4b0c0..1d30a2ea 100644 --- a/test/serialization_test.py +++ b/test/serialization_test.py @@ -1,3 +1,5 @@ +from datetime import datetime, timezone + from seam import Seam @@ -40,9 +42,9 @@ def test_serializes_array_params_when_explicitly_using_client(server): endpoint, seed = server seam = Seam.from_api_key(seed["seam_apikey1_token"], endpoint=endpoint) - response = seam.client.post( + response = seam.client.get( "/devices/list", - json={"device_ids": [seed["august_device_1"], seed["ecobee_device_1"]]}, + params={"device_ids": [seed["august_device_1"], seed["ecobee_device_1"]]}, ) device_ids = [device["device_id"] for device in response["devices"]] @@ -50,3 +52,58 @@ def test_serializes_array_params_when_explicitly_using_client(server): assert len(device_ids) == 2 assert seed["august_device_1"] in device_ids assert seed["ecobee_device_1"] in device_ids + + +def test_serializes_array_params_when_empty_and_explicitly_using_get(seam: Seam): + # The empty array is serialized to a single empty value, e.g., device_ids=, + # which the Seam API parses back to the empty array. + response = seam.client.get("/devices/list", params={"device_ids": []}) + + assert len(response["devices"]) == 0 + + +def test_serializes_array_params_when_none_and_explicitly_using_get(seam: Seam): + response = seam.client.get("/devices/list", params={"device_ids": None}) + database = seam.client.get("/_fake/database") + + assert len(response["devices"]) == len(database["devices"]) + + +def test_serializes_string_params_when_explicitly_using_get(server): + endpoint, seed = server + seam = Seam.from_api_key(seed["seam_apikey1_token"], endpoint=endpoint) + + response = seam.client.get( + "/devices/get", params={"device_id": seed["august_device_1"]} + ) + + assert response["device"]["device_id"] == seed["august_device_1"] + + +def test_serializes_number_params_when_explicitly_using_get(seam: Seam): + # A float is serialized as the Seam API expects a number, e.g., limit=2, + # never as 2.0. + response = seam.client.get("/devices/list", params={"limit": 2.0}) + + assert len(response["devices"]) == 2 + + +def test_serializes_datetime_params_when_explicitly_using_get(seam: Seam): + created_before = datetime(2999, 1, 1, tzinfo=timezone.utc) + + response = seam.client.get( + "/devices/list", params={"created_before": created_before} + ) + database = seam.client.get("/_fake/database") + + assert len(response["devices"]) == len(database["devices"]) + + +def test_serializes_params_for_a_route_using_the_semantic_method(server): + # /devices/list is a GET, so its params are serialized to the query string. + endpoint, seed = server + seam = Seam.from_api_key(seed["seam_apikey1_token"], endpoint=endpoint) + + devices = seam.devices.list(device_ids=[seed["august_device_1"]]) + + assert [device.device_id for device in devices] == [seed["august_device_1"]] diff --git a/test/timeout_test.py b/test/timeout_test.py index a7aff192..14a99155 100644 --- a/test/timeout_test.py +++ b/test/timeout_test.py @@ -90,7 +90,7 @@ def test_per_request_timeout_overrides_the_client_timeout(recording_server): with recording_server([(200, {"devices": []})]) as (endpoint, _): seam = Seam.from_api_key("seam_apikey_token", endpoint=endpoint, timeout=30) - response = seam.client.post("/devices/list", json={}, timeout=10) + response = seam.client.get("/devices/list", params={}, timeout=10) assert response == {"devices": []} @@ -101,7 +101,9 @@ def test_seam_times_out_a_slow_request(): "seam_apikey_token", endpoint=endpoint, timeout=0.25, - retries=Retry(total=0), + # GET is idempotent, so urllib3 would retry the read timeout and + # raise its own error instead of surfacing the timeout. + retries=Retry(total=0, read=False), ) with pytest.raises(TimeoutException): @@ -113,13 +115,17 @@ def slow_server(): class Handler(BaseHTTPRequestHandler): protocol_version = "HTTP/1.1" - # pylint: disable-next=invalid-name - def do_POST(self): + def serve_slowly(self): time.sleep(5) self.send_response(200) self.send_header("content-length", "0") self.end_headers() + # BaseHTTPRequestHandler dispatches on these names. + # pylint: disable=invalid-name + do_GET = serve_slowly + do_POST = serve_slowly + def log_message(self, *args): pass From b83a89d4df39a5b6155794d54584ce936baac5e8 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 04:25:06 +0000 Subject: [PATCH 07/11] feat: apply the retry policy to API requests Serving each endpoint over its semantic method also settles the retries option. httpx-retries treats GET, PUT and DELETE as retryable and POST as not, so the option had no effect while every request was a POST. It now reaches API requests without the SDK exposing the HTTP method, so remove the xfail markers from the tests that record that. Set the serialized query on the URL rather than handing it to httpx as params, because httpx re-encodes a query string it is given: it escapes "*" and unescapes "~", neither of which the serialization standard does. The README documented how the serialization works, which is an internal detail of the SDK. Say instead that the serializer is exported for callers using their own HTTP client, and reference both the reference implementation and the parser the Seam API uses. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PpqwDhZmyikGbjmCPqFW2A --- README.rst | 48 ++++++++++----------------- seam/client.py | 7 +++- test/null_test.py | 78 ++++++++++++++++++++++++++++---------------- test/timeout_test.py | 4 +-- 4 files changed, 73 insertions(+), 64 deletions(-) diff --git a/README.rst b/README.rst index 0994f8bf..dd2eda9f 100644 --- a/README.rst +++ b/README.rst @@ -566,46 +566,32 @@ precedence over the defaults the SDK sets: Serializing URL search params ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -The Seam API parses URL search params as complex types. -This SDK implements the `Seam URL search params serialization standard`_, -which defines how the Seam SDKs serialize objects to URL search params. -Use it directly when building requests to the Seam API by hand: +The SDK serializes URL search params for you. +If you call the Seam API with your own HTTP client, +``serialize_url_search_params`` is exported for that purpose: .. code-block:: python + import httpx from seam import serialize_url_search_params - serialize_url_search_params( - { - "name": "Dax", - "age": 27, - "is_admin": True, - "tags": ["cars", "planes"], - } - ) - # => 'age=27&is_admin=true&name=Dax&tags=cars&tags=planes' - -Params are sorted by name, so equivalent input always produces the same query string. -Nested dicts are serialized to dot-path keys, e.g., ``{"a": {"b": 1}}`` becomes ``a.b=1``. -Params set to ``None`` are omitted, -while params set to ``seam.NULL`` are serialized to an empty value, e.g., ``a=``. -See `Omitted params and null params`_. -A param that cannot be represented raises a ``seam.UnserializableParamError``. + query = serialize_url_search_params({"device_ids": ["device1", "device2"]}) -To merge serialized params into existing params, use ``update_url_search_params``: - -.. code-block:: python - - from seam import UrlSearchParams, update_url_search_params - - search_params = UrlSearchParams("?foo=bar") + httpx.get( + f"https://connect.getseam.com/devices/list?{query}", + headers={"Authorization": "Bearer your-api-key"}, + ) - update_url_search_params(search_params, {"name": "Dax"}) +It returns a query string, so it works with any HTTP client. +Put it on the URL as above rather than handing it to the client as params: +clients re-encode a query string they are given, e.g. httpx escapes ``*`` +and unescapes ``~``, which this serialization does not. - str(search_params) - # => 'foo=bar&name=Dax' +The `reference implementation`_ defines this serialization, +and the Seam API parses it with the corresponding `parser`_. -.. _Seam URL search params serialization standard: https://github.com/seamapi/url-search-params-serializer +.. _reference implementation: https://github.com/seamapi/url-search-params-serializer +.. _parser: https://github.com/seamapi/url-search-params-parser Development and Testing ----------------------- diff --git a/seam/client.py b/seam/client.py index daa83ed6..49d13f0a 100644 --- a/seam/client.py +++ b/seam/client.py @@ -114,8 +114,13 @@ def request(self, method, url, *args, **kwargs) -> Any: # Search params are serialized to the Seam API standard, which httpx # does not implement. The NULL sentinel is serialized to an empty value. + # The query is set on the URL rather than passed to httpx as params, + # because httpx re-encodes a query string it is given, e.g. it escapes + # "*" and unescapes "~", which the standard does not. if isinstance(kwargs.get("params"), Mapping): - kwargs["params"] = serialize_url_search_params(kwargs["params"]) + query = serialize_url_search_params(kwargs.pop("params")) + if query: + url = httpx.URL(url, query=query.encode()) response = super().request(method, url, *args, **kwargs) diff --git a/test/null_test.py b/test/null_test.py index 1b0b8e35..cc06f35c 100644 --- a/test/null_test.py +++ b/test/null_test.py @@ -1,8 +1,5 @@ from collections import OrderedDict -import niquests -import pytest - from seam.client import SeamHttpClient from seam.null import NULL, Null, is_null, replace_null @@ -67,44 +64,67 @@ def test_replace_null_normalizes_mappings_to_dicts(): assert result == {"a": None} -class StubResponse: - status_code = 200 - headers = {"content-type": "application/json"} +def sent_request(recording_server, send): + """Return the single request the given call put on the wire.""" + + with recording_server([(200, {})]) as (endpoint, requests): + send(SeamHttpClient(base_url=endpoint, auth_headers={})) + + [request] = requests + + return request - def json(self): - return {} +def test_client_sends_null_params_as_json_null(recording_server): + request = sent_request( + recording_server, + lambda client: client.patch( + "/devices/update", json={"device_id": "a", "name": NULL} + ), + ) -@pytest.fixture(name="sent_payloads") -def sent_payloads_fixture(monkeypatch): - payloads = [] + assert request["body"] == {"device_id": "a", "name": None} - # pylint: disable=unused-argument - def request(self, method, url, *args, **kwargs): - payloads.append(kwargs.get("json")) - return StubResponse() - monkeypatch.setattr(niquests.Session, "request", request) +def test_client_sends_nested_null_params_as_json_null(recording_server): + request = sent_request( + recording_server, + lambda client: client.patch( + "/spaces/update", json={"customer_data": {"check_in": NULL}} + ), + ) - return payloads + assert request["body"] == {"customer_data": {"check_in": None}} -def test_client_sends_null_params_as_json_null(sent_payloads): - client = SeamHttpClient(base_url="https://example.com", auth_headers={}) - client.patch("/devices/update", json={"device_id": "a", "name": NULL}) +def test_client_passes_through_payloads_without_null_params(recording_server): + request = sent_request( + recording_server, + lambda client: client.patch( + "/devices/update", json={"device_id": "a", "name": "Front Door"} + ), + ) - assert sent_payloads == [{"device_id": "a", "name": None}] + assert request["body"] == {"device_id": "a", "name": "Front Door"} -def test_client_sends_nested_null_params_as_json_null(sent_payloads): - client = SeamHttpClient(base_url="https://example.com", auth_headers={}) - client.patch("/spaces/update", json={"customer_data": {"check_in": NULL}}) +def test_client_sends_null_search_params_as_an_empty_value(recording_server): + request = sent_request( + recording_server, + lambda client: client.get( + "/devices/list", params={"device_id": NULL, "limit": 2} + ), + ) - assert sent_payloads == [{"customer_data": {"check_in": None}}] + assert request["query"] == "device_id=&limit=2" -def test_client_passes_through_payloads_without_null_params(sent_payloads): - client = SeamHttpClient(base_url="https://example.com", auth_headers={}) - client.patch("/devices/update", json={"device_id": "a", "name": "Front Door"}) +def test_client_omits_none_search_params(recording_server): + request = sent_request( + recording_server, + lambda client: client.get( + "/devices/list", params={"device_id": None, "limit": 2} + ), + ) - assert sent_payloads == [{"device_id": "a", "name": "Front Door"}] + assert request["query"] == "limit=2" diff --git a/test/timeout_test.py b/test/timeout_test.py index 14a99155..d731f01c 100644 --- a/test/timeout_test.py +++ b/test/timeout_test.py @@ -101,9 +101,7 @@ def test_seam_times_out_a_slow_request(): "seam_apikey_token", endpoint=endpoint, timeout=0.25, - # GET is idempotent, so urllib3 would retry the read timeout and - # raise its own error instead of surfacing the timeout. - retries=Retry(total=0, read=False), + retries=Retry(total=0), ) with pytest.raises(TimeoutException): From b12081eb74b3c00d5053e55c02673f2fbacda80d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 05:06:44 +0000 Subject: [PATCH 08/11] refactor: build search params through the URLSearchParams layer The serialization defines the name and value of each search param, where every value is a string, and leaves rendering the query string to URLSearchParams. UrlSearchParams is that layer here, so hand the query it renders to httpx as params, the way the reference implementation hands it to axios as a paramsSerializer. httpx re-encodes the query it is given, escaping "*" and unescaping "~", so the earlier commit set the query on the URL to keep those bytes. That was unnecessary: re-encoding changes no param. Across the 3141 query strings of the randomized corpus, 1714 differ from ours in bytes and none differ in decoded name-value pairs. The README said to avoid a client's params for that reason, which was wrong. Describe the pairs and the layer that renders them instead. Narrow the null module to NULL and the Null type it instantiates. Whether a value is the sentinel, and replacing it for JSON serialization, are internal concerns of this SDK: a caller building their own request body writes None, which already serializes to null. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PpqwDhZmyikGbjmCPqFW2A --- README.rst | 37 ++++++++++++++++------ seam/client.py | 13 +++----- seam/null.py | 14 ++++---- seam/utils/url_search_params_serializer.py | 6 ++-- test/null_test.py | 36 ++++++++++----------- 5 files changed, 62 insertions(+), 44 deletions(-) diff --git a/README.rst b/README.rst index dd2eda9f..b8d092d0 100644 --- a/README.rst +++ b/README.rst @@ -575,20 +575,39 @@ If you call the Seam API with your own HTTP client, import httpx from seam import serialize_url_search_params - query = serialize_url_search_params({"device_ids": ["device1", "device2"]}) - httpx.get( - f"https://connect.getseam.com/devices/list?{query}", + "https://connect.getseam.com/devices/list", + params=serialize_url_search_params({"device_ids": ["device1", "device2"]}), headers={"Authorization": "Bearer your-api-key"}, ) -It returns a query string, so it works with any HTTP client. -Put it on the URL as above rather than handing it to the client as params: -clients re-encode a query string they are given, e.g. httpx escapes ``*`` -and unescapes ``~``, which this serialization does not. +The serialization defines the name and value of each search param, +where every value is a string. +``UrlSearchParams`` holds those pairs and renders the query string, +as `URLSearchParams`_ does for the `reference implementation`_: + +.. code-block:: python + + from seam import UrlSearchParams, update_url_search_params + + search_params = UrlSearchParams() + + update_url_search_params(search_params, {"device_ids": ["device1", "device2"]}) + + list(search_params) + # => [('device_ids', 'device1'), ('device_ids', 'device2')] + + str(search_params) + # => 'device_ids=device1&device_ids=device2' + +Pass either the query string or the pairs to your HTTP client. +A client may percent-encode a few characters differently than +``URLSearchParams`` does, e.g. httpx escapes ``*`` and unescapes ``~``, +which the Seam API reads as the same params either way. + +The Seam API parses these params with the corresponding `parser`_. -The `reference implementation`_ defines this serialization, -and the Seam API parses it with the corresponding `parser`_. +.. _URLSearchParams: https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams .. _reference implementation: https://github.com/seamapi/url-search-params-serializer .. _parser: https://github.com/seamapi/url-search-params-parser diff --git a/seam/client.py b/seam/client.py index 49d13f0a..2f428409 100644 --- a/seam/client.py +++ b/seam/client.py @@ -13,7 +13,7 @@ SeamHttpInvalidInputError, SeamHttpUnauthorizedError, ) -from .null import replace_null +from .null import _replace_null from .utils.url_search_params_serializer import serialize_url_search_params SDK_HEADERS = { @@ -110,17 +110,14 @@ def request(self, method, url, *args, **kwargs) -> Any: # Route methods omit params set to None, so any remaining NULL sentinel # is an explicit null and becomes None for JSON serialization. if "json" in kwargs: - kwargs["json"] = replace_null(kwargs["json"]) + kwargs["json"] = _replace_null(kwargs["json"]) # Search params are serialized to the Seam API standard, which httpx # does not implement. The NULL sentinel is serialized to an empty value. - # The query is set on the URL rather than passed to httpx as params, - # because httpx re-encodes a query string it is given, e.g. it escapes - # "*" and unescapes "~", which the standard does not. + # httpx percent-encodes a few characters differently than the standard + # when it re-encodes the query, which the Seam API reads the same way. if isinstance(kwargs.get("params"), Mapping): - query = serialize_url_search_params(kwargs.pop("params")) - if query: - url = httpx.URL(url, query=query.encode()) + kwargs["params"] = serialize_url_search_params(kwargs["params"]) response = super().request(method, url, *args, **kwargs) diff --git a/seam/null.py b/seam/null.py index fe8bc4d8..28872d19 100644 --- a/seam/null.py +++ b/seam/null.py @@ -13,6 +13,8 @@ from collections.abc import Mapping from typing import Any +__all__ = ["NULL", "Null"] + class Null: """Type of the :data:`NULL` sentinel.""" @@ -57,7 +59,7 @@ def __bool__(self): """ -def is_null(value: Any) -> bool: +def _is_null(value: Any) -> bool: """Returns whether a value is the :data:`NULL` sentinel. :param value: The value to check @@ -68,7 +70,7 @@ def is_null(value: Any) -> bool: return isinstance(value, Null) -def replace_null(value: Any) -> Any: +def _replace_null(value: Any) -> Any: """Recursively replaces the :data:`NULL` sentinel with ``None``. Returns a copy, so the given value is never modified. @@ -80,16 +82,16 @@ def replace_null(value: Any) -> Any: :returns: A copy of the value with every ``NULL`` sentinel replaced""" - if is_null(value): + if _is_null(value): return None if isinstance(value, Mapping): - return {key: replace_null(item) for key, item in value.items()} + return {key: _replace_null(item) for key, item in value.items()} if isinstance(value, list): - return [replace_null(item) for item in value] + return [_replace_null(item) for item in value] if isinstance(value, tuple): - return tuple(replace_null(item) for item in value) + return tuple(_replace_null(item) for item in value) return value diff --git a/seam/utils/url_search_params_serializer.py b/seam/utils/url_search_params_serializer.py index 0bdfcdeb..884dd5ac 100644 --- a/seam/utils/url_search_params_serializer.py +++ b/seam/utils/url_search_params_serializer.py @@ -40,7 +40,7 @@ from typing import Any, Iterator, List, Optional, Sequence, Tuple, Union from urllib.parse import parse_qsl -from ..null import is_null +from ..null import _is_null Params = Mapping[str, Any] @@ -307,7 +307,7 @@ def _update_url_search_params_from_array( "is an array containing the empty string which is unsupported", ) - if any(value is None or is_null(value) for value in values): + if any(value is None or _is_null(value) for value in values): raise UnserializableParamError( name, "is an array containing null or undefined values which is unsupported", @@ -318,7 +318,7 @@ def _update_url_search_params_from_array( def _serialize(name: str, value: Any) -> str: - if is_null(value): + if _is_null(value): return "" if isinstance(value, str): diff --git a/test/null_test.py b/test/null_test.py index cc06f35c..1d9ffe00 100644 --- a/test/null_test.py +++ b/test/null_test.py @@ -1,20 +1,20 @@ from collections import OrderedDict from seam.client import SeamHttpClient -from seam.null import NULL, Null, is_null, replace_null +from seam.null import NULL, Null, _is_null, _replace_null def test_null_is_a_singleton(): assert Null() is NULL - assert is_null(NULL) - assert is_null(Null()) + assert _is_null(NULL) + assert _is_null(Null()) def test_null_is_not_none(): assert NULL is not None - assert not is_null(None) - assert not is_null("") - assert not is_null(0) + assert not _is_null(None) + assert not _is_null("") + assert not _is_null(0) def test_null_is_falsy(): @@ -26,15 +26,15 @@ def test_null_repr(): def test_replace_null(): - assert replace_null(NULL) is None - assert replace_null(None) is None - assert replace_null("a") == "a" - assert replace_null(0) == 0 - assert replace_null(False) is False + assert _replace_null(NULL) is None + assert _replace_null(None) is None + assert _replace_null("a") == "a" + assert _replace_null(0) == 0 + assert _replace_null(False) is False def test_replace_null_in_dict(): - assert replace_null({"a": NULL, "b": 1, "c": None}) == { + assert _replace_null({"a": NULL, "b": 1, "c": None}) == { "a": None, "b": 1, "c": None, @@ -42,24 +42,24 @@ def test_replace_null_in_dict(): def test_replace_null_in_nested_dict(): - assert replace_null({"a": {"b": {"c": NULL}}}) == {"a": {"b": {"c": None}}} + assert _replace_null({"a": {"b": {"c": NULL}}}) == {"a": {"b": {"c": None}}} def test_replace_null_in_lists_and_tuples(): - assert replace_null(["a", NULL]) == ["a", None] - assert replace_null(("a", NULL)) == ("a", None) - assert replace_null({"a": [{"b": NULL}]}) == {"a": [{"b": None}]} + assert _replace_null(["a", NULL]) == ["a", None] + assert _replace_null(("a", NULL)) == ("a", None) + assert _replace_null({"a": [{"b": NULL}]}) == {"a": [{"b": None}]} def test_replace_null_does_not_modify_the_given_value(): params = {"a": NULL, "b": [NULL]} - replace_null(params) + _replace_null(params) assert params == {"a": NULL, "b": [NULL]} def test_replace_null_normalizes_mappings_to_dicts(): - result = replace_null(OrderedDict([("a", NULL)])) + result = _replace_null(OrderedDict([("a", NULL)])) assert result == {"a": None} From a9ae0b442893506087e0e630f4f3678075d643d9 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 05:15:39 +0000 Subject: [PATCH 09/11] refactor: let the package define what the null module exports The package __init__ is the public surface, so the helpers the SDK uses to recognize the sentinel and replace it for JSON serialization need no underscore to be internal: not exporting them is enough. Export NULL to pass and Null to annotate, since generated route methods type a nullable param as Union[T, Null]. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PpqwDhZmyikGbjmCPqFW2A --- seam/client.py | 4 +-- seam/null.py | 14 ++++----- seam/utils/url_search_params_serializer.py | 6 ++-- test/null_test.py | 36 +++++++++++----------- 4 files changed, 29 insertions(+), 31 deletions(-) diff --git a/seam/client.py b/seam/client.py index 2f428409..88e9f2fb 100644 --- a/seam/client.py +++ b/seam/client.py @@ -13,7 +13,7 @@ SeamHttpInvalidInputError, SeamHttpUnauthorizedError, ) -from .null import _replace_null +from .null import replace_null from .utils.url_search_params_serializer import serialize_url_search_params SDK_HEADERS = { @@ -110,7 +110,7 @@ def request(self, method, url, *args, **kwargs) -> Any: # Route methods omit params set to None, so any remaining NULL sentinel # is an explicit null and becomes None for JSON serialization. if "json" in kwargs: - kwargs["json"] = _replace_null(kwargs["json"]) + kwargs["json"] = replace_null(kwargs["json"]) # Search params are serialized to the Seam API standard, which httpx # does not implement. The NULL sentinel is serialized to an empty value. diff --git a/seam/null.py b/seam/null.py index 28872d19..fe8bc4d8 100644 --- a/seam/null.py +++ b/seam/null.py @@ -13,8 +13,6 @@ from collections.abc import Mapping from typing import Any -__all__ = ["NULL", "Null"] - class Null: """Type of the :data:`NULL` sentinel.""" @@ -59,7 +57,7 @@ def __bool__(self): """ -def _is_null(value: Any) -> bool: +def is_null(value: Any) -> bool: """Returns whether a value is the :data:`NULL` sentinel. :param value: The value to check @@ -70,7 +68,7 @@ def _is_null(value: Any) -> bool: return isinstance(value, Null) -def _replace_null(value: Any) -> Any: +def replace_null(value: Any) -> Any: """Recursively replaces the :data:`NULL` sentinel with ``None``. Returns a copy, so the given value is never modified. @@ -82,16 +80,16 @@ def _replace_null(value: Any) -> Any: :returns: A copy of the value with every ``NULL`` sentinel replaced""" - if _is_null(value): + if is_null(value): return None if isinstance(value, Mapping): - return {key: _replace_null(item) for key, item in value.items()} + return {key: replace_null(item) for key, item in value.items()} if isinstance(value, list): - return [_replace_null(item) for item in value] + return [replace_null(item) for item in value] if isinstance(value, tuple): - return tuple(_replace_null(item) for item in value) + return tuple(replace_null(item) for item in value) return value diff --git a/seam/utils/url_search_params_serializer.py b/seam/utils/url_search_params_serializer.py index 884dd5ac..0bdfcdeb 100644 --- a/seam/utils/url_search_params_serializer.py +++ b/seam/utils/url_search_params_serializer.py @@ -40,7 +40,7 @@ from typing import Any, Iterator, List, Optional, Sequence, Tuple, Union from urllib.parse import parse_qsl -from ..null import _is_null +from ..null import is_null Params = Mapping[str, Any] @@ -307,7 +307,7 @@ def _update_url_search_params_from_array( "is an array containing the empty string which is unsupported", ) - if any(value is None or _is_null(value) for value in values): + if any(value is None or is_null(value) for value in values): raise UnserializableParamError( name, "is an array containing null or undefined values which is unsupported", @@ -318,7 +318,7 @@ def _update_url_search_params_from_array( def _serialize(name: str, value: Any) -> str: - if _is_null(value): + if is_null(value): return "" if isinstance(value, str): diff --git a/test/null_test.py b/test/null_test.py index 1d9ffe00..cc06f35c 100644 --- a/test/null_test.py +++ b/test/null_test.py @@ -1,20 +1,20 @@ from collections import OrderedDict from seam.client import SeamHttpClient -from seam.null import NULL, Null, _is_null, _replace_null +from seam.null import NULL, Null, is_null, replace_null def test_null_is_a_singleton(): assert Null() is NULL - assert _is_null(NULL) - assert _is_null(Null()) + assert is_null(NULL) + assert is_null(Null()) def test_null_is_not_none(): assert NULL is not None - assert not _is_null(None) - assert not _is_null("") - assert not _is_null(0) + assert not is_null(None) + assert not is_null("") + assert not is_null(0) def test_null_is_falsy(): @@ -26,15 +26,15 @@ def test_null_repr(): def test_replace_null(): - assert _replace_null(NULL) is None - assert _replace_null(None) is None - assert _replace_null("a") == "a" - assert _replace_null(0) == 0 - assert _replace_null(False) is False + assert replace_null(NULL) is None + assert replace_null(None) is None + assert replace_null("a") == "a" + assert replace_null(0) == 0 + assert replace_null(False) is False def test_replace_null_in_dict(): - assert _replace_null({"a": NULL, "b": 1, "c": None}) == { + assert replace_null({"a": NULL, "b": 1, "c": None}) == { "a": None, "b": 1, "c": None, @@ -42,24 +42,24 @@ def test_replace_null_in_dict(): def test_replace_null_in_nested_dict(): - assert _replace_null({"a": {"b": {"c": NULL}}}) == {"a": {"b": {"c": None}}} + assert replace_null({"a": {"b": {"c": NULL}}}) == {"a": {"b": {"c": None}}} def test_replace_null_in_lists_and_tuples(): - assert _replace_null(["a", NULL]) == ["a", None] - assert _replace_null(("a", NULL)) == ("a", None) - assert _replace_null({"a": [{"b": NULL}]}) == {"a": [{"b": None}]} + assert replace_null(["a", NULL]) == ["a", None] + assert replace_null(("a", NULL)) == ("a", None) + assert replace_null({"a": [{"b": NULL}]}) == {"a": [{"b": None}]} def test_replace_null_does_not_modify_the_given_value(): params = {"a": NULL, "b": [NULL]} - _replace_null(params) + replace_null(params) assert params == {"a": NULL, "b": [NULL]} def test_replace_null_normalizes_mappings_to_dicts(): - result = _replace_null(OrderedDict([("a", NULL)])) + result = replace_null(OrderedDict([("a", NULL)])) assert result == {"a": None} From 528a93fdc1f7278078e9c9a3c511245b3924b235 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 07:54:04 +0000 Subject: [PATCH 10/11] build: upgrade pylint for Python 3.14 The lock resolved pylint 3.2.2, whose astroid cannot resolve collections.abc under Python 3.14. The lint job runs on 3.14, so importing it failed the run. Also drive the invalid input error from a value that cannot be read as the declared type. The wrong-typed id it used no longer reaches validation: /devices/list is served over its semantic method, GET, and a query string carries no types, so 4242 is read as the number it declares. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PpqwDhZmyikGbjmCPqFW2A --- test/http_error_test.py | 5 +++-- uv.lock | 12 ++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/test/http_error_test.py b/test/http_error_test.py index b09b83df..5617b7ef 100644 --- a/test/http_error_test.py +++ b/test/http_error_test.py @@ -39,9 +39,10 @@ def test_seam_http_throws_invalid_input_error(server): seam = Seam(api_key=seed["seam_apikey1_token"], endpoint=endpoint) - # /devices/get requires either device_id or name. + # A query string carries no types, so an id given as a number is read as + # its digits. Send a value that cannot be read as the declared type. with pytest.raises(SeamHttpInvalidInputError) as exc_info: - seam.devices.list(device_ids=4242) + seam.devices.list(limit="abc") err = exc_info.value assert err.status_code == 400 assert err.code == "invalid_input" diff --git a/uv.lock b/uv.lock index 1d822959..646d4302 100644 --- a/uv.lock +++ b/uv.lock @@ -39,11 +39,11 @@ wheels = [ [[package]] name = "astroid" -version = "3.2.4" +version = "3.3.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9e/53/1067e1113ecaf58312357f2cd93063674924119d80d173adc3f6f2387aa2/astroid-3.2.4.tar.gz", hash = "sha256:0e14202810b30da1b735827f78f5157be2bbd4a7a59b7707ca0bfc2fb4c0063a", size = 397576, upload-time = "2024-07-20T12:57:43.26Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/74/dfb75f9ccd592bbedb175d4a32fc643cf569d7c218508bfbd6ea7ef9c091/astroid-3.3.11.tar.gz", hash = "sha256:1e5a5011af2920c7c67a53f65d536d65bfa7116feeaf2354d8b94f29573bb0ce", size = 400439, upload-time = "2025-07-13T18:04:23.177Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/96/b32bbbb46170a1c8b8b1f28c794202e25cfe743565e9d3469b8eb1e0cc05/astroid-3.2.4-py3-none-any.whl", hash = "sha256:413658a61eeca6202a59231abb473f932038fbcbf1666587f66d482083413a25", size = 276348, upload-time = "2024-07-20T12:57:40.886Z" }, + { url = "https://files.pythonhosted.org/packages/af/0f/3b8fdc946b4d9cc8cc1e8af42c4e409468c84441b933d037e101b3d72d86/astroid-3.3.11-py3-none-any.whl", hash = "sha256:54c760ae8322ece1abd213057c4b5bba7c49818853fc901ef09719a60dbf9dec", size = 275612, upload-time = "2025-07-13T18:04:21.07Z" }, ] [[package]] @@ -698,7 +698,7 @@ wheels = [ [[package]] name = "pylint" -version = "3.2.2" +version = "3.3.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "astroid" }, @@ -709,9 +709,9 @@ dependencies = [ { name = "platformdirs" }, { name = "tomlkit" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0c/4c/b561478a1ccb91e9b02965cb999d2281894d43e68c0bf3777d023af15f11/pylint-3.2.2.tar.gz", hash = "sha256:d068ca1dfd735fb92a07d33cb8f288adc0f6bc1287a139ca2425366f7cbe38f8", size = 1505895, upload-time = "2024-05-20T07:22:43.252Z" } +sdist = { url = "https://files.pythonhosted.org/packages/04/9d/81c84a312d1fa8133b0db0c76148542a98349298a01747ab122f9314b04e/pylint-3.3.9.tar.gz", hash = "sha256:d312737d7b25ccf6b01cc4ac629b5dcd14a0fcf3ec392735ac70f137a9d5f83a", size = 1525946, upload-time = "2025-10-05T18:41:43.786Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/23/7a546224d2931cda031ee3cddc9e723650ad8e491d7c64efbab97e43e16d/pylint-3.2.2-py3-none-any.whl", hash = "sha256:3f8788ab20bb8383e06dd2233e50f8e08949cfd9574804564803441a4946eab4", size = 519092, upload-time = "2024-05-20T07:22:40.191Z" }, + { url = "https://files.pythonhosted.org/packages/1a/a7/69460c4a6af7575449e615144aa2205b89408dc2969b87bc3df2f262ad0b/pylint-3.3.9-py3-none-any.whl", hash = "sha256:01f9b0462c7730f94786c283f3e52a1fbdf0494bbe0971a78d7277ef46a751e7", size = 523465, upload-time = "2025-10-05T18:41:41.766Z" }, ] [[package]] From 7eaa200afd97051f605091ec62100b337cc092c1 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 08:07:02 +0000 Subject: [PATCH 11/11] refactor: move the serializer beside the rest of the package It sits with null.py, route.py and the other modules of the SDK rather than under utils, which holds the two helpers that back generated resources. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PpqwDhZmyikGbjmCPqFW2A --- seam/__init__.py | 2 +- seam/client.py | 2 +- seam/{utils => }/url_search_params_serializer.py | 2 +- test/url_search_params_serializer_test.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename seam/{utils => }/url_search_params_serializer.py (99%) diff --git a/seam/__init__.py b/seam/__init__.py index 662b43ee..4c912626 100644 --- a/seam/__init__.py +++ b/seam/__init__.py @@ -16,7 +16,7 @@ from .seam_webhook import SeamWebhook from svix.webhooks import WebhookVerificationError as SeamWebhookVerificationError from .null import NULL, Null -from .utils.url_search_params_serializer import ( +from .url_search_params_serializer import ( UnserializableParamError, UrlSearchParams, serialize_url_search_params, diff --git a/seam/client.py b/seam/client.py index 88e9f2fb..97f44ab6 100644 --- a/seam/client.py +++ b/seam/client.py @@ -14,7 +14,7 @@ SeamHttpUnauthorizedError, ) from .null import replace_null -from .utils.url_search_params_serializer import serialize_url_search_params +from .url_search_params_serializer import serialize_url_search_params SDK_HEADERS = { "seam-sdk-name": "seamapi/python", diff --git a/seam/utils/url_search_params_serializer.py b/seam/url_search_params_serializer.py similarity index 99% rename from seam/utils/url_search_params_serializer.py rename to seam/url_search_params_serializer.py index 0bdfcdeb..973fc3df 100644 --- a/seam/utils/url_search_params_serializer.py +++ b/seam/url_search_params_serializer.py @@ -40,7 +40,7 @@ from typing import Any, Iterator, List, Optional, Sequence, Tuple, Union from urllib.parse import parse_qsl -from ..null import is_null +from .null import is_null Params = Mapping[str, Any] diff --git a/test/url_search_params_serializer_test.py b/test/url_search_params_serializer_test.py index f56a601e..48c94e04 100644 --- a/test/url_search_params_serializer_test.py +++ b/test/url_search_params_serializer_test.py @@ -4,7 +4,7 @@ import pytest from seam.null import NULL -from seam.utils.url_search_params_serializer import ( +from seam.url_search_params_serializer import ( UnserializableParamError, UrlSearchParams, serialize_url_search_params,