Skip to content

Commit 399c692

Browse files
committed
Version 0.5.0
Remove v1 APIs update tests and README
1 parent 41efb14 commit 399c692

File tree

8 files changed

+88
-263
lines changed

8 files changed

+88
-263
lines changed

Cargo.toml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "c2pa-python"
3-
version = "0.4.0"
3+
version = "0.5.0"
44
edition = "2021"
55
authors = ["Gavin Peacock <[email protected]"]
66

@@ -9,8 +9,6 @@ authors = ["Gavin Peacock <[email protected]"]
99
name = "c2pa"
1010
crate-type = ["cdylib"]
1111

12-
[features]
13-
v1 = ["c2pa/file_io"]
1412

1513
[dependencies]
1614
c2pa = {version = "0.32.0", features = ["unstable_api", "openssl"]}

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,20 @@ deactivate
214214

215215
## Release notes
216216

217+
### Version 0.5.0
218+
219+
- This release rewrites the API to be stream based using a Builder and Reader model.
220+
- The functions now support throwing c2pa.Error values, caught with try/except.
221+
- Instead of `c2pa.read_file` you now call `c2pa_api.Reader.from_file` and `reader.json`.
222+
- Read thumbnails and other resources use `reader.resource_to_stream` or `reader.resource.to_file`.
223+
- Instead of `c2pa.sign_file` use `c2pa_api.Builder.from_json` and `builder.sign` or `builder.sign_file`.
224+
- Add thumbnails or other resources with `builder.add_resource` or `builder.add_resource_file`.
225+
- Add Ingredients with `builder.add_ingredient` or `builder.add_ingredient_file`.
226+
- You can archive a `Builder` using `builder.to_archive` and reconstruct it with `builder.from_archive`.
227+
- Signers can be constructed with `c2pa_api.create_signer`.
228+
- The signer now requires a signing function to keep private keys private.
229+
- Example signing functions are provided in c2pa_api.py
230+
217231
### Version 0.4.0
218232

219233
This release:

src/c2pa.udl

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,6 @@
22
namespace c2pa {
33
string version();
44
string sdk_version();
5-
/// [Throws=Error]
6-
/// CallbackSigner create_signer(SignerCallback callback, SigningAlg alg, bytes certs, string? ta_url);
7-
/// [Throws=Error]
8-
/// string read_file([ByRef] string path, string? data_dir);
9-
/// [Throws=Error]
10-
/// string read_ingredient_file([ByRef] string path, [ByRef] string data_dir);
11-
/// [Throws=Error]
12-
/// sequence<u8> sign_file([ByRef] string source, [ByRef] string dest, [ByRef] string manifest, [ByRef] SignerInfo signer_info, string? data_dir);
135
};
146

157
[Error]

tests/c2pa_api.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323

2424
import c2pa;
2525

26+
from c2pa import Error, SigningAlg, version, sdk_version
27+
2628
# This module provides a simple Python API for the C2PA library.
2729

2830
# Reader is used to read a manifest store from a stream or file.
@@ -186,9 +188,9 @@ def create_signer(callback, alg, certs, timestamp_url=None):
186188
def sign_ps256_shell(data: bytes, key_path: str) -> bytes:
187189
with tempfile.NamedTemporaryFile() as bytes:
188190
bytes.write(data)
189-
signature = tempfile.NamedTemporaryFile()
190-
os.system("openssl dgst -sha256 -sign {} -out {} {}".format(key_path, signature.name, bytes.name))
191-
return signature.read()
191+
signature = tempfile.NamedTemporaryFile()
192+
os.system("openssl dgst -sha256 -sign {} -out {} {}".format(key_path, signature.name, bytes.name))
193+
return signature.read()
192194

193195
# Example of using python crypto to sign data using openssl with Ps256
194196
from cryptography.hazmat.primitives import hashes, serialization

tests/test_api.py

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
# Copyright 2023 Adobe. All rights reserved.
32
# This file is licensed to you under the Apache License,
43
# Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
@@ -11,16 +10,21 @@
1110
# specific language governing permissions and limitations under
1211
# each license.
1312

14-
import c2pa
15-
import pytest
13+
1614
import json
15+
import pytest
1716
import tempfile
18-
import c2pa_api
17+
18+
from c2pa_api import Builder, Error, Reader, SigningAlg, create_signer, sdk_version, sign_ps256, version
1919

2020
# a little helper function to get a value from a nested dictionary
2121
from functools import reduce
2222
import operator
2323

