Skip to content

Commit f68d9e1

Browse files
tmatherngpeacock
andauthored
Fix: Fix builds, use with when handling files (#46)
* with file * Remove unused dependency * File handling * Fix indent * Fix indent 2 * Remove whitespace * Fix build and test run issues * Build readme * Build readme --------- Co-authored-by: Gavin Peacock <[email protected]>
1 parent 992e133 commit f68d9e1

File tree

7 files changed

+76
-81
lines changed

7 files changed

+76
-81
lines changed

README.md

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ This library enables you to read and validate C2PA data in supported media files
1212

1313
Install from PyPI by entering this command:
1414

15-
```
15+
```bash
1616
pip install -U c2pa-python
1717
```
1818

@@ -22,17 +22,17 @@ This is a platform wheel built with Rust that works on Windows, macOS, and most
2222

2323
Determine what version you've got by entering this command:
2424

25-
```
25+
```bash
2626
pip list | grep c2pa-python
2727
```
2828

2929
If the version shown is lower than the most recent version, then update by [reinstalling](#installation).
3030

31-
### Reinstalling
31+
### Reinstalling
3232

33-
If you tried unsuccessfully to install this package before the [0.40 release](https://github.com/contentauth/c2pa-python/releases/tag/v0.4), then use this command to reinstall:
33+
If you tried unsuccessfully to install this package before the [0.40 release](https://github.com/contentauth/c2pa-python/releases/tag/v0.4), then use this command to reinstall:
3434

35-
```
35+
```bash
3636
pip install --upgrade --force-reinstall c2pa-python
3737
```
3838

@@ -98,11 +98,11 @@ def sign_ps256(data: bytes, key_path: str) -> bytes:
9898

9999
### File-based operation
100100

101-
**Read and validate C2PA data from an asset file**
101+
**Read and validate C2PA data from an asset file**
102102

103103
Use the `Reader` to read C2PA data from the specified asset file (see [supported file formats](#supported-file-formats)).
104104

105-
This examines the specified media file for C2PA data and generates a report of any data it finds. If there are validation errors, the report includes a `validation_status` field.
105+
This examines the specified media file for C2PA data and generates a report of any data it finds. If there are validation errors, the report includes a `validation_status` field.
106106

107107
An asset file may contain many manifests in a manifest store. The most recent manifest is identified by the value of the `active_manifest` field in the manifests map. The manifests may contain binary resources such as thumbnails which can be retrieved with `resource_to_stream` or `resource_to_file` using the associated `identifier` field values and a `uri`.
108108

@@ -142,9 +142,9 @@ try:
142142
def private_sign(data: bytes) -> bytes:
143143
return sign_ps256(data, "tests/fixtures/ps256.pem")
144144

145-
# read our public certs into memory
145+
# read our public certs into memory
146146
certs = open(data_dir + "ps256.pub", "rb").read()
147-
147+
148148
# Create a signer from the private signer, certs and a time stamp service url
149149
signer = create_signer(private_sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com")
150150

@@ -225,9 +225,9 @@ try:
225225
def private_sign(data: bytes) -> bytes:
226226
return sign_ps256(data, "tests/fixtures/ps256.pem")
227227

228-
# read our public certs into memory
228+
# read our public certs into memory
229229
certs = open(data_dir + "ps256.pub", "rb").read()
230-
230+
231231
# Create a signer from the private signer, certs and a time stamp service url
232232
signer = create_signer(private_sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com")
233233

@@ -295,37 +295,38 @@ except Exception as err:
295295

296296
## Development
297297

298-
It is best to [set up a virtual environment](https://virtualenv.pypa.io/en/latest/installation.html) for development and testing. To build from source on Linux, install `curl` and `rustup` then set up Python.
298+
It is best to [set up a virtual environment](https://virtualenv.pypa.io/en/latest/installation.html) for development and testing.
299+
300+
To build from source on Linux, install `curl` and `rustup` then set up Python.
299301

300302
First update `apt` then (if needed) install `curl`:
301303

302-
```
304+
```bash
303305
apt update
304306
apt install curl
305307
```
306308

307309
Install Rust:
308310

309-
```
311+
```bash
310312
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
311313
source "$HOME/.cargo/env"
312314
```
313315

314316
Install Python, `pip`, and `venv`:
315317

316-
```
318+
```bash
317319
apt install python3
318320
apt install pip
319321
apt install python3.11-venv
320322
python3 -m venv .venv
321323
```
322324

323-
Build the wheel for your platform:
325+
Build the wheel for your platform (from the root of the repository):
324326

325-
```
327+
```bash
326328
source .venv/bin/activate
327-
pip install maturin
328-
pip install uniffi-bindgen
329+
pip install -r requirements.txt
329330
python3 -m pip install build
330331
pip install -U pytest
331332

@@ -336,7 +337,7 @@ python3 -m build --wheel
336337

337338
Build using [manylinux](https://github.com/pypa/manylinux) by using a Docker image as follows:
338339

339-
```
340+
```bash
340341
docker run -it quay.io/pypa/manylinux_2_28_aarch64 bash
341342
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
342343
source "$HOME/.cargo/env"
@@ -347,28 +348,27 @@ pip install build
347348
pip install -U pytest
348349

349350
cd home
350-
git clone https://github.com/contentauth/c2pa-python.git
351+
git clone https://github.com/contentauth/c2pa-python.git
351352
cd c2pa-python
352353
python3 -m build --wheel
353-
auditwheel repair target/wheels/c2pa_python-0.4.0-py3-none-linux_aarch64.whl
354+
auditwheel repair target/wheels/c2pa_python-0.4.0-py3-none-linux_aarch64.whl
354355
```
355356

356357
### Testing
357358

358359
We use [PyTest](https://docs.pytest.org/) for testing.
359360

360-
Run tests by entering this command:
361+
Run tests by following these steps:
361362

362-
```
363-
source .venv/bin/activate
364-
maturin develop
365-
pytest
366-
deactivate
367-
```
363+
1. Activate the virtual environment: `source .venv/bin/activate`
364+
2. (optional) Install dependencies: `pip install -r requirements.txt`
365+
3. Setup the virtual environment with local changes: `maturin develop`
366+
4. Run the tests: `pytest`
367+
5. Deactivate the virtual environment: `deactivate`
368368

369369
For example:
370370

371-
```
371+
```bash
372372
source .venv/bin/activate
373373
maturin develop
374374
python3 tests/training.py
@@ -413,6 +413,7 @@ This release:
413413
### Version 0.3.0
414414

415415
This release includes some breaking changes to align with future APIs:
416+
416417
- `C2paSignerInfo` moves the `alg` to the first parameter from the 3rd.
417418
- `c2pa.verify_from_file_json` is now `c2pa.read_file`.
418419
- `c2pa.ingredient_from_file_json` is now `c2pa.read_ingredient_file`.
@@ -430,5 +431,3 @@ Note that some components and dependent crates are licensed under different term
430431
### Contributions and feedback
431432

432433
We welcome contributions to this project. For information on contributing, providing feedback, and about ongoing work, see [Contributing](https://github.com/contentauth/c2pa-python/blob/main/CONTRIBUTING.md).
433-
434-

c2pa/c2pa_api/c2pa_api.py

Lines changed: 29 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,14 @@
1515
import os
1616
import sys
1717
import tempfile
18-
import shutil
18+
1919
PROJECT_PATH = os.getcwd()
2020
SOURCE_PATH = os.path.join(
2121
PROJECT_PATH,"target","python"
2222
)
2323
sys.path.append(SOURCE_PATH)
2424

2525
import c2pa.c2pa as api
26-
2726
#from c2pa import Error, SigningAlg, version, sdk_version
2827

2928
# This module provides a simple Python API for the C2PA library.
@@ -45,29 +44,29 @@ def __init__(self, format, stream, manifest_data=None):
4544

4645
@classmethod
4746
def from_file(cls, path: str, format=None):
48-
file = open(path, "rb")
49-
if format is None:
50-
# determine the format from the file extension
51-
format = os.path.splitext(path)[1][1:]
52-
return cls(format, file)
53-
47+
with open(path, "rb") as file:
48+
if format is None:
49+
# determine the format from the file extension
50+
format = os.path.splitext(path)[1][1:]
51+
return cls(format, file)
52+
5453
def get_manifest(self, label):
5554
manifest_store = json.loads(self.json())
5655
return manifest_store["manifests"].get(label)
57-
56+
5857
def get_active_manifest(self):
5958
manifest_store = json.loads(self.json())
6059
active_label = manifest_store.get("active_manifest")
6160
if active_label:
6261
return manifest_store["manifests"].get(active_label)
6362
return None
64-
63+
6564
def resource_to_stream(self, uri, stream) -> None:
6665
return super().resource_to_stream(uri, C2paStream(stream))
6766

6867
def resource_to_file(self, uri, path) -> None:
69-
file = open(path, "wb")
70-
return self.resource_to_stream(uri, file)
68+
with open(path, "wb") as file:
69+
return self.resource_to_stream(uri, file)
7170

7271
# The Builder is used to construct a new Manifest and add it to a stream or file.
7372
# The initial manifest is defined by a Manifest Definition dictionary.
@@ -101,49 +100,49 @@ def set_manifest(self, manifest):
101100
manifest = json.dumps(manifest)
102101
super().with_json(manifest)
103102
return self
104-
103+
105104
def add_resource(self, uri, stream):
106105
return super().add_resource(uri, C2paStream(stream))
107-
106+
108107
def add_resource_file(self, uri, path):
109-
file = open(path, "rb")
110-
return self.add_resource(uri, file)
111-
108+
with open(path, "rb") as file:
109+
return self.add_resource(uri, file)
110+
112111
def add_ingredient(self, ingredient, format, stream):
113112
if not isinstance(ingredient, str):
114113
ingredient = json.dumps(ingredient)
115114
return super().add_ingredient(ingredient, format, C2paStream(stream))
116-
115+
117116
def add_ingredient_file(self, ingredient, path):
118117
format = os.path.splitext(path)[1][1:]
119-
file = open(path, "rb")
120-
return self.add_ingredient(ingredient, format, file)
121-
118+
with open(path, "rb") as file:
119+
return self.add_ingredient(ingredient, format, file)
120+
122121
def to_archive(self, stream):
123122
return super().to_archive(C2paStream(stream))
124-
123+
125124
@classmethod
126125
def from_archive(cls, stream):
127126
self = cls({})
128127
super().from_archive(self, C2paStream(stream))
129128
return self
130-
129+
131130
def sign(self, signer, format, input, output = None):
132131
return super().sign(signer, format, C2paStream(input), C2paStream(output))
133132

134133
def sign_file(self, signer, sourcePath, outputPath):
135134
return super().sign_file(signer, sourcePath, outputPath)
136-
135+
137136

138137

139138
# Implements a C2paStream given a stream handle
140139
# This is used to pass a file handle to the c2pa library
141-
# It is used by the Reader and Builder classes internally
140+
# It is used by the Reader and Builder classes internally
142141
class C2paStream(api.Stream):
143142
def __init__(self, stream):
144143
self.stream = stream
145-
146-
def read_stream(self, length: int) -> bytes:
144+
145+
def read_stream(self, length: int) -> bytes:
147146
#print("Reading " + str(length) + " bytes")
148147
return self.stream.read(length)
149148

@@ -172,14 +171,14 @@ def open_file(path: str, mode: str) -> api.Stream:
172171
class SignerCallback(api.SignerCallback):
173172
def __init__(self, callback):
174173
self.sign = callback
175-
super().__init__()
174+
super().__init__()
176175

177176

178177
# Convenience class so we can just pass in a callback function
179178
#class CallbackSigner(c2pa.CallbackSigner):
180179
# def __init__(self, callback, alg, certs, timestamp_url=None):
181180
# cb = SignerCallback(callback)
182-
# super().__init__(cb, alg, certs, timestamp_url)
181+
# super().__init__(cb, alg, certs, timestamp_url)
183182

184183
# Creates a Signer given a callback and configuration values
185184
# It is used by the Builder class to sign the asset
@@ -192,7 +191,7 @@ def __init__(self, callback):
192191
# signer = c2pa_api.create_signer(sign_ps256, "ps256", certs, "http://timestamp.digicert.com")
193192
#
194193
def create_signer(callback, alg, certs, timestamp_url=None):
195-
return api.CallbackSigner(SignerCallback(callback), alg, certs, timestamp_url)
194+
return api.CallbackSigner(SignerCallback(callback), alg, certs, timestamp_url)
196195

197196

198197

@@ -210,7 +209,6 @@ def sign_ps256_shell(data: bytes, key_path: str) -> bytes:
210209
from cryptography.hazmat.primitives.asymmetric import padding
211210

212211
def sign_ps256(data: bytes, key: bytes) -> bytes:
213-
214212
private_key = serialization.load_pem_private_key(
215213
key,
216214
password=None,

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
maturin==1.2.0
2-
uniffi-bindgen==0.24.1
2+
uniffi-bindgen==0.24.1
3+
cryptography==43.0.1

src/c2pa.udl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ interface CallbackSigner {
7878

7979
interface Builder {
8080
constructor();
81-
81+
8282
[Throws=Error]
8383
void with_json([ByRef] string json);
8484

tests/test_api.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def test_sdk_version():
6363
assert "c2pa-rs/" in sdk_version()
6464

6565
def test_v2_read():
66-
#example of reading a manifest store from a file
66+
#example of reading a manifest store from a file
6767
try:
6868
reader = Reader.from_file("tests/fixtures/C.jpg")
6969
manifest = reader.get_active_manifest()
@@ -92,7 +92,7 @@ def test_v2_read():
9292
exit(1)
9393

9494
def test_reader_from_file_no_store():
95-
with pytest.raises(Error.ManifestNotFound) as err:
95+
with pytest.raises(Error.ManifestNotFound) as err:
9696
reader = Reader.from_file("tests/fixtures/A.jpg")
9797

9898
def test_v2_sign():
@@ -102,7 +102,7 @@ def test_v2_sign():
102102
key = open(data_dir + "ps256.pem", "rb").read()
103103
def sign(data: bytes) -> bytes:
104104
return sign_ps256(data, key)
105-
105+
106106
certs = open(data_dir + "ps256.pub", "rb").read()
107107
# Create a local signer from a certificate pem file
108108
signer = create_signer(sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com")
@@ -143,7 +143,7 @@ def test_v2_sign_file_same():
143143
key = open(data_dir + "ps256.pem", "rb").read()
144144
def sign(data: bytes) -> bytes:
145145
return sign_ps256(data, key)
146-
146+
147147
certs = open(data_dir + "ps256.pub", "rb").read()
148148
# Create a local signer from a certificate pem file
149149
signer = create_signer(sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com")
@@ -169,4 +169,4 @@ def sign(data: bytes) -> bytes:
169169
assert manifest.get("validation_status") == None
170170
except Exception as e:
171171
print("Failed to sign manifest store: " + str(e))
172-
#exit(1)
172+
#exit(1)

0 commit comments

Comments
 (0)