Skip to content

Commit 0ae7cb9

Browse files
committed
wip: testability
1 parent 502b76a commit 0ae7cb9

18 files changed

Lines changed: 71 additions & 120 deletions

File tree

questionpy_server/dependencies/_solutions.py renamed to questionpy_common/dependencies.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from questionpy_common import PackageNamespaceAndShortName
77
from questionpy_common.constants import RE_SEMVER
88
from questionpy_common.manifest import DistDependencies
9+
from questionpy_common.package_location import PackageLocation
910

1011

1112
@dataclass(frozen=True)
@@ -53,3 +54,5 @@ def __str__(self) -> str:
5354

5455

5556
type DependencySolution = StaticDependencySolution | DynamicDependencySolution
57+
58+
type SolutionAndLocation = tuple[DynamicDependencySolution, PackageLocation] | tuple[StaticDependencySolution, None]
File renamed without changes.

questionpy_server/dependencies/__init__.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,22 @@
33
DynamicDependencyResolver,
44
NoPackageMatchingVersionSpecError,
55
)
6-
from ._solutions import (
7-
DependencySolution,
8-
DynamicDependencySolution,
9-
StaticDependencySolution,
10-
)
116
from ._solver import resolve_dependency_tree
127
from ._solver.errors import (
138
DependencyConflictError,
149
DependencyCycleError,
1510
QPyDependencyError,
1611
TooDeeplyNestedDependencyError,
1712
)
18-
from ._worker_dependency_resolver import SolutionAndLocation, WorkerDependencyResolver
13+
from ._worker_dependency_resolver import WorkerDependencyResolver
1914

