feat: allow to use simplified keys for context

Initial implementation requires explicit context key name to be set.
Though it is still useful sometimes (e.g. if there should be two
variables with the same type), in the most used scenarios internally
only type is required. This commit extends set and get methods to allow
to construct ContextKey from type directly

Also it breaks old keys, since - in order to reduce amount of possible
mistakes - internal classes uses this generation method
This commit is contained in:
Evgenii Alekseev 2024-05-10 17:31:45 +03:00
parent c74cd68ad6
commit ac19c407d3
12 changed files with 64 additions and 30 deletions

View File

@ -38,12 +38,12 @@ class _Context:
"""
self._content: dict[str, Any] = {}
def get(self, key: ContextKey[T]) -> T:
def get(self, key: ContextKey[T] | type[T]) -> T:
"""
get value for the specified key
Args:
key(ContextKey[T]): context key name
key(ContextKey[T] | type[T]): context key name
Returns:
T: value associated with the key
@ -52,29 +52,37 @@ class _Context:
KeyError: in case if the specified context variable was not found
ValueError: in case if type of value is not an instance of specified return type
"""
if not isinstance(key, ContextKey):
key = ContextKey.from_type(key)
if key.key not in self._content:
raise KeyError(key.key)
value = self._content[key.key]
if not isinstance(value, key.return_type):
raise ValueError(f"Value {value} is not an instance of {key.return_type}")
return value
def set(self, key: ContextKey[T], value: T) -> None:
def set(self, key: ContextKey[T] | type[T], value: T) -> None:
"""
set value for the specified key
Args:
key(ContextKey[T]): context key name
key(ContextKey[T] | type[T]): context key name
value(T): context value associated with the specified key
Raises:
KeyError: in case if the specified context variable already exists
ValueError: in case if type of value is not an instance of specified return type
"""
if not isinstance(key, ContextKey):
key = ContextKey.from_type(key)
if key.key in self._content:
raise KeyError(key.key)
if not isinstance(value, key.return_type):
raise ValueError(f"Value {value} is not an instance of {key.return_type}")
self._content[key.key] = value
def __iter__(self) -> Iterator[str]:

View File

@ -22,7 +22,6 @@ from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.gitremote.remote_push import RemotePush
from ahriman.core.triggers import Trigger
from ahriman.models.context_key import ContextKey
from ahriman.models.package import Package
from ahriman.models.repository_id import RepositoryId
from ahriman.models.result import Result
@ -111,7 +110,7 @@ class RemotePushTrigger(Trigger):
GitRemoteError: if database is not set in context
"""
ctx = context.get()
database = ctx.get(ContextKey("database", SQLite))
database = ctx.get(SQLite)
for target in self.targets:
section, _ = self.configuration.gettype(

View File

@ -26,7 +26,6 @@ from ahriman.core.database import SQLite
from ahriman.core.repository.executor import Executor
from ahriman.core.repository.update_handler import UpdateHandler
from ahriman.core.sign.gpg import GPG
from ahriman.models.context_key import ContextKey
from ahriman.models.pacman_synchronization import PacmanSynchronization
from ahriman.models.repository_id import RepositoryId
@ -89,11 +88,11 @@ class Repository(Executor, UpdateHandler):
# directly without loader
ctx = _Context()
ctx.set(ContextKey("database", SQLite), self.database)
ctx.set(ContextKey("configuration", Configuration), self.configuration)
ctx.set(ContextKey("pacman", Pacman), self.pacman)
ctx.set(ContextKey("sign", GPG), self.sign)
ctx.set(SQLite, self.database)
ctx.set(Configuration, self.configuration)
ctx.set(Pacman, self.pacman)
ctx.set(GPG, self.sign)
ctx.set(ContextKey("repository", type(self)), self)
ctx.set(type(self), self)
context.set(ctx)

View File

@ -24,7 +24,6 @@ from ahriman.core.sign.gpg import GPG
from ahriman.core.support.package_creator import PackageCreator
from ahriman.core.support.pkgbuild.keyring_generator import KeyringGenerator
from ahriman.core.triggers import Trigger
from ahriman.models.context_key import ContextKey
from ahriman.models.repository_id import RepositoryId
@ -134,8 +133,8 @@ class KeyringTrigger(Trigger):
trigger action which will be called at the start of the application
"""
ctx = context.get()
sign = ctx.get(ContextKey("sign", GPG))
database = ctx.get(ContextKey("database", SQLite))
sign = ctx.get(GPG)
database = ctx.get(SQLite)
for target in self.targets:
generator = KeyringGenerator(database, sign, self.repository_id, self.configuration, target)

View File

@ -25,7 +25,6 @@ from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.support.pkgbuild.pkgbuild_generator import PkgbuildGenerator
from ahriman.models.build_status import BuildStatus
from ahriman.models.context_key import ContextKey
from ahriman.models.package import Package
@ -65,7 +64,7 @@ class PackageCreator:
# register package
ctx = context.get()
database: SQLite = ctx.get(ContextKey("database", SQLite))
database = ctx.get(SQLite)
_, repository_id = self.configuration.check_loaded()
package = Package.from_build(local_path, repository_id.architecture, None)
database.package_update(package, BuildStatus())

View File

@ -18,7 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from dataclasses import dataclass
from typing import Generic, TypeVar
from typing import Generic, Self, TypeVar
T = TypeVar("T")
@ -35,3 +35,16 @@ class ContextKey(Generic[T]):
"""
key: str
return_type: type[T]
@classmethod
def from_type(cls, return_type: type[T]) -> Self:
"""
construct key from type
Args:
return_type(type[T]): return type used for the specified context key
Returns:
Self: context key with autogenerated
"""
return cls(return_type.__name__, return_type)

View File

@ -3,7 +3,6 @@ from pytest_mock import MockerFixture
from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.gitremote import RemotePushTrigger
from ahriman.models.context_key import ContextKey
from ahriman.models.package import Package
from ahriman.models.result import Result
@ -30,5 +29,5 @@ def test_on_result(configuration: Configuration, result: Result, package_ahriman
trigger = RemotePushTrigger(repository_id, configuration)
trigger.on_result(result, [package_ahriman])
database_mock.assert_called_once_with(ContextKey("database", SQLite))
database_mock.assert_called_once_with(SQLite)
run_mock.assert_called_once_with(result)

View File

@ -6,7 +6,6 @@ from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.repository import Repository
from ahriman.core.sign.gpg import GPG
from ahriman.models.context_key import ContextKey
def test_load(configuration: Configuration, database: SQLite, mocker: MockerFixture) -> None:
@ -29,9 +28,9 @@ def test_set_context(configuration: Configuration, database: SQLite, mocker: Moc
instance = Repository.load(repository_id, configuration, database, report=False)
set_mock.assert_has_calls([
MockCall(ContextKey("database", SQLite), instance.database),
MockCall(ContextKey("configuration", Configuration), instance.configuration),
MockCall(ContextKey("pacman", Pacman), instance.pacman),
MockCall(ContextKey("sign", GPG), instance.sign),
MockCall(ContextKey("repository", Repository), instance),
MockCall(SQLite, instance.database),
MockCall(Configuration, instance.configuration),
MockCall(Pacman, instance.pacman),
MockCall(GPG, instance.sign),
MockCall(Repository, instance),
])

View File

@ -5,7 +5,6 @@ from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.sign.gpg import GPG
from ahriman.core.support import KeyringTrigger
from ahriman.models.context_key import ContextKey
def test_configuration_sections(configuration: Configuration) -> None:
@ -29,5 +28,5 @@ def test_on_start(configuration: Configuration, mocker: MockerFixture) -> None:
trigger = KeyringTrigger(repository_id, configuration)
trigger.on_start()
context_mock.assert_has_calls([MockCall(ContextKey("sign", GPG)), MockCall(ContextKey("database", SQLite))])
context_mock.assert_has_calls([MockCall(GPG), MockCall(SQLite)])
run_mock.assert_called_once_with()

View File

@ -4,7 +4,6 @@ from pytest_mock import MockerFixture
from ahriman.core.database import SQLite
from ahriman.core.support.package_creator import PackageCreator
from ahriman.models.context_key import ContextKey
from ahriman.models.package import Package
from ahriman.models.package_description import PackageDescription
from ahriman.models.package_source import PackageSource
@ -38,5 +37,5 @@ def test_run(package_creator: PackageCreator, database: SQLite, mocker: MockerFi
init_mock.assert_called_once_with(local_path)
package_mock.assert_called_once_with(local_path, "x86_64", None)
database_mock.assert_called_once_with(ContextKey("database", SQLite))
database_mock.assert_called_once_with(SQLite)
insert_mock.assert_called_once_with(package, pytest.helpers.anyvar(int))

View File

@ -15,6 +15,18 @@ def test_get_set() -> None:
assert ctx.get(key) == value
def test_get_set_type() -> None:
"""
must set and get variable by type
"""
key, value = int, 42
ctx = _Context()
ctx.set(key, value)
assert ctx.get(key) == value
assert ctx.get(ContextKey.from_type(int)) == value
def test_get_key_exception() -> None:
"""
must raise KeyError in case if key was not found

View File

@ -0,0 +1,9 @@
from ahriman.models.context_key import ContextKey
def test_from_type() -> None:
"""
must construct key from type
"""
assert ContextKey.from_type(int) == ContextKey("int", int)
assert ContextKey.from_type(ContextKey) == ContextKey("ContextKey", ContextKey)