Skip to content

Commit ea747bd

Browse files
MOmarMiraj1PasswordSDKBothculea
authored
Python SDK 0.2.0 RC (#156)
* Update core to version 7e6c0600 * Release v0.2.0 * update relaese notes * update examples * update readme * add size checks for invocations * Clear up SSH key example * add files for File example and clean up example file * fix path for file examples * remove unnecessary semicolon * update dev doc for snippets * update comments * clean up example, update error msg for limit and ensure we are checking for bytes not characaters on invocation size check * fmt example file and update build number * decode content correctly and update error msg for invoke sync --------- Co-authored-by: 1PasswordSDKBot <[email protected]> Co-authored-by: Horia Culea <[email protected]>
1 parent ef6be31 commit ea747bd

File tree

17 files changed

+437
-18
lines changed

17 files changed

+437
-18
lines changed

README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,18 +87,18 @@ Operations:
8787
- [x] [Create items](https://developer.1password.com/docs/sdks/manage-items#create-an-item)
8888
- [x] [Update items](https://developer.1password.com/docs/sdks/manage-items#update-an-item)
8989
- [x] [Delete items](https://developer.1password.com/docs/sdks/manage-items#delete-an-item)
90-
- [x] Archive items
90+
- [x] [Archive items](https://developer.1password.com/docs/sdks/manage-items/#archive-an-item)
9191
- [x] [List items](https://developer.1password.com/docs/sdks/list-vaults-items/)
92-
- [x] Share items
93-
- [x] Generate PIN, random and memorable passwords
92+
- [x] [Share items](https://developer.1password.com/docs/sdks/share-items) (items with files cannot be shared)
93+
- [x] [Generate PIN, random and memorable passwords](https://developer.1password.com/docs/sdks/manage-items#generate-a-password)
9494

9595
Field types:
9696
- [x] API Keys
9797
- [x] Passwords
9898
- [x] Concealed fields
9999
- [x] Text fields
100100
- [x] Notes
101-
- [x] SSH private keys, public keys, fingerprint and key type (partially supported: supported in resolving secret references, not yet supported in item create/get/update)
101+
- [x] SSH private keys, public keys, fingerprint and key type
102102
- [x] One-time passwords
103103
- [x] URLs
104104
- [x] Websites (used to suggest and autofill logins)
@@ -108,8 +108,10 @@ Field types:
108108
- [x] Emails
109109
- [x] References to other items
110110
- [ ] Address
111-
- [ ] Date / MM/YY
112-
- [ ] Files attachments and Document items
111+
- [ ] Date
112+
- [x] MM/YY
113+
- [x] Files attachments and Document items
114+
- [x] Menu
113115

114116
### Vault management
115117
- [ ] Retrieve vaults

example/example.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import asyncio
22
import os
3+
from pathlib import Path
34

45
# [developer-docs.sdk.python.sdk-import]-start
56
from onepassword import *
7+
68
# [developer-docs.sdk.python.sdk-import]-end
9+
from cryptography.hazmat.primitives.asymmetric import rsa
10+
from cryptography.hazmat.primitives import serialization
711

812

913
async def main():
@@ -167,6 +171,12 @@ async def main():
167171

168172
await share_item(client, created_item.vault_id, updated_item.id)
169173

174+
await create_ssh_key_item(client)
175+
176+
await create_and_replace_document_item(client)
177+
178+
await create_attach_and_delete_file_field_item(client)
179+
170180
# [developer-docs.sdk.python.delete-item]-start
171181
# Delete a item from your vault.
172182
await client.items.delete(created_item.vault_id, updated_item.id)
@@ -218,5 +228,151 @@ async def share_item(client: Client, vault_id: str, item_id: str):
218228
# [developer-docs.sdk.python.item-share-create-share]-end
219229

220230

231+
async def create_ssh_key_item(client: Client):
232+
# Generate a 2048-bit RSA private key
233+
private_key = rsa.generate_private_key(
234+
public_exponent=65537,
235+
key_size=4096,
236+
)
237+
238+
# Serialize the private key in PKCS8 format (PEM)
239+
ssh_key_pkcs8_pem = private_key.private_bytes(
240+
encoding=serialization.Encoding.PEM,
241+
format=serialization.PrivateFormat.PKCS8,
242+
encryption_algorithm=serialization.NoEncryption(),
243+
)
244+
245+
# [developer-docs.sdk.python.create-sshkey-item]-start
246+
# Create an Item containing SSH Key and add it to your vault.
247+
to_create = ItemCreateParams(
248+
title="SSH Key Item Created With Python SDK",
249+
category=ItemCategory.SSHKEY,
250+
vault_id="7turaasywpymt3jecxoxk5roli",
251+
fields=[
252+
ItemField(
253+
id="private_key",
254+
title="private key",
255+
field_type=ItemFieldType.SSHKEY,
256+
value=ssh_key_pkcs8_pem,
257+
sectionId="",
258+
),
259+
],
260+
sections=[
261+
ItemSection(id="", title=""),
262+
],
263+
)
264+
created_item = await client.items.create(to_create)
265+
266+
print(created_item.fields[0].value)
267+
print(created_item.fields[0].details.content.public_key)
268+
print(created_item.fields[0].details.content.fingerprint)
269+
print(created_item.fields[0].details.content.key_type)
270+
# [developer-docs.sdk.python.create-sshkey-item]-end
271+
await client.items.delete(created_item.vault_id, created_item.id)
272+
273+
274+
async def create_and_replace_document_item(client: Client):
275+
# [developer-docs.sdk.python.create-document-item]-start
276+
# Create a Document Item
277+
to_create = ItemCreateParams(
278+
title="Document Item Created with Python SDK",
279+
category=ItemCategory.DOCUMENT,
280+
vault_id="7turaasywpymt3jecxoxk5roli",
281+
sections=[
282+
ItemSection(id="", title=""),
283+
],
284+
document=DocumentCreateParams(
285+
name="file.txt", content=Path("./example/file.txt").read_bytes()
286+
),
287+
)
288+
created_item = await client.items.create(to_create)
289+
# [developer-docs.sdk.python.create-document-item]-end
290+
291+
# [developer-docs.sdk.python.replace-document-item]-start
292+
# Replace the document in the item
293+
replaced_item = await client.items.files.replace_document(
294+
created_item,
295+
DocumentCreateParams(
296+
name="file2.txt", content=Path("./example/file2.txt").read_bytes()
297+
),
298+
)
299+
# [developer-docs.sdk.python.replace-document-item]-end
300+
301+
# [developer-docs.sdk.python.read-document-item]-start
302+
# Read the document in the item
303+
content = await client.items.files.read(
304+
replaced_item.vault_id, replaced_item.id, replaced_item.document
305+
)
306+
# [developer-docs.sdk.python.read-document-item]-end
307+
308+
print(content.decode())
309+
310+
await client.items.delete(replaced_item.vault_id, replaced_item.id)
311+
312+
313+
async def create_attach_and_delete_file_field_item(client: Client):
314+
# [developer-docs.sdk.python.create-item-with-file-field]-start
315+
# Create a File Field Item
316+
to_create = ItemCreateParams(
317+
title="FileField Item created with Python SDK",
318+
category=ItemCategory.LOGIN,
319+
vault_id="7turaasywpymt3jecxoxk5roli",
320+
fields=[
321+
ItemField(
322+
id="username",
323+
title="username",
324+
field_type=ItemFieldType.TEXT,
325+
value="mynameisjeff",
326+
),
327+
ItemField(
328+
id="password",
329+
title="password",
330+
field_type=ItemFieldType.CONCEALED,
331+
value="jeff",
332+
),
333+
],
334+
sections=[
335+
ItemSection(id="", title=""),
336+
],
337+
files=[
338+
FileCreateParams(
339+
name="file.txt",
340+
content=Path("./example/file.txt").read_bytes(),
341+
sectionId="",
342+
fieldId="file_field",
343+
)
344+
],
345+
)
346+
347+
created_item = await client.items.create(to_create)
348+
# [developer-docs.sdk.python.create-item-with-file-field]-end
349+
350+
# [developer-docs.sdk.python.attach-file-field-item]-start
351+
# Attach a file field to the item
352+
attached_item = await client.items.files.attach(
353+
created_item,
354+
FileCreateParams(
355+
name="file2.txt",
356+
content=Path("./example/file2.txt").read_bytes(),
357+
sectionId="",
358+
fieldId="new_file_field",
359+
),
360+
)
361+
# [developer-docs.sdk.python.attach-file-field-item]-end
362+
363+
# [developer-docs.sdk.python.delete-file-field-item]-start
364+
# Delete a file field from an item
365+
deleted_file_item = await client.items.files.delete(
366+
attached_item,
367+
attached_item.files[1].section_id,
368+
attached_item.files[1].field_id,
369+
)
370+
# [developer-docs.sdk.python.delete-file-field-item]-end
371+
372+
print(len(deleted_file_item.files))
373+
374+
await client.items.delete(deleted_file_item.vault_id, deleted_file_item.id)
375+
376+
221377
if __name__ == "__main__":
222378
asyncio.run(main())

example/file.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Hello World!

example/file2.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Hello again, world!

src/onepassword/build_number.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
SDK_BUILD_NUMBER = "0010701"
1+
SDK_BUILD_NUMBER = "0020001"

src/onepassword/core.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33

44
from onepassword.errors import raise_typed_exception
55

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

711
machine_arch = platform.machine().lower()
812

@@ -26,16 +30,24 @@ async def _init_client(client_config):
2630

2731
# Invoke calls specified business logic from the SDK core.
2832
async def _invoke(invoke_config):
33+
serialized_config = json.dumps(invoke_config)
34+
if len(serialized_config.encode()) > MESSAGE_LIMIT:
35+
raise ValueError(
36+
f"message size exceeds the limit of {MESSAGE_LIMIT} bytes, please contact 1Password at [email protected] or https://developer.1password.com/joinslack if you need help.")
2937
try:
30-
return await core.invoke(json.dumps(invoke_config))
38+
return await core.invoke(serialized_config)
3139
except Exception as e:
3240
raise_typed_exception(e)
3341

3442

3543
# Invoke calls specified business logic from the SDK core.
3644
def _invoke_sync(invoke_config):
45+
serialized_config = json.dumps(invoke_config)
46+
if len(serialized_config.encode()) > MESSAGE_LIMIT:
47+
raise ValueError(
48+
f"message size exceeds the limit of {MESSAGE_LIMIT} bytes, please contact 1Password at [email protected] or https://developer.1password.com/joinslack if you need help.")
3749
try:
38-
return core.invoke_sync(json.dumps(invoke_config))
50+
return core.invoke_sync(serialized_config)
3951
except Exception as e:
4052
raise_typed_exception(e)
4153

src/onepassword/items.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from typing import Optional, List
66
from pydantic import TypeAdapter
77
from .items_shares import ItemsShares
8+
from .items_files import ItemsFiles
89
from .types import Item, ItemCreateParams, ItemOverview
910

1011

@@ -17,6 +18,8 @@ def __init__(self, client_id):
1718
self.client_id = client_id
1819
self.shares = ItemsShares(client_id)
1920

21+
self.files = ItemsFiles(client_id)
22+
2023
async def create(self, params: ItemCreateParams) -> Item:
2124
"""
2225
Create a new item.

src/onepassword/items_files.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Code generated by op-codegen - DO NO EDIT MANUALLY
2+
3+
from .core import _invoke, _invoke_sync
4+
from .iterator import SDKIterator
5+
from typing import Optional, List
6+
from pydantic import TypeAdapter
7+
from .types import DocumentCreateParams, FileAttributes, FileCreateParams, Item
8+
9+
10+
class ItemsFiles:
11+
def __init__(self, client_id):
12+
self.client_id = client_id
13+
14+
async def attach(self, item: Item, file_params: FileCreateParams) -> Item:
15+
"""
16+
Attach files to Items
17+
"""
18+
response = await _invoke(
19+
{
20+
"invocation": {
21+
"clientId": self.client_id,
22+
"parameters": {
23+
"name": "ItemsFilesAttach",
24+
"parameters": {
25+
"item": item.model_dump(by_alias=True),
26+
"file_params": file_params.model_dump(by_alias=True),
27+
},
28+
},
29+
}
30+
}
31+
)
32+
33+
response = TypeAdapter(Item).validate_json(response)
34+
return response
35+
36+
async def read(self, vault_id: str, item_id: str, attr: FileAttributes) -> bytes:
37+
"""
38+
Read file content from the Item
39+
"""
40+
response = await _invoke(
41+
{
42+
"invocation": {
43+
"clientId": self.client_id,
44+
"parameters": {
45+
"name": "ItemsFilesRead",
46+
"parameters": {
47+
"vault_id": vault_id,
48+
"item_id": item_id,
49+
"attr": attr.model_dump(by_alias=True),
50+
},
51+
},
52+
}
53+
}
54+
)
55+
56+
response = bytes(TypeAdapter(List[int]).validate_json(response))
57+
return response
58+
59+
async def delete(self, item: Item, section_id: str, field_id: str) -> Item:
60+
"""
61+
Delete a field file from Item using the section and field IDs
62+
"""
63+
response = await _invoke(
64+
{
65+
"invocation": {
66+
"clientId": self.client_id,
67+
"parameters": {
68+
"name": "ItemsFilesDelete",
69+
"parameters": {
70+
"item": item.model_dump(by_alias=True),
71+
"section_id": section_id,
72+
"field_id": field_id,
73+
},
74+
},
75+
}
76+
}
77+
)
78+
79+
response = TypeAdapter(Item).validate_json(response)
80+
return response
81+
82+
async def replace_document(
83+
self, item: Item, doc_params: DocumentCreateParams
84+
) -> Item:
85+
"""
86+
Replace the document file within a document item
87+
"""
88+
response = await _invoke(
89+
{
90+
"invocation": {
91+
"clientId": self.client_id,
92+
"parameters": {
93+
"name": "ItemsFilesReplaceDocument",
94+
"parameters": {
95+
"item": item.model_dump(by_alias=True),
96+
"doc_params": doc_params.model_dump(by_alias=True),
97+
},
98+
},
99+
}
100+
}
101+
)
102+
103+
response = TypeAdapter(Item).validate_json(response)
104+
return response

src/onepassword/iterator.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# Code generated by op-codegen - DO NOT EDIT MANUALLY
2+
13
import asyncio
24
from typing import AsyncIterator, Iterable, TypeVar
35

1.21 MB
Binary file not shown.

0 commit comments

Comments
 (0)