From 61b851a0447063160f09dd747ab72e9c44c749a8 Mon Sep 17 00:00:00 2001 From: Maximilian Haye Date: Fri, 8 Nov 2024 14:49:06 +0100 Subject: [PATCH] feat: index Environment.packages by namespace and short name Closes: #123 --- pyproject.toml | 2 +- questionpy_common/environment.py | 19 ++++++++++++++----- questionpy_server/worker/impl/_base.py | 3 +-- questionpy_server/worker/runtime/manager.py | 15 ++++++++------- questionpy_server/worker/runtime/messages.py | 1 - 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index acd3cf2..f7a0bed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ description = "QuestionPy application server" authors = ["Technische Universität Berlin, innoCampus "] license = "MIT" homepage = "https://questionpy.org" -version = "0.3.1" +version = "0.4.0" packages = [ { include = "questionpy_common" }, { include = "questionpy_server" } diff --git a/questionpy_common/environment.py b/questionpy_common/environment.py index 0e6da4c..62a6fd0 100644 --- a/questionpy_common/environment.py +++ b/questionpy_common/environment.py @@ -6,7 +6,7 @@ from contextvars import ContextVar from dataclasses import dataclass from importlib.resources.abc import Traversable -from typing import Protocol, TypeAlias +from typing import NamedTuple, Protocol, TypeAlias from questionpy_common.api.package import QPyPackageInterface from questionpy_common.api.qtype import QuestionTypeInterface @@ -18,6 +18,7 @@ "OnRequestCallback", "Package", "PackageInitFunction", + "PackageNamespaceAndShortName", "RequestUser", "WorkerResourceLimits", "get_qpy_environment", @@ -42,10 +43,8 @@ class WorkerResourceLimits: class Package(Protocol): @property - @abstractmethod def manifest(self) -> Manifest: ... - @abstractmethod def get_path(self, path: str) -> Traversable: """Gets a [Traversable][] object which allows reading files from the package. @@ -59,6 +58,13 @@ def get_path(self, path: str) -> Traversable: OnRequestCallback: TypeAlias = Callable[[RequestUser], None] +class PackageNamespaceAndShortName(NamedTuple): + """Tuple of namespace and short name, identifying any version of a specific package.""" + + namespace: str + short_name: str + + class Environment(Protocol): type: str """The kind of worker we are running in. @@ -81,8 +87,11 @@ class Environment(Protocol): """ main_package: Package """The main package whose entrypoint was called.""" - packages: Mapping[str, Package] - """All packages loaded in the worker, including the main package.""" + packages: Mapping[PackageNamespaceAndShortName, Package] + """All packages loaded in the worker, including the main package. + + Keys are the package namespace and short name. Only one version of a package can be loaded at a time. + """ @abstractmethod def register_on_request_callback(self, callback: OnRequestCallback) -> None: diff --git a/questionpy_server/worker/impl/_base.py b/questionpy_server/worker/impl/_base.py index 5b0fea1..f6b6ef4 100644 --- a/questionpy_server/worker/impl/_base.py +++ b/questionpy_server/worker/impl/_base.py @@ -203,8 +203,7 @@ async def stop(self, timeout: float) -> None: log.info("Worker was killed because it did not stop gracefully") async def get_manifest(self) -> ComparableManifest: - msg = GetQPyPackageManifest(path=str(self.package)) - ret = await self.send_and_wait_for_response(msg, GetQPyPackageManifest.Response) + ret = await self.send_and_wait_for_response(GetQPyPackageManifest(), GetQPyPackageManifest.Response) return ComparableManifest(**ret.manifest.model_dump()) async def get_options_form( diff --git a/questionpy_server/worker/runtime/manager.py b/questionpy_server/worker/runtime/manager.py index 980f537..0f53141 100644 --- a/questionpy_server/worker/runtime/manager.py +++ b/questionpy_server/worker/runtime/manager.py @@ -11,6 +11,7 @@ from questionpy_common.environment import ( Environment, OnRequestCallback, + PackageNamespaceAndShortName, RequestUser, WorkerResourceLimits, get_qpy_environment, @@ -41,7 +42,7 @@ class EnvironmentImpl(Environment): type: str main_package: ImportablePackage - packages: dict[str, ImportablePackage] + packages: dict[PackageNamespaceAndShortName, ImportablePackage] _on_request_callbacks: list[OnRequestCallback] request_user: RequestUser | None = None limits: WorkerResourceLimits | None = None @@ -59,7 +60,7 @@ def __init__(self, server_connection: WorkerToServerConnection): self._connection: WorkerToServerConnection = server_connection self._worker_type: str | None = None - self._loaded_packages: dict[str, ImportablePackage] = {} + self._loaded_packages: dict[PackageNamespaceAndShortName, ImportablePackage] = {} self._limits: WorkerResourceLimits | None = None @@ -127,15 +128,15 @@ def on_msg_load_qpy_package(self, msg: LoadQPyPackage) -> MessageToServer: if msg.main: self._question_type = cast(QuestionTypeInterface, package_interface) - self._loaded_packages[str(msg.location)] = package + nssn = PackageNamespaceAndShortName(package.manifest.namespace, package.manifest.short_name) + self._loaded_packages[nssn] = package return LoadQPyPackage.Response() def on_msg_get_qpy_package_manifest(self, msg: GetQPyPackageManifest) -> MessageToServer: - if not self._worker_type: - self._raise_not_initialized(msg) + if not self._env: + self._raise_no_main_package_loaded(msg) - package = self._loaded_packages[msg.path] - return GetQPyPackageManifest.Response(manifest=package.manifest) + return GetQPyPackageManifest.Response(manifest=self._env.main_package.manifest) def on_msg_get_options_form_definition(self, msg: GetOptionsForm) -> MessageToServer: if not self._worker_type: diff --git a/questionpy_server/worker/runtime/messages.py b/questionpy_server/worker/runtime/messages.py index 1b7d398..e838181 100644 --- a/questionpy_server/worker/runtime/messages.py +++ b/questionpy_server/worker/runtime/messages.py @@ -118,7 +118,6 @@ class GetQPyPackageManifest(MessageToWorker): """Get the manifest data of the main package, which must previously have been loaded.""" message_id: ClassVar[MessageIds] = MessageIds.GET_QPY_PACKAGE_MANIFEST - path: str class Response(MessageToServer): """Execute a QuestionPy package."""