Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Features:
- Add assertions and ingredients to assets.
- Examples and unit tests to demonstrate usage.

<div style={{display: 'none'}}>
<div class="github-only">

For the best experience, read the docs on the [CAI Open Source SDK documentation website](https://opensource.contentauthenticity.org/docs/c2pa-c).

Expand All @@ -21,7 +21,7 @@ If you want to view the documentation in GitHub, see:
- [Supported formats](https://github.com/contentauth/c2pa-rs/blob/main/docs/supported-formats.md)
- [Configuring the SDK using `Context` and `Settings`](docs/context-settings.md)
- [Using Builder intents](docs/intents.md) to ensure spec-compliant manifests
- Using [working stores and archvies](docs/working-stores.md)
- Using [working stores and archives](docs/working-stores.md)
- Selectively constructing manifests by [filtering actions and ingredients](docs/selective-manifests.md)
- [Diagram of public classes in the Python library and their relationships](docs/class-diagram.md)
- [Release notes](docs/release-notes.md)
Expand Down Expand Up @@ -72,7 +72,7 @@ export C2PA_RS_PATH=/path/to/c2pa-rs
make build-from-source C2PA_RS_PATH=$C2PA_RS_PATH
```

This does a clean build of the `c2pa-c-ffi` crate (with the `file_io` feature, which the Python wrapper requires), stages the resulting library under both `artifacts/` and `src/c2pa/libs/`, and installs the package in editable mode, replacing any prebuilt artifacts from `make download-native-artifacts`. The release profile is used by default; to build the debug profile instead, pass `EXTRA_BUILD_ARGS="--debug"`:
This does a clean build of the `c2pa-c-ffi` crate (with the `file_io` feature, which the Python wrapper requires), stages the resulting library under both `artifacts/` and `src/c2pa/libs/`, and installs the package in editable mode, replacing any prebuilt artifacts from `make download-native-artifacts`. By default, the build uses the release profile; to build the debug profile instead, pass `EXTRA_BUILD_ARGS="--debug"`:

```sh
make build-from-source C2PA_RS_PATH=$C2PA_RS_PATH EXTRA_BUILD_ARGS="--debug"
Expand Down
1 change: 0 additions & 1 deletion docs/class-diagram.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

# Class diagram

This diagram shows the public classes in the Python library and their relationships.
Expand Down
18 changes: 9 additions & 9 deletions docs/context-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,10 +274,10 @@ The Settings JSON has this top-level structure:

The settings format is **JSON** only. Pass JSON strings to `Settings.from_json()` or `Context.from_json()`, and dictionaries to `Settings.from_dict()` or `Context.from_dict()`. The `from_dict()` methods convert Python dictionaries to a format compatible with the underlying native libraries.

Notes:
- All properties are optional. If you don't specify a value, the SDK uses the default value.
- If you specify a value of `null` (or `None` in a dict), the property is explicitly set to `null`, not the default. This distinction is important when you want to override a default behavior.
- For Boolean values, use JSON Booleans `true`/`false` in JSON strings, or Python `True`/`False` in dicts.
> [!NOTE]
> - All properties are optional. If you don't specify a value, the SDK uses the default value.
> - If you specify a value of `null` (or `None` in a dict), the property is explicitly set to `null`, not the default. This distinction is important when you want to override a default behavior.
> - For Boolean values, use JSON Booleans `true`/`false` in JSON strings, or Python `True`/`False` in dicts.

The settings JSON schema is shared across all C2PA SDKs (Rust, C/C++, Python, and so on). For a complete reference to all properties, see the [SDK object reference - Settings](https://opensource.contentauthenticity.org/docs/manifest/json-ref/settings-schema).

Expand Down Expand Up @@ -420,8 +420,8 @@ The following properties default to `true` (verification enabled):

- `verify_after_reading` - Automatically verify manifests when reading assets. Disable only if you want to manually control verification timing.
- `verify_after_sign` - Automatically verify manifests after signing. Recommended to keep enabled to catch signing errors immediately.
- `verify_trust` - Verify signing certificates against configured trust anchors. WARNING: Disabling makes verification non-compliant.
- `verify_timestamp_trust` - Verify timestamp authority (TSA) certificates. WARNING: Disabling makes verification non-compliant.
- `verify_trust` - Verify signing certificates against configured trust anchors. **Warning:** Disabling makes verification non-compliant.
- `verify_timestamp_trust` - Verify timestamp authority (TSA) certificates. **Warning:** Disabling makes verification non-compliant.
- `remote_manifest_fetch` - Fetch remote manifests referenced in the asset. Disable in offline or air-gapped environments.

> [!WARNING]
Expand Down Expand Up @@ -628,9 +628,9 @@ C2PA uses a certificate-based trust model to prove who signed an asset. When cre
- **Certificate chain** (`sign_cert`): An X.509 certificate chain in PEM format. The first certificate identifies the signer; subsequent certificates form a chain up to a trusted root. Verifiers use this chain to confirm the signature comes from a trusted source.
- **Timestamp authority URL** (`ta_url`): An optional [RFC 3161](https://www.rfc-editor.org/rfc/rfc3161) timestamp server URL. When provided, the SDK requests a trusted timestamp during signing, proving _when_ the signature was made. This keeps signatures verifiable even after the signing certificate expires.

### Signer from settings (recommended)
### Signer from settings

Configure signer credentials directly in settings. This is the most common approach:
Configure signer credentials directly in settings. This is the most common and recommended approach:

```py
ctx = Context.from_dict({
Expand All @@ -649,7 +649,7 @@ with open("source.jpg", "rb") as src, open("output.jpg", "w+b") as dst:
builder.sign("image/jpeg", src, dst)
```

### Signer on Context (signer object)
### Signer object on Context

Create a `Signer` object and pass it to the `Context`. The signer is **consumed**: the `Signer` object becomes invalid after this call and the `Context` takes ownership.

Expand Down
6 changes: 3 additions & 3 deletions docs/intents.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ for path in image_paths:
builder.sign_file(path, output_path(path), signer)
```

### Using `set_intent` on the Builder
### Using set_intent on the Builder

Call `set_intent` directly on a `Builder` instance for one-off operations or when the intent is determined at runtime. For example:

Expand Down Expand Up @@ -166,7 +166,7 @@ flowchart LR

For `Edit` and `Update` intents, `Builder` looks at the source stream, and if no `parentOf` ingredient has been added manually, it automatically creates one from that stream (and adds the needed action). The source stream *becomes* the parent ingredient. If a `parentOf` ingredient has already been added manually (via `add_ingredient`), `Builder` uses that one instead and does not automatically create one from the source.

### How intent relates to `add_ingredient`
### How intent relates to add_ingredient

The `Builder` intent controls what the `Builder` does with the source stream (source asset) at sign time. The `add_ingredient` method adds other ingredients explicitly. These are separate concerns.

Expand Down Expand Up @@ -208,7 +208,7 @@ from c2pa import (
)
```

### Using `set_intent`
### Using set_intent

Use the `Builder.set_intent` method to specify the intent:

Expand Down
20 changes: 10 additions & 10 deletions docs/native-resources-management.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Native resource management (ManagedResource class)
# Native resource management

`ManagedResource` is the internal base class used by the C2PA Python SDK to wrap native (Rust/FFI) pointers. When adding new wrappers around native resources `ManagedResource` should be subclassed and follow the documented lifecycle rules.

## Why `ManagedResource`?
## Why ManagedResource?

`ManagedResource` is the internal base class responsible for managing native pointers owned by the C2PA Python SDK. It guarantees:

Expand All @@ -27,7 +27,7 @@ Python manages its own objects' memory automatically through garbage collection.

This system works well for pure Python objects, but native memory sits outside of it entirely. The garbage collector sees the Python wrapper object (e.g. a `Reader` instance) and tracks references to it, but it has no visibility into the native memory that the wrapper's `_handle` attribute points to. Memory allocated by native libraries is invisible to the garbage collector: it does not know the size of that native allocation, cannot tell when it is no longer needed, and will not call the native library's `c2pa_free` function to release it. If the Python wrapper of those native resources is collected without first calling `c2pa_free`, the native memory is never released and leaks.

### Why `__del__` is not reliable enough
### Why __del__ is not reliable enough

Python does offer `__del__` as a hook that runs when an object is collected (finalizer), and `ManagedResource` uses it as a fallback to possibly clean up leftover resources at that point. But `__del__` cannot be relied on as the primary cleanup mechanism: its timing is unpredictable (due to being called when the garbage collection runs, which is non-deterministic itself), it may not run at all during interpreter shutdown, and other Python implementations (PyPy, GraalPy) that do not use reference counting make its behavior even less deterministic.

Expand Down Expand Up @@ -70,7 +70,7 @@ Notes:
> The MRO is computed using C3 linearization, which enforces two rules: children appear before their parents, and left-to-right order from the class definition is preserved. For `class Context(ManagedResource, ContextProvider)`:
>
> 1. `Context`: the class itself always comes first.
> 2. `ManagedResource` :first listed parent, nothing else requires it to appear later.
> 2. `ManagedResource`: first listed parent, nothing else requires it to appear later.
> 3. `ContextProvider`: second listed parent, must come after `ManagedResource` to preserve declaration order.
> 4. `ABC`: parent of `ContextProvider`, must come after its child.
> 5. `object`: root of everything (all objects), always last.
Expand Down Expand Up @@ -134,7 +134,7 @@ Every public method calls `_ensure_valid_state()` before doing any work. Besides

## Ways to clean up

### Context manager (`with` statement)
### Using a context manager

```python
with Reader("image.jpg") as reader:
Expand All @@ -144,7 +144,7 @@ with Reader("image.jpg") as reader:

When the `with` block exits, `__exit__` calls `close()`, which frees the native pointer. This is the safest approach because cleanup happens even if the code inside the block raises an exception.

### Explicit `.close()`
### Explicit close

```python
reader = Reader("image.jpg")
Expand All @@ -156,7 +156,7 @@ finally:

Calling `.close()` directly is equivalent to exiting a `with` block. It is idempotent: calling it multiple times is safe and does nothing after the first call.

### Destructor fallback (`__del__`)
### Destructor fallback

If neither of the above is used, `__del__` attempts to free the native pointer when Python garbage-collects the object. As described above, `__del__` timing is unpredictable and it may not run at all, so it is a safety net rather than a primary cleanup mechanism.

Expand Down Expand Up @@ -269,7 +269,7 @@ self._handle = new_ptr

The object stays `ACTIVE` throughout because the Python-side object is still valid: it has a live native pointer, its public methods still work, and callers may continue using it (e.g. reading the updated manifest or feeding in another fragment). The lifecycle state does not change because from `ManagedResource`'s perspective nothing has closed. Only the underlying native pointer has been swapped. This is different from `_mark_consumed()`, where the object transitions to `CLOSED` and becomes unusable. The old pointer must not be freed by `ManagedResource` because the native library already consumed it as part of the FFI call.

## Subclass-specific cleanup with `_release()`
## Subclass-specific cleanup

Each subclass can override `_release()` to clean up its own resources before the native pointer is freed. The base implementation does nothing.

Expand All @@ -285,15 +285,15 @@ Examples from the codebase:

The cleanup order matters: `_release()` runs first (closing streams, dropping callbacks), then `c2pa_free` frees the native pointer. This order prevents the native library from accessing Python objects that no longer exist.

## Why is `Stream` not a `ManagedResource`?
## Why is Stream not a ManagedResource?

`Stream` wraps a Python stream-like object (file stream or memory stream) so the native library can read from and write to it via callbacks. It does not inherit from `ManagedResource`, and it uses `c2pa_release_stream()` instead of `c2pa_free()` for cleanup.

The reason is that ownership runs in the opposite direction. A `Reader` or `Builder` holds a native resource that Python code calls methods on. A `Stream` holds a native handle that the native library calls *back into* (read, seek, write, flush). The native library needs a different release function to tear down the callback machinery.

`Stream` tracks its own state with `_closed` and `_initialized` flags rather than `LifecycleState`, but it supports the same three cleanup paths: context manager, explicit `.close()`, and `__del__` fallback.

## Implementing a subclass of `ManagedResource`
## Implementing a subclass of ManagedResource

To wrap a new native resource, inherit from `ManagedResource` and follow these rules:

Expand Down
22 changes: 11 additions & 11 deletions docs/project-contributions.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Contributing to the project
# Contributing to the project

The information in this page is primarily for those who wish to contribute to the c2pa-python library project itself, rather than those who simply wish to use it in an application. For general contribution guidelines, see [CONTRIBUTING.md](../CONTRIBUTING.md).
The information in this page is primarily for those who wish to contribute to the c2pa-python library project itself, rather than those who simply wish to use it in an application. For general contribution guidelines, see [CONTRIBUTING.md](../CONTRIBUTING.md).

## Setup

Expand Down Expand Up @@ -28,7 +28,7 @@ pip install -r requirements.txt
pip install -r requirements-dev.txt
```

Download [c2pa-rs library artifacts](https://github.com/contentauth/c2pa-rs/tags) for the current version you want, (for example, as shown below for v0.73.1):
Download [c2pa-rs library artifacts](https://github.com/contentauth/c2pa-rs/tags) for the current version you want (for example, as shown below for v0.73.1):

```bash
python scripts/download_artifacts.py c2pa-v0.73.1
Expand Down Expand Up @@ -58,7 +58,7 @@ pip install -U pytest
python3 -m build --wheel
```

To test local wheels locally, enter this command:
To test the local wheel build, enter this command:

```bash
make test-local-wheel-build
Expand Down Expand Up @@ -94,26 +94,28 @@ make verify-wheel-build

## Testing

The project uses [PyTest](https://docs.pytest.org/) and [unittest](https://docs.python.org/3/library/unittest.html) for testing.
The project uses [pytest](https://docs.pytest.org/) and [unittest](https://docs.python.org/3/library/unittest.html) for testing.

Run tests by following these steps:

1. Activate the virtual environment: `source .venv/bin/activate`
2. (optional) Install dependencies: `pip install -r requirements.txt`
4. Run the tests:
3. Run the tests:
```bash
make test
```
5. Alternatively, install `pytest` (if not already installed) and run it:
4. Alternatively, install `pytest` (if not already installed) and run it:
```bash
pip install pytest
pytest
```
**Warning**: Using `pytest` can lead to issues if you often switch between virtual environments.

> [!WARNING]
> Using `pytest` can lead to issues if you often switch between virtual environments.

### Testing during bindings development

While developing bindings locally, we use [unittest](https://docs.python.org/3/library/unittest.html), since [PyTest](https://docs.pytest.org/) can get confused by virtual environment re-deployments (especially if you bump the version number).
While developing bindings locally, we use [unittest](https://docs.python.org/3/library/unittest.html), since [pytest](https://docs.pytest.org/) can get confused by virtual environment re-deployments (especially if you bump the version number).

To run tests while developing bindings, enter this command:

Expand Down Expand Up @@ -150,5 +152,3 @@ View the output by loading `api-docs/build/html/index.html` in a web browser.
This uses `sphinx-autoapi` to parse `src/c2pa` directly, avoiding imports of native libs.
- Entry script: `scripts/generate_api_docs.py`
- Config: `api-docs/conf.py`; index: `api-docs/index.rst`

Sphinx config is in `api-docs/conf.py`, which uses `index.rst`.
14 changes: 7 additions & 7 deletions docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ See [Release tag 0.6.0](https://github.com/contentauth/c2pa-python/releases/tag/

### Breaking changes

The signature of the `c2pa.sign_ps256()` method changed. Previously, the argument was a file path but now its the PEM certificate string.
The signature of the `c2pa.sign_ps256()` method changed. Previously, the argument was a file path, but now it's the PEM certificate string.

## Version 0.5.2

Expand All @@ -55,23 +55,23 @@ New features in this release:
- You can archive a `Builder` using `builder.to_archive` and reconstruct it with `builder.from_archive`.
- Signers can be constructed with `c2pa_api.create_signer`.
- The signer now requires a signing function to keep private keys private.
- Example signing functions are provided in c2pa_api.py
- Example signing functions are provided in `c2pa_api.py`.

## Version 0.4.0

This release:

- Changes the name of the import from `c2pa-python` to `c2pa`.
- Supports pre-built platform wheels for macOS, Windows and [manylinux](https://github.com/pypa/manylinux).
- Supports pre-built platform wheels for macOS, Windows, and [manylinux](https://github.com/pypa/manylinux).

## Version 0.3.0

This release includes some breaking changes to align with future APIs:

- `C2paSignerInfo` moves the `alg` to the first parameter from the 3rd.
- `C2paSignerInfo` moves the `alg` to the first parameter from the third.
- `c2pa.verify_from_file_json` is now `c2pa.read_file`.
- `c2pa.ingredient_from_file_json` is now `c2pa.read_ingredient_file`.
- `c2pa.add_manifest_to_file_json` is now `c2pa.sign_file`.
- There are many more specific errors types now, and Error messages always start with the name of the error i.e (str(err.value).startswith("ManifestNotFound")).
- The ingredient thumbnail identifier may be jumbf uri reference if a valid thumb already exists in the active manifest.
- Extracted file paths for read_file now use a folder structure and different naming conventions.
- There are many more specific error types now, and error messages always start with the name of the error, i.e., `str(err.value).startswith("ManifestNotFound")`.
- The ingredient thumbnail identifier may be a JUMBF URI reference if a valid thumb already exists in the active manifest.
- Extracted file paths for `read_file` now use a folder structure and different naming conventions.
Loading
Loading