From e6f4f808977f5dba7c5cf1b47b22c1ad57b2efaf Mon Sep 17 00:00:00 2001 From: david Date: Thu, 25 Jun 2026 18:51:17 +0200 Subject: [PATCH] Change KnowledgeInteractionInfo to KnowledgeInteraction object and make name required --- CONTEXT.md | 3 +- examples/02-binding_models.py | 6 +- .../05-custom-settings/05-custom_settings.py | 10 +- examples/06-dependency_injection.py | 4 +- examples/08-async_handlers.py | 6 +- src/knowledge_mapper/__init__.py | 2 +- src/knowledge_mapper/dependency_injection.py | 2 +- src/knowledge_mapper/kb/builder.py | 16 +-- src/knowledge_mapper/kb/knowledge_base.py | 107 +++++++++--------- src/knowledge_mapper/ke/client.py | 15 ++- src/knowledge_mapper/ke/models.py | 55 ++++++++- src/knowledge_mapper/knowledge_interaction.py | 17 +-- src/knowledge_mapper/settings.py | 47 +++++++- src/knowledge_mapper/testing/fake_client.py | 8 +- tests/configuration/test_configuration.py | 8 +- tests/test_client.py | 6 +- tests/test_dispatch.py | 24 ++-- tests/test_handling_loop.py | 20 ++-- tests/test_kb_new_operations.py | 10 +- tests/test_ki_registration.py | 42 +++---- tests/test_knowledge_base_builder.py | 28 +++-- tests/test_sync_bridge.py | 10 +- 22 files changed, 266 insertions(+), 180 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index 2309652..7ca5ff8 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -190,7 +190,7 @@ def get_db() -> MyDatabase: @kb.answer_ki(name="...", graph_pattern="...") def handler( binding_set: list[PersonBinding], - info: KnowledgeInteractionInfo, + info: KnowledgeInteraction, db: Annotated[MyDatabase, Depends(get_db)], ) -> list[PersonBinding]: return db.query(binding_set) @@ -434,6 +434,7 @@ The pre-overhaul, config-file-driven mapper implementation has been removed from - **`KnowledgeBase` ≠ Smart Connector**: The SC is a separate Java process (usually containerized). `KnowledgeBase` is the Python representation of a KB that registers with the SC over REST. - **Pydantic throughout**: Models, settings, and binding validation all use Pydantic v2. `BindingModel` uses `alias_generator=to_camel` to match the TKE REST API's camelCase fields. - **`ClientProtocol`**: The `Client` (real HTTP) and `TestClient` (fake) both satisfy this Protocol. Injecting a fake client is the standard testing pattern. `ClientProtocol` includes `post_handle_response` — the method that sends a handler's result back to the SC after an incoming KI call. +- **Split KI definition vs. info**: User-defined KIs (handler arguments, settings, `register_ki` payloads) use `KnowledgeInteraction` (subclassed as `AskAnswerKnowledgeInteraction` and `PostReactKnowledgeInteraction`) where `name: str` is required and there is no `id`. The KE returns `KnowledgeInteractionInfo` (subclassed `AskAnswerInteractionInfo`, `PostReactInteractionInfo`) where `id: str` is required and `name: str | None` (the KE may return KIs without a name). `KnowledgeInteractionContext` holds the user's `definition: KnowledgeInteraction` plus a `ke_id: str | None` populated after registration. The bridge helper `info_from_definition(definition, id)` constructs the matching Info subtype from a definition once the KE assigns an id. - **Deferred KI registration**: By default, `answer_ki`/`react_ki` etc. use `defer_ke_registration=True`, meaning KIs are registered locally but not sent to the SC until `kb.register()` or `kb.sync_knowledge_interactions()` is called. - **KI registry indexed by ID after registration**: `KnowledgeBase` maintains a secondary index (`_ki_registry_by_id`) populated once a KI is registered with the SC and assigned an ID. The handling loop dispatches by ID using this index. - **Handler introspection**: `KnowledgeInteractionContext.__post_init__` inspects handler signatures to auto-detect binding models, enabling transparent (de)serialization without manual type dispatch. Dispatch logic (validate → call → serialize for ANSWER/REACT; prepare_outgoing + parse_result for ASK/POST) lives in `KnowledgeInteractionContext`, not in `KnowledgeBase`. diff --git a/examples/02-binding_models.py b/examples/02-binding_models.py index 14ddb74..ce99d6d 100644 --- a/examples/02-binding_models.py +++ b/examples/02-binding_models.py @@ -14,7 +14,7 @@ BindingModel, BindingSet, KnowledgeBase, - KnowledgeInteractionInfo, + KnowledgeInteraction, Literal, Uri, ) @@ -49,7 +49,7 @@ class CurrentTemperatureBinding(BindingModel): prefixes={"ex": "http://example.org/knowledge-mapper/binding-models#"}, ) def binding_models_answer_ki( - binding_set: list[CurrentTemperatureBinding], info: KnowledgeInteractionInfo + binding_set: list[CurrentTemperatureBinding], info: KnowledgeInteraction ) -> list[CurrentTemperatureBinding]: logger.info( f"Handling a call to the binding models answer KI with incoming bindings: " @@ -79,7 +79,7 @@ def binding_models_answer_ki( prefixes={"ex": "http://example.org/knowledge-mapper/binding-models#"}, ) def binding_models_raw_answer_ki( - binding_set: BindingSet, info: KnowledgeInteractionInfo + binding_set: BindingSet, info: KnowledgeInteraction ) -> BindingSet: logger.info( f"Handling a call to the binding models raw answer KI with incoming bindings: " diff --git a/examples/05-custom-settings/05-custom_settings.py b/examples/05-custom-settings/05-custom_settings.py index 32e9427..a4077b9 100644 --- a/examples/05-custom-settings/05-custom_settings.py +++ b/examples/05-custom-settings/05-custom_settings.py @@ -30,7 +30,7 @@ BindingSet, KnowledgeBase, KnowledgeBaseSettings, - KnowledgeInteractionInfo, + KnowledgeInteraction, ) EXAMPLE_NAME = "custom-settings" @@ -69,13 +69,13 @@ def settings_customise_sources(cls, settings_cls, **kwargs): # type: ignore def example_answer_from_settings( - binding_set: BindingSet, info: KnowledgeInteractionInfo + binding_set: BindingSet, info: KnowledgeInteraction ) -> BindingSet: return binding_set def example_react_from_settings( - binding_set: BindingSet, info: KnowledgeInteractionInfo + binding_set: BindingSet, info: KnowledgeInteraction ) -> BindingSet: return binding_set @@ -101,5 +101,5 @@ def example_react_from_settings( logger.info(f"KB id: {kb.info.id}") logger.info(f"DB host:port: {settings.db_host}:{settings.db_port}") logger.info(f"Debug: {settings.debug}") - logger.info(f"ASK KI name: {ask_ctx.info.name}") - logger.info(f"POST KI name: {post_ctx.info.name}") + logger.info(f"ASK KI name: {ask_ctx.definition.name}") + logger.info(f"POST KI name: {post_ctx.definition.name}") diff --git a/examples/06-dependency_injection.py b/examples/06-dependency_injection.py index 3d9bfb6..aa1f68e 100644 --- a/examples/06-dependency_injection.py +++ b/examples/06-dependency_injection.py @@ -30,7 +30,7 @@ BindingModel, Depends, KnowledgeBase, - KnowledgeInteractionInfo, + KnowledgeInteraction, Literal, Uri, ) @@ -134,7 +134,7 @@ class SensorReadingBinding(BindingModel): ) def answer_sensor_readings( binding_set: list[SensorReadingBinding], - info: KnowledgeInteractionInfo, + info: KnowledgeInteraction, repo: Annotated[SensorRepository, Depends(get_sensor_repository)], config: Annotated[AppConfig, Depends(get_config)], ) -> list[SensorReadingBinding]: diff --git a/examples/08-async_handlers.py b/examples/08-async_handlers.py index a26bd15..7df4306 100644 --- a/examples/08-async_handlers.py +++ b/examples/08-async_handlers.py @@ -15,7 +15,7 @@ from knowledge_mapper import ( BindingModel, KnowledgeBase, - KnowledgeInteractionInfo, + KnowledgeInteraction, Literal, Uri, ) @@ -59,7 +59,7 @@ class DeviceAckBinding(BindingModel): ) def react_device_sync( binding_set: list[DeviceCommandBinding], - info: KnowledgeInteractionInfo, + info: KnowledgeInteraction, ) -> list[DeviceAckBinding]: # Sync handlers are executed with asyncio.to_thread(...). # That keeps the event loop responsive, but this function itself still blocks @@ -85,7 +85,7 @@ def react_device_sync( ) async def react_device_async( binding_set: list[DeviceCommandBinding], - info: KnowledgeInteractionInfo, + info: KnowledgeInteraction, ) -> list[DeviceAckBinding]: # Async handlers are awaited directly on the event loop. # Use this style when the handler performs awaitable I/O (HTTP calls, DB diff --git a/src/knowledge_mapper/__init__.py b/src/knowledge_mapper/__init__.py index 9fc20e7..16936bf 100644 --- a/src/knowledge_mapper/__init__.py +++ b/src/knowledge_mapper/__init__.py @@ -3,7 +3,7 @@ from .dependency_injection import Depends from .kb.builder import KnowledgeBaseBuilder from .kb.knowledge_base import KnowledgeBase -from .ke.models import BindingModel, BindingSet, KnowledgeInteractionInfo, Literal, Uri +from .ke.models import BindingModel, BindingSet, KnowledgeInteraction, Literal, Uri from .settings import KnowledgeBaseSettings __version__ = "0.1.0a0" diff --git a/src/knowledge_mapper/dependency_injection.py b/src/knowledge_mapper/dependency_injection.py index 5401752..428c30a 100644 --- a/src/knowledge_mapper/dependency_injection.py +++ b/src/knowledge_mapper/dependency_injection.py @@ -20,7 +20,7 @@ def get_db() -> MyDatabase: @kb.answer_ki(name="...", graph_pattern="...") def handler( binding_set: list[PersonBinding], - info: KnowledgeInteractionInfo, + info: KnowledgeInteraction, db: Annotated[MyDatabase, Depends(get_db)], ) -> list[PersonBinding]: return db.query(binding_set) diff --git a/src/knowledge_mapper/kb/builder.py b/src/knowledge_mapper/kb/builder.py index 821662d..5fe0c0d 100644 --- a/src/knowledge_mapper/kb/builder.py +++ b/src/knowledge_mapper/kb/builder.py @@ -51,7 +51,7 @@ def handler(self, ki_name: str, func: Handler) -> Self: Args: ki_name: Name of the KI as declared in settings. func: Handler callable; receives a binding set and - :class:`~.ke.models.KnowledgeInteractionInfo` and returns a binding set. + :class:`~.ke.models.KnowledgeInteraction` and returns a binding set. Raises: ValueError: If *ki_name* is not declared in settings, or if the KI is of @@ -59,17 +59,19 @@ def handler(self, ki_name: str, func: Handler) -> Self: automatically). """ try: - info = self._settings.get_configured_interaction(ki_name) + definition = self._settings.get_configured_interaction(ki_name) except ValueError as err: raise ValueError(f"KI named '{ki_name}' not found in settings.") from err - if info.type not in (KiTypes.ANSWER, KiTypes.REACT): + if definition.type not in (KiTypes.ANSWER, KiTypes.REACT): raise ValueError( - f"KI '{ki_name}' is of type {info.type}. Only ANSWER and REACT KIs " - f"accept a handler; ASK and POST KIs are registered automatically." + f"KI '{ki_name}' is of type {definition.type}. Only ANSWER and REACT " + f"KIs accept a handler; ASK and POST KIs are registered automatically." ) - self._kb._register_ki_decorator(info=info, defer_ke_registration=True)(func) + self._kb._register_ki_decorator( + definition=definition, defer_ke_registration=True + )(func) self._unhandled_incoming.discard(ki_name) return self @@ -95,7 +97,7 @@ def build(self) -> KnowledgeBase: if ki.type in (KiTypes.ASK, KiTypes.POST): self._kb._register_ki_locally( KnowledgeInteractionContext( - info=ki, + definition=ki, handler=None, ), ) diff --git a/src/knowledge_mapper/kb/knowledge_base.py b/src/knowledge_mapper/kb/knowledge_base.py index 367e9be..68f8e98 100644 --- a/src/knowledge_mapper/kb/knowledge_base.py +++ b/src/knowledge_mapper/kb/knowledge_base.py @@ -12,13 +12,14 @@ from ..ke.client import ClientProtocol, HandleRequest, PollResult from ..ke.errors import KnowledgeEngineNotAvailableError from ..ke.models import ( - AskAnswerInteractionInfo, + AskAnswerKnowledgeInteraction, BindingModel, BindingSet, KiTypes, KnowledgeBaseInfo, + KnowledgeInteraction, KnowledgeInteractionInfo, - PostReactInteractionInfo, + PostReactKnowledgeInteraction, SmartConnectorLease, ) from ..knowledge_interaction import ( @@ -144,25 +145,31 @@ def _register_ki_locally( async registration, or call this from synchronous code (e.g. decorators) followed by :meth:`sync_knowledge_interactions` to push to the KE. """ - if ki_ctx.info.name in (ki.info.name for ki in self.ki_registry.values()): + if ki_ctx.definition.name in ( + ki.definition.name for ki in self.ki_registry.values() + ): raise ValueError( - f"A KI named '{ki_ctx.info.name}' is already registered for this KB." + f"A KI named '{ki_ctx.definition.name}' is already registered for " + f"this KB." ) if ki_ctx.status == KnowledgeInteractionStatus.REGISTERED: raise ValueError( - f"Cannot register KI '{ki_ctx.info.name}' because it is already " - f"registered." + f"Cannot register KI '{ki_ctx.definition.name}' because it is " + f"already registered." ) - self.ki_registry[ki_ctx.info.name] = ki_ctx + self.ki_registry[ki_ctx.definition.name] = ki_ctx async def register_ki( self, ki_ctx: KnowledgeInteractionContext[Any, ...], defer_ke_registration: bool = False, - ) -> KnowledgeInteractionInfo: + ) -> KnowledgeInteractionInfo | None: """Register a knowledge interaction for this knowledge base at the KE runtime and store it in this object's registry of interactions. + Returns the :class:`KnowledgeInteractionInfo` reported by the KE, or ``None`` + if ``defer_ke_registration`` is ``True`` (in which case no KE call was made). + Raises: ValueError: If the KB is not yet registered and ``defer_ke_registration`` is ``False``, if a KI with the same name is already registered, or if the @@ -174,28 +181,27 @@ async def register_ki( """ if self.state != KnowledgeBaseState.REGISTERED and not defer_ke_registration: raise ValueError( - f"Cannot register KI '{ki_ctx.info.name}' because the KB is not " - f"registered. Consider setting defer_ke_registration=True to defer " - f"registration until the KB itself is registered." + f"Cannot register KI '{ki_ctx.definition.name}' because the KB is " + f"not registered. Consider setting defer_ke_registration=True to " + f"defer registration until the KB itself is registered." ) self._register_ki_locally(ki_ctx) if defer_ke_registration: - return ki_ctx.info + return None registered_ki = await self.client.register_ki( kb_id=self.info.id, - ki=ki_ctx.info, + ki=ki_ctx.definition, ) - ki_ctx.info = registered_ki + ki_ctx.ke_id = registered_ki.id ki_ctx.status = KnowledgeInteractionStatus.REGISTERED - assert registered_ki.id is not None self._ki_registry_by_id[registered_ki.id] = ki_ctx return registered_ki def _register_ki_decorator( - self, info: KnowledgeInteractionInfo, defer_ke_registration: bool + self, definition: KnowledgeInteraction, defer_ke_registration: bool ) -> Callable[[Handler], Handler]: """Return a decorator that registers the decorated function as a KI handler. @@ -214,7 +220,7 @@ def decorator(func: Handler) -> Handler: @wraps(func) async def async_wrapper( binding_set: BindingSet | list[BindingModel], - info: KnowledgeInteractionInfo, + info: KnowledgeInteraction, *args, **kwargs, ) -> BindingSet | Sequence[BindingModel]: @@ -222,7 +228,7 @@ async def async_wrapper( self._register_ki_locally( KnowledgeInteractionContext( - info=info, + definition=definition, handler=async_wrapper, status=KnowledgeInteractionStatus.UNREGISTERED, ), @@ -233,7 +239,7 @@ async def async_wrapper( @wraps(func) def wrapper( binding_set: BindingSet | list[BindingModel], - info: KnowledgeInteractionInfo, + info: KnowledgeInteraction, *args, **kwargs, ) -> BindingSet | Sequence[BindingModel]: @@ -241,7 +247,7 @@ def wrapper( self._register_ki_locally( KnowledgeInteractionContext( - info=info, + definition=definition, handler=wrapper, status=KnowledgeInteractionStatus.UNREGISTERED, ), @@ -270,13 +276,13 @@ async def sync_knowledge_interactions(self) -> None: for ki_ctx in self.ki_registry.values(): if ki_ctx.status == KnowledgeInteractionStatus.REGISTERED: continue - ki_ctx.info = await self.client.register_ki( + registered_ki = await self.client.register_ki( kb_id=self.info.id, - ki=ki_ctx.info, + ki=ki_ctx.definition, ) + ki_ctx.ke_id = registered_ki.id ki_ctx.status = KnowledgeInteractionStatus.REGISTERED - assert ki_ctx.info.id is not None - self._ki_registry_by_id[ki_ctx.info.id] = ki_ctx + self._ki_registry_by_id[registered_ki.id] = ki_ctx return async def unregister_ki(self, ki_name: str) -> None: @@ -303,16 +309,16 @@ async def unregister_ki(self, ki_name: str) -> None: ki_ctx = self.ki_registry[ki_name] if ( ki_ctx.status != KnowledgeInteractionStatus.REGISTERED - or ki_ctx.info.id is None + or ki_ctx.ke_id is None ): raise ValueError( f"Cannot unregister KI '{ki_name}' because it is not currently " f"registered at the KE runtime." ) - logger.info("Unregistering KI '%s' (%s).", ki_name, ki_ctx.info.id) - await self.client.unregister_ki(kb_id=self.info.id, ki_id=ki_ctx.info.id) - self._ki_registry_by_id.pop(ki_ctx.info.id, None) + logger.info("Unregistering KI '%s' (%s).", ki_name, ki_ctx.ke_id) + await self.client.unregister_ki(kb_id=self.info.id, ki_id=ki_ctx.ke_id) + self._ki_registry_by_id.pop(ki_ctx.ke_id, None) self.ki_registry.pop(ki_name, None) async def renew_lease(self) -> SmartConnectorLease: @@ -353,13 +359,13 @@ async def load_domain_knowledge(self, knowledge: str) -> None: logger.debug("Loading domain knowledge for KB '%s'.", self.info.id) await self.client.load_domain_knowledge(kb_id=self.info.id, knowledge=knowledge) - def ki_from_info( + def ki_from_definition( self, - info: KnowledgeInteractionInfo, + definition: KnowledgeInteraction, defer_ke_registration: bool = True, ) -> Callable[[Handler], Handler]: """Return a decorator that registers the decorated function as a KI handler - based on the provided KnowledgeInteractionInfo. + based on the provided :class:`KnowledgeInteraction` definition. Raises: ValueError: Propagated from ``register_ki`` if registration constraints are @@ -370,7 +376,7 @@ def ki_from_info( the KE runtime. """ return self._register_ki_decorator( - info=info, defer_ke_registration=defer_ke_registration + definition=definition, defer_ke_registration=defer_ke_registration ) def ask_ki( @@ -381,8 +387,7 @@ def ask_ki( prefixes: dict | None = None, defer_ke_registration: bool = True, ) -> None: - """Return a decorator that registers the decorated function as an ASK KI - handler. + """Register an ASK KI on this KB. Call :meth:`ask` to query the network. Raises: ValueError: Propagated from ``register_ki`` if registration constraints are @@ -394,7 +399,7 @@ def ask_ki( """ self._register_ki_locally( KnowledgeInteractionContext( - info=AskAnswerInteractionInfo( + definition=AskAnswerKnowledgeInteraction( type=KiTypes.ASK, name=name, prefixes=prefixes or dict(), @@ -427,7 +432,7 @@ def answer_ki( the KE runtime. """ return self._register_ki_decorator( - info=AskAnswerInteractionInfo( + definition=AskAnswerKnowledgeInteraction( type=KiTypes.ANSWER, name=name, prefixes=prefixes or dict(), @@ -459,7 +464,7 @@ def post_ki( """ self._register_ki_locally( KnowledgeInteractionContext( - info=PostReactInteractionInfo( + definition=PostReactKnowledgeInteraction( type=KiTypes.POST, name=name, prefixes=prefixes or dict(), @@ -494,7 +499,7 @@ def react_ki( the KE runtime. """ return self._register_ki_decorator( - info=PostReactInteractionInfo( + definition=PostReactKnowledgeInteraction( type=KiTypes.REACT, name=name, prefixes=prefixes or dict(), @@ -525,21 +530,21 @@ async def post( ValueError: If the KI is not registered at the KE runtime. """ ki_ctx = self.ki_registry[ki_name] - if ki_ctx.info.type != KiTypes.POST: + if ki_ctx.definition.type != KiTypes.POST: raise ValueError( - f"KI named '{ki_name}' is of type {ki_ctx.info.type}, not POST, and " - f"cannot be called with the post() method." + f"KI named '{ki_name}' is of type {ki_ctx.definition.type}, not " + f"POST, and cannot be called with the post() method." ) if ki_ctx.status != KnowledgeInteractionStatus.REGISTERED: raise ValueError( f"Cannot call KI '{ki_name}' because it is not registered. Please " f"register the KB and sync KIs first." ) - assert ki_ctx.info.id is not None # Should always be set for registered KIs + assert ki_ctx.ke_id is not None # Should always be set for registered KIs post_result = await self.client.post( kb_id=self.info.id, - ki_id=ki_ctx.info.id, + ki_id=ki_ctx.ke_id, binding_set=ki_ctx.prepare_outgoing(binding_set), ) return ki_ctx.parse_result(post_result.result_binding_set) @@ -554,21 +559,21 @@ async def ask( ValueError: If the KI is not registered at the KE runtime. """ ki_ctx = self.ki_registry[ki_name] - if ki_ctx.info.type != KiTypes.ASK: + if ki_ctx.definition.type != KiTypes.ASK: raise ValueError( - f"KI named '{ki_name}' is of type {ki_ctx.info.type}, not ASK, and " - f"cannot be called with the ask() method." + f"KI named '{ki_name}' is of type {ki_ctx.definition.type}, not " + f"ASK, and cannot be called with the ask() method." ) if ki_ctx.status != KnowledgeInteractionStatus.REGISTERED: raise ValueError( f"Cannot call KI '{ki_name}' because it is not registered. Please " f"register the KB and sync KIs first." ) - assert ki_ctx.info.id is not None # Should always be set for registered KIs + assert ki_ctx.ke_id is not None # Should always be set for registered KIs ask_result = await self.client.ask( kb_id=self.info.id, - ki_id=ki_ctx.info.id, + ki_id=ki_ctx.ke_id, binding_set=ki_ctx.prepare_outgoing(binding_set), ) return ki_ctx.parse_result(ask_result.binding_set) @@ -684,13 +689,13 @@ async def _handle( try: result_binding_set = await self.call( handle_request.binding_set, - ki_ctx.info.name, + ki_ctx.definition.name, ) except Exception: logger.exception( "Handler for KI '%s' raised an exception " "(request %d from %s). Posting empty binding set.", - ki_ctx.info.name, + ki_ctx.definition.name, handle_request.handle_request_id, handle_request.requesting_knowledge_base_id, ) @@ -707,7 +712,7 @@ async def _handle( logger.exception( "Failed to post handle response for KI '%s' " "(request %d from %s).", - ki_ctx.info.name, + ki_ctx.definition.name, handle_request.handle_request_id, handle_request.requesting_knowledge_base_id, ) diff --git a/src/knowledge_mapper/ke/client.py b/src/knowledge_mapper/ke/client.py index a74f095..c64116e 100644 --- a/src/knowledge_mapper/ke/client.py +++ b/src/knowledge_mapper/ke/client.py @@ -13,10 +13,12 @@ BindingSet, KiTypes, KnowledgeBaseInfo, + KnowledgeInteraction, KnowledgeInteractionInfo, PostReactInteractionInfo, PostResult, SmartConnectorLease, + info_from_definition, ) logger = logging.getLogger(__name__) @@ -110,10 +112,10 @@ async def get_all_knowledge_interactions( ... async def register_ki( - self, kb_id: str, ki: KnowledgeInteractionInfo + self, kb_id: str, ki: KnowledgeInteraction ) -> KnowledgeInteractionInfo: - """Register a knowledge interaction for the given KB and return it with its - assigned ID set in the info. + """Register a knowledge interaction for the given KB and return the + :class:`KnowledgeInteractionInfo` reported by the KE (with its assigned id). Raises: SmartConnectorNotFoundError: If no smart connector exists for the given KB @@ -329,7 +331,7 @@ async def get_all_knowledge_interactions( return kis async def register_ki( - self, kb_id: str, ki: KnowledgeInteractionInfo + self, kb_id: str, ki: KnowledgeInteraction ) -> KnowledgeInteractionInfo: logger.debug( "Registering knowledge interaction '%s' for KB '%s' at %s.", @@ -347,10 +349,7 @@ async def register_ki( if not response.is_success: raise UnexpectedHttpResponseError(response) - registered_ki = ki.model_copy( - update={"id": response.json()["knowledgeInteractionId"]} - ) - return registered_ki + return info_from_definition(ki, response.json()["knowledgeInteractionId"]) async def unregister_ki(self, kb_id: str, ki_id: str) -> None: logger.debug( diff --git a/src/knowledge_mapper/ke/models.py b/src/knowledge_mapper/ke/models.py index b28ad7f..514cd55 100644 --- a/src/knowledge_mapper/ke/models.py +++ b/src/knowledge_mapper/ke/models.py @@ -140,17 +140,54 @@ class KiTypes(StrEnum): REACT = "ReactKnowledgeInteraction" -class KnowledgeInteractionInfo(BaseModel): +class KnowledgeInteraction(BaseModel): + """A knowledge interaction defined by the application. + + Carries the user-supplied identity (``name``, which is required) plus the + graph patterns and prefixes that describe the interaction. Used when + configuring a KB (from settings or programmatically) and when registering + the KI with the KE. The KE-assigned ``id`` lives on + :class:`KnowledgeInteractionInfo`, which is returned by the KE. + """ + model_config = ConfigDict( alias_generator=to_camel, extra="allow", frozen=True, populate_by_name=True ) type: Annotated[KiTypes, Field(..., alias="knowledgeInteractionType")] - id: Annotated[str | None, Field(..., alias="knowledgeInteractionId")] = None name: Annotated[str, Field(..., alias="knowledgeInteractionName")] prefixes: Annotated[dict[str, str], Field(default_factory=dict)] +class AskAnswerKnowledgeInteraction(KnowledgeInteraction): + graph_pattern: str + + +class PostReactKnowledgeInteraction(KnowledgeInteraction): + argument_graph_pattern: str + result_graph_pattern: str | None = None + + +class KnowledgeInteractionInfo(BaseModel): + """A knowledge interaction as reported by the KE. + + Always carries a KE-assigned ``id``; ``name`` may be ``None`` because KIs + registered by other knowledge bases (not via this knowledge-mapper) may be + anonymous on the wire. + """ + + model_config = ConfigDict( + alias_generator=to_camel, extra="allow", frozen=True, populate_by_name=True + ) + + type: Annotated[KiTypes, Field(..., alias="knowledgeInteractionType")] + id: Annotated[str, Field(..., alias="knowledgeInteractionId")] + name: Annotated[ + str | None, Field(default=None, alias="knowledgeInteractionName") + ] = None + prefixes: Annotated[dict[str, str], Field(default_factory=dict)] + + class AskAnswerInteractionInfo(KnowledgeInteractionInfo): graph_pattern: str @@ -160,6 +197,20 @@ class PostReactInteractionInfo(KnowledgeInteractionInfo): result_graph_pattern: str | None = None +def info_from_definition( + definition: KnowledgeInteraction, id: str +) -> KnowledgeInteractionInfo: + """Build a :class:`KnowledgeInteractionInfo` from a user-defined + :class:`KnowledgeInteraction` and the KE-assigned ``id``.""" + data = definition.model_dump(by_alias=True) + data["knowledgeInteractionId"] = id + if isinstance(definition, AskAnswerKnowledgeInteraction): + return AskAnswerInteractionInfo.model_validate(data) + if isinstance(definition, PostReactKnowledgeInteraction): + return PostReactInteractionInfo.model_validate(data) + return KnowledgeInteractionInfo.model_validate(data) + + class Initiator(StrEnum): KNOWLEDGE_BASE = "knowledgeBase" REASONER = "reasoner" diff --git a/src/knowledge_mapper/knowledge_interaction.py b/src/knowledge_mapper/knowledge_interaction.py index 2ee7db7..4e2dd16 100644 --- a/src/knowledge_mapper/knowledge_interaction.py +++ b/src/knowledge_mapper/knowledge_interaction.py @@ -6,14 +6,14 @@ from typing import Any, Concatenate, get_args from .dependency_injection import resolve_dependencies -from .ke.models import BindingModel, BindingSet, KiTypes, KnowledgeInteractionInfo +from .ke.models import BindingModel, BindingSet, KiTypes, KnowledgeInteraction type _HandlerReturn = BindingSet | Sequence[BindingModel] type Handler[B, **P] = ( - Callable[Concatenate[B, KnowledgeInteractionInfo, P], _HandlerReturn] + Callable[Concatenate[B, KnowledgeInteraction, P], _HandlerReturn] | Callable[ - Concatenate[B, KnowledgeInteractionInfo, P], + Concatenate[B, KnowledgeInteraction, P], Coroutine[Any, Any, _HandlerReturn], ] ) @@ -26,14 +26,15 @@ class KnowledgeInteractionStatus(StrEnum): @dataclass class KnowledgeInteractionContext[B, **P]: - info: KnowledgeInteractionInfo + definition: KnowledgeInteraction handler: Handler[B, P] | None status: KnowledgeInteractionStatus = KnowledgeInteractionStatus.UNREGISTERED + ke_id: str | None = None validation_model: type[BindingModel] | None = None serialization_model: type[BindingModel] | None = None def __post_init__(self): - if self.info.type == KiTypes.ANSWER or self.info.type == KiTypes.REACT: + if self.definition.type in (KiTypes.ANSWER, KiTypes.REACT): if not callable(self.handler): raise ValueError("Handler must be a callable.") @@ -67,10 +68,12 @@ async def dispatch( input_data = binding_set if inspect.iscoroutinefunction(self.handler): - result_bindings = await self.handler(input_data, self.info, **dep_kwargs) + result_bindings = await self.handler( + input_data, self.definition, **dep_kwargs + ) else: result_bindings = await asyncio.to_thread( - self.handler, input_data, self.info, **dep_kwargs + self.handler, input_data, self.definition, **dep_kwargs ) if self.serialization_model and result_bindings: diff --git a/src/knowledge_mapper/settings.py b/src/knowledge_mapper/settings.py index e155933..8bc1f45 100644 --- a/src/knowledge_mapper/settings.py +++ b/src/knowledge_mapper/settings.py @@ -1,4 +1,6 @@ -from pydantic import Field +from typing import Annotated, Any + +from pydantic import Discriminator, Field, Tag from pydantic_settings import ( BaseSettings, JsonConfigSettingsSource, @@ -7,7 +9,42 @@ YamlConfigSettingsSource, ) -from .ke.models import KnowledgeBaseInfo, KnowledgeInteractionInfo +from .ke.models import ( + AskAnswerKnowledgeInteraction, + KiTypes, + KnowledgeBaseInfo, + KnowledgeInteraction, + PostReactKnowledgeInteraction, +) + + +def _ki_discriminator(v: Any) -> str: + if isinstance(v, dict): + t = v.get("type") or v.get("knowledgeInteractionType") + else: + t = getattr(v, "type", None) + if t in ( + KiTypes.ASK, + KiTypes.ANSWER, + KiTypes.ASK.value, + KiTypes.ANSWER.value, + ): + return "ask_answer" + if t in ( + KiTypes.POST, + KiTypes.REACT, + KiTypes.POST.value, + KiTypes.REACT.value, + ): + return "post_react" + raise ValueError(f"Unknown knowledge interaction type: {t!r}") + + +KnowledgeInteractionUnion = Annotated[ + Annotated[AskAnswerKnowledgeInteraction, Tag("ask_answer")] + | Annotated[PostReactKnowledgeInteraction, Tag("post_react")], + Discriminator(_ki_discriminator), +] class KnowledgeBaseSettings(BaseSettings): @@ -62,7 +99,9 @@ def settings_customise_sources(cls, settings_cls, **kwargs): knowledge_base: KnowledgeBaseInfo knowledge_engine_endpoint: str - knowledge_interactions: list[KnowledgeInteractionInfo] = Field(default_factory=list) + knowledge_interactions: list[KnowledgeInteractionUnion] = Field( + default_factory=list + ) @classmethod def settings_customise_sources( @@ -81,7 +120,7 @@ def settings_customise_sources( JsonConfigSettingsSource(settings_cls), ) - def get_configured_interaction(self, name: str) -> KnowledgeInteractionInfo: + def get_configured_interaction(self, name: str) -> KnowledgeInteraction: for ki in self.knowledge_interactions: if ki.name == name: return ki diff --git a/src/knowledge_mapper/testing/fake_client.py b/src/knowledge_mapper/testing/fake_client.py index 02aa4f1..812b61c 100644 --- a/src/knowledge_mapper/testing/fake_client.py +++ b/src/knowledge_mapper/testing/fake_client.py @@ -11,9 +11,11 @@ ExchangeInfo, Initiator, KnowledgeBaseInfo, + KnowledgeInteraction, KnowledgeInteractionInfo, PostResult, SmartConnectorLease, + info_from_definition, ) @@ -71,9 +73,9 @@ async def get_all_knowledge_interactions( return list(self._knowledge_interactions.get(kb_id, [])) async def register_ki( - self, kb_id: str, ki: KnowledgeInteractionInfo + self, kb_id: str, ki: KnowledgeInteraction ) -> KnowledgeInteractionInfo: - registered = ki.model_copy(update={"id": f"fake-ki-{self._next_ki_id}"}) + registered = info_from_definition(ki, f"fake-ki-{self._next_ki_id}") self._next_ki_id += 1 self._knowledge_interactions.setdefault(kb_id, []).append(registered) return registered @@ -162,7 +164,7 @@ def enqueue_handle_request( ), None, ) - if ki is None or ki.id is None: + if ki is None: raise KeyError( f"No registered KI named '{ki_name}' found in TestClient. " "Register the KI before enqueueing a handle request." diff --git a/tests/configuration/test_configuration.py b/tests/configuration/test_configuration.py index d180073..a3c6a98 100644 --- a/tests/configuration/test_configuration.py +++ b/tests/configuration/test_configuration.py @@ -49,10 +49,10 @@ def react(binding_set, info): kb = builder.build() ask_ki = kb.ki_registry["ask-from-settings"] - assert ask_ki.info.name == "ask-from-settings" + assert ask_ki.definition.name == "ask-from-settings" post_ki = kb.ki_registry["post-from-settings"] - assert post_ki.info.name == "post-from-settings" + assert post_ki.definition.name == "post-from-settings" answer_ki = kb.ki_registry["answer-from-settings"] - assert answer_ki.info.name == "answer-from-settings" + assert answer_ki.definition.name == "answer-from-settings" react_ki = kb.ki_registry["react-from-settings"] - assert react_ki.info.name == "react-from-settings" + assert react_ki.definition.name == "react-from-settings" diff --git a/tests/test_client.py b/tests/test_client.py index 83f2f0e..b805559 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -9,8 +9,8 @@ ) from knowledge_mapper.ke.models import ( AskAnswerInteractionInfo, + AskAnswerKnowledgeInteraction, KnowledgeBaseInfo, - KnowledgeInteractionInfo, PostReactInteractionInfo, SmartConnectorLease, ) @@ -174,10 +174,10 @@ async def test_register_knowledge_interaction(client: Client): ): registered_ki = await client.register_ki( kb_id="http://example.org/test#kb", - ki=KnowledgeInteractionInfo( + ki=AskAnswerKnowledgeInteraction( type="AskKnowledgeInteraction", name="ask-interaction", - graph_pattern="?s ?p ?o . ", # pyright: ignore[reportCallIssue] + graph_pattern="?s ?p ?o . ", prefixes={"test": "http://example.org/test#"}, ), ) diff --git a/tests/test_dispatch.py b/tests/test_dispatch.py index dd9e42a..e9922b3 100644 --- a/tests/test_dispatch.py +++ b/tests/test_dispatch.py @@ -3,12 +3,12 @@ from rdflib import URIRef from knowledge_mapper.ke.models import ( - AskAnswerInteractionInfo, + AskAnswerKnowledgeInteraction, BindingModel, BindingSet, KiTypes, Literal, - PostReactInteractionInfo, + PostReactKnowledgeInteraction, Uri, ) from knowledge_mapper.knowledge_interaction import KnowledgeInteractionContext @@ -39,7 +39,7 @@ def handler(binding_set: BindingSet, info) -> BindingSet: return [{"sensor": b["sensor"]} for b in binding_set] ctx = KnowledgeInteractionContext( - info=AskAnswerInteractionInfo( + definition=AskAnswerKnowledgeInteraction( type=KiTypes.ANSWER, name="ki", prefixes={}, graph_pattern=GRAPH_PATTERN ), handler=handler, @@ -56,7 +56,7 @@ def handler(binding_set: list[SensorBinding], info) -> list[SensorBinding]: return binding_set ctx = KnowledgeInteractionContext( - info=AskAnswerInteractionInfo( + definition=AskAnswerKnowledgeInteraction( type=KiTypes.ANSWER, name="ki", prefixes={}, graph_pattern=GRAPH_PATTERN ), handler=handler, @@ -74,7 +74,7 @@ def handler(binding_set: list[MeasurementBinding], info) -> list[ResultBinding]: return [ResultBinding(measurement=b.measurement) for b in binding_set] ctx = KnowledgeInteractionContext( - info=PostReactInteractionInfo( + definition=PostReactKnowledgeInteraction( type=KiTypes.REACT, name="ki", prefixes={}, @@ -100,7 +100,7 @@ def handler(binding_set: list[MeasurementBinding], info) -> list[ResultBinding]: def test_prepare_outgoing_no_model(): """Without a serialization model, bindings pass through untouched.""" ctx = KnowledgeInteractionContext( - info=AskAnswerInteractionInfo( + definition=AskAnswerKnowledgeInteraction( type=KiTypes.ASK, name="ki", prefixes={}, graph_pattern=GRAPH_PATTERN ), handler=None, @@ -113,7 +113,7 @@ def test_prepare_outgoing_no_model(): def test_prepare_outgoing_with_model(): """With a serialization model, BindingModels are dumped to raw dicts.""" ctx = KnowledgeInteractionContext( - info=AskAnswerInteractionInfo( + definition=AskAnswerKnowledgeInteraction( type=KiTypes.ASK, name="ki", prefixes={}, graph_pattern=GRAPH_PATTERN ), handler=None, @@ -131,7 +131,7 @@ def test_prepare_outgoing_with_model(): def test_parse_result_no_model(): """Without a validation model, bindings pass through untouched.""" ctx = KnowledgeInteractionContext( - info=AskAnswerInteractionInfo( + definition=AskAnswerKnowledgeInteraction( type=KiTypes.ASK, name="ki", prefixes={}, graph_pattern=GRAPH_PATTERN ), handler=None, @@ -144,7 +144,7 @@ def test_parse_result_no_model(): def test_parse_result_with_model(): """With a validation model, raw dicts are validated into BindingModels.""" ctx = KnowledgeInteractionContext( - info=AskAnswerInteractionInfo( + definition=AskAnswerKnowledgeInteraction( type=KiTypes.ASK, name="ki", prefixes={}, graph_pattern=GRAPH_PATTERN ), handler=None, @@ -159,7 +159,7 @@ def test_parse_result_with_model(): def test_parse_result_empty_binding_set(): """parse_result with an empty binding set returns it as-is.""" ctx = KnowledgeInteractionContext( - info=AskAnswerInteractionInfo( + definition=AskAnswerKnowledgeInteraction( type=KiTypes.ASK, name="ki", prefixes={}, graph_pattern=GRAPH_PATTERN ), handler=None, @@ -179,7 +179,7 @@ async def handler(binding_set: BindingSet, info) -> BindingSet: return [{"sensor": b["sensor"]} for b in binding_set] ctx = KnowledgeInteractionContext( - info=AskAnswerInteractionInfo( + definition=AskAnswerKnowledgeInteraction( type=KiTypes.ANSWER, name="ki", prefixes={}, graph_pattern=GRAPH_PATTERN ), handler=handler, @@ -202,7 +202,7 @@ def handler(binding_set: BindingSet, info) -> BindingSet: return binding_set ctx = KnowledgeInteractionContext( - info=AskAnswerInteractionInfo( + definition=AskAnswerKnowledgeInteraction( type=KiTypes.ANSWER, name="ki", prefixes={}, graph_pattern=GRAPH_PATTERN ), handler=handler, diff --git a/tests/test_handling_loop.py b/tests/test_handling_loop.py index af83b88..8f55a9b 100644 --- a/tests/test_handling_loop.py +++ b/tests/test_handling_loop.py @@ -8,7 +8,7 @@ from knowledge_mapper import KnowledgeBase from knowledge_mapper.ke.models import ( BindingSet, - KnowledgeInteractionInfo, + KnowledgeInteraction, ) from knowledge_mapper.testing import TestClient @@ -34,9 +34,7 @@ async def kb(client: TestClient) -> KnowledgeBase: name="echo-ki", graph_pattern="?s ?p ?o .", ) - def echo_handler( - binding_set: BindingSet, info: KnowledgeInteractionInfo - ) -> BindingSet: + def echo_handler(binding_set: BindingSet, info: KnowledgeInteraction) -> BindingSet: captured.append(binding_set) return binding_set @@ -114,7 +112,7 @@ async def test_concurrent_dispatch_overlaps_in_time(client: TestClient): @kb.answer_ki(name="slow-ki", graph_pattern="?s ?p ?o .") async def slow_handler( - binding_set: BindingSet, info: KnowledgeInteractionInfo + binding_set: BindingSet, info: KnowledgeInteraction ) -> BindingSet: handler_entries.append(time.monotonic()) await asyncio.sleep(0.1) @@ -151,13 +149,13 @@ async def test_handler_exception_posts_empty_binding_set(client: TestClient): @kb.answer_ki(name="boom-ki", graph_pattern="?s ?p ?o .") async def boom_handler( - binding_set: BindingSet, info: KnowledgeInteractionInfo + binding_set: BindingSet, info: KnowledgeInteraction ) -> BindingSet: raise RuntimeError("handler exploded") @kb.answer_ki(name="ok-ki", graph_pattern="?s ?p ?o .") async def ok_handler( - binding_set: BindingSet, info: KnowledgeInteractionInfo + binding_set: BindingSet, info: KnowledgeInteraction ) -> BindingSet: return binding_set @@ -190,7 +188,7 @@ async def test_exit_awaits_in_flight_handlers(client: TestClient): @kb.answer_ki(name="slow-ki", graph_pattern="?s ?p ?o .") async def slow_handler( - binding_set: BindingSet, info: KnowledgeInteractionInfo + binding_set: BindingSet, info: KnowledgeInteraction ) -> BindingSet: nonlocal handler_completed await asyncio.sleep(0.1) @@ -226,7 +224,7 @@ async def test_semaphore_bounds_concurrency(client: TestClient): @kb.answer_ki(name="counting-ki", graph_pattern="?s ?p ?o .") async def counting_handler( - binding_set: BindingSet, info: KnowledgeInteractionInfo + binding_set: BindingSet, info: KnowledgeInteraction ) -> BindingSet: nonlocal max_observed, current async with lock: @@ -262,9 +260,7 @@ async def test_event_loop_stored_on_kb(client: TestClient): kb.client = client @kb.answer_ki(name="noop-ki", graph_pattern="?s ?p ?o .") - async def noop( - binding_set: BindingSet, info: KnowledgeInteractionInfo - ) -> BindingSet: + async def noop(binding_set: BindingSet, info: KnowledgeInteraction) -> BindingSet: return binding_set await kb.register() diff --git a/tests/test_kb_new_operations.py b/tests/test_kb_new_operations.py index 83170e8..ed7ab6d 100644 --- a/tests/test_kb_new_operations.py +++ b/tests/test_kb_new_operations.py @@ -10,7 +10,7 @@ from knowledge_mapper import KnowledgeBase from knowledge_mapper.ke.errors import SmartConnectorNotFoundError from knowledge_mapper.ke.models import ( - KnowledgeInteractionInfo, + KnowledgeInteraction, SmartConnectorLease, ) from knowledge_mapper.testing import TestClient @@ -34,7 +34,7 @@ async def test_unregister_ki_removes_from_registries_and_calls_client(): ki_ctx=_ki_ctx("ask-it"), ) assert "ask-it" in kb.ki_registry - ki_id = kb.ki_registry["ask-it"].info.id + ki_id = kb.ki_registry["ask-it"].ke_id assert ki_id is not None and ki_id in kb._ki_registry_by_id await kb.unregister_ki("ask-it") @@ -102,14 +102,14 @@ async def test_load_domain_knowledge_when_kb_not_registered_raises(): def _ki_ctx(name: str): """Build a registered ASK-style KI context for use in unregister tests.""" - from knowledge_mapper.ke.models import AskAnswerInteractionInfo, KiTypes + from knowledge_mapper.ke.models import AskAnswerKnowledgeInteraction, KiTypes from knowledge_mapper.knowledge_interaction import ( KnowledgeInteractionContext, KnowledgeInteractionStatus, ) - return KnowledgeInteractionContext[KnowledgeInteractionInfo, ...]( - info=AskAnswerInteractionInfo( + return KnowledgeInteractionContext[KnowledgeInteraction, ...]( + definition=AskAnswerKnowledgeInteraction( type=KiTypes.ASK, name=name, graph_pattern="?s ?p ?o . ", diff --git a/tests/test_ki_registration.py b/tests/test_ki_registration.py index 78fa0e4..31bf566 100644 --- a/tests/test_ki_registration.py +++ b/tests/test_ki_registration.py @@ -2,10 +2,10 @@ from knowledge_mapper import KnowledgeBase from knowledge_mapper.ke.models import ( - AskAnswerInteractionInfo, + AskAnswerKnowledgeInteraction, BindingSet, KiTypes, - KnowledgeInteractionInfo, + KnowledgeInteraction, ) from knowledge_mapper.knowledge_interaction import ( KnowledgeInteractionContext, @@ -33,11 +33,11 @@ def shared_prefixes(): def ki_ctx_setup() -> KnowledgeInteractionContext: - def handler(binding_set: BindingSet, info: KnowledgeInteractionInfo) -> BindingSet: + def handler(binding_set: BindingSet, info: KnowledgeInteraction) -> BindingSet: return binding_set return KnowledgeInteractionContext( - info=AskAnswerInteractionInfo( + definition=AskAnswerKnowledgeInteraction( name="test-ki", type=KiTypes.ANSWER, graph_pattern="""?s ?p ?o . """, @@ -54,7 +54,7 @@ async def test_register_ki(): await kb.register_ki(ki_ctx=ki_ctx_setup()) assert len(kb.ki_registry) == 1 ki_ctx = next(iter(kb.ki_registry.values())) - assert ki_ctx.info.name == "test-ki" + assert ki_ctx.definition.name == "test-ki" async def test_register_ki_before_kb_registration(): @@ -128,17 +128,15 @@ async def test_register_answer_ki(): """, prefixes=shared_prefixes(), ) - def answer_test( - binding_set: BindingSet, info: KnowledgeInteractionInfo - ) -> BindingSet: + def answer_test(binding_set: BindingSet, info: KnowledgeInteraction) -> BindingSet: return binding_set await kb.register() assert len(kb.ki_registry) == 1 - ki_info = next(iter(kb.ki_registry.values())).info - assert ki_info.name == "answer-test" - assert ki_info.type == KiTypes.ANSWER + ki_def = next(iter(kb.ki_registry.values())).definition + assert ki_def.name == "answer-test" + assert ki_def.type == KiTypes.ANSWER async def test_register_react_ki(): @@ -156,17 +154,15 @@ async def test_register_react_ki(): """, prefixes=shared_prefixes(), ) - def react_test( - binding_set: BindingSet, info: KnowledgeInteractionInfo - ) -> BindingSet: + def react_test(binding_set: BindingSet, info: KnowledgeInteraction) -> BindingSet: return binding_set await kb.register() assert len(kb.ki_registry) == 1 - ki_info = next(iter(kb.ki_registry.values())).info - assert ki_info.name == "react-test" - assert ki_info.type == KiTypes.REACT + ki_def = next(iter(kb.ki_registry.values())).definition + assert ki_def.name == "react-test" + assert ki_def.type == KiTypes.REACT def test_register_ki_with_same_name(): @@ -179,7 +175,7 @@ def test_register_ki_with_same_name(): """, ) def first_handler( - binding_set: BindingSet, info: KnowledgeInteractionInfo + binding_set: BindingSet, info: KnowledgeInteraction ) -> BindingSet: return binding_set @@ -191,7 +187,7 @@ def first_handler( result_graph_pattern="""?s ?p ?o . """, ) def second_handler( - binding_set: BindingSet, info: KnowledgeInteractionInfo + binding_set: BindingSet, info: KnowledgeInteraction ) -> BindingSet: return binding_set @@ -227,14 +223,12 @@ async def test_call_handler(): """, prefixes=shared_prefixes(), ) - def echo_handler( - binding_set: BindingSet, info: KnowledgeInteractionInfo - ) -> BindingSet: + def echo_handler(binding_set: BindingSet, info: KnowledgeInteraction) -> BindingSet: return binding_set await kb.register() - ki_info = next(iter(kb.ki_registry.values())).info + ki_def = next(iter(kb.ki_registry.values())).definition input_binding_set = [{"input": "test:Input1", "value": "Hello"}] - result = await kb.call(binding_set=input_binding_set, ki_name=ki_info.name) + result = await kb.call(binding_set=input_binding_set, ki_name=ki_def.name) assert result == input_binding_set diff --git a/tests/test_knowledge_base_builder.py b/tests/test_knowledge_base_builder.py index 1f35fdc..ed199e4 100644 --- a/tests/test_knowledge_base_builder.py +++ b/tests/test_knowledge_base_builder.py @@ -2,17 +2,17 @@ from knowledge_mapper import KnowledgeBase, KnowledgeBaseSettings from knowledge_mapper.ke.models import ( - AskAnswerInteractionInfo, + AskAnswerKnowledgeInteraction, BindingSet, KiTypes, KnowledgeBaseInfo, - KnowledgeInteractionInfo, - PostReactInteractionInfo, + KnowledgeInteraction, + PostReactKnowledgeInteraction, ) def settings_factory( - ki_infos: list[KnowledgeInteractionInfo] | None = None, + ki_infos: list[KnowledgeInteraction] | None = None, ) -> KnowledgeBaseSettings: return KnowledgeBaseSettings( knowledge_base=KnowledgeBaseInfo( @@ -25,20 +25,20 @@ def settings_factory( ) -def answer_ki_info(name: str = "answer-ki") -> AskAnswerInteractionInfo: - return AskAnswerInteractionInfo( +def answer_ki_info(name: str = "answer-ki") -> AskAnswerKnowledgeInteraction: + return AskAnswerKnowledgeInteraction( name=name, type=KiTypes.ANSWER, graph_pattern="?s ?p ?o ." ) -def ask_ki_info(name: str = "ask-ki") -> AskAnswerInteractionInfo: - return AskAnswerInteractionInfo( +def ask_ki_info(name: str = "ask-ki") -> AskAnswerKnowledgeInteraction: + return AskAnswerKnowledgeInteraction( name=name, type=KiTypes.ASK, graph_pattern="?s ?p ?o ." ) -def post_ki_info(name: str = "post-ki") -> PostReactInteractionInfo: - return PostReactInteractionInfo( +def post_ki_info(name: str = "post-ki") -> PostReactKnowledgeInteraction: + return PostReactKnowledgeInteraction( name=name, type=KiTypes.POST, argument_graph_pattern="?s ?p ?o .", @@ -46,8 +46,8 @@ def post_ki_info(name: str = "post-ki") -> PostReactInteractionInfo: ) -def react_ki_info(name: str = "react-ki") -> PostReactInteractionInfo: - return PostReactInteractionInfo( +def react_ki_info(name: str = "react-ki") -> PostReactKnowledgeInteraction: + return PostReactKnowledgeInteraction( name=name, type=KiTypes.REACT, argument_graph_pattern="?s ?p ?o .", @@ -55,9 +55,7 @@ def react_ki_info(name: str = "react-ki") -> PostReactInteractionInfo: ) -def dummy_handler( - binding_set: BindingSet, info: KnowledgeInteractionInfo -) -> BindingSet: +def dummy_handler(binding_set: BindingSet, info: KnowledgeInteraction) -> BindingSet: return binding_set diff --git a/tests/test_sync_bridge.py b/tests/test_sync_bridge.py index 62628a6..f440f6a 100644 --- a/tests/test_sync_bridge.py +++ b/tests/test_sync_bridge.py @@ -3,7 +3,7 @@ import pytest from knowledge_mapper import KnowledgeBase -from knowledge_mapper.ke.models import BindingSet, KnowledgeInteractionInfo +from knowledge_mapper.ke.models import BindingSet, KnowledgeInteraction from knowledge_mapper.testing import TestClient @@ -52,9 +52,7 @@ async def test_ask_sync_from_sync_handler(kb: KnowledgeBase, client: TestClient) argument_graph_pattern="?x a ?t .", result_graph_pattern="?x a ?t .", ) - def sync_handler( - binding_set: BindingSet, info: KnowledgeInteractionInfo - ) -> BindingSet: + def sync_handler(binding_set: BindingSet, info: KnowledgeInteraction) -> BindingSet: result = kb.ask_sync([{}], ki_name="lookup") ask_result_capture.append(result) return binding_set @@ -107,9 +105,7 @@ async def test_post_sync_from_sync_handler(kb: KnowledgeBase, client: TestClient argument_graph_pattern="?x a ?t .", result_graph_pattern="?x a ?t .", ) - def sync_handler( - binding_set: BindingSet, info: KnowledgeInteractionInfo - ) -> BindingSet: + def sync_handler(binding_set: BindingSet, info: KnowledgeInteraction) -> BindingSet: result = kb.post_sync([{"x": "ex:A", "t": "ex:Thing"}], ki_name="push") post_result_capture.append(result) return binding_set