Skip to content

Commit b22eec2

Browse files
committed
refactor: apply suggested changes
1 parent 4efec60 commit b22eec2

File tree

6 files changed

+44
-53
lines changed

6 files changed

+44
-53
lines changed

questionpy_common/constants.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
# QuestionPy is free software released under terms of the MIT license. See LICENSE.md.
33
# (c) Technische Universität Berlin, innoCampus <info@isis.tu-berlin.de>
44
import re
5-
from typing import Annotated, Final
5+
from typing import Final
66

7-
from pydantic import ByteSize, Field
7+
from pydantic import ByteSize
88

99
# General.
1010
KiB: Final[int] = 1024
@@ -28,4 +28,3 @@
2828
)
2929

3030
ENVIRONMENT_VARIABLE_REGEX: Final[str] = r"[a-zA-Z_][a-zA-Z0-9_]*"
31-
ENVIRONMENT_VARIABLE = Annotated[str, Field(pattern=f"^{ENVIRONMENT_VARIABLE_REGEX}$")]

questionpy_common/manifest.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from pydantic import BaseModel, ByteSize, PositiveInt, StringConstraints, conset, field_validator
1111
from pydantic.fields import Field
1212

13-
from questionpy_common.constants import ENVIRONMENT_VARIABLE
13+
from questionpy_common.constants import ENVIRONMENT_VARIABLE_REGEX
1414

1515

1616
class PackageType(StrEnum):
@@ -88,6 +88,9 @@ class PartialPackagePermissions(BaseModel):
8888
lms_attributes: set[str] | None = None
8989

9090

