Skip to content
Open
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
8 changes: 4 additions & 4 deletions docs/context-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The `Context` class encapsulates configuration for:
- **Simplifies testing**: Create isolated configurations for tests without worrying about cleanup or interference.

> [!NOTE]
> The deprecated `load_settings()` function still works for backward compatibility, but you are encouraged to migrate your code to use `Context`. See [Migrating from load_settings](#migrating-from-load_settings).
> Without a `Context`, `Reader` and `Builder` use one with SDK default settings. Pass a `Context` for custom settings or a context signer. The deprecated `load_settings()` function no longer affects `Reader` or `Builder`. See [Migrating from load_settings](#migrating-from-load_settings).

## Quick start

Expand Down Expand Up @@ -235,7 +235,7 @@ Archives (`.c2pa` files) store only the manifest definition. They do **not** sto
- **`Builder.from_archive(stream)`** creates a context-free builder. All settings revert to SDK defaults regardless of what context the original builder had.
- **`Builder({}, ctx).with_archive(stream)`** creates a builder with a context first, then loads the archived manifest definition into it. The context settings are preserved.

Use `with_archive()` when your workflow depends on specific settings (thumbnails, claim generator, intent, and so on). Use `from_archive()` only for quick prototyping where SDK defaults are acceptable.
Use `with_archive()`. `from_archive()` is deprecated and will be removed when c2pa-python moves to c2pa-rs 0.92.0.

```py
ctx = Context.from_dict({
Expand Down Expand Up @@ -754,7 +754,7 @@ assert isinstance(ctx, ContextProvider) # True

## Migrating from load_settings

The `load_settings()` function is deprecated. Replace it with `Settings` and `Context` APIs:
The `load_settings()` function is deprecated and will be removed when c2pa-python moves to c2pa-rs 0.92.0. Replace it with `Settings` and `Context` APIs:

| Aspect | `load_settings` (legacy) | `Context` |
|--------|--------------------------|-----------|
Expand All @@ -768,7 +768,7 @@ The `load_settings()` function is deprecated. Replace it with `Settings` and `Co
from c2pa import load_settings, Reader

load_settings({"builder": {"thumbnail": {"enabled": False}}})
reader = Reader("image.jpg") # uses global settings
reader = Reader("image.jpg") # no longer sees thread local settings
```

**Current approach:**
Expand Down
11 changes: 3 additions & 8 deletions docs/intents.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ You can set the intent on a `Builder` instance by:
- [Using Context](#using-context)
- [Using `set_intent` on the `Builder`](#using-set_intent-on-the-builder)

Don't set the intent using the deprecated `load_settings()` function. For existing code, see [Context and settings - Migrating from load_settings](context-settings.md#migrating-from-load_settings).
The deprecated `load_settings()` function no longer sets the intent for a `Builder`. To migrate existing code, see [Context and settings - Migrating from load_settings](context-settings.md#migrating-from-load_settings).

### Using Context

Expand Down Expand Up @@ -114,11 +114,7 @@ flowchart TD
Check --> |No| CheckCtx{Was a Context with
builder.intent provided?}
CheckCtx --> |Yes| UseCtx["Use Context intent"]
CheckCtx --> |No| CheckGlobal{Was load_settings called
with builder.intent?}
CheckGlobal --> |Yes| UseGlobal["Use global intent
(deprecated)"]
CheckGlobal --> |No| NoIntent["No intent set.
CheckCtx --> |No| NoIntent["No intent set.
Caller must define actions
manually in manifest JSON."]
```
Expand Down Expand Up @@ -173,8 +169,7 @@ The `Builder` intent controls what the `Builder` does with the source stream (so
```mermaid
flowchart TD
Intent["Intent
(via Context, set_intent,
or load_settings)"] --> Q{Intent type?}
(via Context or set_intent)"] --> Q{Intent type?}
Q --> |CREATE| CreateFlow["No parent allowed
Source stream is new content"]
Q --> |EDIT or UPDATE| EditFlow{Was a parentOf ingredient
Expand Down
24 changes: 12 additions & 12 deletions docs/selective-manifests.md
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ There are two distinct types of archives, sharing the same binary format but bei

### Builder archives vs. ingredient archives

A **builder archive** (also called a working store archive) is a serialized snapshot of a `Builder`. It contains the manifest definition, all resources, and any ingredients that were added. It is created by `builder.to_archive()` and restored with `Builder.from_archive()` to create a new builder instance from an archive, or `builder.with_archive()` to load a working store from a builder archive into an existing builder instance.
A **builder archive** (also called a working store archive) is a serialized snapshot of a `Builder`. It contains the manifest definition, all resources, and any ingredients that were added. It is created by `builder.to_archive()` and restored with `builder.with_archive()`, which loads a working store from a builder archive into an existing builder instance. `Builder.from_archive()` also creates a new builder instance from an archive, but it is deprecated.

An **ingredient archive** contains the manifest store from an asset that was added as an ingredient.

Expand Down Expand Up @@ -576,7 +576,7 @@ Producer side, build the catalog:
import io
from c2pa import Builder

catalog_builder = Builder.from_json(manifest_json)
catalog_builder = Builder.from_json(manifest_json, context=ctx)
with open("photo-A.jpg", "rb") as f:
catalog_builder.add_ingredient(
{"title": "photo-A.jpg", "relationship": "componentOf", "instance_id": "catalog:ingredient-A"},
Expand All @@ -603,7 +603,7 @@ catalog_builder.write_ingredient_archive("catalog:ingredient-C", archive_c)
Consumer side, pick one archive and load it:

```py
final_builder = Builder.from_json(manifest_json)
final_builder = Builder.from_json(manifest_json, context=ctx)
archive_b.seek(0)
final_builder.add_ingredient_from_archive(archive_b)

Expand Down Expand Up @@ -631,7 +631,7 @@ signing_manifest = {
}]
}

signing_builder = Builder.from_json(signing_manifest)
signing_builder = Builder.from_json(signing_manifest, context=ctx)
for archive in (archive_a, archive_b, archive_c):
archive.seek(0)
signing_builder.add_ingredient_from_archive(archive)
Expand All @@ -654,7 +654,7 @@ Switch to the dedicated ingredient archive APIs: set `instance_id` per ingredien
Producer side:

```py
catalog_builder = Builder.from_json(manifest_json)
catalog_builder = Builder.from_json(manifest_json, context=ctx)
with open("photo-A.jpg", "rb") as f:
catalog_builder.add_ingredient(
{"title": "photo-A.jpg", "relationship": "componentOf", "instance_id": "catalog:ingredient-A"},
Expand All @@ -674,7 +674,7 @@ catalog_builder.write_ingredient_archive("catalog:ingredient-B", archive_b)
Consumer side:

```py
final_builder = Builder.from_json(manifest_json)
final_builder = Builder.from_json(manifest_json, context=ctx)
archive_b.seek(0)
final_builder.add_ingredient_from_archive(archive_b)
with open("source.jpg", "rb") as src, open("output.jpg", "w+b") as dst:
Expand All @@ -695,7 +695,7 @@ When building an ingredient archive, you can set `instance_id` on the ingredient

```py
# Set instance_id when adding the ingredient to the archive builder.
builder = Builder.from_json(manifest_json)
builder = Builder.from_json(manifest_json, context=ctx)
with open("photo-A.jpg", "rb") as f:
builder.add_ingredient(
{
Expand All @@ -715,7 +715,7 @@ Later, when reading the archive, select ingredients by their `instance_id`:

```py
archive.seek(0)
reader = Reader("application/c2pa", archive)
reader = Reader("application/c2pa", archive, context=ctx)
manifest_data = json.loads(reader.json())
active = manifest_data["active_manifest"]
ingredients = manifest_data["manifests"][active]["ingredients"]
Expand Down Expand Up @@ -860,7 +860,7 @@ with Builder({
```

> [!NOTE]
> When restoring from an archive, `with_archive()` preserves context settings while `from_archive()` does not. See [Working with archives](working-stores.md#working-with-archives) for the full comparison.
> When restoring from an archive, `with_archive()` preserves context settings. The deprecated `from_archive()` creates a builder with SDK default settings. See [Working with archives](working-stores.md#working-with-archives) for the full comparison.

**Step 2:** Read the archive and extract ingredients:

Expand Down Expand Up @@ -1129,7 +1129,7 @@ Use the same approach with format `"application/c2pa"` and an archive stream:

```py
with open("builder_archive.c2pa", "rb") as archive_file:
reader = Reader("application/c2pa", archive_file)
reader = Reader("application/c2pa", archive_file, context=ctx)
# Then parse and iterate assertions as in the example above
```

Expand Down Expand Up @@ -1232,7 +1232,7 @@ new_manifest = {"claim_generator_info": [{"name": "an-application", "version": "
if kept_actions:
new_manifest["assertions"] = [{"label": "c2pa.actions", "data": {"actions": kept_actions}}]

builder = Builder.from_json(new_manifest)
builder = Builder.from_json(new_manifest, context=ctx)
with open("source.jpg", "rb") as src, open("output.jpg", "w+b") as dst:
builder.sign(signer, "image/jpeg", src, dst)
```
Expand Down Expand Up @@ -1300,7 +1300,7 @@ with open("source.jpg", "rb") as f:
if kept_actions:
new_manifest["assertions"] = [{"label": "c2pa.actions", "data": {"actions": kept_actions}}]

builder = Builder.from_json(new_manifest)
builder = Builder.from_json(new_manifest, context=ctx)

# Transfer binary resources for kept ingredients
for ingredient in kept_ingredients:
Expand Down
2 changes: 1 addition & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ manifest_json = json.dumps({

## Settings, Context, and ContextProvider

The `Settings` and `Context` classes provide per-instance configuration for `Reader` and `Builder` operations, replacing the global `load_settings()` function, which is now deprecated.
The `Settings` and `Context` classes provide per-instance configuration for `Reader` and `Builder` operations, replacing the global `load_settings()` function, which is now deprecated. Without a `Context`, `Reader` and `Builder` use one with SDK default settings.

See [Context and settings](context-settings.md) for details.

Expand Down
36 changes: 18 additions & 18 deletions docs/working-stores.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ For full details on `Context` and `Settings`, see [Context and settings](context
### Checking if the manifest store is embedded

```py
reader = Reader("signed_image.jpg")
reader = Reader("signed_image.jpg", context=ctx)

if reader.is_embedded():
print("Manifest store is embedded in the asset")
Expand Down Expand Up @@ -515,7 +515,7 @@ import io, json
archive_builder = Builder.from_json({
"claim_generator_info": [{"name": "an-application", "version": "0.1.0"}],
"assertions": [],
})
}, context=ctx)
with open("photo.jpg", "rb") as f:
archive_builder.add_ingredient(
{"title": "photo.jpg", "relationship": "componentOf"},
Expand Down Expand Up @@ -842,7 +842,7 @@ manifest = {
"claim_generator_info": [{"name": "c2pa-test", "version": "0.1.0"}],
"assertions": [],
}
builder = Builder.from_json(manifest)
builder = Builder.from_json(manifest, context=ctx)

# Register three ingredients. The `label` becomes each ingredient's id.
with open("first.jpg", "rb") as f:
Expand All @@ -865,7 +865,7 @@ The archive contains exactly one ingredient.
import io
from c2pa import Builder

consumer = Builder.from_json(manifest)
consumer = Builder.from_json(manifest, context=ctx)

# `archive` is a stream produced by write_ingredient_archive on another builder.
archive.seek(0)
Expand All @@ -888,7 +888,7 @@ with open("source.jpg", "rb") as src, open("output.jpg", "w+b") as dst:
- The id does not match any registered ingredient's `label` or `instance_id`.

```py
builder = Builder.from_json(manifest)
builder = Builder.from_json(manifest, context=ctx)
with open("photo.jpg", "rb") as f:
builder.add_ingredient({"title": "photo.jpg", "relationship": "componentOf", "label": "real-id"}, "image/jpeg", f)

Expand All @@ -900,7 +900,7 @@ builder.write_ingredient_archive("wrong-id", archive)
To correct, pass the same string used in the ingredient's `label` or `instance_id` field:

```py
builder = Builder.from_json(manifest)
builder = Builder.from_json(manifest, context=ctx)
with open("photo.jpg", "rb") as f:
builder.add_ingredient({"title": "photo.jpg", "relationship": "componentOf", "label": "ingredient-id"}, "image/jpeg", f)

Expand All @@ -917,15 +917,15 @@ The legacy approach wrapped one ingredient in a full builder archive, then resto

```py
# Legacy: one ingredient archived as a full builder, restored with with_archive
builder = Builder.from_json(manifest)
builder = Builder.from_json(manifest, context=ctx)
with open("photo.jpg", "rb") as f:
builder.add_ingredient({"title": "photo.jpg", "relationship": "componentOf"}, "image/jpeg", f)
archive = io.BytesIO()
builder.to_archive(archive)

# Consumer:
archive.seek(0)
restored = Builder({})
restored = Builder({}, context=ctx)
restored.with_archive(archive)
with open("source.jpg", "rb") as src, open("output.jpg", "w+b") as dst:
restored.sign(signer, "image/jpeg", src, dst)
Expand All @@ -935,15 +935,15 @@ With the dedicated ingredient archive APIs, the producer writes a single-ingredi

```py
# Current API: one archive per ingredient via write_ingredient_archive
builder = Builder.from_json(manifest)
builder = Builder.from_json(manifest, context=ctx)
with open("photo.jpg", "rb") as f:
builder.add_ingredient({"title": "photo.jpg", "relationship": "componentOf", "instance_id": "my-photo"}, "image/jpeg", f)

archive = io.BytesIO()
builder.write_ingredient_archive("my-photo", archive)

# Consumer:
consumer = Builder.from_json(manifest)
consumer = Builder.from_json(manifest, context=ctx)
archive.seek(0)
consumer.add_ingredient_from_archive(archive)
with open("source.jpg", "rb") as src, open("output.jpg", "w+b") as dst:
Expand Down Expand Up @@ -1003,7 +1003,7 @@ Producer:
import io
from c2pa import Builder

producer = Builder.from_json(manifest_str)
producer = Builder.from_json(manifest_str, context=ctx)
with open(source_path, "rb") as f:
producer.add_ingredient(
{"title": "photo.jpg", "relationship": "componentOf", "instance_id": "catalog:photo-A"},
Expand All @@ -1026,7 +1026,7 @@ signing_manifest = {
}]
}

consumer = Builder.from_json(signing_manifest)
consumer = Builder.from_json(signing_manifest, context=ctx)
archive.seek(0)
consumer.add_ingredient_from_archive(archive)
with open(source_path, "rb") as src, open(output_path, "w+b") as dst:
Expand All @@ -1042,7 +1042,7 @@ This works even though signing does not preserve `label` as an ingredient field.
Producer:

```py
producer = Builder.from_json(manifest_str)
producer = Builder.from_json(manifest_str, context=ctx)
with open(source_path, "rb") as f:
producer.add_ingredient(
{"title": "photo.jpg", "relationship": "componentOf", "label": "my-photo"},
Expand All @@ -1065,7 +1065,7 @@ signing_manifest = {
}]
}

consumer = Builder.from_json(signing_manifest)
consumer = Builder.from_json(signing_manifest, context=ctx)
archive.seek(0)
consumer.add_ingredient_from_archive(archive)
with open(source_path, "rb") as src, open(output_path, "w+b") as dst:
Expand All @@ -1079,7 +1079,7 @@ If both `label` and `instance_id` are set on an ingredient, pass whichever value
Producer (passing `label` as the key):

```py
producer = Builder.from_json(manifest_str)
producer = Builder.from_json(manifest_str, context=ctx)
with open(source_path, "rb") as f:
producer.add_ingredient(
{"title": "photo.jpg", "relationship": "componentOf", "label": "my-photo", "instance_id": "iid:abc123"},
Expand All @@ -1106,7 +1106,7 @@ signing_manifest = {
}]
}

consumer = Builder.from_json(signing_manifest)
consumer = Builder.from_json(signing_manifest, context=ctx)
archive.seek(0)
consumer.add_ingredient_from_archive(archive)
with open(source_path, "rb") as src, open(output_path, "w+b") as dst:
Expand All @@ -1121,7 +1121,7 @@ With the legacy `to_archive` + `Reader` pattern, `instance_id` survives into the
import json
from c2pa import Reader

reader = Reader("archive.c2pa")
reader = Reader("archive.c2pa", context=ctx)
parsed = json.loads(reader.json())
active = parsed["active_manifest"]
ingredients = parsed["manifests"][active].get("ingredients", [])
Expand Down Expand Up @@ -1194,7 +1194,7 @@ with open("source.jpg", "rb") as src, open("output.jpg", "w+b") as dst:
### Checking manifest store location

```py
reader = Reader("asset.jpg")
reader = Reader("asset.jpg", context=ctx)

if reader.is_embedded():
print("Manifest store is embedded in the asset")
Expand Down
Loading
Loading