Skip to content

Commit 52cbec9

Browse files
authored
fix: Improve Reader internal code by using with statements (#171)
* fix: 1 * fix: Update c2pa.py code * fix: Renamings * fix: Format * fix: UTF8 encoding * fix: Stream Id * fix: Clear key memory * fix: Makefile changes * fix: Update examples too * fix: Docs * fix: COmment format
1 parent 58e984e commit 52cbec9

File tree

6 files changed

+189
-188
lines changed

6 files changed

+189
-188
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ publish: release
9494

9595
# Code analysis
9696
check-format:
97+
python3 -m py_compile src/c2pa/c2pa.py
9798
flake8 src/c2pa/c2pa.py
9899

99100
# Formats Python source code using autopep8 with aggressive settings

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import c2pa
3333
## Examples
3434

3535
See the [`examples` directory](https://github.com/contentauth/c2pa-python/tree/main/examples) for some helpful examples:
36+
3637
- `examples/sign.py` shows how to sign and verify an asset with a C2PA manifest.
3738
- `examples/training.py` demonstrates how to add a "Do Not Train" assertion to an asset and verify it.
3839

examples/sign.py

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@
3838
# Load certificates and private key (here from the test fixtures).
3939
# This is OK for development, but in production you should use a
4040
# secure way to load the certificates and private key.
41-
certs = open(fixtures_dir + "es256_certs.pem", "rb").read()
42-
key = open(fixtures_dir + "es256_private.key", "rb").read()
41+
with open(fixtures_dir + "es256_certs.pem", "rb") as cert_file:
42+
certs = cert_file.read()
43+
with open(fixtures_dir + "es256_private.key", "rb") as key_file:
44+
key = key_file.read()
4345

4446
# Define a callback signer function
4547
def callback_signer_es256(data: bytes) -> bytes:
@@ -55,14 +57,6 @@ def callback_signer_es256(data: bytes) -> bytes:
5557
)
5658
return signature
5759

58-
# Create a signer using the callback function we defined
59-
signer = c2pa.Signer.from_callback(
60-
callback=callback_signer_es256,
61-
alg=c2pa.C2paSigningAlg.ES256,
62-
certs=certs.decode('utf-8'),
63-
tsa_url="http://timestamp.digicert.com"
64-
)
65-
6660
# Create a manifest definition as a dictionary.
6761
# This manifest follows the V2 manifest format.
6862
manifest_definition = {
@@ -92,29 +86,28 @@ def callback_signer_es256(data: bytes) -> bytes:
9286
]
9387
}
9488

95-
# Create the builder with the manifest definition
96-
builder = c2pa.Builder(manifest_definition)
97-
9889
# Sign the image with the signer created above,
9990
# which will use the callback signer
10091
print("\nSigning the image file...")
10192

102-
builder.sign_file(
103-
source_path=fixtures_dir + "A.jpg",
104-
dest_path=output_dir + "A_signed.jpg",
105-
signer=signer
106-
)
107-
108-
# Clean up
109-
signer.close()
110-
builder.close()
93+
with c2pa.Signer.from_callback(
94+
callback=callback_signer_es256,
95+
alg=c2pa.C2paSigningAlg.ES256,
96+
certs=certs.decode('utf-8'),
97+
tsa_url="http://timestamp.digicert.com"
98+
) as signer:
99+
with c2pa.Builder(manifest_definition) as builder:
100+
builder.sign_file(
101+
source_path=fixtures_dir + "A.jpg",
102+
dest_path=output_dir + "A_signed.jpg",
103+
signer=signer
104+
)
111105

112106
# Re-Read the signed image to verify
113107
print("\nReading signed image metadata:")
114108
with open(output_dir + "A_signed.jpg", "rb") as file:
115-
reader = c2pa.Reader("image/jpeg", file)
116-
print(reader.json())
117-
reader.close()
109+
with c2pa.Reader("image/jpeg", file) as reader:
110+
print(reader.json())
118111

119112
print("\nExample completed successfully!")
120113

examples/sign_info.py

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@
2626
output_dir = os.path.join(os.path.dirname(__file__), "../output/")
2727

2828
# Note: Builder, Reader, and Signer support being used as context managers
29-
# (with 'with' statements), but this example shows manual usage which requires
30-
# explicitly calling the close() function to clean up resources.
29+
# (with 'with' statements) for proper resource management.
3130

