Skip to content

Commit 2c82e55

Browse files
committed
Merge branch 'main' into morgan/150/example-test
2 parents bd4355e + ea747bd commit 2c82e55

File tree

17 files changed

+434
-23
lines changed

17 files changed

+434
-23
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():
@@ -171,6 +175,12 @@ async def main():
171175

172176
await share_item(client, created_item.vault_id, updated_item.id)
173177

178+
await create_ssh_key_item(client)
179+
180+
await create_and_replace_document_item(client)
181+
182+
await create_attach_and_delete_file_field_item(client)
183+
174184
# [developer-docs.sdk.python.delete-item]-start
175185
# Delete a item from your vault.
176186
await client.items.delete(created_item.vault_id, updated_item.id)
@@ -222,5 +232,151 @@ async def share_item(client: Client, vault_id: str, item_id: str):
222232
# [developer-docs.sdk.python.item-share-create-share]-end
223233

224234

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

0 commit comments

Comments
 (0)