From d9cba0b454f5f620a618a45a20bc473f1047c1c8 Mon Sep 17 00:00:00 2001 From: Maximilian Haye Date: Thu, 22 Aug 2024 13:33:48 +0200 Subject: [PATCH] refactor: consolidate web package structure --- questionpy_server/app.py | 2 +- .../collector/_package_collection.py | 2 +- questionpy_server/collector/lms_collector.py | 2 +- .../collector/local_collector.py | 2 +- questionpy_server/factories/package.py | 2 +- questionpy_server/factories/question_state.py | 2 +- questionpy_server/{misc.py => hash.py} | 6 ++ questionpy_server/{api => }/models.py | 0 questionpy_server/package.py | 2 +- questionpy_server/repository/helper.py | 2 +- questionpy_server/settings.py | 7 +- questionpy_server/types.py | 18 ----- questionpy_server/{api => web}/__init__.py | 0 .../{decorators.py => web/_decorators.py} | 76 +++++-------------- questionpy_server/web/_errors.py | 58 ++++++++++++++ .../{api/routes => web/_routes}/__init__.py | 0 .../{api/routes => web/_routes}/_attempts.py | 6 +- .../{api/routes => web/_routes}/_files.py | 2 +- .../{api/routes => web/_routes}/_packages.py | 6 +- .../{api/routes => web/_routes}/_status.py | 4 +- questionpy_server/{web.py => web/_utils.py} | 8 +- questionpy_server/worker/pool.py | 3 +- questionpy_server/worker/worker/__init__.py | 2 +- questionpy_server/worker/worker/base.py | 2 +- ruff_defaults.toml | 2 +- .../collector/test_lms_collector.py | 2 +- .../collector/test_package_collection.py | 2 +- .../{api => web}/__init__.py | 0 .../{api => web}/routes/__init__.py | 0 .../{api => web}/routes/test_files.py | 0 .../{api => web}/routes/test_packages.py | 2 +- 31 files changed, 111 insertions(+), 111 deletions(-) rename questionpy_server/{misc.py => hash.py} (89%) rename questionpy_server/{api => }/models.py (100%) delete mode 100644 questionpy_server/types.py rename questionpy_server/{api => web}/__init__.py (100%) rename questionpy_server/{decorators.py => web/_decorators.py} (79%) create mode 100644 questionpy_server/web/_errors.py rename questionpy_server/{api/routes => web/_routes}/__init__.py (100%) rename questionpy_server/{api/routes => web/_routes}/_attempts.py (92%) rename questionpy_server/{api/routes => web/_routes}/_files.py (96%) rename questionpy_server/{api/routes => web/_routes}/_packages.py (93%) rename questionpy_server/{api/routes => web/_routes}/_status.py (90%) rename questionpy_server/{web.py => web/_utils.py} (95%) rename tests/questionpy_server/{api => web}/__init__.py (100%) rename tests/questionpy_server/{api => web}/routes/__init__.py (100%) rename tests/questionpy_server/{api => web}/routes/test_files.py (100%) rename tests/questionpy_server/{api => web}/routes/test_packages.py (96%) diff --git a/questionpy_server/app.py b/questionpy_server/app.py index 7381d9a4..1dbb9004 100644 --- a/questionpy_server/app.py +++ b/questionpy_server/app.py @@ -19,7 +19,7 @@ class QPyServer(web.AppKey["QPyServer"]): def __init__(self, settings: Settings): # We import here, so we don't have to work around circular imports. - from .api.routes import routes # noqa: PLC0415 + from questionpy_server.web._routes import routes # noqa: PLC0415 self.settings: Settings = settings self.web_app = web.Application(client_max_size=settings.webservice.max_main_size) diff --git a/questionpy_server/collector/_package_collection.py b/questionpy_server/collector/_package_collection.py index 3b79da26..24768e5e 100644 --- a/questionpy_server/collector/_package_collection.py +++ b/questionpy_server/collector/_package_collection.py @@ -19,8 +19,8 @@ if TYPE_CHECKING: from questionpy_server.collector.abc import BaseCollector + from questionpy_server.hash import HashContainer from questionpy_server.package import Package - from questionpy_server.web import HashContainer class PackageCollection: diff --git a/questionpy_server/collector/lms_collector.py b/questionpy_server/collector/lms_collector.py index 2530daa2..497986f2 100644 --- a/questionpy_server/collector/lms_collector.py +++ b/questionpy_server/collector/lms_collector.py @@ -11,8 +11,8 @@ if TYPE_CHECKING: from questionpy_server.collector.indexer import Indexer + from questionpy_server.hash import HashContainer from questionpy_server.package import Package - from questionpy_server.web import HashContainer class LMSCollector(CachedCollector): diff --git a/questionpy_server/collector/local_collector.py b/questionpy_server/collector/local_collector.py index db64291d..fba75789 100644 --- a/questionpy_server/collector/local_collector.py +++ b/questionpy_server/collector/local_collector.py @@ -13,7 +13,7 @@ from watchdog.utils.dirsnapshot import DirectorySnapshot, DirectorySnapshotDiff, EmptyDirectorySnapshot from questionpy_server.collector.abc import BaseCollector -from questionpy_server.misc import calculate_hash +from questionpy_server.hash import calculate_hash if TYPE_CHECKING: from questionpy_server.collector.indexer import Indexer diff --git a/questionpy_server/factories/package.py b/questionpy_server/factories/package.py index 32a461cf..fb1f1e7a 100644 --- a/questionpy_server/factories/package.py +++ b/questionpy_server/factories/package.py @@ -6,7 +6,7 @@ from faker import Faker from polyfactory.factories.pydantic_factory import ModelFactory -from questionpy_server.api.models import PackageInfo +from questionpy_server.models import PackageInfo languages = ["en", "de"] fake = Faker() diff --git a/questionpy_server/factories/question_state.py b/questionpy_server/factories/question_state.py index e4cc85a6..c04e372f 100644 --- a/questionpy_server/factories/question_state.py +++ b/questionpy_server/factories/question_state.py @@ -4,7 +4,7 @@ from polyfactory.factories.pydantic_factory import ModelFactory -from questionpy_server.api.models import RequestBaseData +from questionpy_server.models import RequestBaseData class RequestBaseDataFactory(ModelFactory): diff --git a/questionpy_server/misc.py b/questionpy_server/hash.py similarity index 89% rename from questionpy_server/misc.py rename to questionpy_server/hash.py index 31e7ddcc..b8e56d21 100644 --- a/questionpy_server/misc.py +++ b/questionpy_server/hash.py @@ -4,6 +4,7 @@ from hashlib import sha256 from pathlib import Path +from typing import NamedTuple from questionpy_common.constants import MiB @@ -27,3 +28,8 @@ def calculate_hash(source: bytes | Path) -> str: sha.update(chunk) return sha.hexdigest() + + +class HashContainer(NamedTuple): + data: bytes + hash: str diff --git a/questionpy_server/api/models.py b/questionpy_server/models.py similarity index 100% rename from questionpy_server/api/models.py rename to questionpy_server/models.py diff --git a/questionpy_server/package.py b/questionpy_server/package.py index 5fc7bf08..35bab089 100644 --- a/questionpy_server/package.py +++ b/questionpy_server/package.py @@ -6,10 +6,10 @@ from pathlib import Path from typing import TYPE_CHECKING -from questionpy_server.api.models import PackageInfo from questionpy_server.collector.lms_collector import LMSCollector from questionpy_server.collector.local_collector import LocalCollector from questionpy_server.collector.repo_collector import RepoCollector +from questionpy_server.models import PackageInfo from questionpy_server.utils.manifest import ComparableManifest if TYPE_CHECKING: diff --git a/questionpy_server/repository/helper.py b/questionpy_server/repository/helper.py index 3d43518e..48ccbd8c 100644 --- a/questionpy_server/repository/helper.py +++ b/questionpy_server/repository/helper.py @@ -5,7 +5,7 @@ from aiohttp import ClientError, ClientSession -from questionpy_server.misc import calculate_hash +from questionpy_server.hash import calculate_hash class DownloadError(Exception): diff --git a/questionpy_server/settings.py b/questionpy_server/settings.py index 27e81067..726d888c 100644 --- a/questionpy_server/settings.py +++ b/questionpy_server/settings.py @@ -1,7 +1,7 @@ # 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 - +import builtins import logging from configparser import ConfigParser from datetime import timedelta @@ -20,7 +20,6 @@ ) from questionpy_common.constants import MAX_PACKAGE_SIZE, MiB -from questionpy_server.types import WorkerType from questionpy_server.worker.worker import Worker from questionpy_server.worker.worker.subprocess import SubprocessWorker @@ -83,14 +82,14 @@ def max_package_size_bigger_then_predefined_value(cls, value: ByteSize) -> ByteS class WorkerSettings(BaseModel): - type: WorkerType = SubprocessWorker + type: builtins.type[Worker] = SubprocessWorker """Fully qualified name of the worker class or the class itself (for the default).""" max_workers: int = 8 max_memory: ByteSize = ByteSize(500 * MiB) @field_validator("type", mode="before") @classmethod - def _load_worker_class(cls, value: object) -> WorkerType: + def _load_worker_class(cls, value: object) -> builtins.type[Worker]: if isinstance(value, str): value = locate(value) diff --git a/questionpy_server/types.py b/questionpy_server/types.py deleted file mode 100644 index 4827058e..00000000 --- a/questionpy_server/types.py +++ /dev/null @@ -1,18 +0,0 @@ -# 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 - -from collections.abc import Awaitable, Callable -from typing import Any, TypeVar - -from pydantic import BaseModel - -from questionpy_server.worker.worker import Worker - -AwaitFuncT = Callable[..., Awaitable[Any]] - -RouteHandler = TypeVar("RouteHandler", bound=AwaitFuncT) - -M = TypeVar("M", bound=BaseModel) - -WorkerType = type[Worker] diff --git a/questionpy_server/api/__init__.py b/questionpy_server/web/__init__.py similarity index 100% rename from questionpy_server/api/__init__.py rename to questionpy_server/web/__init__.py diff --git a/questionpy_server/decorators.py b/questionpy_server/web/_decorators.py similarity index 79% rename from questionpy_server/decorators.py rename to questionpy_server/web/_decorators.py index f195bc58..058ccf9a 100644 --- a/questionpy_server/decorators.py +++ b/questionpy_server/web/_decorators.py @@ -1,8 +1,11 @@ +# 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 import inspect from collections.abc import Awaitable, Callable from functools import wraps from inspect import Parameter -from typing import Concatenate, NamedTuple, ParamSpec, TypeAlias +from typing import Concatenate, NamedTuple, ParamSpec, TypeAlias, TypeVar from aiohttp import BodyPartReader, web from aiohttp.log import web_logger @@ -10,15 +13,19 @@ from pydantic import BaseModel, ValidationError from questionpy_common import constants -from questionpy_server.api.models import MainBaseModel, NotFoundStatus, NotFoundStatusWhat from questionpy_server.app import QPyServer from questionpy_server.cache import CacheItemTooLargeError +from questionpy_server.hash import HashContainer +from questionpy_server.models import MainBaseModel from questionpy_server.package import Package -from questionpy_server.types import M -from questionpy_server.web import ( - HashContainer, - read_part, +from questionpy_server.web._errors import ( + MainBodyMissingError, + PackageHashMismatchError, + PackageMissingByHashError, + PackageMissingWithoutHashError, + QuestionStateMissingError, ) +from questionpy_server.web._utils import read_part _P = ParamSpec("_P") _HandlerFunc: TypeAlias = Callable[Concatenate[web.Request, _P], Awaitable[web.StreamResponse]] @@ -94,56 +101,6 @@ async def _read_body_parts(request: web.Request) -> _RequestBodyParts: return parts -class _ExceptionMixin(web.HTTPException): - def __init__(self, msg: str, body: BaseModel | None = None) -> None: - if body: - # Send structured error body as JSON. - super().__init__(reason=type(self).__name__, text=body.model_dump_json(), content_type="application/json") - else: - # Send the detailed message. - super().__init__(reason=type(self).__name__, text=msg) - - # web.HTTPException uses the HTTP reason (which should be very short) as the exception message (which should be - # detailed). This sets the message to our detailed one. - Exception.__init__(self, msg) - - web_logger.info(msg) - - -class MainBodyMissingError(web.HTTPBadRequest, _ExceptionMixin): - def __init__(self) -> None: - super().__init__("The main body is required but was not provided.") - - -class PackageMissingWithoutHashError(web.HTTPBadRequest, _ExceptionMixin): - def __init__(self) -> None: - super().__init__("The package is required but was not provided.") - - -class PackageMissingByHashError(web.HTTPNotFound, _ExceptionMixin): - def __init__(self, package_hash: str) -> None: - super().__init__( - f"The package was not provided, is not cached and could not be found by its hash. ('{package_hash}')", - NotFoundStatus(what=NotFoundStatusWhat.PACKAGE), - ) - - -class PackageHashMismatchError(web.HTTPBadRequest, _ExceptionMixin): - def __init__(self, from_uri: str, from_body: str) -> None: - super().__init__( - f"The request URI specifies a package with hash '{from_uri}', but the sent package has a hash of " - f"'{from_body}'." - ) - - -class QuestionStateMissingError(web.HTTPBadRequest, _ExceptionMixin): - def __init__(self) -> None: - super().__init__( - "A question state part is required but was not provided.", - NotFoundStatus(what=NotFoundStatusWhat.QUESTION_STATE), - ) - - def ensure_package(handler: _HandlerFunc, *, param: inspect.Parameter | None = None) -> _HandlerFunc: """Decorator ensuring that the package needed by the handler is present.""" if not param: @@ -283,12 +240,15 @@ async def _parse_form_data(request: web.Request) -> _RequestBodyParts: return _RequestBodyParts(main, package, question_state) -def _validate_from_http(raw_body: str | bytes, param_class: type[M]) -> M: +_M = TypeVar("_M", bound=BaseModel) + + +def _validate_from_http(raw_body: str | bytes, param_class: type[_M]) -> _M: """Validates the given json which was presumably an HTTP body to the given Pydantic model. Args: raw_body: raw json body - param_class: the [pydantic.BaseModel][] subclass to valdiate to + param_class: the [pydantic.BaseModel][] subclass to validate to """ try: return param_class.model_validate_json(raw_body) diff --git a/questionpy_server/web/_errors.py b/questionpy_server/web/_errors.py new file mode 100644 index 00000000..44f388c7 --- /dev/null +++ b/questionpy_server/web/_errors.py @@ -0,0 +1,58 @@ +# 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 +from aiohttp import web +from aiohttp.log import web_logger +from pydantic import BaseModel + +from questionpy_server.models import NotFoundStatus, NotFoundStatusWhat + + +class _ExceptionMixin(web.HTTPException): + def __init__(self, msg: str, body: BaseModel | None = None) -> None: + if body: + # Send structured error body as JSON. + super().__init__(reason=type(self).__name__, text=body.model_dump_json(), content_type="application/json") + else: + # Send the detailed message. + super().__init__(reason=type(self).__name__, text=msg) + + # web.HTTPException uses the HTTP reason (which should be very short) as the exception message (which should be + # detailed). This sets the message to our detailed one. + Exception.__init__(self, msg) + + web_logger.info(msg) + + +class MainBodyMissingError(web.HTTPBadRequest, _ExceptionMixin): + def __init__(self) -> None: + super().__init__("The main body is required but was not provided.") + + +class PackageMissingWithoutHashError(web.HTTPBadRequest, _ExceptionMixin): + def __init__(self) -> None: + super().__init__("The package is required but was not provided.") + + +class PackageMissingByHashError(web.HTTPNotFound, _ExceptionMixin): + def __init__(self, package_hash: str) -> None: + super().__init__( + f"The package was not provided, is not cached and could not be found by its hash. ('{package_hash}')", + NotFoundStatus(what=NotFoundStatusWhat.PACKAGE), + ) + + +class PackageHashMismatchError(web.HTTPBadRequest, _ExceptionMixin): + def __init__(self, from_uri: str, from_body: str) -> None: + super().__init__( + f"The request URI specifies a package with hash '{from_uri}', but the sent package has a hash of " + f"'{from_body}'." + ) + + +class QuestionStateMissingError(web.HTTPBadRequest, _ExceptionMixin): + def __init__(self) -> None: + super().__init__( + "A question state part is required but was not provided.", + NotFoundStatus(what=NotFoundStatusWhat.QUESTION_STATE), + ) diff --git a/questionpy_server/api/routes/__init__.py b/questionpy_server/web/_routes/__init__.py similarity index 100% rename from questionpy_server/api/routes/__init__.py rename to questionpy_server/web/_routes/__init__.py diff --git a/questionpy_server/api/routes/_attempts.py b/questionpy_server/web/_routes/_attempts.py similarity index 92% rename from questionpy_server/api/routes/_attempts.py rename to questionpy_server/web/_routes/_attempts.py index f4d6445f..2adc7d11 100644 --- a/questionpy_server/api/routes/_attempts.py +++ b/questionpy_server/web/_routes/_attempts.py @@ -7,11 +7,11 @@ from aiohttp import web from questionpy_common.environment import RequestUser -from questionpy_server.api.models import AttemptScoreArguments, AttemptStartArguments, AttemptViewArguments from questionpy_server.app import QPyServer -from questionpy_server.decorators import ensure_required_parts +from questionpy_server.models import AttemptScoreArguments, AttemptStartArguments, AttemptViewArguments from questionpy_server.package import Package -from questionpy_server.web import json_response +from questionpy_server.web._decorators import ensure_required_parts +from questionpy_server.web._utils import json_response from questionpy_server.worker.runtime.package_location import ZipPackageLocation if TYPE_CHECKING: diff --git a/questionpy_server/api/routes/_files.py b/questionpy_server/web/_routes/_files.py similarity index 96% rename from questionpy_server/api/routes/_files.py rename to questionpy_server/web/_routes/_files.py index 1c447857..11839487 100644 --- a/questionpy_server/api/routes/_files.py +++ b/questionpy_server/web/_routes/_files.py @@ -7,8 +7,8 @@ from aiohttp.web_exceptions import HTTPNotImplemented from questionpy_server.app import QPyServer -from questionpy_server.decorators import ensure_package from questionpy_server.package import Package +from questionpy_server.web._decorators import ensure_package from questionpy_server.worker.runtime.package_location import ZipPackageLocation if TYPE_CHECKING: diff --git a/questionpy_server/api/routes/_packages.py b/questionpy_server/web/_routes/_packages.py similarity index 93% rename from questionpy_server/api/routes/_packages.py rename to questionpy_server/web/_routes/_packages.py index 19dd8e76..53ef6e28 100644 --- a/questionpy_server/api/routes/_packages.py +++ b/questionpy_server/web/_routes/_packages.py @@ -8,11 +8,11 @@ from aiohttp.web_exceptions import HTTPMethodNotAllowed, HTTPNotFound from questionpy_common.environment import RequestUser -from questionpy_server.api.models import QuestionCreateArguments, QuestionEditFormResponse, RequestBaseData from questionpy_server.app import QPyServer -from questionpy_server.decorators import ensure_package, ensure_required_parts +from questionpy_server.models import QuestionCreateArguments, QuestionEditFormResponse, RequestBaseData from questionpy_server.package import Package -from questionpy_server.web import json_response +from questionpy_server.web._decorators import ensure_package, ensure_required_parts +from questionpy_server.web._utils import json_response from questionpy_server.worker.runtime.package_location import ZipPackageLocation if TYPE_CHECKING: diff --git a/questionpy_server/api/routes/_status.py b/questionpy_server/web/_routes/_status.py similarity index 90% rename from questionpy_server/api/routes/_status.py rename to questionpy_server/web/_routes/_status.py index 27c65b59..5d563910 100644 --- a/questionpy_server/api/routes/_status.py +++ b/questionpy_server/web/_routes/_status.py @@ -5,9 +5,9 @@ from aiohttp import web from questionpy_server import __version__ -from questionpy_server.api.models import ServerStatus, Usage from questionpy_server.app import QPyServer -from questionpy_server.web import json_response +from questionpy_server.models import ServerStatus, Usage +from questionpy_server.web._utils import json_response status_routes = web.RouteTableDef() diff --git a/questionpy_server/web.py b/questionpy_server/web/_utils.py similarity index 95% rename from questionpy_server/web.py rename to questionpy_server/web/_utils.py index ec54dd9a..88efe6be 100644 --- a/questionpy_server/web.py +++ b/questionpy_server/web/_utils.py @@ -5,7 +5,7 @@ from collections.abc import Sequence from hashlib import sha256 from io import BytesIO -from typing import Literal, NamedTuple, overload +from typing import Literal, overload from aiohttp import BodyPartReader from aiohttp.log import web_logger @@ -14,6 +14,7 @@ from pydantic import BaseModel from questionpy_common.constants import KiB +from questionpy_server.hash import HashContainer def json_response(data: Sequence[BaseModel] | BaseModel, status: int = 200) -> Response: @@ -32,11 +33,6 @@ def json_response(data: Sequence[BaseModel] | BaseModel, status: int = 200) -> R return Response(text=data.model_dump_json(), status=status, content_type="application/json") -class HashContainer(NamedTuple): - data: bytes - hash: str - - @overload async def read_part(part: BodyPartReader, max_size: int, *, calculate_hash: Literal[True]) -> HashContainer: ... diff --git a/questionpy_server/worker/pool.py b/questionpy_server/worker/pool.py index a9bd4c1b..3cdd0014 100644 --- a/questionpy_server/worker/pool.py +++ b/questionpy_server/worker/pool.py @@ -8,7 +8,6 @@ from questionpy_common.constants import MiB from questionpy_common.environment import WorkerResourceLimits -from questionpy_server.types import WorkerType from questionpy_server.worker.runtime.package_location import PackageLocation from .exception import WorkerStartError @@ -17,7 +16,7 @@ class WorkerPool: - def __init__(self, max_workers: int, max_memory: int, worker_type: WorkerType = SubprocessWorker): + def __init__(self, max_workers: int, max_memory: int, worker_type: type[Worker] = SubprocessWorker): """Initialize the worker pool. Args: diff --git a/questionpy_server/worker/worker/__init__.py b/questionpy_server/worker/worker/__init__.py index eb161e45..c255fb95 100644 --- a/questionpy_server/worker/worker/__init__.py +++ b/questionpy_server/worker/worker/__init__.py @@ -10,7 +10,7 @@ from questionpy_common.elements import OptionsFormDefinition from questionpy_common.environment import RequestUser, WorkerResourceLimits from questionpy_common.manifest import PackageFile -from questionpy_server.api.models import AttemptStarted, QuestionCreated +from questionpy_server.models import AttemptStarted, QuestionCreated from questionpy_server.utils.manifest import ComparableManifest from questionpy_server.worker import WorkerResources from questionpy_server.worker.runtime.messages import MessageToWorker diff --git a/questionpy_server/worker/worker/base.py b/questionpy_server/worker/worker/base.py index c6981402..5da0f8a8 100644 --- a/questionpy_server/worker/worker/base.py +++ b/questionpy_server/worker/worker/base.py @@ -15,7 +15,7 @@ from questionpy_common.elements import OptionsFormDefinition from questionpy_common.environment import RequestUser, WorkerResourceLimits from questionpy_common.manifest import Manifest, PackageFile -from questionpy_server.api.models import AttemptStarted, QuestionCreated +from questionpy_server.models import AttemptStarted, QuestionCreated from questionpy_server.utils.manifest import ComparableManifest from questionpy_server.worker.exception import WorkerNotRunningError, WorkerStartError from questionpy_server.worker.runtime.messages import ( diff --git a/ruff_defaults.toml b/ruff_defaults.toml index ed4ed2e8..a41f0432 100644 --- a/ruff_defaults.toml +++ b/ruff_defaults.toml @@ -109,7 +109,7 @@ pydocstyle = { convention = "google" } "**/scripts/*" = ["INP001", "T201"] "**/tests/**/*" = ["PLC1901", "PLC2701", "PLR2004", "S", "TID252", "FBT"] # unused-async (aiohttp handlers must be async even if they don't use it) -"questionpy_server/api/routes/**/*" = ["RUF029"] +"questionpy_server/web/**/*" = ["RUF029"] [lint.flake8-builtins] # help: (guess it's ok since built-in `help()` is for interactive use and no collisions are expected) diff --git a/tests/questionpy_server/collector/test_lms_collector.py b/tests/questionpy_server/collector/test_lms_collector.py index 9fcbff11..b666bef0 100644 --- a/tests/questionpy_server/collector/test_lms_collector.py +++ b/tests/questionpy_server/collector/test_lms_collector.py @@ -12,8 +12,8 @@ from questionpy_server.cache import FileLimitLRU from questionpy_server.collector.indexer import Indexer from questionpy_server.collector.lms_collector import LMSCollector +from questionpy_server.hash import HashContainer from questionpy_server.package import Package -from questionpy_server.web import HashContainer from tests.conftest import PACKAGE diff --git a/tests/questionpy_server/collector/test_package_collection.py b/tests/questionpy_server/collector/test_package_collection.py index e439c152..163e0128 100644 --- a/tests/questionpy_server/collector/test_package_collection.py +++ b/tests/questionpy_server/collector/test_package_collection.py @@ -13,7 +13,7 @@ from questionpy_server.collector.indexer import Indexer from questionpy_server.collector.lms_collector import LMSCollector from questionpy_server.collector.local_collector import LocalCollector -from questionpy_server.web import HashContainer +from questionpy_server.hash import HashContainer async def test_start() -> None: diff --git a/tests/questionpy_server/api/__init__.py b/tests/questionpy_server/web/__init__.py similarity index 100% rename from tests/questionpy_server/api/__init__.py rename to tests/questionpy_server/web/__init__.py diff --git a/tests/questionpy_server/api/routes/__init__.py b/tests/questionpy_server/web/routes/__init__.py similarity index 100% rename from tests/questionpy_server/api/routes/__init__.py rename to tests/questionpy_server/web/routes/__init__.py diff --git a/tests/questionpy_server/api/routes/test_files.py b/tests/questionpy_server/web/routes/test_files.py similarity index 100% rename from tests/questionpy_server/api/routes/test_files.py rename to tests/questionpy_server/web/routes/test_files.py diff --git a/tests/questionpy_server/api/routes/test_packages.py b/tests/questionpy_server/web/routes/test_packages.py similarity index 96% rename from tests/questionpy_server/api/routes/test_packages.py rename to tests/questionpy_server/web/routes/test_packages.py index 501cfbb7..90bcf3bd 100644 --- a/tests/questionpy_server/api/routes/test_packages.py +++ b/tests/questionpy_server/web/routes/test_packages.py @@ -8,7 +8,7 @@ from aiohttp.test_utils import TestClient from pydantic import TypeAdapter -from questionpy_server.api.models import PackageInfo +from questionpy_server.models import PackageInfo from tests.conftest import PACKAGE