Skip to content

Commit

Permalink
feat: reload package
Browse files Browse the repository at this point in the history
  • Loading branch information
tumidi committed Jun 14, 2024
1 parent 15fa81e commit 544d911
Show file tree
Hide file tree
Showing 5 changed files with 33 additions and 16 deletions.
3 changes: 2 additions & 1 deletion questionpy_server/worker/runtime/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ def on_msg_load_qpy_package(self, msg: LoadQPyPackage) -> MessageToServer:
packages=self.loaded_packages,
main_package=package,
_on_request_callbacks=self._on_request_callbacks,
)
),
reload=msg.reload,
)
if not isinstance(qtype, BaseQuestionType):
msg = f"Package initialization returned '{qtype}', BaseQuestionType expected"
Expand Down
2 changes: 2 additions & 0 deletions questionpy_server/worker/runtime/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ class LoadQPyPackage(MessageToWorker):
location: PackageLocation
main: bool
"""Set this package as the main package and execute its entry point."""
reload: bool = False
"""Force reload of module."""

class Response(MessageToServer):
"""Success message in return to LoadQPyPackage."""
Expand Down
27 changes: 15 additions & 12 deletions questionpy_server/worker/runtime/package.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# This file is part of the QuestionPy Server. (https://questionpy.org)
# The QuestionPy Server is free software released under terms of the MIT license. See LICENSE.md.
# (c) Technische Universität Berlin, innoCampus <[email protected]>
import importlib
import inspect
import json
import sys
import zipfile
from abc import ABC, abstractmethod
from functools import cached_property
from importlib import import_module, resources
from importlib.resources.abc import Traversable
from pathlib import Path
from types import ModuleType
Expand Down Expand Up @@ -38,20 +38,23 @@ class ImportablePackage(ABC, Package):
def setup_imports(self) -> None:
"""Modifies ``sys.path`` to include the package's python code."""

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

main_module: ModuleType
if self.manifest.entrypoint:
main_module = import_module(
f"{self.manifest.namespace}.{self.manifest.short_name}.{self.manifest.entrypoint}"
)
else:
main_module = import_module(f"{self.manifest.namespace}.{self.manifest.short_name}")
module_name = f"{self.manifest.namespace}.{self.manifest.short_name}"
main_module_name = f"{module_name}.{self.manifest.entrypoint}" if self.manifest.entrypoint else module_name

main_module = importlib.import_module(main_module_name)

if reload:
for name, module in sys.modules.copy().items():
if name.startswith(module_name) and name != main_module_name:
importlib.reload(module)
main_module = importlib.reload(main_module)

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

def get_path(self, path: str) -> Traversable:
return resources.files(self.module_name).joinpath(path)
return importlib.resources.files(self.module_name).joinpath(path)

def setup_imports(self) -> None:
# Nothing to do.
pass

def init_as_main(self, env: Environment) -> BaseQuestionType:
def init_as_main(self, env: Environment, *, reload: bool = False) -> BaseQuestionType:
set_qpy_environment(env)

main_module = import_module(self.module_name)
main_module = importlib.import_module(self.module_name)
init_function = getattr(main_module, self.function_name, None)
if not init_function or not callable(init_function):
raise NoInitFunctionError(main_module)
Expand Down
8 changes: 8 additions & 0 deletions questionpy_server/worker/worker/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ def send(self, message: MessageToWorker) -> None:
async def get_resource_usage(self) -> WorkerResources | None:
"""Get the worker's current resource usage. If unknown or unsupported, return None."""

@abstractmethod
async def load_package(self, *, reload: bool = False) -> None:
"""Loads a package.
Args:
reload: Force reload of module.
"""

@abstractmethod
async def get_manifest(self) -> ComparableManifest:
"""Get manifest of the main package in the worker."""
Expand Down
9 changes: 6 additions & 3 deletions questionpy_server/worker/worker/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,7 @@ async def _initialize(self) -> None:
),
InitWorker.Response,
)
await self._send_and_wait_response(
LoadQPyPackage(location=self.package, main=True), LoadQPyPackage.Response
)
await self.load_package()
except WorkerNotRunningError as e:
msg = "Worker has exited before or during initialization."
raise WorkerStartError(msg) from e
Expand Down Expand Up @@ -169,6 +167,11 @@ async def stop(self, timeout: float) -> None:
except TimeoutError:
log.info("Worker was killed because it did not stop gracefully")

async def load_package(self, *, reload: bool = False) -> None:
await self._send_and_wait_response(
LoadQPyPackage(location=self.package, main=True, reload=reload), LoadQPyPackage.Response
)

async def get_manifest(self) -> ComparableManifest:
msg = GetQPyPackageManifest(path=str(self.package))
ret = await self._send_and_wait_response(msg, GetQPyPackageManifest.Response)
Expand Down

0 comments on commit 544d911

Please sign in to comment.