Skip to content

Commit ccc28b3

Browse files
committed
feat: simplify how subjects are created from existing manifest
Signed-off-by: Tomas Coufal <tcoufal@redhat.com>
1 parent cb575ab commit ccc28b3

File tree

3 files changed

+59
-0
lines changed

3 files changed

+59
-0
lines changed

docs/getting_started/user-guide.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,28 @@ def push(uri, root):
489489

490490
</details>
491491

492+
<details>
493+
494+
<summary>Example of basic artifact attachment</summary>
495+
496+
We are assuming an `derived-artifact.txt` in the present working directory and that there's already a `localhost:5000/dinosaur/artifact:v1` artifact present in the registry. Here is an example of how to [attach](https://oras.land/docs/concepts/reftypes/) a derived artifact to the existing artifact.
497+
498+
```python
499+
import oras.client
500+
import oras.provider
501+
502+
client = oras.client.OrasClient(insecure=True)
503+
504+
manifest = client.remote.get_manifest(f"localhost:5000/dinosaur/artifact:v1")
505+
subject = oras.provider.Subject.from_manifest(manifest)
506+
507+
client.push(files=["derived-artifact.txt"], target="localhost:5000/dinosaur/artifact:v1-derived", subject=subject)
508+
Successfully pushed localhost:5000/dinosaur/artifact:v1
509+
Out[4]: <Response [201]>
510+
```
511+
512+
</details>
513+
492514
The above examples are just a start! See our [examples](https://github.com/oras-project/oras-py/tree/main/examples)
493515
folder alongside the repository for more code examples and clients. If you would like help
494516
for an example, or to contribute an example, [you know what to do](https://github.com/oras-project/oras-py/issues)!

oras/provider.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
__license__ = "Apache-2.0"
44

55
import copy
6+
import hashlib
7+
import json
68
import os
79
import urllib
810
from contextlib import contextmanager, nullcontext
@@ -17,6 +19,7 @@
1719
import oras.auth
1820
import oras.container
1921
import oras.decorator as decorator
22+
import oras.defaults
2023
import oras.oci
2124
import oras.schemas
2225
import oras.utils
@@ -41,6 +44,23 @@ class Subject:
4144
digest: str
4245
size: int
4346

47+
@classmethod
48+
def from_manifest(cls, manifest: dict) -> "Subject":
49+
"""
50+
Create a new Subject from a Manifest
51+
52+
:param manifest: manifest to convert to subject
53+
"""
54+
manifest_string = json.dumps(manifest).encode("utf-8")
55+
digest = "sha256:" + hashlib.sha256(manifest_string).hexdigest()
56+
size = len(manifest_string)
57+
58+
return cls(
59+
manifest["mediaType"] or oras.defaults.default_manifest_media_type,
60+
digest,
61+
size,
62+
)
63+
4464

4565
class Registry:
4666
"""

oras/tests/test_provider.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import oras.client
1111
import oras.defaults
12+
import oras.oci
1213
import oras.provider
1314
import oras.utils
1415

@@ -132,3 +133,19 @@ def test_sanitize_path():
132133
str(e.value)
133134
== f"Filename {Path(os.path.join(os.getcwd(), '..', '..')).resolve()} is not in {Path('../').resolve()} directory"
134135
)
136+
137+
138+
@pytest.mark.with_auth(False)
139+
def test_create_subject_from_manifest():
140+
"""
141+
Basic tests for oras Subject creation from empty manifest
142+
"""
143+
manifest = oras.oci.NewManifest()
144+
subject = oras.provider.Subject.from_manifest(manifest)
145+
146+
assert subject.mediaType == oras.defaults.default_manifest_media_type
147+
assert (
148+
subject.digest
149+
== "sha256:7a6f84d8c73a71bf9417c13f721ed102f74afac9e481f89e5a72d28954e7d0c5"
150+
)
151+
assert subject.size == 126

0 commit comments

Comments
 (0)