3231
# Ensure the output directory exists
3332
if not os.path.exists(output_dir):
@@ -40,13 +39,14 @@
4039
# Read existing C2PA metadata from the file
4140
print("\nReading existing C2PA metadata:")
4241
with open(fixtures_dir + "C.jpg", "rb") as file:
43-
reader = c2pa.Reader("image/jpeg", file)
44-
print(reader.json())
45-
reader.close()
42+
with c2pa.Reader("image/jpeg", file) as reader:
43+
print(reader.json())
4644

4745
# Create a signer from certificate and key files
48-
certs = open(fixtures_dir + "es256_certs.pem", "rb").read()
49-
key = open(fixtures_dir + "es256_private.key", "rb").read()
46+
with open(fixtures_dir + "es256_certs.pem", "rb") as cert_file:
47+
certs = cert_file.read()
48+
with open(fixtures_dir + "es256_private.key", "rb") as key_file:
49+
key = key_file.read()
5050

5151
# Define Signer information
5252
signer_info = c2pa.C2paSignerInfo(
@@ -56,9 +56,6 @@
5656
ta_url=b"http://timestamp.digicert.com" # Use bytes and add timestamp URL
5757
)
5858

59-
# Create the Signer from the information
60-
signer = c2pa.Signer.from_info(signer_info)
61-
6259
# Create a manifest definition as a dictionary
6360
# This examples signs using a V1 manifest
6461
# Note that this is a v1 spec manifest (legacy)
@@ -89,27 +86,21 @@
8986
]
9087
}
9188

92-
# Create the builder with the manifest definition
93-
builder = c2pa.Builder(manifest_definition)
94-
9589
# Sign the image
9690
print("\nSigning the image...")
97-
with open(fixtures_dir + "C.jpg", "rb") as source:
98-
# File needs to be opened in write+read mode to be signed
99-
# and verified properly.
100-
with open(output_dir + "C_signed.jpg", "w+b") as dest:
101-
result = builder.sign(signer, "image/jpeg", source, dest)
91+
with c2pa.Signer.from_info(signer_info) as signer:
92+
with c2pa.Builder(manifest_definition) as builder:
93+
with open(fixtures_dir + "C.jpg", "rb") as source:
94+
# File needs to be opened in write+read mode to be signed
95+
# and verified properly.
96+
with open(output_dir + "C_signed.jpg", "w+b") as dest:
97+
result = builder.sign(signer, "image/jpeg", source, dest)
10298

10399
# Read the signed image to verify
104100
print("\nReading signed image metadata:")
105101
with open(output_dir + "C_signed.jpg", "rb") as file:
106-
reader = c2pa.Reader("image/jpeg", file)
107-
print(reader.json())
108-
reader.close()
109-
110-
# Clean up resources manually, since we are not using with statements
111-
signer.close()
112-
builder.close()
102+
with c2pa.Reader("image/jpeg", file) as reader:
103+
print(reader.json())
113104

114105
print("\nExample completed successfully!")
115106

examples/training.py

Lines changed: 38 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,10 @@ def getitem(d, key):
9393
# V2 signing API example
9494
try:
9595
# Read the private key and certificate files
96-
key = open(keyFile,"rb").read()
97-
certs = open(pemFile,"rb").read()
96+
with open(keyFile, "rb") as key_file:
97+
key = key_file.read()
98+
with open(pemFile, "rb") as cert_file:
99+
certs = cert_file.read()
98100

99101
# Create a signer using the new API
100102
signer_info = c2pa.C2paSignerInfo(
@@ -103,66 +105,56 @@ def getitem(d, key):
103105
private_key=key,
104106
ta_url=b"http://timestamp.digicert.com"
105107
)
106-
signer = c2pa.Signer.from_info(signer_info)
107108

108-
# Create the builder
109-
builder = c2pa.Builder(manifest_json)
109+
with c2pa.Signer.from_info(signer_info) as signer:
110+
with c2pa.Builder(manifest_json) as builder:
111+
# Add the thumbnail resource using a stream
112+
with open(fixtures_dir + "A_thumbnail.jpg", "rb") as thumbnail_file:
113+
builder.add_resource("thumbnail", thumbnail_file)
110114

