[pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci
This commit is contained in:
pre-commit-ci[bot]
2025-05-26 17:16:56 +00:00
parent 315eaf56a4
commit 3be67e0286
4 changed files with 71 additions and 171 deletions

View File

@@ -313,8 +313,9 @@ class TestPathToolAssetManager(unittest.TestCase):
non_existent_uri = AssetUri("type://id/1")
with self.assertRaises(FileNotFoundError) as cm:
manager.get_raw(non_existent_uri, store="non_existent_store")
self.assertIn("Asset 'type://id/1' not found in stores '['non_existent_store']'.",
str(cm.exception))
self.assertIn(
"Asset 'type://id/1' not found in stores '['non_existent_store']'.", str(cm.exception)
)
def test_is_empty(self):
# Setup AssetManager with a real MemoryStore

View File

@@ -57,9 +57,7 @@ class _AssetConstructionData:
raw_data: bytes
asset_class: Type[Asset]
# Stores AssetConstructionData for dependencies, keyed by their AssetUri
dependencies_data: Optional[
Dict[AssetUri, Optional["_AssetConstructionData"]]
] = None
dependencies_data: Optional[Dict[AssetUri, Optional["_AssetConstructionData"]]] = None
class AssetManager:
@@ -69,15 +67,11 @@ class AssetManager:
self._asset_classes: Dict[str, Type[Asset]] = {}
self.asset_cache = AssetCache(max_size_bytes=cache_max_size_bytes)
self._cacheable_stores: Set[str] = set()
logger.debug(
f"AssetManager initialized (Thread: {threading.current_thread().name})"
)
logger.debug(f"AssetManager initialized (Thread: {threading.current_thread().name})")
def register_store(self, store: AssetStore, cacheable: bool = False):
"""Registers an AssetStore with the manager."""
logger.debug(
f"Registering store: {store.name}, cacheable: {cacheable}"
)
logger.debug(f"Registering store: {store.name}, cacheable: {cacheable}")
self.stores[store.name] = store
if cacheable:
self._cacheable_stores.add(store.name)
@@ -88,31 +82,21 @@ class AssetManager:
return serializer
raise ValueError(f"No serializer found for class {asset_class}")
def register_asset(
self, asset_class: Type[Asset], serializer: Type[AssetSerializer]
):
def register_asset(self, asset_class: Type[Asset], serializer: Type[AssetSerializer]):
"""Registers an Asset class with the manager."""
if not issubclass(asset_class, Asset):
raise TypeError(
f"Item '{asset_class.__name__}' must be a subclass of Asset."
)
raise TypeError(f"Item '{asset_class.__name__}' must be a subclass of Asset.")
if not issubclass(serializer, AssetSerializer):
raise TypeError(
f"Item '{serializer.__name__}' must be a subclass of AssetSerializer."
)
raise TypeError(f"Item '{serializer.__name__}' must be a subclass of AssetSerializer.")
self._serializers.append((serializer, asset_class))
asset_type_name = getattr(asset_class, "asset_type", None)
if (
not isinstance(asset_type_name, str) or not asset_type_name
): # Ensure not empty
if not isinstance(asset_type_name, str) or not asset_type_name: # Ensure not empty
raise TypeError(
f"Asset class '{asset_class.__name__}' must have a non-empty string 'asset_type' attribute."
)
logger.debug(
f"Registering asset type: '{asset_type_name}' -> {asset_class.__name__}"
)
logger.debug(f"Registering asset type: '{asset_type_name}' -> {asset_class.__name__}")
self._asset_classes[asset_type_name] = asset_class
async def _fetch_asset_construction_data_recursive_async(
@@ -136,9 +120,7 @@ class AssetManager:
asset_class = self._asset_classes.get(uri.asset_type)
if not asset_class:
raise ValueError(
f"No asset class registered for asset type: {asset_class}"
)
raise ValueError(f"No asset class registered for asset type: {asset_class}")
# Fetch the requested asset, trying each store in order
raw_data = None
@@ -146,9 +128,7 @@ class AssetManager:
for current_store_name in store_names:
store = self.stores.get(current_store_name)
if not store:
logger.warning(
f"Store '{current_store_name}' not registered. Skipping."
)
logger.warning(f"Store '{current_store_name}' not registered. Skipping.")
continue
try:
@@ -178,15 +158,11 @@ class AssetManager:
# Extract the list of dependencies (non-recursive)
serializer = self.get_serializer_for_class(asset_class)
dependency_uris = asset_class.extract_dependencies(
raw_data, serializer
)
dependency_uris = asset_class.extract_dependencies(raw_data, serializer)
# Initialize deps_construction_data_map. Any dependencies mapped to None
# indicate that dependencies were intentionally not fetched.
deps_construction_data: Dict[
AssetUri, Optional[_AssetConstructionData]
] = {}
deps_construction_data: Dict[AssetUri, Optional[_AssetConstructionData]] = {}
for dep_uri in dependency_uris:
visited_uris.add(uri)
@@ -226,15 +202,10 @@ class AssetManager:
deps_signature_tuple: Tuple = ("shallow_children",)
else:
deps_signature_tuple = tuple(
sorted(
str(uri)
for uri in construction_data.dependencies_data.keys()
)
sorted(str(uri) for uri in construction_data.dependencies_data.keys())
)
raw_data_hash = int(
hashlib.sha256(construction_data.raw_data).hexdigest(), 16
)
raw_data_hash = int(hashlib.sha256(construction_data.raw_data).hexdigest(), 16)
return CacheKey(
store_name=store_name_for_cache,
@@ -283,9 +254,7 @@ class AssetManager:
# this would need more complex store_name propagation.
# For now, use the parent's store_name_for_cache.
try:
dep = self._build_asset_tree_from_data_sync(
dep_data_node, store_name_for_cache
)
dep = self._build_asset_tree_from_data_sync(dep_data_node, store_name_for_cache)
except Exception as e:
logger.error(
f"Error building dependency '{dep_uri}' for asset '{construction_data.uri}': {e}",
@@ -315,8 +284,7 @@ class AssetManager:
direct_deps_uris_strs: Set[str] = set()
if construction_data.dependencies_data is not None:
direct_deps_uris_strs = {
str(uri)
for uri in construction_data.dependencies_data.keys()
str(uri) for uri in construction_data.dependencies_data.keys()
}
raw_data_size = len(construction_data.raw_data)
self.asset_cache.put(
@@ -347,8 +315,7 @@ class AssetManager:
)
if (
QtGui.QApplication.instance()
and QtCore.QThread.currentThread()
is not QtGui.QApplication.instance().thread()
and QtCore.QThread.currentThread() is not QtGui.QApplication.instance().thread()
):
logger.warning(
"AssetManager.get() called from a non-main thread! UI in from_bytes may fail!"
@@ -378,9 +345,7 @@ class AssetManager:
if all_construction_data is None:
# This means the top-level asset itself was not found by _fetch_...
raise FileNotFoundError(
f"Asset '{asset_uri_obj}' not found in stores '{stores_list}'."
)
raise FileNotFoundError(f"Asset '{asset_uri_obj}' not found in stores '{stores_list}'.")
# Step 2: Synchronously build the asset tree (and call from_bytes)
# This happens in the current thread (which is assumed to be the main UI thread)
@@ -403,9 +368,7 @@ class AssetManager:
final_asset = self._build_asset_tree_from_data_sync(
all_construction_data, store_name_for_cache=store_name_for_cache
)
logger.debug(
f"Get: Synchronous asset tree build for '{asset_uri_obj}' completed."
)
logger.debug(f"Get: Synchronous asset tree build for '{asset_uri_obj}' completed.")
return final_asset
def get_or_none(
@@ -443,10 +406,8 @@ class AssetManager:
asset_uri_obj = AssetUri(uri) if isinstance(uri, str) else uri
all_construction_data = (
await self._fetch_asset_construction_data_recursive_async(
asset_uri_obj, stores_list, set(), depth
)
all_construction_data = await self._fetch_asset_construction_data_recursive_async(
asset_uri_obj, stores_list, set(), depth
)
if all_construction_data is None:
@@ -484,9 +445,7 @@ class AssetManager:
for current_store_name in stores_list:
store = self.stores.get(current_store_name)
if not store:
logger.warning(
f"Store '{current_store_name}' not registered. Skipping."
)
logger.warning(f"Store '{current_store_name}' not registered. Skipping.")
continue
try:
raw_data = await store.get(asset_uri_obj)
@@ -499,9 +458,7 @@ class AssetManager:
f"GetRawAsync: Asset {asset_uri_obj} not found in store {current_store_name}"
)
continue
raise FileNotFoundError(
f"Asset '{asset_uri_obj}' not found in stores '{stores_list}'."
)
raise FileNotFoundError(f"Asset '{asset_uri_obj}' not found in stores '{stores_list}'.")
try:
return asyncio.run(_fetch_raw_async(stores_list))
@@ -527,9 +484,7 @@ class AssetManager:
for current_store_name in stores_list:
store = self.stores.get(current_store_name)
if not store:
logger.warning(
f"Store '{current_store_name}' not registered. Skipping."
)
logger.warning(f"Store '{current_store_name}' not registered. Skipping.")
continue
try:
raw_data = await store.get(asset_uri_obj)
@@ -543,9 +498,7 @@ class AssetManager:
)
continue
raise FileNotFoundError(
f"Asset '{asset_uri_obj}' not found in stores '{stores_list}'."
)
raise FileNotFoundError(f"Asset '{asset_uri_obj}' not found in stores '{stores_list}'.")
def get_bulk(
self,
@@ -575,13 +528,9 @@ class AssetManager:
try:
logger.debug("GetBulk: Starting bulk data fetching")
all_construction_data_list = asyncio.run(
_fetch_all_construction_data_bulk_async()
)
all_construction_data_list = asyncio.run(_fetch_all_construction_data_bulk_async())
logger.debug("GetBulk: bulk data fetching completed")
except (
Exception
) as e: # Should ideally not happen if gather returns exceptions
except Exception as e: # Should ideally not happen if gather returns exceptions
logger.error(
f"GetBulk: Unexpected error during asyncio.run for bulk data: {e}",
exc_info=False,
@@ -601,20 +550,14 @@ class AssetManager:
elif isinstance(data_or_exc, _AssetConstructionData):
# Build asset instance synchronously. Exceptions during build should propagate.
# Use the first store from the list for caching purposes in build_asset_tree
store_name_for_cache = (
stores_list[0] if stores_list else "local"
)
store_name_for_cache = stores_list[0] if stores_list else "local"
assets.append(
self._build_asset_tree_from_data_sync(
data_or_exc, store_name_for_cache=store_name_for_cache
)
)
elif (
data_or_exc is None
): # From _fetch_... returning None for not found
logger.debug(
f"GetBulk: Asset '{original_uri_input}' not found"
)
elif data_or_exc is None: # From _fetch_... returning None for not found
logger.debug(f"GetBulk: Asset '{original_uri_input}' not found")
assets.append(None)
else: # Should not happen
logger.error(
@@ -646,26 +589,19 @@ class AssetManager:
)
for u in uris
]
all_construction_data_list = await asyncio.gather(
*tasks, return_exceptions=True
)
all_construction_data_list = await asyncio.gather(*tasks, return_exceptions=True)
assets = []
for i, data_or_exc in enumerate(all_construction_data_list):
if isinstance(data_or_exc, _AssetConstructionData):
# Use the first store from the list for caching purposes in build_asset_tree
store_name_for_cache = (
stores_list[0] if stores_list else "local"
)
store_name_for_cache = stores_list[0] if stores_list else "local"
assets.append(
self._build_asset_tree_from_data_sync(
data_or_exc, store_name_for_cache=store_name_for_cache
)
)
elif (
isinstance(data_or_exc, FileNotFoundError)
or data_or_exc is None
):
elif isinstance(data_or_exc, FileNotFoundError) or data_or_exc is None:
assets.append(None)
elif isinstance(data_or_exc, Exception):
assets.append(data_or_exc) # Caller must check
@@ -688,9 +624,7 @@ class AssetManager:
for current_store_name in stores_list:
store = self.stores.get(current_store_name)
if not store:
logger.warning(
f"Store '{current_store_name}' not registered. Skipping."
)
logger.warning(f"Store '{current_store_name}' not registered. Skipping.")
continue
try:
exists = await store.exists(asset_uri_obj)
@@ -732,17 +666,13 @@ class AssetManager:
) -> List[Asset]:
"""Fetches asset instances based on type, limit, and offset (synchronous), to a specified depth."""
stores_list = [store] if isinstance(store, str) else store
logger.debug(
f"Fetch(type='{asset_type}', stores='{stores_list}', depth='{depth}')"
)
logger.debug(f"Fetch(type='{asset_type}', stores='{stores_list}', depth='{depth}')")
# Note: list_assets currently only supports a single store.
# If fetching from multiple stores is needed for listing, this needs
# to be updated. For now, list from the first store.
list_store = stores_list[0] if stores_list else "local"
asset_uris = self.list_assets(asset_type, limit, offset, list_store)
results = self.get_bulk(
asset_uris, stores_list, depth
) # Pass stores_list to get_bulk
results = self.get_bulk(asset_uris, stores_list, depth) # Pass stores_list to get_bulk
# Filter out non-Asset objects (e.g., None for not found, or exceptions if collected)
return [asset for asset in results if isinstance(asset, Asset)]
@@ -756,16 +686,12 @@ class AssetManager:
) -> List[Asset]:
"""Fetches asset instances based on type, limit, and offset (asynchronous), to a specified depth."""
stores_list = [store] if isinstance(store, str) else store
logger.debug(
f"FetchAsync(type='{asset_type}', stores='{stores_list}', depth='{depth}')"
)
logger.debug(f"FetchAsync(type='{asset_type}', stores='{stores_list}', depth='{depth}')")
# Note: list_assets_async currently only supports a single store.
# If fetching from multiple stores is needed for listing, this needs
# to be updated. For now, list from the first store.
list_store = stores_list[0] if stores_list else "local"
asset_uris = await self.list_assets_async(
asset_type, limit, offset, list_store
)
asset_uris = await self.list_assets_async(asset_type, limit, offset, list_store)
results = await self.get_bulk_async(
asset_uris, stores_list, depth
) # Pass stores_list to get_bulk_async
@@ -779,16 +705,12 @@ class AssetManager:
store: Union[str, Sequence[str]] = "local",
) -> List[AssetUri]:
stores_list = [store] if isinstance(store, str) else store
logger.debug(
f"ListAssets(type='{asset_type}', stores='{stores_list}')"
)
logger.debug(f"ListAssets(type='{asset_type}', stores='{stores_list}')")
# Note: list_assets_async currently only supports a single store.
# If listing from multiple stores is needed, this needs to be updated.
# For now, list from the first store.
list_store = stores_list[0] if stores_list else "local"
return asyncio.run(
self.list_assets_async(asset_type, limit, offset, list_store)
)
return asyncio.run(self.list_assets_async(asset_type, limit, offset, list_store))
async def list_assets_async(
self,
@@ -798,9 +720,7 @@ class AssetManager:
store: Union[str, Sequence[str]] = "local",
) -> List[AssetUri]:
stores_list = [store] if isinstance(store, str) else store
logger.debug(
f"ListAssetsAsync executing for type='{asset_type}', stores='{stores_list}'"
)
logger.debug(f"ListAssetsAsync executing for type='{asset_type}', stores='{stores_list}'")
# Note: list_assets_async currently only supports a single store.
# If listing from multiple stores is needed, this needs to be updated.
# For now, list from the first store.
@@ -820,9 +740,7 @@ class AssetManager:
store: Union[str, Sequence[str]] = "local",
) -> int:
stores_list = [store] if isinstance(store, str) else store
logger.debug(
f"CountAssets(type='{asset_type}', stores='{stores_list}')"
)
logger.debug(f"CountAssets(type='{asset_type}', stores='{stores_list}')")
# Note: count_assets_async currently only supports a single store.
# If counting across multiple stores is needed, this needs to be updated.
# For now, count from the first store.
@@ -835,9 +753,7 @@ class AssetManager:
store: Union[str, Sequence[str]] = "local",
) -> int:
stores_list = [store] if isinstance(store, str) else store
logger.debug(
f"CountAssetsAsync executing for type='{asset_type}', stores='{stores_list}'"
)
logger.debug(f"CountAssetsAsync executing for type='{asset_type}', stores='{stores_list}'")
# Note: count_assets_async currently only supports a single store.
# If counting across multiple stores is needed, this needs to be updated.
# For now, count from the first store.
@@ -863,20 +779,14 @@ class AssetManager:
Adds an asset to the store, either creating a new one or updating an existing one.
Uses obj.get_url() to determine if the asset exists.
"""
logger.debug(
f"AddAsync: Adding {type(obj).__name__} to store '{store}'"
)
logger.debug(f"AddAsync: Adding {type(obj).__name__} to store '{store}'")
uri = obj.get_uri()
if not self._is_registered_type(obj):
logger.warning(
f"Asset has unregistered type '{uri.asset_type}' ({type(obj).__name__})"
)
logger.warning(f"Asset has unregistered type '{uri.asset_type}' ({type(obj).__name__})")
serializer = self.get_serializer_for_class(obj.__class__)
data = obj.to_bytes(serializer)
return await self.add_raw_async(
uri.asset_type, uri.asset_id, data, store
)
return await self.add_raw_async(uri.asset_type, uri.asset_id, data, store)
def add(self, obj: Asset, store: str = "local") -> AssetUri:
"""Synchronous wrapper for adding an asset to the store."""
@@ -891,13 +801,9 @@ class AssetManager:
"""
Adds raw asset data to the store, either creating a new asset or updating an existing one.
"""
logger.debug(
f"AddRawAsync: type='{asset_type}', id='{asset_id}', store='{store}'"
)
logger.debug(f"AddRawAsync: type='{asset_type}', id='{asset_id}', store='{store}'")
if not asset_type or not asset_id:
raise ValueError(
"asset_type and asset_id must be provided for add_raw."
)
raise ValueError("asset_type and asset_id must be provided for add_raw.")
if not isinstance(data, bytes):
raise TypeError("Data for add_raw must be bytes.")
selected_store = self.stores.get(store)
@@ -914,9 +820,7 @@ class AssetManager:
uri = await selected_store.create(asset_type, asset_id, data)
if store in self._cacheable_stores:
self.asset_cache.invalidate_for_uri(
str(uri)
) # Invalidate after add/update
self.asset_cache.invalidate_for_uri(str(uri)) # Invalidate after add/update
return uri
def add_raw(
@@ -927,9 +831,7 @@ class AssetManager:
f"AddRaw: type='{asset_type}', id='{asset_id}', store='{store}' from T:{threading.current_thread().name}"
)
try:
return asyncio.run(
self.add_raw_async(asset_type, asset_id, data, store)
)
return asyncio.run(self.add_raw_async(asset_type, asset_id, data, store))
except Exception as e:
logger.error(
f"AddRaw: Error for type='{asset_type}', id='{asset_id}': {e}",
@@ -948,9 +850,7 @@ class AssetManager:
Convenience wrapper around add_raw().
If asset_id is None, the path.stem is used as the id.
"""
return self.add_raw(
asset_type, asset_id or path.stem, path.read_bytes(), store=store
)
return self.add_raw(asset_type, asset_id or path.stem, path.read_bytes(), store=store)
def delete(self, uri: Union[AssetUri, str], store: str = "local") -> None:
logger.debug(f"Delete URI '{uri}' from store '{store}'")
@@ -964,9 +864,7 @@ class AssetManager:
asyncio.run(_do_delete_async())
async def delete_async(
self, uri: Union[AssetUri, str], store: str = "local"
) -> None:
async def delete_async(self, uri: Union[AssetUri, str], store: str = "local") -> None:
logger.debug(f"DeleteAsync URI '{uri}' from store '{store}'")
asset_uri_obj = AssetUri(uri) if isinstance(uri, str) else uri
selected_store = self.stores[store]
@@ -974,9 +872,7 @@ class AssetManager:
if store in self._cacheable_stores:
self.asset_cache.invalidate_for_uri(str(asset_uri_obj))
async def is_empty_async(
self, asset_type: Optional[str] = None, store: str = "local"
) -> bool:
async def is_empty_async(self, asset_type: Optional[str] = None, store: str = "local") -> bool:
"""Checks if the asset store has any assets of a given type (asynchronous)."""
logger.debug(f"IsEmptyAsync: type='{asset_type}', store='{store}'")
logger.debug(
@@ -987,9 +883,7 @@ class AssetManager:
raise ValueError(f"No store registered for name: {store}")
return await selected_store.is_empty(asset_type)
def is_empty(
self, asset_type: Optional[str] = None, store: str = "local"
) -> bool:
def is_empty(self, asset_type: Optional[str] = None, store: str = "local") -> bool:
"""Checks if the asset store has any assets of a given type (synchronous wrapper)."""
logger.debug(
f"IsEmpty: type='{asset_type}', store='{store}' from T:{threading.current_thread().name}"

View File

@@ -122,12 +122,14 @@ builtin_asset_store = FileStore(
mapping=asset_mapping,
)
class CamAssetManager(AssetManager):
"""
Custom CAM Asset Manager that extends the base AssetManager, such
that the get methods return fallbacks: if the asset is not present
in the "local" store, then it falls back to the builtin-asset store.
"""
def __init__(self):
super().__init__()
self.register_store(user_asset_store)

View File

@@ -513,7 +513,9 @@ class LibraryEditor(object):
# Create a new dictionary to hold the updated tool numbers and bits
for row in range(self.toolModel.rowCount()):
tool_nr_item = self.toolModel.item(row, 0)
tool_uri_item = self.toolModel.item(row, 0) # Tool URI is stored in column 0 with _PathRole
tool_uri_item = self.toolModel.item(
row, 0
) # Tool URI is stored in column 0 with _PathRole
tool_nr = tool_nr_item.data(Qt.EditRole)
tool_uri_string = tool_uri_item.data(_PathRole)
@@ -530,25 +532,26 @@ class LibraryEditor(object):
self.current_library.assign_new_bit_no(found_bit, int(tool_nr))
Path.Log.debug(f"Assigned tool number {tool_nr} to {tool_uri_string}")
else:
Path.Log.warning(f"Toolbit with URI {tool_uri_string} not found in current library.")
Path.Log.warning(
f"Toolbit with URI {tool_uri_string} not found in current library."
)
except Exception as e:
Path.Log.error(f"Error processing row {row} (tool_nr: {tool_nr}, uri: {tool_uri_string}): {e}")
Path.Log.error(
f"Error processing row {row} (tool_nr: {tool_nr}, uri: {tool_uri_string}): {e}"
)
# Continue processing other rows even if one fails
continue
else:
Path.Log.warning(f"Skipping row {row}: Invalid tool number or URI.")
Path.Log.warning(f"Skipping row {row}: Invalid tool number or URI.")
# The current_library object has been modified in the loop by assign_new_bit_no
# Now save the modified library asset
try:
cam_assets.add(self.current_library)
Path.Log.debug(
f"saveLibrary: Library " f"{self.current_library.get_uri()} saved."
)
Path.Log.debug(f"saveLibrary: Library " f"{self.current_library.get_uri()} saved.")
except Exception as e:
Path.Log.error(
f"saveLibrary: Failed to save library "
f"{self.current_library.get_uri()}: {e}"
f"saveLibrary: Failed to save library " f"{self.current_library.get_uri()}: {e}"
)
PySide.QtGui.QMessageBox.critical(
self.form,