You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/native-resources-management.md
+12-7Lines changed: 12 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -54,14 +54,13 @@ classDiagram
54
54
ManagedResource <|-- Signer
55
55
56
56
ContextProvider <|-- Context
57
-
ContextProvider <|-- Settings
58
57
```
59
58
60
-
`Context`and `Settings` inherit from both `ManagedResource` and `ContextProvider` (Python supports multiple inheritance). `ContextProvider` is an ABC that requires two properties: `is_valid` and `execution_context`. The `is_valid` implementation lives on `ManagedResource`, so `Context`and `Settings` satisfy the `ContextProvider` contract without duplicating the property.
59
+
`Context`inherits from both `ManagedResource` and `ContextProvider` (Python supports multiple inheritance). `Settings` inherits from `ManagedResource` only. `ContextProvider` is an ABC that requires two properties: `is_valid` and `execution_context`. The `is_valid` implementation lives on `ManagedResource`, so `Context`satisfies that part of the `ContextProvider` contract without duplicating the property.
61
60
62
61
## Guarantees provided by ManagedResource
63
62
64
-
`ManagedResource` provides the following guarantees. Subclasses and callers can rely on them. These guarantees invariants must be maintained when subclassing the `ManagedResource` class in new implementation/new native resources handlers.
63
+
`ManagedResource` provides the following guarantees. Subclasses and callers can rely on them. These invariants must be maintained when subclassing the `ManagedResource` class in new implementation/new native resources handlers.
65
64
66
65
| Guarantee | Description |
67
66
| --------- | ----------- |
@@ -280,7 +279,7 @@ The reason is that ownership runs in the opposite direction. A `Reader` or `Buil
280
279
To wrap a new native resource, inherit from `ManagedResource` and follow these rules:
281
280
282
281
```python
283
-
classMyResource(ManagedResource):
282
+
classNativeResource(ManagedResource):
284
283
def__init__(self, arg):
285
284
super().__init__()
286
285
@@ -304,8 +303,14 @@ class MyResource(ManagedResource):
304
303
305
304
def_release(self):
306
305
# 4. Clean up class-specific resources.
307
-
# Never let this method raise. Use try/except with
308
-
# logging if needed.
306
+
# Never let this method raise. Must be idempotent.
307
+
#
308
+
# Consider defining a simple lifecycle for native resources
309
+
# so _release() can check whether they are releasable
310
+
# before attempting cleanup. The if-guard below
311
+
# verifies the stream exists and has not
312
+
# already been released. The try/except is a fallback
313
+
# that silences unexpected errors from .close().
309
314
ifself._my_stream:
310
315
try:
311
316
self._my_stream.close()
@@ -327,7 +332,7 @@ class MyResource(ManagedResource):
327
332
328
333
- If `_lifecycle_state = ACTIVE` is set before the FFI call and the call fails, cleanup will try to free a null or invalid pointer. Activation should happen only after a valid handle exists.
329
334
330
-
- If `_release()` raises, the exception is silently swallowed by `_cleanup_resources()`. It will not be visible unless logs are checked. Wrap risky operations in try/except.
335
+
- If `_release()` raises, the exception is silently swallowed by `_cleanup_resources()`. It will not be visible unless logs are checked. Define a lifecycle for managed resources so `_release()` can check whether they need releasing. Wrap the actual release call in try/except as a fallback for unexpected failures.
331
336
332
337
-`_release()` can be called more than once (via `close()` then `__del__`, or multiple `close()` calls). Make sure it handles being called on an already-cleaned-up object. Setting attributes to `None` after closing them is the standard pattern.
You can call `with_settings()` multiple times. This is useful when different code paths each need to configure settings before the context is built. Each call replaces the previous `Settings` object entirely (the last one wins):
322
+
323
+
```py
324
+
# Only settings_b is used, settings_a is replaced
325
+
ctx = (
326
+
Context.builder()
327
+
.with_settings(settings_a)
328
+
.with_settings(settings_b)
329
+
.build()
330
+
)
331
+
```
332
+
333
+
To merge multiple configurations into one, use `Settings.update()` on a single `Settings` object, and then pass the built Settings object to the context:
When a `Signer` is passed to `Context`, the `Signer` object is consumed and must not be reused directly. The `Context` takes ownership of the underlying native signer. This allows signing without passing an explicit signer to `Builder.sign()`.
`ContextProvider` is an abstract base class (ABC) that allows third-party implementations of custom context providers. Any class that implements the `is_valid` and `execution_context` properties satisfies the interface and can be passed to `Reader` or `Builder` as `context`.
377
+
`ContextProvider` is an abstract base class (ABC) that defines the interface `Reader` and `Builder` use to access a context. It requires two properties:
378
+
379
+
-`is_valid` (bool): Whether the provider is in a usable state. `Reader` and `Builder` check this before every operation.
380
+
-`execution_context`: The raw native context pointer (`C2paContext` handle). `Reader` and `Builder` pass this to the native library for FFI calls.
381
+
382
+
The built-in `Context` class is the standard implementation to provide context:
357
383
358
384
```py
359
385
from c2pa import ContextProvider, Context
360
386
361
-
# The built-in Context satisfies ContextProvider
362
387
ctx = Context()
363
388
assertisinstance(ctx, ContextProvider)
364
389
```
365
390
391
+
Any class can become a `ContextProvider` by inheriting from `ContextProvider` and implementing both properties. The two properties can live on any object through multiple inheritance, but a dedicated context class (as done in the SDK with `Context`) is preferred because it handles native memory management, lifecycle states, and signer ownership.
392
+
393
+
In practice, `execution_context` must return a pointer that the native C2PA library understands, so custom providers will likely wrap a compatible native resource, rather than constructing native pointers independently:
394
+
395
+
```py
396
+
from c2pa import ContextProvider, Context, Settings
397
+
398
+
classMyContextProvider(ContextProvider):
399
+
"""Custom provider that wraps a Context with application-specific logic."""
`Settings` is not a `ContextProvider`. It inherits from `ManagedResource` only and cannot be passed directly as the `context` parameter to `Reader` or `Builder`.
417
+
366
418
### Migrating from load_settings
367
419
368
420
The `load_settings()` function that set settings in a thread-local fashion is deprecated.
0 commit comments