111-
# Add the thumbnail resource using a stream
112-
with open(fixtures_dir + "A_thumbnail.jpg", "rb") as thumbnail_file:
113-
builder.add_resource("thumbnail", thumbnail_file)
115+
# Add the ingredient using the correct method
116+
with open(fixtures_dir + "A_thumbnail.jpg", "rb") as ingredient_file:
117+
builder.add_ingredient(json.dumps(ingredient_json), "image/jpeg", ingredient_file)
114118

115-
# Add the ingredient using the correct method
116-
with open(fixtures_dir + "A_thumbnail.jpg", "rb") as ingredient_file:
117-
builder.add_ingredient(json.dumps(ingredient_json), "image/jpeg", ingredient_file)
119+
if os.path.exists(testOutputFile):
120+
os.remove(testOutputFile)
118121

119-
if os.path.exists(testOutputFile):
120-
os.remove(testOutputFile)
122+
# Sign the file using the stream-based sign method
123+
with open(testFile, "rb") as source_file:
124+
with open(testOutputFile, "w+b") as dest_file:
125+
result = builder.sign(signer, "image/jpeg", source_file, dest_file)
121126

122-
# Sign the file using the stream-based sign method
123-
with open(testFile, "rb") as source_file:
124-
with open(testOutputFile, "w+b") as dest_file:
125-
result = builder.sign(signer, "image/jpeg", source_file, dest_file)
126-
127-
# As an alternative, you can also use file paths directly during signing:
128-
# builder.sign_file(testFile, testOutputFile, signer)
129-
130-
# Clean up native resources (using a with statement works too!)
131-
signer.close()
132-
builder.close()
127+
# As an alternative, you can also use file paths directly during signing:
128+
# builder.sign_file(testFile, testOutputFile, signer)
133129

134130
except Exception as err:
135-
print("Exception during signing: ", err)
131+
print(f"Exception during signing: {err}")
136132

137-
print("\nSuccessfully added do not train manifest to file " + testOutputFile)
133+
print(f"\nSuccessfully added do not train manifest to file {testOutputFile}")
138134

139135
# now verify the asset and check the manifest for a do not train assertion...
140136

141137
allowed = True # opt out model, assume training is ok if the assertion doesn't exist
142138
try:
143139
# Create reader using the Reader API
144-
reader = c2pa.Reader(testOutputFile)
145-
146-
# Retrieve the manifest store
147-
manifest_store = json.loads(reader.json())
148-
149-
# Look at data in the active manifest
150-
manifest = manifest_store["manifests"][manifest_store["active_manifest"]]
151-
for assertion in manifest["assertions"]:
152-
if assertion["label"] == "cawg.training-mining":
153-
if getitem(assertion, ("data","entries","cawg.ai_generative_training","use")) == "notAllowed":
154-
allowed = False
155-
156-
# Get the ingredient thumbnail and save it to a file using resource_to_stream
157-
uri = getitem(manifest,("ingredients", 0, "thumbnail", "identifier"))
158-
with open(output_dir + "thumbnail_v2.jpg", "wb") as thumbnail_output:
159-
reader.resource_to_stream(uri, thumbnail_output)
160-
161-
# Clean up native resources (using a with statement works too!)
162-
reader.close()
140+
with c2pa.Reader(testOutputFile) as reader:
141+
# Retrieve the manifest store
142+
manifest_store = json.loads(reader.json())
143+
144+
# Look at data in the active manifest
145+
manifest = manifest_store["manifests"][manifest_store["active_manifest"]]
146+
for assertion in manifest["assertions"]:
147+
if assertion["label"] == "cawg.training-mining":
148+
if getitem(assertion, ("data","entries","cawg.ai_generative_training","use")) == "notAllowed":
149+
allowed = False
150+
151+
# Get the ingredient thumbnail and save it to a file using resource_to_stream
152+
uri = getitem(manifest,("ingredients", 0, "thumbnail", "identifier"))
153+
with open(output_dir + "thumbnail_v2.jpg", "wb") as thumbnail_output:
154+
reader.resource_to_stream(uri, thumbnail_output)
163155

164156
except Exception as err:
165-
print("Exception during assertions reading: ", err)
157+
print(f"Exception during assertions reading: {err}")
166158

167159
if allowed:
168160
print("Training is allowed")

0 commit comments

Comments
 (0)