diff --git a/c2pa-native-version.txt b/c2pa-native-version.txt index 4b9d645b..5b6314c1 100644 --- a/c2pa-native-version.txt +++ b/c2pa-native-version.txt @@ -1 +1 @@ -c2pa-v0.91.10 +c2pa-v0.92.0-rc.1 diff --git a/docs/class-diagram.md b/docs/class-diagram.md index 33cfd308..cdc98045 100644 --- a/docs/class-diagram.md +++ b/docs/class-diagram.md @@ -46,7 +46,6 @@ classDiagram class Builder { +from_json(manifest_json, context) Builder$ - +from_archive(stream) Builder$ +get_supported_mime_types() list~str~$ +set_no_embed() +set_remote_url(url) diff --git a/docs/context-settings.md b/docs/context-settings.md index 6010adba..5f6c9e69 100644 --- a/docs/context-settings.md +++ b/docs/context-settings.md @@ -232,10 +232,9 @@ with open("source.jpg", "rb") as src, open("output.jpg", "w+b") as dst: Archives (`.c2pa` files) store only the manifest definition. They do **not** store settings or context: -- **`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()`. `from_archive()` is deprecated and will be removed when c2pa-python moves to c2pa-rs 0.92.0. +Use `with_archive()`. `Builder.from_archive()` was removed in c2pa-rs 0.92.0. ```py ctx = Context.from_dict({ @@ -754,7 +753,7 @@ assert isinstance(ctx, ContextProvider) # True ## Migrating from load_settings -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: +The `load_settings()` function was removed in c2pa-rs 0.92.0. Replace it with `Settings` and `Context` APIs: | Aspect | `load_settings` (legacy) | `Context` | |--------|--------------------------|-----------| diff --git a/docs/intents.md b/docs/intents.md index 79edd7a6..5732bf69 100644 --- a/docs/intents.md +++ b/docs/intents.md @@ -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) -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). +The removed `load_settings()` function no longer set 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 diff --git a/docs/project-contributions.md b/docs/project-contributions.md index 45613332..7046513d 100644 --- a/docs/project-contributions.md +++ b/docs/project-contributions.md @@ -35,7 +35,7 @@ make download-native-artifacts ``` Without `make`, pass the tag from `c2pa-native-version.txt` directly to -`python scripts/download_artifacts.py` (currently `c2pa-v0.91.7`). +`python scripts/download_artifacts.py` (currently `c2pa-v0.92.0-rc.1`). Install the package in development mode: diff --git a/docs/release-notes.md b/docs/release-notes.md index 1382f915..bf19e1df 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -4,7 +4,7 @@ ### TLS trust stores -Release builds select c2pa-rs `c2pa-v0.91.7` through +Release builds select c2pa-rs `c2pa-v0.92.0-rc.1` through `c2pa-native-version.txt`. Rebuild or upgrade installed wheels to update the bundled native library; updating Python dependencies alone does not update it. Local source builds and externally supplied libraries must also use patched @@ -39,7 +39,7 @@ Migration: - Instead of `Builder.add_ingredient_from_file_path(json, format, filepath)`, open the file and call `Builder.add_ingredient_from_stream(json, format, stream)`. -The `Builder.sign_file` method and `load_settings` are unaffected and remain available. +The `Builder.sign_file` method is unaffected and remains available. (`load_settings`, `Builder.from_archive` and `format_embeddable` were later removed with c2pa-rs 0.92.0.) ## Version 0.6.0 diff --git a/docs/selective-manifests.md b/docs/selective-manifests.md index 5830c8ed..de530f00 100644 --- a/docs/selective-manifests.md +++ b/docs/selective-manifests.md @@ -860,7 +860,7 @@ with Builder({ ``` > [!NOTE] -> 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. +> When restoring from an archive, `with_archive()` preserves context settings (`from_archive()` was removed in c2pa-rs 0.92.0). See [Working with archives](working-stores.md#working-with-archives) for the full comparison. **Step 2:** Read the archive and extract ingredients: diff --git a/docs/usage.md b/docs/usage.md index 524b6765..07eecb3b 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -144,7 +144,7 @@ The built-in `Context` class is the standard `ContextProvider` implementation. C ### Migrating from load_settings -The `load_settings()` function is deprecated. Replace it with `Settings` and `Context`. See [Migrating from load_settings](context-settings.md#migrating-from-load_settings) for details. +The `load_settings()` function was removed in c2pa-rs 0.92.0. Replace it with `Settings` and `Context`. See [Migrating from load_settings](context-settings.md#migrating-from-load_settings) for details. ## File-based operation diff --git a/src/c2pa/__init__.py b/src/c2pa/__init__.py index bd581d58..50e38dce 100644 --- a/src/c2pa/__init__.py +++ b/src/c2pa/__init__.py @@ -31,8 +31,7 @@ Context, ContextBuilder, ContextProvider, - sdk_version, - load_settings + sdk_version ) # NOQA # Re-export C2paError and its subclasses @@ -50,6 +49,5 @@ 'Context', 'ContextBuilder', 'ContextProvider', - 'sdk_version', - 'load_settings' + 'sdk_version' ] diff --git a/src/c2pa/c2pa.py b/src/c2pa/c2pa.py index a9090cc1..fc2ce1dc 100644 --- a/src/c2pa/c2pa.py +++ b/src/c2pa/c2pa.py @@ -42,10 +42,6 @@ 'c2pa_version', # Error retriever and parser 'c2pa_error', - # Legacy APIs, deprecated. - 'c2pa_load_settings', - 'c2pa_builder_from_archive', - 'c2pa_format_embeddable', # Stream 'c2pa_create_stream', 'c2pa_release_stream', @@ -1305,9 +1301,6 @@ def _setup_function(func, argtypes, restype=None): # Set up function prototypes not attached to an API object _setup_function(_lib.c2pa_version, [], ctypes.c_void_p) _setup_function(_lib.c2pa_error, [], ctypes.c_void_p) -_setup_function( - _lib.c2pa_load_settings, [ - ctypes.c_char_p, ctypes.c_char_p], ctypes.c_int) _setup_function( _lib.c2pa_free_string_array, [ctypes.POINTER(ctypes.c_char_p), ctypes.c_size_t], @@ -1343,9 +1336,6 @@ def _setup_function(func, argtypes, restype=None): ) # Set up Builder function prototypes -_setup_function(_lib.c2pa_builder_from_archive, - [ctypes.POINTER(C2paStream)], - ctypes.POINTER(C2paBuilder)) _setup_function( _lib.c2pa_builder_with_archive, [ctypes.POINTER(C2paBuilder), ctypes.POINTER(C2paStream)], @@ -1390,12 +1380,6 @@ def _setup_function(func, argtypes, restype=None): ctypes.POINTER(C2paSigner), ctypes.POINTER(ctypes.POINTER(ctypes.c_ubyte))], ctypes.c_int64) -_setup_function( - _lib.c2pa_format_embeddable, [ - ctypes.c_char_p, ctypes.POINTER( - ctypes.c_ubyte), ctypes.c_size_t, ctypes.POINTER( - ctypes.POINTER( - ctypes.c_ubyte))], ctypes.c_int64) _setup_function(_lib.c2pa_signer_create, [ctypes.c_void_p, SignerCallback, @@ -1956,72 +1940,6 @@ def _warn_deprecated_api(message: str, stacklevel: int) -> None: ) -@overload -def load_settings(settings: str, format: str = "json") -> None: - ... - - -@overload -def load_settings(settings: dict) -> None: - ... - - -def load_settings(settings: Union[str, dict], format: str = "json") -> None: - """Load C2PA settings into thread-local storage from a string or dict. - - .. deprecated:: - Use :class:`Settings` and :class:`Context` for - per-instance configuration instead. Settings and - Context will propagate configurations through object instances, - no thread-local configurations. Avoid mixing Context APIs - and legacy load_settings usage. - c2pa-rs 0.92.0 removes c2pa_load_settings, so this function - will be removed when c2pa-python moves to that release. - Reader and Builder no longer read these settings: without a - Context they use one with default settings. Only the deprecated - Builder.from_archive() still reads them. - - Args: - settings: The settings string or dict to load - format: The format of the settings string (default: "json"). - Ignored when settings is a dict. - - Raises: - C2paError: If there was an error loading the settings - """ - _warn_deprecated_api( - "load_settings() is deprecated. Reader and Builder no longer read" - " these settings. Use Settings and Context for per-instance" - " configuration.", - stacklevel=2, - ) - - # Convert to JSON string as necessary - try: - if isinstance(settings, dict): - settings_str = json.dumps(settings) - format = "json" - else: - settings_str = settings - except (TypeError, ValueError) as e: - raise C2paError(f"Failed to serialize settings to JSON: {e}") - - try: - _check_cstr_arg("settings", settings_str) - _check_cstr_arg("format", format) - settings_bytes = settings_str.encode('utf-8') - format_bytes = format.encode('utf-8') - except (AttributeError, UnicodeEncodeError) as e: - raise C2paError(f"Failed to encode settings to UTF-8: {e}") - - with _native_section(): - result = _lib.c2pa_load_settings(settings_bytes, format_bytes) - _check_ffi_operation_result( - result, - "Error loading settings", - check=lambda r: r != 0) - - @contextlib.contextmanager def _context_guard(context): """Hold a caller-supplied context valid across a native call. @@ -3960,57 +3878,6 @@ def from_json( """ return cls(manifest_json, context=context) - @classmethod - def from_archive( - cls, - stream: Any, - ) -> 'Builder': - """Create a new Builder from an archive stream. - - This creates builder without a context. To use a context, - create a Builder with a context first, then call with_archive() on it. - - .. deprecated:: - Use ``Builder({}, context=Context()).with_archive(stream)``. - c2pa-rs 0.92.0 removes c2pa_builder_from_archive, so this method - will be removed when c2pa-python moves to that release. - - Args: - stream: The stream containing the archive - (any Python stream-like object) - - Returns: - A new Builder instance (without any context) - - Raises: - C2paError: If there was an error creating the builder - from archive - """ - _warn_deprecated_api( - "Builder.from_archive() is deprecated. Use" - " Builder({}, context=Context()).with_archive(stream).", - stacklevel=2, - ) - - stream_obj = Stream(stream) - - try: - with _native_section(): - handle = _lib.c2pa_builder_from_archive(stream_obj._stream) - - _check_ffi_operation_result( - handle, "Failed to create builder from archive") - - try: - # A builder from an archive here carries no context. - return cls._wrap_native_handle(handle) - except Exception: - # No instance took ownership, so the handle is still ours. - ManagedResource._free_native_ptr(handle) - raise - finally: - stream_obj.close() - @overload def __init__( self, @@ -4661,62 +4528,6 @@ def sign_file( raise C2paError(f"Error signing file: {str(e)}") from e -def format_embeddable(format: str, manifest_bytes: bytes) -> tuple[int, bytes]: - """Convert a binary C2PA manifest into an embeddable version. - - .. deprecated:: - c2pa-rs 0.92.0 removes c2pa_format_embeddable, so this function - will be removed when c2pa-python moves to that release. - - Args: - format: The MIME type or extension of the target format - manifest_bytes: The raw manifest bytes - - Returns: - A tuple of (size of result bytes, embeddable manifest bytes) - - Raises: - C2paError: If there was an error converting the manifest - """ - _warn_deprecated_api( - "format_embeddable() is deprecated.", stacklevel=2) - - _check_cstr_arg("format", format) - format_str = format.encode('utf-8') - manifest_array = (ctypes.c_ubyte * len(manifest_bytes)).from_buffer_copy( - manifest_bytes - ) - result_bytes_ptr = ctypes.POINTER(ctypes.c_ubyte)() - - with _native_section(): - result = _lib.c2pa_format_embeddable( - format_str, - manifest_array, - len(manifest_bytes), - ctypes.byref(result_bytes_ptr) - ) - - _check_ffi_operation_result( - result, - "Failed to format embeddable manifest", - check=lambda r: r < 0) - - size = result - if not result_bytes_ptr: - raise C2paError( - "Failed to format embeddable manifest: no data returned") - try: - result_bytes = ctypes.string_at(result_bytes_ptr, size) - except Exception as e: - raise C2paError( - f"Failed to convert embeddable manifest bytes: {e}" - ) from e - finally: - _lib.c2pa_free(result_bytes_ptr) - - return size, result_bytes - - def create_signer( callback: Callable[[bytes], bytes], alg: C2paSigningAlg, @@ -4864,8 +4675,6 @@ def ed25519_sign(data: bytes, private_key: str) -> bytes: 'Reader', 'Builder', 'Signer', - 'load_settings', - 'format_embeddable', 'version', 'sdk_version' ] diff --git a/tests/perf/baseline.json b/tests/perf/baseline.json index 5d7017ee..d7dabe2f 100644 --- a/tests/perf/baseline.json +++ b/tests/perf/baseline.json @@ -2,7 +2,7 @@ "_meta": { "memray_version": "1.19.3", "python_version": "3.12.13", - "c2pa_native_version": "c2pa-v0.90.16", + "c2pa_native_version": "c2pa-v0.92.0-rc.1", "iterations": 200, "perf_env": "python-3.12-slim", "arch": "aarch64" @@ -142,11 +142,6 @@ "leaked_bytes": 3545507, "total_allocations": 3432412 }, - "builder_from_archive_roundtrip": { - "peak_bytes": 14353258, - "leaked_bytes": 3542427, - "total_allocations": 3024501 - }, "builder_with_archive_swap": { "peak_bytes": 3753525, "leaked_bytes": 3421374, @@ -282,11 +277,6 @@ "leaked_bytes": 3525035, "total_allocations": 37359891 }, - "fork_contended_mutex_wrap": { - "peak_bytes": 7140341, - "leaked_bytes": 3522965, - "total_allocations": 34204380 - }, "fork_consumed_signer": { "peak_bytes": 3421645, "leaked_bytes": 3359803, @@ -302,4 +292,4 @@ "leaked_bytes": 3412690, "total_allocations": 2448668 } -} \ No newline at end of file +} diff --git a/tests/perf/scenarios.py b/tests/perf/scenarios.py index a026997a..10ccb2b5 100644 --- a/tests/perf/scenarios.py +++ b/tests/perf/scenarios.py @@ -531,28 +531,6 @@ def scenario_reader_with_fragment_swap(iterations: int = 100) -> None: reader.close() -def scenario_builder_from_archive_roundtrip(iterations: int = 100) -> None: - """Loop Builder.from_archive() itself (context-less alternate constructor), - then sign. Regression guard for the classmethod's native-handle wrapping. - """ - signer = _make_signer() - source_bytes = SOURCE_JPEG.read_bytes() - ingredient_bytes = SIGNED_JPEG.read_bytes() - archive_bytes = io.BytesIO() - Builder(MANIFEST_BASE).to_archive(archive_bytes) - archive_bytes = archive_bytes.getvalue() - for _ in _iterate(iterations): - # from_archive() yields a context-less Builder, so sign() needs an - # explicit signer (no Context to pull one from). - builder = Builder.from_archive(io.BytesIO(archive_bytes)) - with io.BytesIO(ingredient_bytes) as ing: - builder.add_ingredient( - {"relationship": "parentOf", "instance_id": _PARENT_ID}, - "image/jpeg", ing, - ) - builder.sign(signer, "image/jpeg", io.BytesIO(source_bytes), io.BytesIO()) - - # Consume-and-return failure paths. # # These calls take ownership partway through their body, so a null return is @@ -1242,22 +1220,6 @@ def _make(): _fork_contended_over(_make, iterations) -def scenario_fork_contended_mutex_wrap(iterations: int = 100) -> None: - """Fork safety benchmark scenario: - fork over a Builder built by from_archive(), under thread contention. - from_archive is the only path that bypasses __init__, so it is the one - most likely to be missing the PID stamp the child's close() depends on. - """ - if not hasattr(os, "fork"): - return - archive = io.BytesIO() - Builder(MANIFEST_BASE).to_archive(archive) - archive_bytes = archive.getvalue() - - _fork_contended_over( - lambda: Builder.from_archive(io.BytesIO(archive_bytes)), iterations) - - def scenario_fork_consumed_signer(iterations: int = 100) -> None: """Fork safety benchmark scenario: the parent builds a Context that consumed a Signer, then forks. The child @@ -1408,7 +1370,6 @@ def scenario_fork_stream_cleanup(iterations: int = 100) -> None: "builder_sign_jpeg_two_components_same_mime": scenario_builder_sign_jpeg_two_components_same_mime, "builder_sign_jpeg_two_components_mixed_mime": scenario_builder_sign_jpeg_two_components_mixed_mime, "builder_sign_jpeg_archive_roundtrip": scenario_builder_sign_jpeg_archive_roundtrip, - "builder_from_archive_roundtrip": scenario_builder_from_archive_roundtrip, "builder_with_archive_swap": scenario_builder_with_archive_swap, "reader_with_fragment_swap": scenario_reader_with_fragment_swap, "with_fragment_pre_consume_rejection": @@ -1441,7 +1402,6 @@ def scenario_fork_stream_cleanup(iterations: int = 100) -> None: "fork_stream_cleanup": scenario_fork_stream_cleanup, "fork_swap_cleanup": scenario_fork_swap_cleanup, "fork_contended_mutex_swap": scenario_fork_contended_mutex_swap, - "fork_contended_mutex_wrap": scenario_fork_contended_mutex_wrap, "fork_consumed_signer": scenario_fork_consumed_signer, "swap_chain_churn": scenario_swap_chain_churn, "deferred_teardown_flush_queue": scenario_deferred_teardown_flush_queue, diff --git a/tests/test_unit_tests.py b/tests/test_unit_tests.py index 69ba7875..8a10e289 100644 --- a/tests/test_unit_tests.py +++ b/tests/test_unit_tests.py @@ -40,7 +40,7 @@ from c2pa import Builder, C2paError as Error, Reader, C2paSigningAlg as SigningAlg, C2paSignerInfo, Signer, sdk_version, C2paBuilderIntent, C2paDigitalSourceType from c2pa import Settings, Context, ContextBuilder, ContextProvider -from c2pa.c2pa import Stream, LifecycleState, ManagedResource, load_settings, create_signer, create_signer_from_info, ed25519_sign, format_embeddable, _get_mime_type_from_path, _encode_format, _format_ffi_arg +from c2pa.c2pa import Stream, LifecycleState, ManagedResource, create_signer, create_signer_from_info, ed25519_sign, _get_mime_type_from_path, _encode_format, _format_ffi_arg import c2pa.c2pa as c2pa_module from pathlib import Path @@ -303,7 +303,6 @@ def test_literal_none_string_passes_through(self): class TestReader(unittest.TestCase): def setUp(self): - warnings.filterwarnings("ignore", message="load_settings\\(\\) is deprecated") self.data_dir = FIXTURES_DIR self.testPath = DEFAULT_TEST_FILE @@ -2506,7 +2505,7 @@ def test_archive_sign(self): builder = Builder(self.manifestDefinition) archive = io.BytesIO(bytearray()) builder.to_archive(archive) - builder = Builder.from_archive(archive) + builder = Builder({}).with_archive(archive) output = io.BytesIO(bytearray()) builder.sign(self.signer, "image/jpeg", file, output) output.seek(0) @@ -2538,7 +2537,7 @@ def sign_and_validate_with_trust_config(): builder = Builder(self.manifestDefinition, context=settings_context) archive = io.BytesIO(bytearray()) builder.to_archive(archive) - builder = Builder.from_archive(archive) + builder = Builder({}, context=settings_context).with_archive(archive) output = io.BytesIO(bytearray()) builder.sign(self.signer, "image/jpeg", file, output) output.seek(0) @@ -2575,7 +2574,7 @@ def test_archive_sign_with_added_ingredient(self): builder = Builder(self.manifestDefinitionV2) archive = io.BytesIO(bytearray()) builder.to_archive(archive) - builder = Builder.from_archive(archive) + builder = Builder({}).with_archive(archive) output = io.BytesIO(bytearray()) ingredient_json = '{"test": "ingredient"}' with open(self.testPath, 'rb') as f: @@ -2610,7 +2609,7 @@ def sign_and_validate_with_trust_config(): builder = Builder(self.manifestDefinitionV2, context=settings_context) archive = io.BytesIO(bytearray()) builder.to_archive(archive) - builder = Builder.from_archive(archive) + builder = Builder({}, context=settings_context).with_archive(archive) output = io.BytesIO(bytearray()) ingredient_json = '{"test": "ingredient"}' with open(self.testPath, 'rb') as f: @@ -4280,16 +4279,6 @@ def test_sign_file_video(self): # Clean up the temporary directory shutil.rmtree(temp_dir) - def test_sign_file_format_manifest_bytes_embeddable(self): - builder = Builder(self.manifestDefinition) - output = io.BytesIO(bytearray()) - - with open(self.testPath, "rb") as file: - manifest_bytes = builder.sign(self.signer, "image/jpeg", file, output) - res = format_embeddable("image/jpeg", manifest_bytes) - output.seek(0) - output.close() - def test_builder_sign_file_callback_signer_from_callback(self): """Test signing a file using the sign_file method with Signer.from_callback.""" @@ -6213,7 +6202,6 @@ def setUp(self): # Filter specific deprecation warnings for legacy API tests warnings.filterwarnings("ignore", message="The create_signer function is deprecated") warnings.filterwarnings("ignore", message="The create_signer_from_info function is deprecated") - warnings.filterwarnings("ignore", message="load_settings\\(\\) is deprecated") self.data_dir = FIXTURES_DIR self.testPath = DEFAULT_TEST_FILE @@ -7406,47 +7394,6 @@ def test_with_archive_on_closed_builder_raises(self): builder.with_archive(archive) context.close() - def test_from_archive_roundtrip(self): - """from_archive() can't propagate contexts.""" - settings = Settings.from_dict({ - "builder": { - "thumbnail": {"enabled": False} - } - }) - context = Context(settings) - signer = self._ctx_make_signer() - builder = Builder( - self.test_manifest, context=context, - ) - archive = io.BytesIO(bytearray()) - builder.to_archive(archive) - - # from_archive creates a context-free builder - builder2 = Builder.from_archive(archive) - with ( - open(DEFAULT_TEST_FILE, "rb") as source, - io.BytesIO(bytearray()) as output, - ): - builder2.sign( - signer, "image/jpeg", source, output, - ) - output.seek(0) - reader = Reader( - "image/jpeg", output, context=Context(), - ) - manifest = reader.get_active_manifest() - # from_archive can't propagate contexts - self.assertIsNotNone( - manifest.get("thumbnail"), - "from_archive should lose settings and generate thumbnail", - ) - reader.close() - archive.close() - builder2.close() - signer.close() - context.close() - settings.close() - class TestContextIntegration(TestContextAPIs): @@ -8298,20 +8245,6 @@ def test_signer_init_rejects_null_pointer(self): with self.assertRaises(Error): Signer(None) - def test_builder_from_archive_wraps_handle(self): - self._use_real_frees() - archive = io.BytesIO() - Builder({"claim_generator": "test", "format": "image/jpeg"}).to_archive( - archive) - - builder = Builder.from_archive(io.BytesIO(archive.getvalue())) - - self.assertTrue(builder.is_valid) - self.assertIsNone(builder._context) - self.assertFalse(builder._has_context_signer) - builder.close() - self.assertEqual(builder._lifecycle_state, LifecycleState.CLOSED) - def test_context_build_failure_consumes_signer(self): self._use_real_frees() signer = self._make_signer() @@ -8639,13 +8572,6 @@ def test_builder_activation_paths(self): self.addCleanup(with_context.close) self.assertEqual(with_context._owner_pid, pid) - # from_archive is the only caller of _wrap_native_handle, - # which bypasses __init__ entirely - wrapped = Builder.from_archive(self._make_archive()) - self.addCleanup(wrapped.close) - self.assertEqual(wrapped._owner_pid, pid) - self.assertIsNone(wrapped._context) - self.assertFalse(wrapped._has_context_signer) finally: context.close() @@ -8971,16 +8897,6 @@ def test_check_cstr_arg_rejects_none_and_embedded_nul(self): c2pa_module._check_cstr_arg('format', "image/jpeg") c2pa_module._check_cstr_arg('format', b"") - def test_format_embeddable_null_out_pointer_raises_not_crashes(self): - real = c2pa_module._lib.c2pa_format_embeddable - c2pa_module._lib.c2pa_format_embeddable = ( - lambda fmt, data, size, out: 128) - try: - with self.assertRaises(Error): - format_embeddable("image/jpeg", b"junk") - finally: - c2pa_module._lib.c2pa_format_embeddable = real - def test_check_bytes_arg_rejects_none_and_empty(self): for bad in (None, b""): with self.assertRaises(Error): @@ -9318,9 +9234,8 @@ def test_null_return_with_no_native_error_is_treated_as_consumed(self): # returns -1 if native had already taken the value. self.assertEqual(self._free_count(freed, consumed_handle), 1) - # Backfilling a pointer minted by a direct FFI call. Builder.from_archive - # is the only production caller of _wrap_native_handle, so these are the - # only tests that drive the primitive as the generic entry point it is. + # Backfilling a pointer minted by a direct FFI call. These tests drive + # _wrap_native_handle as the generic entry point it is. def _raw_builder_handle(self): manifest = json.dumps( @@ -9569,26 +9484,6 @@ def test_reader_del_clears_caches(self): "cleanup left the manifest cache alive") self.assertIsNone(reader._manifest_data_cache) - def test_from_archive_frees_handle_when_wrap_fails(self): - # The wrap raising means no Python object took ownership, so - # from_archive still holds the handle and has to free it. - archive = self._make_archive() # closes a Builder; keep it off the count - freed = self._instrument_frees() - real_wrap = Builder._wrap_native_handle - - def _boom(*args, **kwargs): - raise Error("wrap failed") - - Builder._wrap_native_handle = _boom - try: - with self.assertRaises(Error): - Builder.from_archive(archive) - finally: - Builder._wrap_native_handle = real_wrap - - self.assertEqual(len(freed), 1, - "from_archive leaked the handle when the wrap failed") - def test_sign_failure_chains_the_original_exception(self): # The wrapper re-raises as C2paError; losing __cause__ hides which # call actually failed. diff --git a/tests/test_unit_tests_threaded.py b/tests/test_unit_tests_threaded.py index 2722ea59..41a62380 100644 --- a/tests/test_unit_tests_threaded.py +++ b/tests/test_unit_tests_threaded.py @@ -1734,7 +1734,7 @@ def archive_sign( archive_stream.seek(0) # Load from archive and sign - builder = Builder.from_archive(archive_stream) + builder = Builder({}).with_archive(archive_stream) builder.sign( self.signer, "image/jpeg", file, output_stream) output_stream.seek(0) @@ -3617,7 +3617,6 @@ def test_every_callback_running_method_is_guarded(self): class_a = { ("Reader", "_create_reader"), ("Reader", "_init_from_context"), - ("Builder", "from_archive"), } stream_use = re.compile( r"(_stream|stream_obj|source_stream|dest_stream|main_obj"