Skip to content
This repository was archived by the owner on Jun 28, 2024. It is now read-only.

Commit 0813005

Browse files
feat: Add appearance and allow_external_modification to access code API + add unmanaged access code API (#118)
* Update access code props and methods, introduce UnmanagedAccessCodes class * Fix type, start testing * Attempt to fix CI * Minor fixes * Fix typo
1 parent 6f40eb1 commit 0813005

File tree

2 files changed

+222
-1
lines changed

2 files changed

+222
-1
lines changed

seamapi/access_codes.py

Lines changed: 173 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import time
22
from datetime import datetime, timezone, timedelta
33
from seamapi.types import (
4+
AbstractUnmanagedAccessCodes,
5+
UnmanagedAccessCode,
46
WaitForAccessCodeFailedException,
57
AbstractAccessCodes,
68
AccessCode,
@@ -30,12 +32,16 @@ class AccessCodes(AbstractAccessCodes):
3032
3133
Methods
3234
-------
33-
list(device)
35+
list(device, access_codes=None)
3436
Gets a list of access codes for a device
3537
get(access_code=None, device=None)
3638
Gets a certain access code of a device
3739
create(device, name=None, code=None, starts_at=None, ends_at=None, attempt_for_offline_device=None, wait_for_code=None, timeout=None, allow_external_modification=None, prefer_native_scheduling=None, use_backup_access_code_pool=None)
3840
Creates an access code on a device
41+
create_multiple(devices, name=None, code=None, starts_at=None, ends_at=None)
42+
Creates multiple access codes across devices
43+
update(access_code, device=None, name=None, code=None, starts_at=None, ends_at=None, type=None, allow_external_modification=None)
44+
Updates an access code on a device
3945
delete(access_code, device=None)
4046
Deletes an access code on a device
4147
pull_backup_access_code(access_code)
@@ -53,6 +59,7 @@ def __init__(self, seam: Seam):
5359
"""
5460

5561
self.seam = seam
62+
self.unmanaged = UnmanagedAccessCodes(seam)
5663

5764
@report_error
5865
def list(
@@ -339,6 +346,7 @@ def update(
339346
starts_at: Optional[str] = None,
340347
ends_at: Optional[str] = None,
341348
type: Optional[str] = None,
349+
allow_external_modification: Optional[bool] = None,
342350
) -> AccessCode:
343351
"""Updates an access code on a device.
344352
@@ -358,6 +366,8 @@ def update(
358366
Time when access code ceases to be effective
359367
type : str, optional
360368
Access code type eg. ongoing or time_bound
369+
allow_external_modification : bool, optional:
370+
Allow external modifications of the access code e.g. through the lock provider's app. False by default.
361371
362372
Raises
363373
------
@@ -383,6 +393,10 @@ def update(
383393
update_payload["ends_at"] = ends_at
384394
if type is not None:
385395
update_payload["type"] = type
396+
if allow_external_modification is not None:
397+
update_payload[
398+
"allow_external_modification"
399+
] = allow_external_modification
386400

387401
res = self.seam.make_request(
388402
"POST",
@@ -470,3 +484,161 @@ def pull_backup_access_code(
470484
)
471485

472486
return AccessCode.from_dict(res["backup_access_code"])
487+
488+
489+
class UnmanagedAccessCodes(AbstractUnmanagedAccessCodes):
490+
"""
491+
A class used to retrieve unmanaged access code data
492+
through interaction with Seam API
493+
494+
...
495+
496+
Attributes
497+
----------
498+
seam : Seam
499+
Initial seam class
500+
501+
Methods
502+
-------
503+
get(device=None, access_code=None, code=None)
504+
Gets an unmanaged access code
505+
list(device)
506+
Gets a list of unmanaged access codes
507+
convert_to_managed(access_code, allow_external_modification=None)
508+
Converts an unmanaged access code to a managed one
509+
"""
510+
511+
seam: Seam
512+
513+
def __init__(self, seam: Seam):
514+
"""
515+
Parameters
516+
----------
517+
seam : Seam
518+
Initial seam class
519+
"""
520+
521+
self.seam = seam
522+
523+
@report_error
524+
def get(
525+
self,
526+
access_code: Optional[Union[AccessCodeId, AccessCode]] = None,
527+
device: Optional[Union[DeviceId, Device]] = None,
528+
code: Optional[str] = None,
529+
) -> UnmanagedAccessCode:
530+
"""Gets an unmanaged access code.
531+
532+
Parameters
533+
----------
534+
access_code : Union[AccessCodeId, UnmanagedAccessCode], optional
535+
Access Code ID or Access Code
536+
device : Union[DeviceId, Device], optional
537+
Device ID or Device
538+
code : str, optional
539+
Pin code of an access code
540+
541+
Raises
542+
------
543+
Exception
544+
If the API request wasn't successful.
545+
546+
Returns
547+
------
548+
An unmanaged access code.
549+
"""
550+
551+
params = {}
552+
553+
if device:
554+
params["device_id"] = to_device_id(device)
555+
if access_code:
556+
params["access_code_id"] = to_access_code_id(access_code)
557+
if code:
558+
params["code"] = code
559+
560+
res = self.seam.make_request(
561+
"GET",
562+
"/access_codes/unmanaged/get",
563+
params=params,
564+
)
565+
json_access_code = res["access_code"]
566+
567+
return UnmanagedAccessCode.from_dict(json_access_code)
568+
569+
@report_error
570+
def list(
571+
self,
572+
device: Union[DeviceId, Device],
573+
) -> List[UnmanagedAccessCode]:
574+
"""Gets a list of unmanaged access codes.
575+
576+
Parameters
577+
----------
578+
device : Union[DeviceId, Device], optional
579+
Device ID or Device
580+
581+
Raises
582+
------
583+
Exception
584+
If the API request wasn't successful.
585+
586+
Returns
587+
------
588+
A list of unmanaged access codes.
589+
"""
590+
591+
res = self.seam.make_request(
592+
"GET",
593+
"/access_codes/unmanaged/list",
594+
params={"device_id": to_device_id(device)},
595+
)
596+
access_codes = res["access_codes"]
597+
598+
return [UnmanagedAccessCode.from_dict(ac) for ac in access_codes]
599+
600+
@report_error
601+
def convert_to_managed(
602+
self,
603+
access_code: Union[AccessCodeId, UnmanagedAccessCode],
604+
allow_external_modification: Optional[bool] = None,
605+
) -> ActionAttempt:
606+
"""Converts an unmanaged access code to a managed one.
607+
608+
Parameters
609+
----------
610+
access_code : AccessCodeId or UnmanagedAccessCode
611+
Access Code ID or UnmanagedAccessCode
612+
allow_external_modification : bool
613+
Allow external modifications of the access code e.g. through the lock provider's app. False by default.
614+
615+
Raises
616+
------
617+
Exception
618+
If the API request wasn't successful.
619+
620+
Returns
621+
------
622+
ActionAttempt
623+
"""
624+
625+
payload = {
626+
"access_code_id": to_access_code_id(access_code),
627+
}
628+
629+
if allow_external_modification is not None:
630+
payload[
631+
"allow_external_modification"
632+
] = allow_external_modification
633+
634+
res = self.seam.make_request(
635+
"POST",
636+
"/access_codes/unmanaged/convert_to_managed",
637+
json=payload,
638+
)
639+
640+
action_attempt = self.seam.action_attempts.poll_until_ready(
641+
res["action_attempt"]["action_attempt_id"]
642+
)
643+
644+
return action_attempt

seamapi/types.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,8 @@ class AccessCode:
198198
type: str
199199
code: str
200200
created_at: str
201+
errors: List[Dict[str, Any]]
202+
warnings: List[Dict[str, Any]]
201203
starts_at: Optional[str] = None
202204
ends_at: Optional[str] = None
203205
name: Optional[str] = ""
@@ -209,6 +211,25 @@ class AccessCode:
209211
pulled_backup_access_code_id: Optional[str] = None
210212
is_backup_access_code_available: Optional[bool] = None
211213
is_backup: Optional[bool] = None
214+
appearance: Optional[Dict[str, Any]] = None
215+
allow_external_modification: Optional[bool] = None
216+
217+
218+
@dataclass_json
219+
@dataclass
220+
class UnmanagedAccessCode:
221+
access_code_id: str
222+
device_id: str
223+
type: str
224+
created_at: str
225+
errors: List[Dict[str, Any]]
226+
warnings: List[Dict[str, Any]]
227+
name: Optional[str] = ""
228+
code: Optional[str] = None
229+
is_managed: Optional[bool] = None
230+
starts_at: Optional[str] = None
231+
ends_at: Optional[str] = None
232+
status: Optional[str] = None
212233

213234

214235
@dataclass
@@ -307,7 +328,35 @@ def unlock_door(self, device: Union[DeviceId, Device]) -> ActionAttempt:
307328
raise NotImplementedError
308329

309330

331+
class AbstractUnmanagedAccessCodes(abc.ABC):
332+
@abc.abstractmethod
333+
def get(
334+
self,
335+
device: Optional[Union[DeviceId, Device]] = None,
336+
access_code: Optional[Union[AccessCodeId, UnmanagedAccessCode]] = None,
337+
code: Optional[str] = None,
338+
) -> UnmanagedAccessCode:
339+
raise NotImplementedError
340+
341+
@abc.abstractmethod
342+
def list(
343+
self,
344+
device: Union[DeviceId, Device],
345+
) -> List[UnmanagedAccessCode]:
346+
raise NotImplementedError
347+
348+
@abc.abstractmethod
349+
def convert_to_managed(
350+
self,
351+
access_code: Union[AccessCodeId, UnmanagedAccessCode],
352+
allow_external_modification: Optional[bool] = None,
353+
) -> ActionAttempt:
354+
raise NotImplementedError
355+
356+
310357
class AbstractAccessCodes(abc.ABC):
358+
unmanaged: AbstractUnmanagedAccessCodes
359+
311360
@abc.abstractmethod
312361
def list(
313362
self,

0 commit comments

Comments
 (0)