Skip to content

Commit 40d4d54

Browse files
authored
Merge pull request #195 from 1Password/sdks-for-desktop-integrations
Prepare SDK beta release 0.4.0b2
2 parents 2588608 + 0491793 commit 40d4d54

File tree

16 files changed

+243
-83
lines changed

16 files changed

+243
-83
lines changed

src/onepassword/client.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from __future__ import annotations
44
import weakref
5-
from .core import UniffiCore
5+
from .core import UniffiCore, InnerClient
66
from .desktop_core import DesktopCore
77
from .defaults import new_default_config, DesktopAuth
88
from .secrets import Secrets
@@ -34,12 +34,14 @@ async def authenticate(
3434

3535
client_id = int(await core.init_client(config))
3636

37+
inner_client = InnerClient(client_id, core, config)
38+
3739
authenticated_client = cls()
3840

39-
authenticated_client.secrets = Secrets(client_id, core)
40-
authenticated_client.items = Items(client_id, core)
41-
authenticated_client.vaults = Vaults(client_id, core)
42-
authenticated_client.groups = Groups(client_id, core)
41+
authenticated_client.secrets = Secrets(inner_client)
42+
authenticated_client.items = Items(inner_client)
43+
authenticated_client.vaults = Vaults(inner_client)
44+
authenticated_client.groups = Groups(inner_client)
4345

4446
authenticated_client._finalizer = weakref.finalize(
4547
cls, core.release_client, client_id

src/onepassword/core.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,45 @@
1+
from __future__ import annotations
12
import json
23
import platform
3-
from typing import Protocol
4-
from onepassword.errors import raise_typed_exception
4+
from typing import Any, Protocol
5+
from onepassword.desktop_core import DesktopCore
6+
from onepassword.errors import raise_typed_exception, DesktopSessionExpiredException
57

68
# In empirical tests, we determined that maximum message size that can cross the FFI boundary
79
# is ~128MB. Past this limit, FFI will throw an error and the program will crash.
810
# 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
911
MESSAGE_LIMIT = 50 * 1024 * 1024
1012

13+
1114
class Core(Protocol):
1215
async def init_client(self, client_config: dict) -> str: ...
1316
async def invoke(self, invoke_config: dict) -> str: ...
1417
def invoke_sync(self, invoke_config: dict) -> str: ...
1518
def release_client(self, client_id: int) -> None: ...
1619

20+
21+
class InnerClient:
22+
client_id: int
23+
core: DesktopCore | UniffiCore
24+
config: dict[str, Any]
25+
26+
def __init__(self, client_id: int, core: "DesktopCore | UniffiCore", config: dict[str, any]):
27+
self.client_id = client_id
28+
self.core = core
29+
self.config = config
30+
31+
async def invoke(self, invoke_config: dict):
32+
try:
33+
return await self.core.invoke(invoke_config)
34+
except DesktopSessionExpiredException:
35+
new_client_id = await self.core.init_client(self.config)
36+
self.client_id = new_client_id
37+
invoke_config["invocation"]["clientId"] = self.client_id
38+
return await self.core.invoke(invoke_config)
39+
except Exception as e:
40+
raise e
41+
42+
1743
class UniffiCore:
1844
def __init__(self):
1945
machine_arch = platform.machine().lower()

src/onepassword/desktop_core.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ def find_1password_lib_path():
2323
"/opt/1Password/libop_sdk_ipc_client.so",
2424
"/snap/bin/1password/libop_sdk_ipc_client.so",
2525
]
26+
elif os_name == "Windows":
27+
locations = [
28+
str(Path.home() / r"AppData\Local\1Password\op_sdk_ipc_client.dll"),
29+
r"C:\Program Files\1Password\app\8\op_sdk_ipc_client.dll",
30+
r"C:\Program Files (x86)\1Password\app\8\op_sdk_ipc_client.dll",
31+
str(Path.home() / r"AppData\Local\1Password\app\8\op_sdk_ipc_client.dll"),
32+
]
2633
else:
2734
raise OSError(f"Unsupported operating system: {os_name}")
2835

@@ -92,7 +99,9 @@ def call_shared_library(self, payload: str, operation_kind: str) -> bytes:
9299

93100
success = parsed.get("success", False)
94101
if not success:
95-
raise_typed_exception(Exception(str(payload)))
102+
e = Exception(payload)
103+
e.msg = payload
104+
raise_typed_exception(e)
96105

97106
return payload
98107

src/onepassword/errors.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
import json
44

55

6+
class DesktopSessionExpiredException(Exception):
7+
def __init__(self, message):
8+
self.message = message
9+
super().__init__(self.message)
10+
11+
612
class RateLimitExceededException(Exception):
713
def __init__(self, message):
814
self.message = message
@@ -18,7 +24,9 @@ def raise_typed_exception(e: Exception):
1824
error_name = typed_error.get("name")
1925
message = typed_error.get("message")
2026

21-
if error_name == "RateLimitExceeded":
27+
if error_name == "DesktopSessionExpired":
28+
raise DesktopSessionExpiredException(message)
29+
elif error_name == "RateLimitExceeded":
2230
raise RateLimitExceededException(message)
2331
elif message is not None:
2432
raise Exception(message)

src/onepassword/groups.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Code generated by op-codegen - DO NO EDIT MANUALLY
22

3-
from .core import Core
3+
from .core import InnerClient
44
from pydantic import TypeAdapter
55
from .types import Group, GroupGetParams
66

@@ -10,15 +10,14 @@ class Groups:
1010
The Groups API holds all the operations the SDK client can perform on 1Password groups.
1111
"""
1212

13-
def __init__(self, client_id, core: Core):
14-
self.client_id = client_id
15-
self.core = core
13+
def __init__(self, inner_client: InnerClient):
14+
self.inner_client = inner_client
1615

1716
async def get(self, group_id: str, group_params: GroupGetParams) -> Group:
18-
response = await self.core.invoke(
17+
response = await self.inner_client.invoke(
1918
{
2019
"invocation": {
21-
"clientId": self.client_id,
20+
"clientId": self.inner_client.client_id,
2221
"parameters": {
2322
"name": "GroupsGet",
2423
"parameters": {

src/onepassword/items.py

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Code generated by op-codegen - DO NO EDIT MANUALLY
22

3-
from .core import Core
3+
from .core import InnerClient
44
from typing import List
55
from pydantic import TypeAdapter
66
from .items_shares import ItemsShares
@@ -21,20 +21,19 @@ class Items:
2121
The Items API holds all operations the SDK client can perform on 1Password items.
2222
"""
2323

24-
def __init__(self, client_id, core: Core):
25-
self.client_id = client_id
26-
self.core = core
27-
self.shares = ItemsShares(client_id, core)
28-
self.files = ItemsFiles(client_id, core)
24+
def __init__(self, inner_client: InnerClient):
25+
self.inner_client = inner_client
26+
self.shares = ItemsShares(inner_client)
27+
self.files = ItemsFiles(inner_client)
2928

3029
async def create(self, params: ItemCreateParams) -> Item:
3130
"""
3231
Create a new item.
3332
"""
34-
response = await self.core.invoke(
33+
response = await self.inner_client.invoke(
3534
{
3635
"invocation": {
37-
"clientId": self.client_id,
36+
"clientId": self.inner_client.client_id,
3837
"parameters": {
3938
"name": "ItemsCreate",
4039
"parameters": {"params": params.model_dump(by_alias=True)},
@@ -52,10 +51,10 @@ async def create_all(
5251
"""
5352
Create items in batch, within a single vault.
5453
"""
55-
response = await self.core.invoke(
54+
response = await self.inner_client.invoke(
5655
{
5756
"invocation": {
58-
"clientId": self.client_id,
57+
"clientId": self.inner_client.client_id,
5958
"parameters": {
6059
"name": "ItemsCreateAll",
6160
"parameters": {
@@ -74,10 +73,10 @@ async def get(self, vault_id: str, item_id: str) -> Item:
7473
"""
7574
Get an item by vault and item ID
7675
"""
77-
response = await self.core.invoke(
76+
response = await self.inner_client.invoke(
7877
{
7978
"invocation": {
80-
"clientId": self.client_id,
79+
"clientId": self.inner_client.client_id,
8180
"parameters": {
8281
"name": "ItemsGet",
8382
"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
9392
"""
9493
Get items by vault and their item IDs.
9594
"""
96-
response = await self.core.invoke(
95+
response = await self.inner_client.invoke(
9796
{
9897
"invocation": {
99-
"clientId": self.client_id,
98+
"clientId": self.inner_client.client_id,
10099
"parameters": {
101100
"name": "ItemsGetAll",
102101
"parameters": {"vault_id": vault_id, "item_ids": item_ids},
@@ -112,10 +111,10 @@ async def put(self, item: Item) -> Item:
112111
"""
113112
Update an existing item.
114113
"""
115-
response = await self.core.invoke(
114+
response = await self.inner_client.invoke(
116115
{
117116
"invocation": {
118-
"clientId": self.client_id,
117+
"clientId": self.inner_client.client_id,
119118
"parameters": {
120119
"name": "ItemsPut",
121120
"parameters": {"item": item.model_dump(by_alias=True)},
@@ -131,10 +130,10 @@ async def delete(self, vault_id: str, item_id: str) -> None:
131130
"""
132131
Delete an item.
133132
"""
134-
response = await self.core.invoke(
133+
response = await self.inner_client.invoke(
135134
{
136135
"invocation": {
137-
"clientId": self.client_id,
136+
"clientId": self.inner_client.client_id,
138137
"parameters": {
139138
"name": "ItemsDelete",
140139
"parameters": {"vault_id": vault_id, "item_id": item_id},
@@ -151,10 +150,10 @@ async def delete_all(
151150
"""
152151
Delete items in batch, within a single vault.
153152
"""
154-
response = await self.core.invoke(
153+
response = await self.inner_client.invoke(
155154
{
156155
"invocation": {
157-
"clientId": self.client_id,
156+
"clientId": self.inner_client.client_id,
158157
"parameters": {
159158
"name": "ItemsDeleteAll",
160159
"parameters": {"vault_id": vault_id, "item_ids": item_ids},
@@ -170,10 +169,10 @@ async def archive(self, vault_id: str, item_id: str) -> None:
170169
"""
171170
Archive an item.
172171
"""
173-
response = await self.core.invoke(
172+
response = await self.inner_client.invoke(
174173
{
175174
"invocation": {
176-
"clientId": self.client_id,
175+
"clientId": self.inner_client.client_id,
177176
"parameters": {
178177
"name": "ItemsArchive",
179178
"parameters": {"vault_id": vault_id, "item_id": item_id},
@@ -188,10 +187,10 @@ async def list(self, vault_id: str, *filters: ItemListFilter) -> List[ItemOvervi
188187
"""
189188
List items based on filters.
190189
"""
191-
response = await self.core.invoke(
190+
response = await self.inner_client.invoke(
192191
{
193192
"invocation": {
194-
"clientId": self.client_id,
193+
"clientId": self.inner_client.client_id,
195194
"parameters": {
196195
"name": "ItemsList",
197196
"parameters": {

src/onepassword/items_files.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,23 @@
11
# Code generated by op-codegen - DO NO EDIT MANUALLY
22

3-
from .core import Core
3+
from .core import InnerClient
44
from typing import List
55
from pydantic import TypeAdapter
66
from .types import DocumentCreateParams, FileAttributes, FileCreateParams, Item
77

88

99
class ItemsFiles:
10-
def __init__(self, client_id, core: Core):
11-
self.client_id = client_id
12-
self.core = core
10+
def __init__(self, inner_client: InnerClient):
11+
self.inner_client = inner_client
1312

1413
async def attach(self, item: Item, file_params: FileCreateParams) -> Item:
1514
"""
1615
Attach files to Items
1716
"""
18-
response = await self.core.invoke(
17+
response = await self.inner_client.invoke(
1918
{
2019
"invocation": {
21-
"clientId": self.client_id,
20+
"clientId": self.inner_client.client_id,
2221
"parameters": {
2322
"name": "ItemsFilesAttach",
2423
"parameters": {
@@ -37,10 +36,10 @@ async def read(self, vault_id: str, item_id: str, attr: FileAttributes) -> bytes
3736
"""
3837
Read file content from the Item
3938
"""
40-
response = await self.core.invoke(
39+
response = await self.inner_client.invoke(
4140
{
4241
"invocation": {
43-
"clientId": self.client_id,
42+
"clientId": self.inner_client.client_id,
4443
"parameters": {
4544
"name": "ItemsFilesRead",
4645
"parameters": {
@@ -60,10 +59,10 @@ async def delete(self, item: Item, section_id: str, field_id: str) -> Item:
6059
"""
6160
Delete a field file from Item using the section and field IDs
6261
"""
63-
response = await self.core.invoke(
62+
response = await self.inner_client.invoke(
6463
{
6564
"invocation": {
66-
"clientId": self.client_id,
65+
"clientId": self.inner_client.client_id,
6766
"parameters": {
6867
"name": "ItemsFilesDelete",
6968
"parameters": {
@@ -85,10 +84,10 @@ async def replace_document(
8584
"""
8685
Replace the document file within a document item
8786
"""
88-
response = await self.core.invoke(
87+
response = await self.inner_client.invoke(
8988
{
9089
"invocation": {
91-
"clientId": self.client_id,
90+
"clientId": self.inner_client.client_id,
9291
"parameters": {
9392
"name": "ItemsFilesReplaceDocument",
9493
"parameters": {

0 commit comments

Comments
 (0)