2015
__all__ = [
2116
"AvailablePackageVersion",
2217
"DependencyConflictError",
2318
"DependencyCycleError",
24-
"DependencySolution",
2519
"DynamicDependencyResolver",
26-
"DynamicDependencySolution",
2720
"NoPackageMatchingVersionSpecError",
2821
"QPyDependencyError",
29-
"SolutionAndLocation",
30-
"StaticDependencySolution",
3122
"TooDeeplyNestedDependencyError",
3223
"WorkerDependencyResolver",
3324
"resolve_dependency_tree",
Lines changed: 14 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import logging
22
from abc import ABC, abstractmethod
3-
from collections.abc import Iterable, Sequence
3+
from collections.abc import Iterable
44
from dataclasses import dataclass
55

6-
from semver import Version
7-
86
from questionpy_common import PackageNamespaceAndShortName
7+
from questionpy_common.package_location import PackageLocation
98
from questionpy_common.version_specifiers import QPyDependencyVersionSpecifier
109
from questionpy_server.utils.manifest import Manifest, ParsableSemverVersion
1110

@@ -19,39 +18,11 @@ class AvailablePackageVersion:
1918
version: ParsableSemverVersion
2019

2120

22-
def _format_dependency(nssn: PackageNamespaceAndShortName, version_spec: QPyDependencyVersionSpecifier | None) -> str:
23-
return f"@{nssn.namespace}/{nssn.short_name}{' ' + str(version_spec) if version_spec else ''}"
24-
25-
26-
class NoPackageMatchingVersionSpecError(Exception):
27-
def __init__(
28-
self,
29-
nssn: PackageNamespaceAndShortName,
30-
version_spec: QPyDependencyVersionSpecifier | None,
31-
available_versions: Sequence[Version],
32-
*,
33-
include_prereleases: bool,
34-
) -> None:
35-
msg = f"No package found for '{_format_dependency(nssn, version_spec)}'."
36-
37-
latest = max(available_versions, default=None)
38-
if latest is None:
39-
msg += " There are no versions of that package available."
40-
else:
41-
msg += f" There are {len(available_versions)} versions available, of which '{latest}' is the latest."
42-
43-
if include_prereleases:
44-
msg += " (Including prereleases.)"
45-
else:
46-
msg += " (Excluding prereleases.)"
47-
21+
class NoPackageWithHashError(Exception):
22+
def __init__(self, hash_: str) -> None:
23+
msg = f"Previously found package with hash '{hash_}' cannot be retrieved."
4824
super().__init__(msg)
4925

50-
self.nssn = nssn
51-
self.version_spec = version_spec
52-
self.include_prereleases = include_prereleases
53-
self.available_versions = available_versions
54-
5526

5627
class DynamicDependencyResolver(ABC):
5728
"""Finds packages matching a given dynamic dependency.
@@ -61,11 +32,19 @@ class DynamicDependencyResolver(ABC):
6132
"""
6233

6334
@abstractmethod
64-
def resolve_all(
35+
def get_matching_versions(
6536
self,
6637
nssn: PackageNamespaceAndShortName,
6738
version_spec: QPyDependencyVersionSpecifier | None,
6839
*,
6940
include_prereleases: bool,
7041
) -> Iterable[AvailablePackageVersion]:
7142
pass
43+
44+
@abstractmethod
45+
async def get_package_location(self, hash_: str) -> PackageLocation:
46+
"""Gets a location for a package that was previously returned by `get_matching_versions`.
47+
48+
Raises:
49+
NoPackageWithHashError
50+
"""

questionpy_server/dependencies/_solver/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
from semver import Version
66

77
from questionpy_common import PackageNamespaceAndShortName
8+
from questionpy_common.dependencies import DependencySolution
89
from questionpy_common.manifest import (
910
DistDependencies,
1011
DistQPyDependency,
1112
SourceManifest,
1213
)
1314
from questionpy_server.dependencies._dynamic_resolver_abc import DynamicDependencyResolver
14-
from questionpy_server.dependencies._solutions import DependencySolution
1515

1616
from ._model import RootPackage
1717
from ._provider import QPyResolvelibProvider

questionpy_server/dependencies/_solver/_model.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
from questionpy_common import PackageNamespaceAndShortName
66
from questionpy_common.manifest import AbstractDynamicQPyDependency, DistDependencies, DistStaticQPyDependency
7-
from questionpy_server.dependencies._solutions import DynamicDependencySolution, StaticDependencySolution
87

98

109
@dataclass(frozen=True)

questionpy_server/dependencies/_solver/_provider.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,12 @@
66
from semver import Version
77

88
from questionpy_common import PackageNamespaceAndShortName
9+
from questionpy_common.dependencies import DependencySolution, DynamicDependencySolution, StaticDependencySolution
910
from questionpy_common.manifest import AbstractDynamicQPyDependency, DistDynamicQPyDependency, DistStaticQPyDependency
1011
from questionpy_common.version_specifiers import QPyDependencyVersionSpecifier
1112
from questionpy_server.dependencies._dynamic_resolver_abc import (
1213
DynamicDependencyResolver,
1314
)
14-
from questionpy_server.dependencies._solutions import (
15-
DependencySolution,
16-
DynamicDependencySolution,
17-
StaticDependencySolution,
18-
)
1915

2016
from ._model import (
2117
Candidate,
@@ -101,7 +97,7 @@ def _find_solutions(
10197
merged = _merge_dynamic_deps(*(req.dep for req in dynamic_reqs))
10298

10399
# TODO: Use locked version if possible.
104-
matching_package_versions = resolver.resolve_all(
100+
matching_package_versions = resolver.get_matching_versions(
105101
nssn=nssn,
106102
version_spec=merged.version,
107103
include_prereleases=merged.include_prereleases,
Lines changed: 8 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,22 @@
11
from collections.abc import Iterable
22

33
from questionpy_common import PackageNamespaceAndShortName
4+
from questionpy_common.package_location import PackageLocation
45
from questionpy_common.version_specifiers import QPyDependencyVersionSpecifier
56
from questionpy_server.collector import PackageCollection
6-
from questionpy_server.utils.manifest import Manifest
7-
from questionpy_server.worker.runtime.package_location import PackageLocation
87

98
from ._dynamic_resolver_abc import (
109
AvailablePackageVersion,
1110
DynamicDependencyResolver,
11+
NoPackageWithHashError,
1212
)
13-
from ._solutions import (
14-
DependencySolution,
15-
DynamicDependencySolution,
16-
StaticDependencySolution,
17-
)
18-
from ._solver import resolve_dependency_tree
1913

2014

21-
class _PackageCollectionDependencyResolver(DynamicDependencyResolver):
15+
class PackageCollectionDependencyResolver(DynamicDependencyResolver):
2216
def __init__(self, package_collection: PackageCollection) -> None:
2317
self._package_collection = package_collection
2418

25-
def resolve_all(
19+
def get_matching_versions(
2620
self,
2721
nssn: PackageNamespaceAndShortName,
2822
version_spec: QPyDependencyVersionSpecifier | None,
@@ -38,36 +32,9 @@ def resolve_all(
3832
and (version_spec is None or version_spec.allows(version))
3933
)
4034

41-
42-
type SolutionAndLocation = tuple[DynamicDependencySolution, PackageLocation] | tuple[StaticDependencySolution, None]
43-
44-
45-
class WorkerDependencyResolver:
46-
"""A server-specific wrapper for the generic solver.
47-
48-
Uses the `PackageCollection` to implement the `DynamicDependencyResolver`.
49-
"""
50-
51-
def __init__(self, package_collection: PackageCollection) -> None:
52-
self._package_collection = package_collection
53-
self._dynamic_resolver = _PackageCollectionDependencyResolver(package_collection)
54-
55-
async def _resolve_if_dynamic(self, solution: DependencySolution) -> SolutionAndLocation:
56-
if isinstance(solution, StaticDependencySolution):
57-
return solution, None
58-
59-
package = self._package_collection.get(solution.hash)
35+
async def get_package_location(self, hash_: str) -> PackageLocation:
36+
package = self._package_collection.get(hash_)
6037
if not package:
61-
# This is a race condition that is technically possible, but probably quite unlikely.
62-
msg = f"Package '{solution.hash}' was just resolved, but cannot be found (anymore)."
63-
raise RuntimeError(msg)
64-
65-
return solution, await package.get_zip_package_location()
66-
67-
async def resolve_and_retrieve(
68-
self, root_manifest: Manifest
69-
) -> dict[PackageNamespaceAndShortName, SolutionAndLocation]:
70-
"""Solves the dependency tree and gets `PackageLocation`s for all dynamic solutions."""
71-
solutions = resolve_dependency_tree(root_manifest, root_manifest.dependencies.qpy, self._dynamic_resolver)
38+
raise NoPackageWithHashError(hash_)
7239

73-
return {nssn: await self._resolve_if_dynamic(solution) for nssn, solution in solutions.items()}
40+
return await package.get_zip_package_location()

questionpy_server/package.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
from pathlib import Path
77
from typing import TYPE_CHECKING
88

9+
from questionpy_common.package_location import ZipPackageLocation
910
from questionpy_server.collector.abc import BaseCollector
1011
from questionpy_server.collector.lms_collector import LMSCollector
1112
from questionpy_server.collector.local_collector import LocalCollector
1213
from questionpy_server.collector.repo_collector import RepoCollector
1314
from questionpy_server.models import PackageVersionInfo
1415
from questionpy_server.utils.manifest import Manifest
15-
from questionpy_server.worker.runtime.package_location import ZipPackageLocation
1616

1717
if TYPE_CHECKING:
1818
from questionpy_server.collector.abc import BaseCollector

questionpy_server/utils/manifest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from questionpy_common.constants import DIST_DIR, MANIFEST_FILENAME, MAX_MANIFEST_SIZE
1515
from questionpy_common.error import QPyBaseError
1616
from questionpy_common.manifest import Manifest
17-
from questionpy_server.worker.runtime.package_location import (
17+
from questionpy_common.package_location import (
1818
FunctionPackageLocation,
1919
PackageLocation,
2020
ZipPackageLocation,

0 commit comments

Comments
 (0)