91+
type EnvironmentVariableName = Annotated[str, Field(pattern=f"^{ENVIRONMENT_VARIABLE_REGEX}$")]
92+
93+
9194
class SourceManifest(BaseModel):
9295
"""Represents the fields in a package source directory.
9396
@@ -113,7 +116,7 @@ class SourceManifest(BaseModel):
113116
type: PackageType = DEFAULT_PACKAGETYPE
114117
license: str | None = None
115118
permissions: PartialPackagePermissions | None = None
116-
environment_variables: set[ENVIRONMENT_VARIABLE] | None = None
119+
environment_variables: set[EnvironmentVariableName] | None = None
117120
tags: set[str] = set()
118121
requirements: str | list[str] | None = None
119122

questionpy_server/settings.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
SettingsConfigDict,
3333
)
3434

35-
from questionpy_common.constants import ENVIRONMENT_VARIABLE, ENVIRONMENT_VARIABLE_REGEX, MAX_PACKAGE_SIZE, GiB, MiB
36-
from questionpy_common.manifest import PartialPackagePermissions, ensure_is_valid_name
35+
from questionpy_common.constants import ENVIRONMENT_VARIABLE_REGEX, MAX_PACKAGE_SIZE, GiB, MiB
36+
from questionpy_common.manifest import EnvironmentVariableName, PartialPackagePermissions, ensure_is_valid_name
3737
from questionpy_server.worker import Worker
3838
from questionpy_server.worker.impl.subprocess import SubprocessWorker
3939

@@ -210,7 +210,7 @@ class PackagePermissionsSettings(BaseModel):
210210
packages: list[SpecificPackagePermissions] = []
211211

212212

213-
class EnvironmentVariables(RootModel[dict[ENVIRONMENT_VARIABLE, str]]):
213+
class EnvironmentVariables(RootModel[dict[EnvironmentVariableName, str]]):
214214
interpolation_pattern: ClassVar[re.Pattern] = re.compile(rf"^\$\{{({ENVIRONMENT_VARIABLE_REGEX})}}$")
215215
"""Matches environment variable interpolation syntax for replacement.
216216
@@ -219,7 +219,6 @@ class EnvironmentVariables(RootModel[dict[ENVIRONMENT_VARIABLE, str]]):
219219
220220
Example:
221221
"${MY_VAR}" matches and becomes the value of the environment variable "MY_VAR"
222-
223222
"""
224223

225224
escaped_interpolation_pattern: ClassVar[re.Pattern] = re.compile(rf"^\$(\$+\{{{ENVIRONMENT_VARIABLE_REGEX}}})$")
Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
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 <info@isis.tu-berlin.de>
4-
from abc import ABC, abstractmethod
54
from typing import NamedTuple
65

7-
from questionpy_server.cache import LRUCacheMemory
86
from questionpy_server.package import Package
97
from questionpy_server.settings import PackageSelector, Selectable
108

@@ -36,32 +34,12 @@ def _is_matching(selector: PackageSelector, query: SelectorQuery) -> bool:
3634
)
3735

3836

39-
class Selector[T: Selectable, V](ABC):
40-
"""Provides helpful methods for getting package and request specific data."""
37+
def get_matching[T: Selectable](selectables: list[T], query: SelectorQuery) -> T | None:
38+
"""Gets the first matching selectable, if any.
4139
42-
def __init__(self, selectables: list[T]):
43-
self._cache: LRUCacheMemory[SelectorQuery, V] = LRUCacheMemory(max_size=128)
44-
self._selectables = selectables
45-
46-
def _get_matching(self, query: SelectorQuery) -> T | None:
47-
"""Gets the first matching selectable, if any.
48-
49-
It assumes that the selectables are ordered from least specific to most specific.
50-
"""
51-
for selectable in reversed(self._selectables):
52-
if _is_matching(selectable.package_selector, query):
53-
return selectable
54-
return None
55-
56-
def get(self, query: SelectorQuery) -> V:
57-
"""Gets the result of the query, which may be cached."""
58-
if cached := self._cache.get(query):
59-
return cached
60-
61-
result = self._get(query)
62-
self._cache.put(query, result)
63-
64-
return result
65-
66-
@abstractmethod
67-
def _get(self, query: SelectorQuery) -> V: ...
40+
It assumes that the selectables are ordered from least specific to most specific.
41+
"""
42+
for selectable in reversed(selectables):
43+
if _is_matching(selectable.package_selector, query):
44+
return selectable
45+
return None

questionpy_server/worker/selector/environment_variables.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,30 @@
22
# The QuestionPy Server is free software released under terms of the MIT license. See LICENSE.md.
33
# (c) Technische Universität Berlin, innoCampus <info@isis.tu-berlin.de>
44
from questionpy_common.error import QPyBaseError
5-
from questionpy_server.settings import EnvironmentVariablesSettings, SpecificPackageEnvironmentVariables
6-
from questionpy_server.worker.selector import Selector, SelectorQuery
5+
from questionpy_server.cache import LRUCacheMemory
6+
from questionpy_server.settings import EnvironmentVariablesSettings
7+
from questionpy_server.worker.selector import SelectorQuery, get_matching
78

89

910
class PackageEnvironmentVariablesError(QPyBaseError):
1011
pass
1112

1213

13-
class EnvironmentVariablesHandler(Selector[SpecificPackageEnvironmentVariables, dict[str, str]]):
14+
class EnvironmentVariablesHandler:
1415
"""Handles environment variables for a request."""
1516

1617
def __init__(self, settings: EnvironmentVariablesSettings):
17-
super().__init__(settings.packages)
18-
18+
self._cache: LRUCacheMemory[SelectorQuery, dict[str, str]] = LRUCacheMemory(max_size=128)
19+
self._environment_variables = settings.packages
1920
self._global_environment_variables = settings.global_.root
2021

21-
def _get(self, query: SelectorQuery) -> dict[str, str]:
22+
def get(self, query: SelectorQuery) -> dict[str, str]:
23+
if cached_environment_variables := self._cache.get(query):
24+
return cached_environment_variables
25+
2226
environment_variables = self._global_environment_variables.copy()
2327

24-
if (specific := self._get_matching(query)) and specific.environment_variables:
28+
if (specific := get_matching(self._environment_variables, query)) and specific.environment_variables:
2529
environment_variables.update(specific.environment_variables.root)
2630

2731
requested_environment_variables = query.package.manifest.environment_variables
@@ -34,4 +38,5 @@ def _get(self, query: SelectorQuery) -> dict[str, str]:
3438
)
3539
raise PackageEnvironmentVariablesError(msg)
3640

41+
self._cache.put(query, environment_variables)
3742
return environment_variables

questionpy_server/worker/selector/permissions.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@
55

66
from questionpy_common.environment import PackagePermissions as EnvironmentPackagePermissions
77
from questionpy_common.error import QPyBaseError
8+
from questionpy_server.cache import LRUCacheMemory
89
from questionpy_server.package import Package
910
from questionpy_server.settings import (
1011
CompletePackagePermissions,
1112
MainProcessExecutionModeValues,
1213
PackagePermissionsSettings,
1314
SpecificPackagePermissions,
1415
)
15-
from questionpy_server.worker.selector import Selector, SelectorQuery
16+
from questionpy_server.worker.selector import SelectorQuery, get_matching
1617

1718
_log = logging.getLogger(__name__)
1819

@@ -31,12 +32,12 @@ def _has_enough_permissions(allowed: CompletePackagePermissions, requested: Comp
3132
)
3233

3334

34-
class PackagePermissionsHandler(Selector[SpecificPackagePermissions, EnvironmentPackagePermissions]):
35+
class PackagePermissionsHandler:
3536
"""Handles package permissions for a request."""
3637

3738
def __init__(self, settings: PackagePermissionsSettings):
38-
super().__init__(settings.packages)
39-
39+
self._cache: LRUCacheMemory[SelectorQuery, EnvironmentPackagePermissions] = LRUCacheMemory(max_size=128)
40+
self._package_permissions = settings.packages
4041
self._default_permissions = CompletePackagePermissions()
4142
self._auto_grant_permissions = settings.auto_grant_permissions
4243

@@ -69,11 +70,14 @@ def _get_actual_auto_grant_permissions(self, permissions: SpecificPackagePermiss
6970
specific_auto_grant_permissions = permissions.auto_grant_permissions.model_dump(exclude_none=True)
7071
return self._auto_grant_permissions.model_copy(update=specific_auto_grant_permissions)
7172

72-
def _get(self, query: SelectorQuery) -> EnvironmentPackagePermissions:
73+
def get(self, query: SelectorQuery) -> EnvironmentPackagePermissions:
74+
if cached_permissions := self._cache.get(query):
75+
return cached_permissions
76+
7377
auto_grant_permissions = self._auto_grant_permissions
7478
requested_permissions = self._get_requested_permissions(query.package)
7579

76-
if specific_permissions := self._get_matching(query):
80+
if specific_permissions := get_matching(self._package_permissions, query):
7781
auto_grant_permissions = self._get_actual_auto_grant_permissions(specific_permissions)
7882

7983
if specific_permissions.override_permissions:
@@ -89,4 +93,7 @@ def _get(self, query: SelectorQuery) -> EnvironmentPackagePermissions:
8993
# Only keep explicitly allowed lms attributes.
9094
requested_permissions.lms_attributes.intersection_update(auto_grant_permissions.lms_attributes)
9195

92-
return EnvironmentPackagePermissions(**requested_permissions.model_dump())
96+
environment_package_permissions = EnvironmentPackagePermissions(**requested_permissions.model_dump())
97+
self._cache.put(query, environment_package_permissions)
98+
99+
return environment_package_permissions

0 commit comments

Comments
 (0)