From e08f0b5eacfad8a1a32ed82f5f61e2d973c98304 Mon Sep 17 00:00:00 2001 From: Jan Britz Date: Thu, 4 Dec 2025 17:29:08 +0100 Subject: [PATCH 1/3] feat: migrations --- questionpy_common/api/qtype.py | 27 +++++++++++++ questionpy_common/manifest.py | 27 +++++++++---- questionpy_server/models.py | 30 +++++++------- questionpy_server/web/_routes/_packages.py | 37 +++++++++++++++--- questionpy_server/web/errors.py | 15 +++++++ questionpy_server/web/middlewares/_error.py | 9 ++++- questionpy_server/worker/__init__.py | 28 ++++++++++++- questionpy_server/worker/impl/_base.py | 22 ++++++++++- questionpy_server/worker/runtime/manager.py | 24 ++++++++++++ questionpy_server/worker/runtime/messages.py | 33 +++++++++++++++- tests/test_data/factories.py | 15 +++---- tests/test_data/package/package_1.qpy | Bin 38241 -> 44493 bytes tests/test_data/package/package_2.qpy | Bin 38241 -> 44494 bytes .../question_state/question_state.json | 6 ++- 14 files changed, 232 insertions(+), 41 deletions(-) diff --git a/questionpy_common/api/qtype.py b/questionpy_common/api/qtype.py index a64aabfa..3c9803d7 100644 --- a/questionpy_common/api/qtype.py +++ b/questionpy_common/api/qtype.py @@ -4,6 +4,7 @@ from __future__ import annotations from abc import abstractmethod +from enum import Enum from typing import TYPE_CHECKING, Protocol from questionpy_common.api.package import BasePackageInterface @@ -19,6 +20,8 @@ __all__ = [ "InvalidAttemptStateError", "InvalidQuestionStateError", + "MigrationError", + "MigrationErrorKind", "OptionsFormValidationError", "QuestionTypeInterface", ] @@ -61,6 +64,14 @@ def create_question_from_state(self, question_state: str) -> QuestionInterface: InvalidQuestionStateError: When the given question state is invalid and cannot be reused. """ + @abstractmethod + def upgrade(self, question_state: str) -> str: + """Upgrade the given question state to the question state version of the main package.""" + + @abstractmethod + def sidegrade(self, question_state: str) -> str: + """Sidegrade the given question state to the version used by the main package.""" + class OptionsFormValidationError(QPyBaseError): def __init__(self, errors: dict[str, str]): @@ -75,3 +86,19 @@ class InvalidAttemptStateError(QPyBaseError): class InvalidQuestionStateError(QPyBaseError): """Error to raise when your package cannot parse the question state it is given.""" + + +class MigrationErrorKind(Enum): + NOT_IMPLEMENTED = "NOT_IMPLEMENTED" + NOT_POSSIBLE = "NOT_POSSIBLE" + PACKAGE_MISSMATCH = "PACKAGE_MISSMATCH" + QUESTION_STATE_INVALID = "QUESTION_STATE_INVALID" + FAILED = "FAILED" + DISCOVERY_ERROR = "DISCOVERY_ERROR" + OTHER_ERROR = "OTHER_ERROR" + + +class MigrationError(QPyBaseError): + """The migration failed.""" + + kind = MigrationErrorKind.OTHER_ERROR diff --git a/questionpy_common/manifest.py b/questionpy_common/manifest.py index 39370c6c..4d3293ab 100644 --- a/questionpy_common/manifest.py +++ b/questionpy_common/manifest.py @@ -7,7 +7,16 @@ from keyword import iskeyword, issoftkeyword from typing import Annotated, NewType -from pydantic import BaseModel, ByteSize, PositiveInt, StringConstraints, conset, field_validator +from pydantic import ( + AfterValidator, + BaseModel, + ByteSize, + NonNegativeInt, + PositiveInt, + StringConstraints, + conset, + field_validator, +) from pydantic.fields import Field from questionpy_common.constants import ENVIRONMENT_VARIABLE_REGEX @@ -79,6 +88,10 @@ def ensure_is_valid_name(name: str) -> str: Bcp47LanguageTag = NewType("Bcp47LanguageTag", str) +type Namespace = Annotated[str, AfterValidator(ensure_is_valid_name)] +type ShortName = Namespace + + class PartialPackagePermissions(BaseModel): cpus: int | None = None memory: ByteSize | None = None @@ -97,8 +110,8 @@ class SourceManifest(BaseModel): These fields are valid inside a package's configuration file. """ - short_name: str - namespace: str = DEFAULT_NAMESPACE + short_name: ShortName + namespace: Namespace = DEFAULT_NAMESPACE version: Annotated[str, Field(pattern=RE_SEMVER)] api_version: Annotated[str, Field(pattern=RE_API)] author: str @@ -120,11 +133,6 @@ class SourceManifest(BaseModel): tags: set[str] = set() requirements: str | list[str] | None = None - @field_validator("short_name", "namespace") - @classmethod - def ensure_is_valid_name(cls, value: str) -> str: - return ensure_is_valid_name(value) - @field_validator("languages", "name") @classmethod def ensure_contains_english_translation( @@ -170,3 +178,6 @@ class Manifest(SourceManifest): static_files: dict[str, PackageFile] = {} dependencies: DistDependencies = DistDependencies() + + state_version: NonNegativeInt = 0 + possible_side_migrations: dict[Namespace, dict[ShortName, set[NonNegativeInt]]] = {} diff --git a/questionpy_server/models.py b/questionpy_server/models.py index 6746d208..0a155b29 100644 --- a/questionpy_server/models.py +++ b/questionpy_server/models.py @@ -8,6 +8,7 @@ from pydantic import BaseModel, ByteSize, ConfigDict, Field from questionpy_common.api.attempt import AttemptModel, AttemptScoredModel, AttemptStartedModel +from questionpy_common.api.qtype import MigrationErrorKind from questionpy_common.api.question import LmsPermissions, QuestionModel from questionpy_common.elements import OptionsFormDefinition from questionpy_common.environment import LmsProvidedAttributes as EnvironmentLmsProvidedAttributes @@ -84,6 +85,18 @@ class QuestionCreated(QuestionModel): question_state: str +class QuestionUpgradeArguments(RequestBaseData): + question_state: str + + +class QuestionSidegradeArguments(RequestBaseData): + question_state: str + + +class QuestionMigrated(BaseModel): + question_state: str + + class LmsProvidedAttributes(BaseModel): lms_provided_attributes: EnvironmentLmsProvidedAttributes | None = None @@ -128,6 +141,7 @@ class RequestErrorCode(Enum): INVALID_OPTIONS_FORM = "INVALID_OPTIONS_FORM" PACKAGE_ERROR = "PACKAGE_ERROR" PACKAGE_NOT_FOUND = "PACKAGE_NOT_FOUND" + MIGRATION_ERROR = "MIGRATION_ERROR" CALLBACK_API_ERROR = "CALLBACK_API_ERROR" SERVER_ERROR = "SERVER_ERROR" @@ -144,20 +158,8 @@ class OptionsFormValidationError(RequestError): errors: dict[str, str] -class QuestionStateMigrationErrorCode(Enum): - NOT_IMPLEMENTED = "NOT_IMPLEMENTED" - DOWNGRADE_NOT_POSSIBLE = "DOWNGRADE_NOT_POSSIBLE" - PACKAGE_MISMATCH = "PACKAGE_MISMATCH" - CURRENT_QUESTION_STATE_INVALID = "CURRENT_QUESTION_STATE_INVALID" - MAJOR_VERSION_MISMATCH = "MAJOR_VERSION_MISMATCH" - OTHER_ERROR = "OTHER_ERROR" - - -class QuestionStateMigrationError(BaseModel): - model_config = ConfigDict(use_enum_values=True) - - error_code: QuestionStateMigrationErrorCode - reason: str | None = None +class MigrationError(RequestError): + kind: MigrationErrorKind class Usage(BaseModel): diff --git a/questionpy_server/web/_routes/_packages.py b/questionpy_server/web/_routes/_packages.py index 8b336f7c..92541b54 100644 --- a/questionpy_server/web/_routes/_packages.py +++ b/questionpy_server/web/_routes/_packages.py @@ -3,10 +3,15 @@ # (c) Technische Universität Berlin, innoCampus from aiohttp import web -from aiohttp.web_exceptions import HTTPMethodNotAllowed from questionpy_common.api.question import LmsPermissions -from questionpy_server.models import QuestionCreateArguments, QuestionEditFormResponse, RequestBaseData +from questionpy_server.models import ( + QuestionCreateArguments, + QuestionEditFormResponse, + QuestionSidegradeArguments, + QuestionUpgradeArguments, + RequestBaseData, +) from questionpy_server.package import Package from questionpy_server.web._decorators import ensure_package, ensure_required_parts from questionpy_server.web._utils import pydantic_json_response @@ -74,10 +79,30 @@ async def post_question( return pydantic_json_response(data=question) -@package_routes.post(r"/packages/{package_hash:\w+}/question/migrate") -async def post_question_migrate(_request: web.Request) -> web.Response: - method = "POST" - raise HTTPMethodNotAllowed(method, []) +@package_routes.post(r"/packages/{package_hash:\w+}/question/upgrade") +@ensure_required_parts +async def post_question_upgrade(request: web.Request, package: Package, data: QuestionUpgradeArguments) -> web.Response: + async with worker_context(request, package, data) as context: + new_question_state = await context.worker.upgrade_question( + context.request_info, + data.question_state, + ) + + return pydantic_json_response(data=new_question_state) + + +@package_routes.post(r"/packages/{package_hash:\w+}/question/sidegrade") +@ensure_required_parts +async def post_question_sidegrade( + request: web.Request, package: Package, data: QuestionSidegradeArguments +) -> web.Response: + async with worker_context(request, package, data) as context: + new_question_state = await context.worker.sidegrade_question( + context.request_info, + data.question_state, + ) + + return pydantic_json_response(data=new_question_state) @package_routes.post(r"/package-extract-info") diff --git a/questionpy_server/web/errors.py b/questionpy_server/web/errors.py index 7a8eb626..131665e2 100644 --- a/questionpy_server/web/errors.py +++ b/questionpy_server/web/errors.py @@ -6,6 +6,8 @@ from aiohttp import web from aiohttp.log import web_logger +from questionpy_common.api.qtype import MigrationErrorKind +from questionpy_server.models import MigrationError as MigrationErrorModel from questionpy_server.models import OptionsFormValidationError, RequestError, RequestErrorCode @@ -156,6 +158,19 @@ def __init__(self, *, reason: str | None, temporary: bool) -> None: ) +class MigrationError(web.HTTPUnprocessableEntity, _ExceptionMixin): + def __init__(self, *, reason: str | None, temporary: bool, kind: MigrationErrorKind) -> None: + super().__init__( + "Migration failed or is not possible.", + MigrationErrorModel( + error_code=RequestErrorCode.MIGRATION_ERROR, + temporary=temporary, + reason=reason, + kind=kind, + ), + ) + + class ServerError(web.HTTPInternalServerError): def __init__(self, *, reason: str | None, temporary: bool) -> None: body = RequestError( diff --git a/questionpy_server/web/middlewares/_error.py b/questionpy_server/web/middlewares/_error.py index 332ee493..705f3115 100644 --- a/questionpy_server/web/middlewares/_error.py +++ b/questionpy_server/web/middlewares/_error.py @@ -9,7 +9,12 @@ from aiohttp.web_response import StreamResponse import questionpy_server.web.errors as web_error -from questionpy_common.api.qtype import InvalidAttemptStateError, InvalidQuestionStateError, OptionsFormValidationError +from questionpy_common.api.qtype import ( + InvalidAttemptStateError, + InvalidQuestionStateError, + MigrationError, + OptionsFormValidationError, +) from questionpy_common.error import QPyBaseError from questionpy_server.utils.manifest import ManifestError from questionpy_server.worker.exception import ( @@ -49,6 +54,8 @@ async def error_middleware(request: Request, handler: Handler) -> StreamResponse raise exception(reason=e.reason, temporary=e.temporary) from e except OptionsFormValidationError as e: raise web_error.InvalidOptionsFormError(reason=e.reason, errors=e.errors) from e + except MigrationError as e: + raise web_error.MigrationError(reason=e.reason, temporary=e.temporary, kind=e.kind) from e except Exception as e: web_logger.exception("There was an unexpected error while processing the request.") raise web_error.ServerError(reason="unknown", temporary=True) from e diff --git a/questionpy_server/worker/__init__.py b/questionpy_server/worker/__init__.py index ece7f186..4d27ca0d 100644 --- a/questionpy_server/worker/__init__.py +++ b/questionpy_server/worker/__init__.py @@ -14,7 +14,7 @@ from questionpy_common.elements import OptionsFormDefinition from questionpy_common.environment import PackagePermissions, RequestInfo from questionpy_common.manifest import PackageFile -from questionpy_server.models import LoadedPackage, QuestionCreated +from questionpy_server.models import LoadedPackage, QuestionCreated, QuestionMigrated from questionpy_server.utils.manifest import ComparableManifest from questionpy_server.worker.runtime.messages import MessageToServer, MessageToWorker from questionpy_server.worker.runtime.package_location import PackageLocation @@ -151,6 +151,32 @@ async def create_question_from_options( New question. """ + @abstractmethod + async def upgrade_question(self, request_info: RequestInfo, question_state: str) -> QuestionMigrated: + """Upgrade the given question state to the question state version of this package. + + Args: + request_info: Information about the current request. + question_state: The question state which should be upgraded. + + Returns: + Migrated question state. + """ + + @abstractmethod + async def sidegrade_question(self, request_info: RequestInfo, question_state: str) -> QuestionMigrated: + """Sidegrade the given question state to the question state version of this package. + + The question state can origin from a different package. + + Args: + request_info: Information about the current request. + question_state: The question state which should be upgraded. + + Returns: + Migrated question state. + """ + @abstractmethod async def start_attempt(self, request_info: RequestInfo, question_state: str, variant: int) -> AttemptStartedModel: """Start an attempt at this question with the given variant. diff --git a/questionpy_server/worker/impl/_base.py b/questionpy_server/worker/impl/_base.py index 38660703..df7b920f 100644 --- a/questionpy_server/worker/impl/_base.py +++ b/questionpy_server/worker/impl/_base.py @@ -20,7 +20,7 @@ from questionpy_common.elements import OptionsFormDefinition from questionpy_common.environment import RequestInfo from questionpy_common.manifest import Manifest, PackageFile -from questionpy_server.models import LoadedPackage, QuestionCreated +from questionpy_server.models import LoadedPackage, QuestionCreated, QuestionMigrated from questionpy_server.utils.manifest import ComparableManifest from questionpy_server.worker import PackageFileData, Worker, WorkerArgs, WorkerState from questionpy_server.worker.exception import ( @@ -43,7 +43,9 @@ MessageToServer, MessageToWorker, ScoreAttempt, + SidegradeQuestion, StartAttempt, + UpgradeQuestion, ViewAttempt, WorkerError, ) @@ -251,6 +253,24 @@ async def create_question_from_options( question_state=ret.question_state, lms_permissions=lms_permissions, **ret.question_model.model_dump() ) + async def upgrade_question(self, request_info: RequestInfo, question_state: str) -> QuestionMigrated: + msg = UpgradeQuestion( + request_info=request_info, + question_state=question_state, + ) + ret = await self.send_and_wait_for_response(msg, UpgradeQuestion.Response) + + return QuestionMigrated(question_state=ret.question_state) + + async def sidegrade_question(self, request_info: RequestInfo, question_state: str) -> QuestionMigrated: + msg = SidegradeQuestion( + request_info=request_info, + question_state=question_state, + ) + ret = await self.send_and_wait_for_response(msg, SidegradeQuestion.Response) + + return QuestionMigrated(question_state=ret.question_state) + async def start_attempt(self, request_info: RequestInfo, question_state: str, variant: int) -> AttemptStartedModel: msg = StartAttempt(question_state=question_state, variant=variant, request_info=request_info) ret = await self.send_and_wait_for_response(msg, StartAttempt.Response) diff --git a/questionpy_server/worker/runtime/manager.py b/questionpy_server/worker/runtime/manager.py index 58b99b21..286c911b 100644 --- a/questionpy_server/worker/runtime/manager.py +++ b/questionpy_server/worker/runtime/manager.py @@ -35,7 +35,9 @@ MessageToServer, MessageToWorker, ScoreAttempt, + SidegradeQuestion, StartAttempt, + UpgradeQuestion, ViewAttempt, WorkerError, ) @@ -114,6 +116,8 @@ def __init__(self, server_connection: WorkerToServerConnection): GetQPyPackageManifest.message_id: self.on_msg_get_qpy_package_manifest, GetOptionsForm.message_id: self.on_msg_get_options_form_definition, CreateQuestionFromOptions.message_id: self.on_msg_create_question_from_options, + UpgradeQuestion.message_id: self.on_msg_upgrade_question, + SidegradeQuestion.message_id: self.on_msg_sidegrade_question, StartAttempt.message_id: self.on_msg_start_attempt, ViewAttempt.message_id: self.on_msg_view_attempt, ScoreAttempt.message_id: self.on_msg_score_attempt, @@ -255,6 +259,26 @@ def on_msg_create_question_from_options(self, msg: CreateQuestionFromOptions) -> question_state=question.export_question_state(), question_model=question.export() ) + def on_msg_upgrade_question(self, msg: UpgradeQuestion) -> UpgradeQuestion.Response: + if not self._env: + self._raise_not_initialized(msg) + if not self._question_type: + self._raise_no_main_package_loaded(msg) + + with self._with_request_info(msg, msg.request_info): + migrated_question_state = self._question_type.upgrade(msg.question_state) + return UpgradeQuestion.Response(question_state=migrated_question_state) + + def on_msg_sidegrade_question(self, msg: SidegradeQuestion) -> SidegradeQuestion.Response: + if not self._env: + self._raise_not_initialized(msg) + if not self._question_type: + self._raise_no_main_package_loaded(msg) + + with self._with_request_info(msg, msg.request_info): + migrated_question_state = self._question_type.sidegrade(msg.question_state) + return SidegradeQuestion.Response(question_state=migrated_question_state) + def on_msg_start_attempt(self, msg: StartAttempt) -> StartAttempt.Response: if not self._env: self._raise_not_initialized(msg) diff --git a/questionpy_server/worker/runtime/messages.py b/questionpy_server/worker/runtime/messages.py index e4447b8b..ac77a426 100644 --- a/questionpy_server/worker/runtime/messages.py +++ b/questionpy_server/worker/runtime/messages.py @@ -11,7 +11,7 @@ from pydantic import BaseModel, JsonValue from questionpy_common.api.attempt import AttemptModel, AttemptScoredModel, AttemptStartedModel -from questionpy_common.api.qtype import InvalidQuestionStateError, OptionsFormValidationError +from questionpy_common.api.qtype import InvalidQuestionStateError, MigrationError, OptionsFormValidationError from questionpy_common.api.question import QuestionModel from questionpy_common.elements import OptionsFormDefinition from questionpy_common.environment import PackageNamespaceAndShortName, PackagePermissions, RequestInfo @@ -35,7 +35,10 @@ class MessageIds(IntEnum): LOAD_QPY_PACKAGE = 10 GET_QPY_PACKAGE_MANIFEST = 20 GET_OPTIONS_FORM_DEFINITION = 30 + CREATE_QUESTION = 40 + UPGRADE_QUESTION = 41 + SIDEGRADE_QUESTION = 43 START_ATTEMPT = 50 VIEW_ATTEMPT = 51 @@ -48,7 +51,10 @@ class MessageIds(IntEnum): LOADED_QPY_PACKAGE = 1010 RETURN_QPY_PACKAGE_MANIFEST = 1020 RETURN_OPTIONS_FORM_DEFINITION = 1030 + RETURN_CREATE_QUESTION = 1040 + RETURN_UPGRADE_QUESTION = 1041 + RETURN_SIDEGRADE_QUESTION = 1043 RETURN_START_ATTEMPT = 1050 RETURN_VIEW_ATTEMPT = 1051 @@ -207,6 +213,26 @@ class Response(MessageToServer): attempt_scored_model: AttemptScoredModel +class UpgradeQuestion(MessageToWorker): + message_id: ClassVar[MessageIds] = MessageIds.UPGRADE_QUESTION + request_info: RequestInfo + question_state: str + + class Response(MessageToServer): + message_id: ClassVar[MessageIds] = MessageIds.RETURN_UPGRADE_QUESTION + question_state: str + + +class SidegradeQuestion(MessageToWorker): + message_id: ClassVar[MessageIds] = MessageIds.SIDEGRADE_QUESTION + request_info: RequestInfo + question_state: str + + class Response(MessageToServer): + message_id: ClassVar[MessageIds] = MessageIds.RETURN_SIDEGRADE_QUESTION + question_state: str + + class WorkerError(MessageToServer): """Error message.""" @@ -217,6 +243,7 @@ class ErrorType(StrEnum): MEMORY_EXCEEDED = auto() QUESTION_STATE_INVALID = auto() FORM_OPTIONS_INVALID = auto() + MIGRATION_ERROR = auto() message_id: ClassVar[MessageIds] = MessageIds.ERROR expected_response_id: MessageIds @@ -239,6 +266,8 @@ def from_exception(cls, error: Exception, cause: MessageToWorker) -> "WorkerErro elif isinstance(error, OptionsFormValidationError): error_type = WorkerError.ErrorType.FORM_OPTIONS_INVALID error_data = error.errors + elif isinstance(error, MigrationError): + error_type = WorkerError.ErrorType.MIGRATION_ERROR else: error_type = WorkerError.ErrorType.UNKNOWN @@ -264,6 +293,8 @@ def to_exception(self, worker_name: str) -> Exception: error = InvalidQuestionStateError(self.message) elif self.type == WorkerError.ErrorType.FORM_OPTIONS_INVALID: error = OptionsFormValidationError(self.error_data or {}) + elif self.type == WorkerError.ErrorType.MIGRATION_ERROR: + error = MigrationError(self.message) else: error = WorkerUnknownError(self.message, worker_name=worker_name) diff --git a/tests/test_data/factories.py b/tests/test_data/factories.py index 21ba16c1..814d67ee 100644 --- a/tests/test_data/factories.py +++ b/tests/test_data/factories.py @@ -7,6 +7,7 @@ from polyfactory import Use from polyfactory.factories.pydantic_factory import ModelFactory +from pydantic import BaseModel from semver import Version from questionpy_common.manifest import Bcp47LanguageTag, PartialPackagePermissions @@ -14,7 +15,7 @@ from questionpy_server.utils.manifest import ComparableManifest -class CustomFactory(ModelFactory[Any]): +class CustomFactory[T: BaseModel](ModelFactory[T]): """Custom factory base class adding support for :class:`Version` fields.""" __is_base_factory__ = True @@ -24,16 +25,14 @@ def get_provider_map(cls) -> dict[Any, Callable[[], Any]]: return {**super().get_provider_map(), Version: lambda: cls.__faker__.numerify(text="#.#.#")} -class RepoMetaFactory(ModelFactory): - __model__ = RepoMeta +class RepoMetaFactory(ModelFactory[RepoMeta]): ... -class RepoPackageVersionsFactory(CustomFactory): - __model__ = RepoPackageVersions +class RepoPackageVersionsFactory(CustomFactory[RepoPackageVersions]): ... -class ManifestFactory(CustomFactory): - __model__ = ComparableManifest +class ManifestFactory(CustomFactory[ComparableManifest]): + __set_as_default_factory_for_type__ = True short_name = Use(lambda: ModelFactory.__faker__.word().lower() + "_sn") namespace = Use(lambda: ModelFactory.__faker__.word().lower() + "_ns") @@ -42,3 +41,5 @@ class ManifestFactory(CustomFactory): url = Use(ModelFactory.__faker__.url) icon = None permissions = PartialPackagePermissions() + state_version = 0 + possible_side_migrations: dict[str, dict[str, int]] = {} diff --git a/tests/test_data/package/package_1.qpy b/tests/test_data/package/package_1.qpy index da022699f8b89af2c3b255c7f7866f9e7d71b7c0..d9da2fe1c8c10d3fa6ac20704bc70e0113ab57d0 100644 GIT binary patch delta 21528 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

|R@B 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 diff --git a/tests/test_data/package/package_2.qpy b/tests/test_data/package/package_2.qpy index 322e126f253623efe623cb598c532d9d324a65e3..ebfe9f397130261a73dd3a455a8ae2f3609596d1 100644 GIT binary patch delta 21529 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 diff --git a/tests/test_data/question_state/question_state.json b/tests/test_data/question_state/question_state.json index 0861c6fb..1a20d746 100644 --- a/tests/test_data/question_state/question_state.json +++ b/tests/test_data/question_state/question_state.json @@ -1,5 +1,6 @@ { - "package_name": "example", + "package_namespace": "example_namespace", + "package_short_name": "example_short_name", "package_version": "0.1.0", "options": { "input": "bar", @@ -9,5 +10,6 @@ }, "state": { "example": "question_state" - } + }, + "state_version": 0 } From b4ac4f03e54674a2874efde58848702484f337db Mon Sep 17 00:00:00 2001 From: Jan Britz Date: Thu, 4 Dec 2025 17:30:28 +0100 Subject: [PATCH 2/3] feat(migrations): downgrade --- questionpy_common/api/qtype.py | 4 ++++ questionpy_server/models.py | 5 +++++ questionpy_server/web/_routes/_packages.py | 12 ++++++++++++ questionpy_server/worker/__init__.py | 14 ++++++++++++++ questionpy_server/worker/impl/_base.py | 11 +++++++++++ questionpy_server/worker/runtime/manager.py | 14 ++++++++++++++ questionpy_server/worker/runtime/messages.py | 13 +++++++++++++ tests/test_data/package/package_1.qpy | Bin 44493 -> 45367 bytes tests/test_data/package/package_2.qpy | Bin 44494 -> 45368 bytes 9 files changed, 73 insertions(+) diff --git a/questionpy_common/api/qtype.py b/questionpy_common/api/qtype.py index 3c9803d7..46c14be7 100644 --- a/questionpy_common/api/qtype.py +++ b/questionpy_common/api/qtype.py @@ -68,6 +68,10 @@ def create_question_from_state(self, question_state: str) -> QuestionInterface: def upgrade(self, question_state: str) -> str: """Upgrade the given question state to the question state version of the main package.""" + @abstractmethod + def downgrade(self, question_state: str, to: int) -> str: + """Downgrade the given question state to the provided question state version of the main package.""" + @abstractmethod def sidegrade(self, question_state: str) -> str: """Sidegrade the given question state to the version used by the main package.""" diff --git a/questionpy_server/models.py b/questionpy_server/models.py index 0a155b29..de19c1f7 100644 --- a/questionpy_server/models.py +++ b/questionpy_server/models.py @@ -89,6 +89,11 @@ class QuestionUpgradeArguments(RequestBaseData): question_state: str +class QuestionDowngradeArguments(RequestBaseData): + question_state: str + to: int + + class QuestionSidegradeArguments(RequestBaseData): question_state: str diff --git a/questionpy_server/web/_routes/_packages.py b/questionpy_server/web/_routes/_packages.py index 92541b54..3d5cb5f3 100644 --- a/questionpy_server/web/_routes/_packages.py +++ b/questionpy_server/web/_routes/_packages.py @@ -7,6 +7,7 @@ from questionpy_common.api.question import LmsPermissions from questionpy_server.models import ( QuestionCreateArguments, + QuestionDowngradeArguments, QuestionEditFormResponse, QuestionSidegradeArguments, QuestionUpgradeArguments, @@ -91,6 +92,17 @@ async def post_question_upgrade(request: web.Request, package: Package, data: Qu return pydantic_json_response(data=new_question_state) +@package_routes.post(r"/packages/{package_hash:\w+}/question/downgrade") +@ensure_required_parts +async def post_question_downgrade( + request: web.Request, package: Package, data: QuestionDowngradeArguments +) -> web.Response: + async with worker_context(request, package, data) as context: + new_question_state = await context.worker.downgrade_question(context.request_info, data.question_state, data.to) + + return pydantic_json_response(data=new_question_state) + + @package_routes.post(r"/packages/{package_hash:\w+}/question/sidegrade") @ensure_required_parts async def post_question_sidegrade( diff --git a/questionpy_server/worker/__init__.py b/questionpy_server/worker/__init__.py index 4d27ca0d..83a9680a 100644 --- a/questionpy_server/worker/__init__.py +++ b/questionpy_server/worker/__init__.py @@ -163,6 +163,20 @@ async def upgrade_question(self, request_info: RequestInfo, question_state: str) Migrated question state. """ + @abstractmethod + async def downgrade_question(self, request_info: RequestInfo, question_state: str, to: int) -> QuestionMigrated: + """Downgrade the given question state to the question state version of this package. + + Args: + request_info: Information about the current request. + question_state: The question state with the same question state version as this package which should be + downgraded. + to: The question state version to downgrade to. + + Returns: + Migrated question state. + """ + @abstractmethod async def sidegrade_question(self, request_info: RequestInfo, question_state: str) -> QuestionMigrated: """Sidegrade the given question state to the question state version of this package. diff --git a/questionpy_server/worker/impl/_base.py b/questionpy_server/worker/impl/_base.py index df7b920f..25b50e59 100644 --- a/questionpy_server/worker/impl/_base.py +++ b/questionpy_server/worker/impl/_base.py @@ -34,6 +34,7 @@ from questionpy_server.worker.runtime.messages import ( BaseWorkerError, CreateQuestionFromOptions, + DowngradeQuestion, Exit, GetOptionsForm, GetQPyPackageManifest, @@ -262,6 +263,16 @@ async def upgrade_question(self, request_info: RequestInfo, question_state: str) return QuestionMigrated(question_state=ret.question_state) + async def downgrade_question(self, request_info: RequestInfo, question_state: str, to: int) -> QuestionMigrated: + msg = DowngradeQuestion( + request_info=request_info, + question_state=question_state, + target_question_state_version=to, + ) + ret = await self.send_and_wait_for_response(msg, DowngradeQuestion.Response) + + return QuestionMigrated(question_state=ret.question_state) + async def sidegrade_question(self, request_info: RequestInfo, question_state: str) -> QuestionMigrated: msg = SidegradeQuestion( request_info=request_info, diff --git a/questionpy_server/worker/runtime/manager.py b/questionpy_server/worker/runtime/manager.py index 286c911b..baed6e0c 100644 --- a/questionpy_server/worker/runtime/manager.py +++ b/questionpy_server/worker/runtime/manager.py @@ -26,6 +26,7 @@ from questionpy_server.worker.runtime.connection import WorkerToServerConnection from questionpy_server.worker.runtime.messages import ( CreateQuestionFromOptions, + DowngradeQuestion, Exit, GetOptionsForm, GetQPyPackageManifest, @@ -117,6 +118,7 @@ def __init__(self, server_connection: WorkerToServerConnection): GetOptionsForm.message_id: self.on_msg_get_options_form_definition, CreateQuestionFromOptions.message_id: self.on_msg_create_question_from_options, UpgradeQuestion.message_id: self.on_msg_upgrade_question, + DowngradeQuestion.message_id: self.on_msg_downgrade_question, SidegradeQuestion.message_id: self.on_msg_sidegrade_question, StartAttempt.message_id: self.on_msg_start_attempt, ViewAttempt.message_id: self.on_msg_view_attempt, @@ -269,6 +271,18 @@ def on_msg_upgrade_question(self, msg: UpgradeQuestion) -> UpgradeQuestion.Respo migrated_question_state = self._question_type.upgrade(msg.question_state) return UpgradeQuestion.Response(question_state=migrated_question_state) + def on_msg_downgrade_question(self, msg: DowngradeQuestion) -> DowngradeQuestion.Response: + if not self._env: + self._raise_not_initialized(msg) + if not self._question_type: + self._raise_no_main_package_loaded(msg) + + with self._with_request_info(msg, msg.request_info): + migrated_question_state = self._question_type.downgrade( + msg.question_state, msg.target_question_state_version + ) + return DowngradeQuestion.Response(question_state=migrated_question_state) + def on_msg_sidegrade_question(self, msg: SidegradeQuestion) -> SidegradeQuestion.Response: if not self._env: self._raise_not_initialized(msg) diff --git a/questionpy_server/worker/runtime/messages.py b/questionpy_server/worker/runtime/messages.py index ac77a426..1987dc9d 100644 --- a/questionpy_server/worker/runtime/messages.py +++ b/questionpy_server/worker/runtime/messages.py @@ -38,6 +38,7 @@ class MessageIds(IntEnum): CREATE_QUESTION = 40 UPGRADE_QUESTION = 41 + DOWNGRADE_QUESTION = 42 SIDEGRADE_QUESTION = 43 START_ATTEMPT = 50 @@ -54,6 +55,7 @@ class MessageIds(IntEnum): RETURN_CREATE_QUESTION = 1040 RETURN_UPGRADE_QUESTION = 1041 + RETURN_DOWNGRADE_QUESTION = 1042 RETURN_SIDEGRADE_QUESTION = 1043 RETURN_START_ATTEMPT = 1050 @@ -223,6 +225,17 @@ class Response(MessageToServer): question_state: str +class DowngradeQuestion(MessageToWorker): + message_id: ClassVar[MessageIds] = MessageIds.DOWNGRADE_QUESTION + request_info: RequestInfo + question_state: str + target_question_state_version: int + + class Response(MessageToServer): + message_id: ClassVar[MessageIds] = MessageIds.RETURN_DOWNGRADE_QUESTION + question_state: str + + class SidegradeQuestion(MessageToWorker): message_id: ClassVar[MessageIds] = MessageIds.SIDEGRADE_QUESTION request_info: RequestInfo diff --git a/tests/test_data/package/package_1.qpy b/tests/test_data/package/package_1.qpy index d9da2fe1c8c10d3fa6ac20704bc70e0113ab57d0..4198c29bcc2b5b11154f0d153ee493f2d2325d49 100644 GIT binary patch delta 7360 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 delta 6448 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

<-XM8=335R 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 A&;S4c delta 6483 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 Date: Tue, 9 Dec 2025 18:16:19 +0100 Subject: [PATCH 3/3] fix: exceptions --- questionpy_common/api/qtype.py | 10 +++++++--- questionpy_server/worker/runtime/messages.py | 14 ++++++++------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/questionpy_common/api/qtype.py b/questionpy_common/api/qtype.py index 46c14be7..f6063677 100644 --- a/questionpy_common/api/qtype.py +++ b/questionpy_common/api/qtype.py @@ -78,10 +78,10 @@ def sidegrade(self, question_state: str) -> str: class OptionsFormValidationError(QPyBaseError): - def __init__(self, errors: dict[str, str]): + def __init__(self, errors: dict[str, str], reason: str | None = None, temporary: bool = False): # noqa: FBT001, FBT002 """There was at least one validation error.""" self.errors = errors # input element name -> error description - super().__init__("Form input data could not be validated successfully.") + super().__init__("Form input data could not be validated successfully.", reason=reason, temporary=temporary) class InvalidAttemptStateError(QPyBaseError): @@ -105,4 +105,8 @@ class MigrationErrorKind(Enum): class MigrationError(QPyBaseError): """The migration failed.""" - kind = MigrationErrorKind.OTHER_ERROR + def __init__( + self, *args: object, kind: MigrationErrorKind, reason: str | None = None, temporary: bool = False + ) -> None: + self.kind = kind + super().__init__(*args, reason=reason, temporary=temporary) diff --git a/questionpy_server/worker/runtime/messages.py b/questionpy_server/worker/runtime/messages.py index 1987dc9d..365997da 100644 --- a/questionpy_server/worker/runtime/messages.py +++ b/questionpy_server/worker/runtime/messages.py @@ -261,8 +261,8 @@ class ErrorType(StrEnum): message_id: ClassVar[MessageIds] = MessageIds.ERROR expected_response_id: MessageIds type: ErrorType + exception_kwargs: dict[str, Any] = {} message: str | None - error_data: dict[str, str] | None = None original_stacktrace: str | None = None """The original worker-side stacktrace.""" @@ -270,17 +270,19 @@ class ErrorType(StrEnum): @classmethod def from_exception(cls, error: Exception, cause: MessageToWorker) -> "WorkerError": """Get a WorkerError message from an exception.""" - error_data: dict[str, str] | None = None + kwargs: dict[str, Any] = {} if isinstance(error, MemoryError): error_type = WorkerError.ErrorType.MEMORY_EXCEEDED elif isinstance(error, InvalidQuestionStateError): error_type = WorkerError.ErrorType.QUESTION_STATE_INVALID + kwargs = {"reason": error.reason, "temporary": error.temporary} elif isinstance(error, OptionsFormValidationError): error_type = WorkerError.ErrorType.FORM_OPTIONS_INVALID - error_data = error.errors + kwargs = {"errors": error.errors, "reason": error.reason, "temporary": error.temporary} elif isinstance(error, MigrationError): error_type = WorkerError.ErrorType.MIGRATION_ERROR + kwargs = {"kind": error.kind, "reason": error.reason, "temporary": error.temporary} else: error_type = WorkerError.ErrorType.UNKNOWN @@ -294,7 +296,7 @@ def from_exception(cls, error: Exception, cause: MessageToWorker) -> "WorkerErro message=str(error), expected_response_id=cause.Response.message_id, original_stacktrace=original_stacktrace, - error_data=error_data, + exception_kwargs=kwargs, ) def to_exception(self, worker_name: str) -> Exception: @@ -305,9 +307,9 @@ def to_exception(self, worker_name: str) -> Exception: elif self.type == WorkerError.ErrorType.QUESTION_STATE_INVALID: error = InvalidQuestionStateError(self.message) elif self.type == WorkerError.ErrorType.FORM_OPTIONS_INVALID: - error = OptionsFormValidationError(self.error_data or {}) + error = OptionsFormValidationError(**self.exception_kwargs) elif self.type == WorkerError.ErrorType.MIGRATION_ERROR: - error = MigrationError(self.message) + error = MigrationError(self.message, **self.exception_kwargs) else: error = WorkerUnknownError(self.message, worker_name=worker_name)