Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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`.
Expand Down
6 changes: 3 additions & 3 deletions examples/02-binding_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
BindingModel,
BindingSet,
KnowledgeBase,
KnowledgeInteractionInfo,
KnowledgeInteraction,
Literal,
Uri,
)
Expand Down Expand Up @@ -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: "
Expand Down Expand Up @@ -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: "
Expand Down
10 changes: 5 additions & 5 deletions examples/05-custom-settings/05-custom_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
BindingSet,
KnowledgeBase,
KnowledgeBaseSettings,
KnowledgeInteractionInfo,
KnowledgeInteraction,
)

EXAMPLE_NAME = "custom-settings"
Expand Down Expand Up @@ -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

Expand All @@ -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}")
4 changes: 2 additions & 2 deletions examples/06-dependency_injection.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
BindingModel,
Depends,
KnowledgeBase,
KnowledgeInteractionInfo,
KnowledgeInteraction,
Literal,
Uri,
)
Expand Down Expand Up @@ -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]:
Expand Down
6 changes: 3 additions & 3 deletions examples/08-async_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from knowledge_mapper import (
BindingModel,
KnowledgeBase,
KnowledgeInteractionInfo,
KnowledgeInteraction,
Literal,
Uri,
)
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/knowledge_mapper/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion src/knowledge_mapper/dependency_injection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
16 changes: 9 additions & 7 deletions src/knowledge_mapper/kb/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,25 +51,27 @@ 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
type ASK or POST (outgoing KIs do not take handlers; they are registered
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

Expand All @@ -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,
),
)
Expand Down
Loading
Loading