diff --git a/src/onepassword/client.py b/src/onepassword/client.py index 0b3d319..290cca9 100644 --- a/src/onepassword/client.py +++ b/src/onepassword/client.py @@ -2,7 +2,7 @@ from __future__ import annotations import weakref -from .core import UniffiCore +from .core import UniffiCore, InnerClient from .desktop_core import DesktopCore from .defaults import new_default_config, DesktopAuth from .secrets import Secrets @@ -34,12 +34,14 @@ async def authenticate( client_id = int(await core.init_client(config)) + inner_client = InnerClient(client_id, core, config) + authenticated_client = cls() - authenticated_client.secrets = Secrets(client_id, core) - authenticated_client.items = Items(client_id, core) - authenticated_client.vaults = Vaults(client_id, core) - authenticated_client.groups = Groups(client_id, core) + authenticated_client.secrets = Secrets(inner_client) + authenticated_client.items = Items(inner_client) + authenticated_client.vaults = Vaults(inner_client) + authenticated_client.groups = Groups(inner_client) authenticated_client._finalizer = weakref.finalize( cls, core.release_client, client_id diff --git a/src/onepassword/core.py b/src/onepassword/core.py index d47068a..623d8cf 100644 --- a/src/onepassword/core.py +++ b/src/onepassword/core.py @@ -1,19 +1,45 @@ +from __future__ import annotations import json import platform -from typing import Protocol -from onepassword.errors import raise_typed_exception +from typing import Any, Protocol +from onepassword.desktop_core import DesktopCore +from onepassword.errors import raise_typed_exception, DesktopSessionExpired # In empirical tests, we determined that maximum message size that can cross the FFI boundary # is ~128MB. Past this limit, FFI will throw an error and the program will crash. # We set the limit to 50MB to be safe and consistent with the other SDKs (where this limit is 64MB), to be reconsidered upon further testing MESSAGE_LIMIT = 50 * 1024 * 1024 + class Core(Protocol): async def init_client(self, client_config: dict) -> str: ... async def invoke(self, invoke_config: dict) -> str: ... def invoke_sync(self, invoke_config: dict) -> str: ... def release_client(self, client_id: int) -> None: ... + +class InnerClient: + client_id: int + core: DesktopCore | UniffiCore + config: dict[str, Any] + + def __init__(self, client_id: int, core: "DesktopCore | UniffiCore", config: dict[str, any]): + self.client_id = client_id + self.core = core + self.config = config + + async def invoke(self, invoke_config: dict): + try: + return await self.core.invoke(invoke_config) + except DesktopSessionExpired as e: + new_client_id = await self.core.init_client(self.config) + self.client_id = new_client_id + invoke_config["invocation"]["clientId"] = self.client_id + return await self.core.invoke(invoke_config) + except Exception as e: + raise e + + class UniffiCore: def __init__(self): machine_arch = platform.machine().lower() diff --git a/src/onepassword/desktop_core.py b/src/onepassword/desktop_core.py index c810585..39d0135 100644 --- a/src/onepassword/desktop_core.py +++ b/src/onepassword/desktop_core.py @@ -99,7 +99,9 @@ def call_shared_library(self, payload: str, operation_kind: str) -> bytes: success = parsed.get("success", False) if not success: - raise_typed_exception(Exception(str(payload))) + e = Exception() + e.msg = payload + raise_typed_exception(e) return payload diff --git a/src/onepassword/errors.py b/src/onepassword/errors.py index df80c3a..599e075 100644 --- a/src/onepassword/errors.py +++ b/src/onepassword/errors.py @@ -3,6 +3,12 @@ import json +class DesktopSessionExpired(Exception): + def __init__(self, message): + self.message = message + super().__init__(self.message) + + class RateLimitExceededException(Exception): def __init__(self, message): self.message = message @@ -18,7 +24,9 @@ def raise_typed_exception(e: Exception): error_name = typed_error.get("name") message = typed_error.get("message") - if error_name == "RateLimitExceeded": + if error_name == "DesktopSessionExpired": + raise DesktopSessionExpired(message) + elif error_name == "RateLimitExceeded": raise RateLimitExceededException(message) elif message is not None: raise Exception(message) diff --git a/src/onepassword/groups.py b/src/onepassword/groups.py index c3f4a7a..2d01959 100644 --- a/src/onepassword/groups.py +++ b/src/onepassword/groups.py @@ -1,6 +1,6 @@ # Code generated by op-codegen - DO NO EDIT MANUALLY -from .core import Core +from .core import InnerClient from pydantic import TypeAdapter from .types import Group, GroupGetParams @@ -10,15 +10,14 @@ class Groups: The Groups API holds all the operations the SDK client can perform on 1Password groups. """ - def __init__(self, client_id, core: Core): - self.client_id = client_id - self.core = core + def __init__(self, inner_client: InnerClient): + self.inner_client = inner_client async def get(self, group_id: str, group_params: GroupGetParams) -> Group: - response = await self.core.invoke( + response = await self.inner_client.invoke( { "invocation": { - "clientId": self.client_id, + "clientId": self.inner_client.client_id, "parameters": { "name": "GroupsGet", "parameters": { diff --git a/src/onepassword/items.py b/src/onepassword/items.py index c6b6901..591622a 100644 --- a/src/onepassword/items.py +++ b/src/onepassword/items.py @@ -1,6 +1,6 @@ # Code generated by op-codegen - DO NO EDIT MANUALLY -from .core import Core +from .core import InnerClient from typing import List from pydantic import TypeAdapter from .items_shares import ItemsShares @@ -21,20 +21,19 @@ class Items: The Items API holds all operations the SDK client can perform on 1Password items. """ - def __init__(self, client_id, core: Core): - self.client_id = client_id - self.core = core - self.shares = ItemsShares(client_id, core) - self.files = ItemsFiles(client_id, core) + def __init__(self, inner_client: InnerClient): + self.inner_client = inner_client + self.shares = ItemsShares(inner_client) + self.files = ItemsFiles(inner_client) async def create(self, params: ItemCreateParams) -> Item: """ Create a new item. """ - response = await self.core.invoke( + response = await self.inner_client.invoke( { "invocation": { - "clientId": self.client_id, + "clientId": self.inner_client.client_id, "parameters": { "name": "ItemsCreate", "parameters": {"params": params.model_dump(by_alias=True)}, @@ -52,10 +51,10 @@ async def create_all( """ Create items in batch, within a single vault. """ - response = await self.core.invoke( + response = await self.inner_client.invoke( { "invocation": { - "clientId": self.client_id, + "clientId": self.inner_client.client_id, "parameters": { "name": "ItemsCreateAll", "parameters": { @@ -74,10 +73,10 @@ async def get(self, vault_id: str, item_id: str) -> Item: """ Get an item by vault and item ID """ - response = await self.core.invoke( + response = await self.inner_client.invoke( { "invocation": { - "clientId": self.client_id, + "clientId": self.inner_client.client_id, "parameters": { "name": "ItemsGet", "parameters": {"vault_id": vault_id, "item_id": item_id}, @@ -93,10 +92,10 @@ async def get_all(self, vault_id: str, item_ids: List[str]) -> ItemsGetAllRespon """ Get items by vault and their item IDs. """ - response = await self.core.invoke( + response = await self.inner_client.invoke( { "invocation": { - "clientId": self.client_id, + "clientId": self.inner_client.client_id, "parameters": { "name": "ItemsGetAll", "parameters": {"vault_id": vault_id, "item_ids": item_ids}, @@ -112,10 +111,10 @@ async def put(self, item: Item) -> Item: """ Update an existing item. """ - response = await self.core.invoke( + response = await self.inner_client.invoke( { "invocation": { - "clientId": self.client_id, + "clientId": self.inner_client.client_id, "parameters": { "name": "ItemsPut", "parameters": {"item": item.model_dump(by_alias=True)}, @@ -131,10 +130,10 @@ async def delete(self, vault_id: str, item_id: str) -> None: """ Delete an item. """ - response = await self.core.invoke( + response = await self.inner_client.invoke( { "invocation": { - "clientId": self.client_id, + "clientId": self.inner_client.client_id, "parameters": { "name": "ItemsDelete", "parameters": {"vault_id": vault_id, "item_id": item_id}, @@ -151,10 +150,10 @@ async def delete_all( """ Delete items in batch, within a single vault. """ - response = await self.core.invoke( + response = await self.inner_client.invoke( { "invocation": { - "clientId": self.client_id, + "clientId": self.inner_client.client_id, "parameters": { "name": "ItemsDeleteAll", "parameters": {"vault_id": vault_id, "item_ids": item_ids}, @@ -170,10 +169,10 @@ async def archive(self, vault_id: str, item_id: str) -> None: """ Archive an item. """ - response = await self.core.invoke( + response = await self.inner_client.invoke( { "invocation": { - "clientId": self.client_id, + "clientId": self.inner_client.client_id, "parameters": { "name": "ItemsArchive", "parameters": {"vault_id": vault_id, "item_id": item_id}, @@ -188,10 +187,10 @@ async def list(self, vault_id: str, *filters: ItemListFilter) -> List[ItemOvervi """ List items based on filters. """ - response = await self.core.invoke( + response = await self.inner_client.invoke( { "invocation": { - "clientId": self.client_id, + "clientId": self.inner_client.client_id, "parameters": { "name": "ItemsList", "parameters": { diff --git a/src/onepassword/items_files.py b/src/onepassword/items_files.py index d8c56ff..6895226 100644 --- a/src/onepassword/items_files.py +++ b/src/onepassword/items_files.py @@ -1,24 +1,23 @@ # Code generated by op-codegen - DO NO EDIT MANUALLY -from .core import Core +from .core import InnerClient from typing import List from pydantic import TypeAdapter from .types import DocumentCreateParams, FileAttributes, FileCreateParams, Item class ItemsFiles: - def __init__(self, client_id, core: Core): - self.client_id = client_id - self.core = core + def __init__(self, inner_client: InnerClient): + self.inner_client = inner_client async def attach(self, item: Item, file_params: FileCreateParams) -> Item: """ Attach files to Items """ - response = await self.core.invoke( + response = await self.inner_client.invoke( { "invocation": { - "clientId": self.client_id, + "clientId": self.inner_client.client_id, "parameters": { "name": "ItemsFilesAttach", "parameters": { @@ -37,10 +36,10 @@ async def read(self, vault_id: str, item_id: str, attr: FileAttributes) -> bytes """ Read file content from the Item """ - response = await self.core.invoke( + response = await self.inner_client.invoke( { "invocation": { - "clientId": self.client_id, + "clientId": self.inner_client.client_id, "parameters": { "name": "ItemsFilesRead", "parameters": { @@ -60,10 +59,10 @@ async def delete(self, item: Item, section_id: str, field_id: str) -> Item: """ Delete a field file from Item using the section and field IDs """ - response = await self.core.invoke( + response = await self.inner_client.invoke( { "invocation": { - "clientId": self.client_id, + "clientId": self.inner_client.client_id, "parameters": { "name": "ItemsFilesDelete", "parameters": { @@ -85,10 +84,10 @@ async def replace_document( """ Replace the document file within a document item """ - response = await self.core.invoke( + response = await self.inner_client.invoke( { "invocation": { - "clientId": self.client_id, + "clientId": self.inner_client.client_id, "parameters": { "name": "ItemsFilesReplaceDocument", "parameters": { diff --git a/src/onepassword/items_shares.py b/src/onepassword/items_shares.py index 715630d..704ad1d 100644 --- a/src/onepassword/items_shares.py +++ b/src/onepassword/items_shares.py @@ -1,14 +1,14 @@ # Code generated by op-codegen - DO NO EDIT MANUALLY +from .core import InnerClient from typing import List from pydantic import TypeAdapter from .types import Item, ItemShareAccountPolicy, ItemShareParams, ValidRecipient class ItemsShares: - def __init__(self, client_id, core): - self.client_id = client_id - self.core = core + def __init__(self, inner_client: InnerClient): + self.inner_client = inner_client async def get_account_policy( self, vault_id: str, item_id: str @@ -16,10 +16,10 @@ async def get_account_policy( """ Get the item sharing policy of your account. """ - response = await self.core.invoke( + response = await self.inner_client.invoke( { "invocation": { - "clientId": self.client_id, + "clientId": self.inner_client.client_id, "parameters": { "name": "ItemsSharesGetAccountPolicy", "parameters": {"vault_id": vault_id, "item_id": item_id}, @@ -37,10 +37,10 @@ async def validate_recipients( """ Validate the recipients of an item sharing link. """ - response = await self.core.invoke( + response = await self.inner_client.invoke( { "invocation": { - "clientId": self.client_id, + "clientId": self.inner_client.client_id, "parameters": { "name": "ItemsSharesValidateRecipients", "parameters": { @@ -61,10 +61,10 @@ async def create( """ Create a new item sharing link. """ - response = await self.core.invoke( + response = await self.inner_client.invoke( { "invocation": { - "clientId": self.client_id, + "clientId": self.inner_client.client_id, "parameters": { "name": "ItemsSharesCreate", "parameters": { diff --git a/src/onepassword/secrets.py b/src/onepassword/secrets.py index f1c45475..8b46dfd 100644 --- a/src/onepassword/secrets.py +++ b/src/onepassword/secrets.py @@ -1,6 +1,6 @@ # Code generated by op-codegen - DO NO EDIT MANUALLY -from .core import Core, UniffiCore +from .core import InnerClient, UniffiCore from typing import List from pydantic import TypeAdapter from .types import GeneratePasswordResponse, PasswordRecipe, ResolveAllResponse @@ -12,18 +12,17 @@ class Secrets: Use secret reference URIs to securely load secrets from 1Password: `op:///[/]/` """ - def __init__(self, client_id, core: Core): - self.client_id = client_id - self.core = core + def __init__(self, inner_client: InnerClient): + self.inner_client = inner_client async def resolve(self, secret_reference: str) -> str: """ Resolve returns the secret the provided secret reference points to. """ - response = await self.core.invoke( + response = await self.inner_client.invoke( { "invocation": { - "clientId": self.client_id, + "clientId": self.inner_client.client_id, "parameters": { "name": "SecretsResolve", "parameters": {"secret_reference": secret_reference}, @@ -39,10 +38,10 @@ async def resolve_all(self, secret_references: List[str]) -> ResolveAllResponse: """ Resolve takes in a list of secret references and returns the secrets they point to or errors if any. """ - response = await self.core.invoke( + response = await self.inner_client.invoke( { "invocation": { - "clientId": self.client_id, + "clientId": self.inner_client.client_id, "parameters": { "name": "SecretsResolveAll", "parameters": {"secret_references": secret_references}, diff --git a/src/onepassword/vaults.py b/src/onepassword/vaults.py index 45ff05a..41c1acb 100644 --- a/src/onepassword/vaults.py +++ b/src/onepassword/vaults.py @@ -1,6 +1,6 @@ # Code generated by op-codegen - DO NO EDIT MANUALLY -from .core import Core +from .core import InnerClient from typing import Optional, List from pydantic import TypeAdapter from .types import ( @@ -18,18 +18,17 @@ class Vaults: The Vaults API holds all the operations the SDK client can perform on 1Password vaults. """ - def __init__(self, client_id, core: Core): - self.client_id = client_id - self.core = core + def __init__(self, inner_client: InnerClient): + self.inner_client = inner_client async def list(self, params: Optional[VaultListParams] = None) -> List[VaultOverview]: """ List information about vaults that's configurable based on some input parameters. """ - response = await self.core.invoke( + response = await self.inner_client.invoke( { "invocation": { - "clientId": self.client_id, + "clientId": self.inner_client.client_id, "parameters": { "name": "VaultsList", "parameters": {"params": params.model_dump(by_alias=True) if params else None}, @@ -42,10 +41,10 @@ async def list(self, params: Optional[VaultListParams] = None) -> List[VaultOver return response async def get_overview(self, vault_uuid: str) -> VaultOverview: - response = await self.core.invoke( + response = await self.inner_client.invoke( { "invocation": { - "clientId": self.client_id, + "clientId": self.inner_client.client_id, "parameters": { "name": "VaultsGetOverview", "parameters": {"vault_uuid": vault_uuid}, @@ -58,10 +57,10 @@ async def get_overview(self, vault_uuid: str) -> VaultOverview: return response async def get(self, vault_uuid: str, vault_params: VaultGetParams) -> Vault: - response = await self.core.invoke( + response = await self.inner_client.invoke( { "invocation": { - "clientId": self.client_id, + "clientId": self.inner_client.client_id, "parameters": { "name": "VaultsGet", "parameters": { @@ -79,10 +78,10 @@ async def get(self, vault_uuid: str, vault_params: VaultGetParams) -> Vault: async def grant_group_permissions( self, vault_id: str, group_permissions_list: List[GroupAccess] ) -> None: - response = await self.core.invoke( + response = await self.inner_client.invoke( { "invocation": { - "clientId": self.client_id, + "clientId": self.inner_client.client_id, "parameters": { "name": "VaultsGrantGroupPermissions", "parameters": { @@ -102,10 +101,10 @@ async def grant_group_permissions( async def update_group_permissions( self, group_permissions_list: List[GroupVaultAccess] ) -> None: - response = await self.core.invoke( + response = await self.inner_client.invoke( { "invocation": { - "clientId": self.client_id, + "clientId": self.inner_client.client_id, "parameters": { "name": "VaultsUpdateGroupPermissions", "parameters": { @@ -122,10 +121,10 @@ async def update_group_permissions( return None async def revoke_group_permissions(self, vault_id: str, group_id: str) -> None: - response = await self.core.invoke( + response = await self.inner_client.invoke( { "invocation": { - "clientId": self.client_id, + "clientId": self.inner_client.client_id, "parameters": { "name": "VaultsRevokeGroupPermissions", "parameters": {"vault_id": vault_id, "group_id": group_id},