24+
def getitem(d, key):
25+
return reduce(operator.getitem, key, d)
26+
27+
# define the manifest we will use for testing
2428
manifest_def = {
2529
"claim_generator_info": [{
2630
"name": "python test",
@@ -46,19 +50,16 @@
4650
]
4751
}
4852

49-
def getitem(d, key):
50-
return reduce(operator.getitem, key, d)
51-
5253
def test_version():
53-
assert c2pa.version() == "0.4.0"
54+
assert version() == "0.5.0"
5455

5556
def test_sdk_version():
56-
assert "c2pa-rs/" in c2pa.sdk_version()
57+
assert "c2pa-rs/" in sdk_version()
5758

5859
def test_v2_read():
5960
#example of reading a manifest store from a file
6061
try:
61-
reader = c2pa_api.Reader.from_file("tests/fixtures/C.jpg")
62+
reader = Reader.from_file("tests/fixtures/C.jpg")
6263
jsonReport = reader.json()
6364
manifest_store = json.loads(jsonReport)
6465
manifest = manifest_store["manifests"][manifest_store["active_manifest"]]
@@ -86,33 +87,33 @@ def test_v2_read():
8687
exit(1)
8788

8889
def test_reader_from_file_no_store():
89-
with pytest.raises(c2pa.Error.ManifestNotFound) as err:
90-
reader = c2pa_api.Reader.from_file("tests/fixtures/A.jpg")
90+
with pytest.raises(Error.ManifestNotFound) as err:
91+
reader = Reader.from_file("tests/fixtures/A.jpg")
9192

9293
def test_v2_sign():
9394
# define a source folder for any assets we need to read
9495
data_dir = "tests/fixtures/"
9596
try:
96-
def sign_ps256(data: bytes) -> bytes:
97-
return c2pa_api.sign_ps256(data, data_dir+"ps256.pem")
97+
def sign(data: bytes) -> bytes:
98+
return sign_ps256(data, data_dir+"ps256.pem")
9899

99100
certs = open(data_dir + "ps256.pub", "rb").read()
100101
# Create a local signer from a certificate pem file
101-
signer = c2pa_api.create_signer(sign_ps256, c2pa.SigningAlg.PS256, certs, "http://timestamp.digicert.com")
102+
signer = create_signer(sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com")
102103

103-
builder = c2pa_api.Builder(manifest_def)
104+
builder = Builder(manifest_def)
104105

105106
builder.add_resource_file("A.jpg", data_dir + "A.jpg")
106107

107108
builder.to_archive(open("target/archive.zip", "wb"))
108109

109-
builder = c2pa_api.Builder.from_archive(open("target/archive.zip", "rb"))
110+
builder = Builder.from_archive(open("target/archive.zip", "rb"))
110111

111112
with tempfile.TemporaryDirectory() as output_dir:
112113
c2pa_data = builder.sign_file(signer, data_dir + "A.jpg", output_dir + "out.jpg")
113114
assert len(c2pa_data) > 0
114115

115-
reader = c2pa_api.Reader.from_file(output_dir + "out.jpg")
116+
reader = Reader.from_file(output_dir + "out.jpg")
116117
print(reader.json())
117118
manifest_store = json.loads(reader.json())
118119
manifest = manifest_store["manifests"][manifest_store["active_manifest"]]

tests/training.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@
1515
import json
1616
import os
1717
import sys
18-
import c2pa
1918

20-
import c2pa_api
19+
from c2pa_api import Builder, Reader, create_signer, SigningAlg, sign_ps256, version
2120

2221
# set up paths to the files we we are using
2322
PROJECT_PATH = os.getcwd()
@@ -32,7 +31,7 @@
3231
def getitem(d, key):
3332
return reduce(operator.getitem, key, d)
3433

35-
print("version = " + c2pa.version())
34+
print("version = " + version())
3635

3736
# first create an asset with a do not train assertion
3837

@@ -74,15 +73,15 @@ def getitem(d, key):
7473
# V2 signing api
7574
try:
7675
# This could be implemented on a server using an HSM
77-
def sign_ps256(data: bytes) -> bytes:
78-
return c2pa_api.sign_ps256(data, "tests/fixtures/ps256.pem")
76+
def sign(data: bytes) -> bytes:
77+
return sign_ps256(data, "tests/fixtures/ps256.pem")
7978

8079
certs = open("tests/fixtures/ps256.pub","rb").read()
8180

8281
# Create a signer from a certificate pem file
83-
signer = c2pa_api.create_signer(sign_ps256, c2pa.SigningAlg.PS256, certs, "http://timestamp.digicert.com")
82+
signer = create_signer(sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com")
8483

85-
builder = c2pa_api.Builder(manifest_json)
84+
builder = Builder(manifest_json)
8685

8786
builder.add_resource_file("thumbnail", "tests/fixtures/A_thumbnail.jpg")
8887

@@ -100,7 +99,7 @@ def sign_ps256(data: bytes) -> bytes:
10099

101100
allowed = True # opt out model, assume training is ok if the assertion doesn't exist
102101
try:
103-
reader = c2pa_api.Reader.from_file(testOutputFile)
102+
reader = Reader.from_file(testOutputFile)
104103
manifest_store = json.loads(reader.json())
105104

106105
manifest = manifest_store["manifests"][manifest_store["active_manifest"]]

tests/unit_tests.py

Lines changed: 42 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -11,51 +11,48 @@
1111
# specific language governing permissions and limitations under
1212
# each license.import unittest
1313

14-
import unittest
15-
from unittest.mock import mock_open, patch
16-
import c2pa_api
17-
from c2pa_api import c2pa
1814
import os
1915
import io
16+
import json
17+
import unittest
18+
from unittest.mock import mock_open, patch
19+
20+
from c2pa_api import Builder, Error, Reader, SigningAlg, create_signer, sdk_version, sign_ps256
21+
2022
PROJECT_PATH = os.getcwd()
2123

2224
testPath = os.path.join(PROJECT_PATH, "tests", "fixtures", "C.jpg")
2325

2426
class TestC2paSdk(unittest.TestCase):
2527

2628
def test_version(self):
27-
self.assertIn("0.4.0",c2pa_api.c2pa.sdk_version())
28-
29-
#def test_supported_extensions(self):
30-
# self.assertIn("jpeg",c2pa_api.c2pa.supported_extensions())
29+
self.assertIn("0.4.0", sdk_version())
3130

3231

3332
class TestReader(unittest.TestCase):
3433

35-
def test_normal_read(self):
34+
def test_stream_read(self):
3635
with open(testPath, "rb") as file:
37-
reader = c2pa_api.Reader("image/jpeg",file)
36+
reader = Reader("image/jpeg",file)
3837
json = reader.json()
3938
self.assertIn("C.jpg", json)
4039

41-
def test_normal_read_and_parse(self):
40+
def test_stream_read_and_parse(self):
4241
with open(testPath, "rb") as file:
43-
reader = c2pa_api.Reader("image/jpeg",file)
44-
json = reader.json()
45-
self.assertIn("C.jpg", json)
46-
#manifest_store = c2pa_api.ManifestStore.from_json(json)
47-
#title= manifest_store.manifests[manifest_store.activeManifest].title
48-
#self.assertEqual(title, "C.jpg")
42+
reader = Reader("image/jpeg",file)
43+
manifest_store = json.loads(reader.json())
44+
title = manifest = manifest_store["manifests"][manifest_store["active_manifest"]]["title"]
45+
self.assertEqual(title, "C.jpg")
46+
47+
def test_json_decode_err(self):
48+
with self.assertRaises(Error.Io):
49+
manifest_store = Reader("image/jpeg","foo")
4950

50-
#def test_json_decode_err(self):
51-
# with self.assertRaises(c2pa_api.json.decoder.JSONDecodeError):
52-
# manifest_store = c2pa_api.ManifestStore.from_json("foo")
51+
def test_reader_bad_format(self):
52+
with self.assertRaises(Error.NotSupported):
53+
with open(testPath, "rb") as file:
54+
reader = Reader("badFormat",file)
5355

54-
#def test_reader_bad_format(self):
55-
# with self.assertRaises(c2pa_api.c2pa.StreamError.Other):
56-
# with open(testPath, "rb") as file:
57-
# manifestStore = c2pa_api.ManifestStoreReader("badFormat",file)
58-
# json = manifestStore.read()
5956

6057
class TestBuilder(unittest.TestCase):
6158
# Define a manifest as a dictionary
@@ -84,26 +81,36 @@ class TestBuilder(unittest.TestCase):
8481
]
8582
}
8683

87-
def sign_ps256(data: bytes) -> bytes:
88-
return c2pa_api.sign_ps256_shell(data, "tests/fixtures/ps256.pem")
84+
# Define a function that signs data with PS256 using a private key
85+
def sign(data: bytes) -> bytes:
86+
return sign_ps256(data, "tests/fixtures/ps256.pem")
8987

9088
# load the public keys from a pem file
91-
pemFile = os.path.join(PROJECT_PATH,"tests","fixtures","ps256.pub")
92-
certs = open(pemFile,"rb").read()
89+
certs = open("tests/fixtures/ps256.pub","rb").read()
9390

94-
# Create a local signer from a certificate pem file
95-
signer = c2pa_api.create_signer(sign_ps256, c2pa.SigningAlg.PS256, certs, "http://timestamp.digicert.com")
91+
# Create a local Ps256 signer with certs and a timestamp server
92+
signer = create_signer(sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com")
9693

97-
def test_normal_build(self):
94+
def test_streams_build(self):
9895
with open(testPath, "rb") as file:
99-
builder = c2pa_api.Builder(TestBuilder.manifestDefinition)
96+
builder = Builder(TestBuilder.manifestDefinition)
10097
output = byte_array = io.BytesIO(bytearray())
10198
builder.sign(TestBuilder.signer, "image/jpeg", file, output)
10299
output.seek(0)
103-
reader = c2pa_api.Reader("image/jpeg", output)
100+
reader = Reader("image/jpeg", output)
104101
self.assertIn("Python Test", reader.json())
105102

106-
103+
def test_streams_build(self):
104+
with open(testPath, "rb") as file:
105+
builder = Builder(TestBuilder.manifestDefinition)
106+
archive = byte_array = io.BytesIO(bytearray())
107+
builder.to_archive(archive)
108+
builder = Builder.from_archive(archive)
109+
output = byte_array = io.BytesIO(bytearray())
110+
builder.sign(TestBuilder.signer, "image/jpeg", file, output)
111+
output.seek(0)
112+
reader = Reader("image/jpeg", output)
113+
self.assertIn("Python Test", reader.json())
107114

108115
if __name__ == '__main__':
109116
unittest.main()

0 commit comments

Comments
 (0)