Skip to content

Commit 544d911

Browse files
committed
feat: reload package
WIP Ref: questionpy-org/questionpy-sdk#101
1 parent 15fa81e commit 544d911

File tree

5 files changed

+33
-16
lines changed

5 files changed

+33
-16
lines changed

questionpy_server/worker/runtime/manager.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,8 @@ def on_msg_load_qpy_package(self, msg: LoadQPyPackage) -> MessageToServer:
110110
packages=self.loaded_packages,
111111
main_package=package,
112112
_on_request_callbacks=self._on_request_callbacks,
113-
)
113+
),
114+
reload=msg.reload,
114115
)
115116
if not isinstance(qtype, BaseQuestionType):
116117
msg = f"Package initialization returned '{qtype}', BaseQuestionType expected"

questionpy_server/worker/runtime/messages.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ class LoadQPyPackage(MessageToWorker):
106106
location: PackageLocation
107107
main: bool
108108
"""Set this package as the main package and execute its entry point."""
109+
reload: bool = False
110+
"""Force reload of module."""
109111

110112
class Response(MessageToServer):
111113
"""Success message in return to LoadQPyPackage."""

questionpy_server/worker/runtime/package.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
# This file is part of the QuestionPy Server. (https://questionpy.org)
22
# The QuestionPy Server is free software released under terms of the MIT license. See LICENSE.md.
33
# (c) Technische Universität Berlin, innoCampus <[email protected]>
4+
import importlib
45
import inspect
56
import json
67
import sys
78
import zipfile
89
from abc import ABC, abstractmethod
910
from functools import cached_property
10-
from importlib import import_module, resources
1111
from importlib.resources.abc import Traversable
1212
from pathlib import Path
1313
from types import ModuleType
@@ -38,20 +38,23 @@ class ImportablePackage(ABC, Package):
3838
def setup_imports(self) -> None:
3939
"""Modifies ``sys.path`` to include the package's python code."""
4040

41-
def init_as_main(self, env: Environment) -> BaseQuestionType:
41+
def init_as_main(self, env: Environment, *, reload: bool = False) -> BaseQuestionType:
4242
"""Imports the package's entrypoint and executes its ``init`` function.
4343
4444
:meth:`setup_imports` should be called beforehand to allow the import.
4545
"""
4646
set_qpy_environment(env)
4747

48-
main_module: ModuleType
49-
if self.manifest.entrypoint:
50-
main_module = import_module(
51-
f"{self.manifest.namespace}.{self.manifest.short_name}.{self.manifest.entrypoint}"
52-
)
53-
else:
54-
main_module = import_module(f"{self.manifest.namespace}.{self.manifest.short_name}")
48+
module_name = f"{self.manifest.namespace}.{self.manifest.short_name}"
49+
main_module_name = f"{module_name}.{self.manifest.entrypoint}" if self.manifest.entrypoint else module_name
50+
51+
main_module = importlib.import_module(main_module_name)
52+
53+
if reload:
54+
for name, module in sys.modules.copy().items():
55+
if name.startswith(module_name) and name != main_module_name:
56+
importlib.reload(module)
57+
main_module = importlib.reload(main_module)
5558

5659
if not hasattr(main_module, "init") or not callable(main_module.init):
5760
raise NoInitFunctionError(main_module)
@@ -141,16 +144,16 @@ def manifest(self) -> Manifest:
141144
return self._manifest
142145

143146
def get_path(self, path: str) -> Traversable:
144-
return resources.files(self.module_name).joinpath(path)
147+
return importlib.resources.files(self.module_name).joinpath(path)
145148

146149
def setup_imports(self) -> None:
147150
# Nothing to do.
148151
pass
149152

150-
def init_as_main(self, env: Environment) -> BaseQuestionType:
153+
def init_as_main(self, env: Environment, *, reload: bool = False) -> BaseQuestionType:
151154
set_qpy_environment(env)
152155

153-
main_module = import_module(self.module_name)
156+
main_module = importlib.import_module(self.module_name)
154157
init_function = getattr(main_module, self.function_name, None)
155158
if not init_function or not callable(init_function):
156159
raise NoInitFunctionError(main_module)

questionpy_server/worker/worker/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,14 @@ def send(self, message: MessageToWorker) -> None:
7575
async def get_resource_usage(self) -> WorkerResources | None:
7676
"""Get the worker's current resource usage. If unknown or unsupported, return None."""
7777

78+
@abstractmethod
79+
async def load_package(self, *, reload: bool = False) -> None:
80+
"""Loads a package.
81+
82+
Args:
83+
reload: Force reload of module.
84+
"""
85+
7886
@abstractmethod
7987
async def get_manifest(self) -> ComparableManifest:
8088
"""Get manifest of the main package in the worker."""

questionpy_server/worker/worker/base.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,7 @@ async def _initialize(self) -> None:
8080
),
8181
InitWorker.Response,
8282
)
83-
await self._send_and_wait_response(
84-
LoadQPyPackage(location=self.package, main=True), LoadQPyPackage.Response
85-
)
83+
await self.load_package()
8684
except WorkerNotRunningError as e:
8785
msg = "Worker has exited before or during initialization."
8886
raise WorkerStartError(msg) from e
@@ -169,6 +167,11 @@ async def stop(self, timeout: float) -> None:
169167
except TimeoutError:
170168
log.info("Worker was killed because it did not stop gracefully")
171169

170+
async def load_package(self, *, reload: bool = False) -> None:
171+
await self._send_and_wait_response(
172+
LoadQPyPackage(location=self.package, main=True, reload=reload), LoadQPyPackage.Response
173+
)
174+
172175
async def get_manifest(self) -> ComparableManifest:
173176
msg = GetQPyPackageManifest(path=str(self.package))
174177
ret = await self._send_and_wait_response(msg, GetQPyPackageManifest.Response)

0 commit comments

Comments
 (0)