Skip to content

Commit 78b14ac

Browse files
authored
fix: Code reorg (#156)
* fix: Builder state * fix: reader state * fix: Clean up states * fix: Comment clean up * fix: Microoptimization * fix: One less copy * fix: Clean up comment * fix: Reorg code * fix: Remove a prop * fix: Small change * fix: Clean up comments * fix: Refactor more * fix: Code reorg merging conflicts
1 parent 0d369fa commit 78b14ac

File tree

2 files changed

+93
-101
lines changed

2 files changed

+93
-101
lines changed

src/c2pa/c2pa.py

Lines changed: 91 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,12 +1024,9 @@ def read_callback(ctx, data, length):
10241024

10251025
# Ensure we don't write beyond the allocated memory
10261026
actual_length = min(len(buffer), length)
1027-
# Create a view of the buffer to avoid copying
1028-
buffer_view = (
1029-
ctypes.c_ubyte *
1030-
actual_length).from_buffer_copy(buffer)
1031-
# Direct memory copy for better performance
1032-
ctypes.memmove(data, buffer_view, actual_length)
1027+
# Direct memory copy
1028+
ctypes.memmove(data, buffer, actual_length)
1029+
10331030
return actual_length
10341031
except Exception:
10351032
return -1
@@ -1051,11 +1048,12 @@ def seek_callback(ctx, offset, whence):
10511048
Returns:
10521049
New position in the stream, or -1 on error
10531050
"""
1051+
file_stream = self._file_like_stream
10541052
if not self._initialized or self._closed:
10551053
return -1
10561054
try:
1057-
self._file_like_stream.seek(offset, whence)
1058-
return self._file_like_stream.tell()
1055+
file_stream.seek(offset, whence)
1056+
return file_stream.tell()
10591057
except Exception:
10601058
return -1
10611059

@@ -1158,11 +1156,12 @@ def __del__(self):
11581156
try:
11591157
# Only cleanup if not already closed and we have a valid stream
11601158
if hasattr(self, '_closed') and not self._closed:
1161-
if hasattr(self, '_stream') and self._stream:
1159+
stream = self._stream
1160+
if hasattr(self, '_stream') and stream:
11621161
# Use internal cleanup to avoid calling close() which could
11631162
# cause issues
11641163
try:
1165-
_lib.c2pa_release_stream(self._stream)
1164+
_lib.c2pa_release_stream(stream)
11661165
except Exception:
11671166
# Destructors shouldn't raise exceptions
11681167
logger.error("Failed to release Stream")
@@ -1183,17 +1182,17 @@ def close(self):
11831182
Errors during cleanup are logged but not raised to ensure cleanup.
11841183
Multiple calls to close() are handled gracefully.
11851184
"""
1186-
11871185
if self._closed:
11881186
return
11891187

11901188
try:
11911189
# Clean up stream first as it depends on callbacks
11921190
# Note: We don't close self._file_like_stream as we don't own it,
11931191
# the opener owns it.
1194-
if self._stream:
1192+
stream = self._stream
1193+
if stream:
11951194
try:
1196-
_lib.c2pa_release_stream(self._stream)
1195+
_lib.c2pa_release_stream(stream)
11971196
except Exception as e:
11981197
logger.error(
11991198
Stream._ERROR_MESSAGES['stream_error'].format(
@@ -1415,7 +1414,8 @@ def __init__(self,
14151414
# If stream is a string, treat it as a path and try to open it
14161415

14171416
# format_or_path is a format
1418-
if format_or_path.lower() not in Reader.get_supported_mime_types():
1417+
format_lower = format_or_path.lower()
1418+
if format_lower not in Reader.get_supported_mime_types():
14191419
raise C2paError.NotSupported(
14201420
f"Reader does not support {format_or_path}")
14211421

@@ -1688,28 +1688,6 @@ class Signer:
16881688
'encoding_error': "Invalid UTF-8 characters in input: {}"
16891689
}
16901690

1691-
def __init__(self, signer_ptr: ctypes.POINTER(C2paSigner)):
1692-
"""Initialize a new Signer instance.
1693-
1694-
Note: This constructor is not meant to be called directly.
1695-
Use from_info() or from_callback() instead.
1696-
1697-
Args:
1698-
signer_ptr: Pointer to the native C2PA signer
1699-
1700-
Raises:
1701-
C2paError: If the signer pointer is invalid
1702-
"""
1703-
# Validate pointer before assignment
1704-
if not signer_ptr:
1705-
raise C2paError("Invalid signer pointer: pointer is null")
1706-
1707-
self._signer = signer_ptr
1708-
self._closed = False
1709-
1710-
# Set only for signers which are callback signers
1711-
self._callback_cb = None
1712-
17131691
@classmethod
17141692
def from_info(cls, signer_info: C2paSignerInfo) -> 'Signer':
17151693
"""Create a new Signer from signer information.
@@ -1876,6 +1854,28 @@ def wrapped_callback(
18761854

18771855
return signer_instance
18781856

1857+
def __init__(self, signer_ptr: ctypes.POINTER(C2paSigner)):
1858+
"""Initialize a new Signer instance.
1859+
1860+
Note: This constructor is not meant to be called directly.
1861+
Use from_info() or from_callback() instead.
1862+
1863+
Args:
1864+
signer_ptr: Pointer to the native C2PA signer
1865+
1866+
Raises:
1867+
C2paError: If the signer pointer is invalid
1868+
"""
1869+
# Validate pointer before assignment
1870+
if not signer_ptr:
1871+
raise C2paError("Invalid signer pointer: pointer is null")
1872+
1873+
self._signer = signer_ptr
1874+
self._closed = False
1875+
1876+
# Set only for signers which are callback signers
1877+
self._callback_cb = None
1878+
18791879
def __enter__(self):
18801880
"""Context manager entry."""
18811881
self._ensure_valid_state()
@@ -1980,15 +1980,6 @@ def reserve_size(self) -> int:
19801980

19811981
return result
19821982

1983-
@property
1984-
def closed(self) -> bool:
1985-
"""Check if the signer is closed.
1986-
1987-
Returns:
1988-
bool: True if the signer is closed, False otherwise
1989-
"""
1990-
return self._closed
1991-
19921983

19931984
class Builder:
19941985
"""High-level wrapper for C2PA Builder operations."""
@@ -2075,6 +2066,51 @@ def get_supported_mime_types(cls) -> list[str]:
20752066

20762067
return cls._supported_mime_types_cache
20772068

2069+
@classmethod
2070+
def from_json(cls, manifest_json: Any) -> 'Builder':
2071+
"""Create a new Builder from a JSON manifest.
2072+
2073+
Args:
2074+
manifest_json: The JSON manifest definition
2075+
2076+
Returns:
2077+
A new Builder instance
2078+
2079+
Raises:
2080+
C2paError: If there was an error creating the builder
2081+
"""
2082+
return cls(manifest_json)
2083+
2084+
@classmethod
2085+
def from_archive(cls, stream: Any) -> 'Builder':
2086+
"""Create a new Builder from an archive stream.
2087+
2088+
Args:
2089+
stream: The stream containing the archive
2090+
(any Python stream-like object)
2091+
2092+
Returns:
2093+
A new Builder instance
2094+
2095+
Raises:
2096+
C2paError: If there was an error creating the builder from archive
2097+
"""
2098+
builder = cls({})
2099+
stream_obj = Stream(stream)
2100+
builder._builder = _lib.c2pa_builder_from_archive(stream_obj._stream)
2101+
2102+
if not builder._builder:
2103+
# Clean up the stream object if builder creation fails
2104+
stream_obj.close()
2105+
2106+
error = _parse_operation_result_for_error(_lib.c2pa_error())
2107+
if error:
2108+
raise C2paError(error)
2109+
raise C2paError("Failed to create builder from archive")
2110+
2111+
builder._initialized = True
2112+
return builder
2113+
20782114
def __init__(self, manifest_json: Any):
20792115
"""Initialize a new Builder instance.
20802116
@@ -2119,50 +2155,16 @@ def __init__(self, manifest_json: Any):
21192155

21202156
self._initialized = True
21212157

2122-
@classmethod
2123-
def from_json(cls, manifest_json: Any) -> 'Builder':
2124-
"""Create a new Builder from a JSON manifest.
2125-
2126-
Args:
2127-
manifest_json: The JSON manifest definition
2128-
2129-
Returns:
2130-
A new Builder instance
2131-
2132-
Raises:
2133-
C2paError: If there was an error creating the builder
2134-
"""
2135-
return cls(manifest_json)
2136-
2137-
@classmethod
2138-
def from_archive(cls, stream: Any) -> 'Builder':
2139-
"""Create a new Builder from an archive stream.
2140-
2141-
Args:
2142-
stream: The stream containing the archive
2143-
(any Python stream-like object)
2144-
2145-
Returns:
2146-
A new Builder instance
2147-
2148-
Raises:
2149-
C2paError: If there was an error creating the builder from archive
2150-
"""
2151-
builder = cls({})
2152-
stream_obj = Stream(stream)
2153-
builder._builder = _lib.c2pa_builder_from_archive(stream_obj._stream)
2154-
2155-
if not builder._builder:
2156-
# Clean up the stream object if builder creation fails
2157-
stream_obj.close()
2158+
def __del__(self):
2159+
"""Ensure resources are cleaned up if close() wasn't called."""
2160+
self._cleanup_resources()
21582161

2159-
error = _parse_operation_result_for_error(_lib.c2pa_error())
2160-
if error:
2161-
raise C2paError(error)
2162-
raise C2paError("Failed to create builder from archive")
2162+
def __enter__(self):
2163+
self._ensure_valid_state()
2164+
return self
21632165

2164-
builder._initialized = True
2165-
return builder
2166+
def __exit__(self, exc_type, exc_val, exc_tb):
2167+
self.close()
21662168

21672169
def _ensure_valid_state(self):
21682170
"""Ensure the builder is in a valid state for operations.
@@ -2208,10 +2210,6 @@ def _cleanup_resources(self):
22082210
# Ensure we don't raise exceptions during cleanup
22092211
pass
22102212

2211-
def __del__(self):
2212-
"""Ensure resources are cleaned up if close() wasn't called."""
2213-
self._cleanup_resources()
2214-
22152213
def close(self):
22162214
"""Release the builder resources.
22172215
@@ -2234,13 +2232,6 @@ def close(self):
22342232
finally:
22352233
self._closed = True
22362234

2237-
def __enter__(self):
2238-
self._ensure_valid_state()
2239-
return self
2240-
2241-
def __exit__(self, exc_type, exc_val, exc_tb):
2242-
self.close()
2243-
22442235
def set_no_embed(self):
22452236
"""Set the no-embed flag.
22462237
@@ -2469,7 +2460,8 @@ def _sign_internal(
24692460
if not signer or not hasattr(signer, '_signer') or not signer._signer:
24702461
raise C2paError("Invalid or closed signer")
24712462

2472-
if format.lower() not in Builder.get_supported_mime_types():
2463+
format_lower = format.lower()
2464+
if format_lower not in Builder.get_supported_mime_types():
24732465
raise C2paError.NotSupported(
24742466
f"Builder does not support {format}")
24752467

tests/test_unit_tests.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ def test_reserve_size_on_closed_signer(self):
443443
)
444444
signer = Signer.from_info(signer_info)
445445
signer.close()
446-
self.assertTrue(signer.closed)
446+
# Verify signer is closed by testing that operations fail
447447
with self.assertRaises(Error):
448448
signer.reserve_size()
449449

@@ -456,7 +456,7 @@ def test_signer_double_close(self):
456456
)
457457
signer = Signer.from_info(signer_info)
458458
signer.close()
459-
self.assertTrue(signer.closed)
459+
# Second close should not raise an exception
460460
signer.close()
461461

462462
def test_builder_detects_malformed_json(self):

0 commit comments

Comments
 (0)