generated from coulomb/repo-seed
asset listing filters
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Iterable
|
||||
from typing import Any, Iterable
|
||||
|
||||
from kontextual_engine.core import (
|
||||
Actor,
|
||||
@@ -21,6 +21,7 @@ from kontextual_engine.core import (
|
||||
MetadataSchema,
|
||||
MetadataSchemaAssignment,
|
||||
RepresentationKind,
|
||||
Sensitivity,
|
||||
)
|
||||
from kontextual_engine.errors import NotFoundError, ValidationError
|
||||
|
||||
@@ -65,12 +66,37 @@ class InMemoryAssetRegistryRepository:
|
||||
*,
|
||||
lifecycle: LifecycleState | None = None,
|
||||
asset_type: str | None = None,
|
||||
sensitivity: Sensitivity | str | None = None,
|
||||
owner: str | None = None,
|
||||
topic: str | None = None,
|
||||
review_state: str | None = None,
|
||||
metadata_filters: dict[str, Any] | None = None,
|
||||
confirmed_metadata_only: bool = False,
|
||||
) -> list[KnowledgeAsset]:
|
||||
assets: Iterable[KnowledgeAsset] = self.assets.values()
|
||||
if lifecycle is not None:
|
||||
assets = [asset for asset in assets if asset.lifecycle == lifecycle]
|
||||
if asset_type is not None:
|
||||
assets = [asset for asset in assets if asset.classification.asset_type == asset_type]
|
||||
if sensitivity is not None:
|
||||
sensitivity = Sensitivity(sensitivity)
|
||||
assets = [asset for asset in assets if asset.classification.sensitivity == sensitivity]
|
||||
if owner is not None:
|
||||
assets = [asset for asset in assets if asset.classification.owner == owner]
|
||||
if topic is not None:
|
||||
assets = [asset for asset in assets if topic in asset.classification.topics]
|
||||
if review_state is not None:
|
||||
assets = [asset for asset in assets if asset.classification.review_state == review_state]
|
||||
if metadata_filters:
|
||||
assets = [
|
||||
asset
|
||||
for asset in assets
|
||||
if _metadata_matches(
|
||||
self.metadata_records.get(asset.id, []),
|
||||
metadata_filters,
|
||||
confirmed_metadata_only=confirmed_metadata_only,
|
||||
)
|
||||
]
|
||||
return sorted(assets, key=lambda asset: (asset.title, asset.id))
|
||||
|
||||
def save_representation(self, representation: AssetRepresentation) -> AssetRepresentation:
|
||||
@@ -225,7 +251,7 @@ class InMemoryAssetRegistryRepository:
|
||||
events = [event for event in events if event.target == target]
|
||||
if correlation_id is not None:
|
||||
events = [event for event in events if event.correlation_id == correlation_id]
|
||||
return sorted(events, key=lambda event: (event.occurred_at, event.event_id))
|
||||
return sorted(events, key=lambda event: event.occurred_at)
|
||||
|
||||
def save_idempotency_record(self, record: IdempotencyRecord) -> IdempotencyRecord:
|
||||
self.idempotency_records[record.key] = record
|
||||
@@ -253,3 +279,24 @@ class InMemoryAssetRegistryRepository:
|
||||
if status is not None:
|
||||
jobs = [job for job in jobs if job.status == status]
|
||||
return sorted(jobs, key=lambda job: (job.created_at, job.job_id))
|
||||
|
||||
|
||||
def _metadata_matches(
|
||||
records: list[MetadataRecord],
|
||||
metadata_filters: dict[str, Any],
|
||||
*,
|
||||
confirmed_metadata_only: bool,
|
||||
) -> bool:
|
||||
for key, expected in metadata_filters.items():
|
||||
candidates = [record for record in records if record.key == key]
|
||||
if confirmed_metadata_only:
|
||||
candidates = [record for record in candidates if record.confirmed]
|
||||
if not any(_metadata_value_matches(record.value, expected) for record in candidates):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _metadata_value_matches(value: Any, expected: Any) -> bool:
|
||||
if isinstance(value, list) and not isinstance(expected, list):
|
||||
return expected in value
|
||||
return value == expected
|
||||
|
||||
@@ -24,6 +24,7 @@ from kontextual_engine.core import (
|
||||
MetadataSchemaAssignment,
|
||||
RepresentationKind,
|
||||
RelationshipTargetKind,
|
||||
Sensitivity,
|
||||
)
|
||||
from kontextual_engine.errors import NotFoundError, ValidationError
|
||||
|
||||
@@ -87,6 +88,12 @@ class SQLiteAssetRegistryRepository:
|
||||
*,
|
||||
lifecycle: LifecycleState | None = None,
|
||||
asset_type: str | None = None,
|
||||
sensitivity: Sensitivity | str | None = None,
|
||||
owner: str | None = None,
|
||||
topic: str | None = None,
|
||||
review_state: str | None = None,
|
||||
metadata_filters: dict[str, Any] | None = None,
|
||||
confirmed_metadata_only: bool = False,
|
||||
) -> list[KnowledgeAsset]:
|
||||
clauses = []
|
||||
params: list[Any] = []
|
||||
@@ -98,7 +105,28 @@ class SQLiteAssetRegistryRepository:
|
||||
params.append(asset_type)
|
||||
where = f" where {' and '.join(clauses)}" if clauses else ""
|
||||
rows = self._all(f"select payload from assets{where} order by title, id", tuple(params))
|
||||
return [KnowledgeAsset.from_dict(_loads(row["payload"])) for row in rows]
|
||||
assets = [KnowledgeAsset.from_dict(_loads(row["payload"])) for row in rows]
|
||||
if sensitivity is not None:
|
||||
sensitivity = Sensitivity(sensitivity)
|
||||
assets = [asset for asset in assets if asset.classification.sensitivity == sensitivity]
|
||||
if owner is not None:
|
||||
assets = [asset for asset in assets if asset.classification.owner == owner]
|
||||
if topic is not None:
|
||||
assets = [asset for asset in assets if topic in asset.classification.topics]
|
||||
if review_state is not None:
|
||||
assets = [asset for asset in assets if asset.classification.review_state == review_state]
|
||||
if metadata_filters:
|
||||
records_by_asset = self._metadata_records_for_assets([asset.id for asset in assets])
|
||||
assets = [
|
||||
asset
|
||||
for asset in assets
|
||||
if _metadata_matches(
|
||||
records_by_asset.get(asset.id, []),
|
||||
metadata_filters,
|
||||
confirmed_metadata_only=confirmed_metadata_only,
|
||||
)
|
||||
]
|
||||
return assets
|
||||
|
||||
def save_representation(self, representation: AssetRepresentation) -> AssetRepresentation:
|
||||
try:
|
||||
@@ -422,7 +450,7 @@ class SQLiteAssetRegistryRepository:
|
||||
clauses.append("correlation_id = ?")
|
||||
params.append(correlation_id)
|
||||
where = f" where {' and '.join(clauses)}" if clauses else ""
|
||||
rows = self._all(f"select payload from audit_events{where} order by occurred_at, id", tuple(params))
|
||||
rows = self._all(f"select payload from audit_events{where} order by occurred_at, rowid", tuple(params))
|
||||
return [AuditEvent.from_dict(_loads(row["payload"])) for row in rows]
|
||||
|
||||
def save_idempotency_record(self, record: IdempotencyRecord) -> IdempotencyRecord:
|
||||
@@ -616,6 +644,23 @@ class SQLiteAssetRegistryRepository:
|
||||
with self._connect() as conn:
|
||||
return list(conn.execute(query, params).fetchall())
|
||||
|
||||
def _metadata_records_for_assets(self, asset_ids: list[str]) -> dict[str, list[MetadataRecord]]:
|
||||
if not asset_ids:
|
||||
return {}
|
||||
placeholders = ",".join("?" for _ in asset_ids)
|
||||
rows = self._all(
|
||||
f"""
|
||||
select asset_id, payload from metadata_records
|
||||
where asset_id in ({placeholders})
|
||||
order by asset_id, key, id
|
||||
""",
|
||||
tuple(asset_ids),
|
||||
)
|
||||
records: dict[str, list[MetadataRecord]] = {}
|
||||
for row in rows:
|
||||
records.setdefault(row["asset_id"], []).append(MetadataRecord.from_dict(_loads(row["payload"])))
|
||||
return records
|
||||
|
||||
|
||||
def _json(value: dict[str, Any]) -> str:
|
||||
return json.dumps(value, sort_keys=True, separators=(",", ":"))
|
||||
@@ -623,3 +668,24 @@ def _json(value: dict[str, Any]) -> str:
|
||||
|
||||
def _loads(value: str) -> dict[str, Any]:
|
||||
return json.loads(value)
|
||||
|
||||
|
||||
def _metadata_matches(
|
||||
records: list[MetadataRecord],
|
||||
metadata_filters: dict[str, Any],
|
||||
*,
|
||||
confirmed_metadata_only: bool,
|
||||
) -> bool:
|
||||
for key, expected in metadata_filters.items():
|
||||
candidates = [record for record in records if record.key == key]
|
||||
if confirmed_metadata_only:
|
||||
candidates = [record for record in candidates if record.confirmed]
|
||||
if not any(_metadata_value_matches(record.value, expected) for record in candidates):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _metadata_value_matches(value: Any, expected: Any) -> bool:
|
||||
if isinstance(value, list) and not isinstance(expected, list):
|
||||
return expected in value
|
||||
return value == expected
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Protocol
|
||||
from typing import Any, Protocol
|
||||
|
||||
from kontextual_engine.core import (
|
||||
Actor,
|
||||
@@ -20,6 +20,7 @@ from kontextual_engine.core import (
|
||||
MetadataSchema,
|
||||
MetadataSchemaAssignment,
|
||||
RepresentationKind,
|
||||
Sensitivity,
|
||||
)
|
||||
|
||||
|
||||
@@ -34,6 +35,12 @@ class AssetRegistryRepository(Protocol):
|
||||
*,
|
||||
lifecycle: LifecycleState | None = None,
|
||||
asset_type: str | None = None,
|
||||
sensitivity: Sensitivity | str | None = None,
|
||||
owner: str | None = None,
|
||||
topic: str | None = None,
|
||||
review_state: str | None = None,
|
||||
metadata_filters: dict[str, Any] | None = None,
|
||||
confirmed_metadata_only: bool = False,
|
||||
) -> list[KnowledgeAsset]: ...
|
||||
|
||||
def save_representation(self, representation: AssetRepresentation) -> AssetRepresentation: ...
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, replace
|
||||
from typing import Any
|
||||
|
||||
from kontextual_engine.core import (
|
||||
AssetRepresentation,
|
||||
@@ -22,6 +23,7 @@ from kontextual_engine.core import (
|
||||
OperationContext,
|
||||
PolicyDecision,
|
||||
RelationshipTargetKind,
|
||||
Sensitivity,
|
||||
SourceReference,
|
||||
VersionChangeType,
|
||||
)
|
||||
@@ -308,6 +310,29 @@ class AssetRegistryService:
|
||||
def get_asset(self, asset_id: str) -> KnowledgeAsset:
|
||||
return self.repository.get_asset(asset_id)
|
||||
|
||||
def list_assets(
|
||||
self,
|
||||
*,
|
||||
lifecycle: LifecycleState | None = None,
|
||||
asset_type: str | None = None,
|
||||
sensitivity: Sensitivity | str | None = None,
|
||||
owner: str | None = None,
|
||||
topic: str | None = None,
|
||||
review_state: str | None = None,
|
||||
metadata_filters: dict[str, Any] | None = None,
|
||||
confirmed_metadata_only: bool = False,
|
||||
) -> list[KnowledgeAsset]:
|
||||
return self.repository.list_assets(
|
||||
lifecycle=lifecycle,
|
||||
asset_type=asset_type,
|
||||
sensitivity=sensitivity,
|
||||
owner=owner,
|
||||
topic=topic,
|
||||
review_state=review_state,
|
||||
metadata_filters=metadata_filters,
|
||||
confirmed_metadata_only=confirmed_metadata_only,
|
||||
)
|
||||
|
||||
def register_context_entity(self, entity: ContextEntity, context: OperationContext) -> ContextEntity:
|
||||
decision = self._authorize(
|
||||
context,
|
||||
|
||||
Reference in New Issue
Block a user