Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Refactor model.from_dict to use kwargs only for override (Sourcery refactored) #813

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ repos:
rev: 6.1.1
hooks:
- id: pydocstyle
files: "^glotaran/(plugin_system|utils|deprecation)"
files: "^glotaran/(plugin_system|utils|deprecation|testing)"
exclude: "docs|tests?"
# this is needed due to the following issue:
# https://github.com/PyCQA/pydocstyle/issues/368
Expand All @@ -87,14 +87,14 @@ repos:
rev: v1.8.0
hooks:
- id: darglint
files: "^glotaran/(plugin_system|utils|deprecation)"
files: "^glotaran/(plugin_system|utils|deprecation|testing)"
exclude: "docs|tests?"

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.910
hooks:
- id: mypy
files: "^glotaran/(plugin_system|utils|deprecation)"
files: "^glotaran/(plugin_system|utils|deprecation|testing)"
exclude: "docs"
additional_dependencies: [types-all]

Expand Down
14 changes: 1 addition & 13 deletions glotaran/builtin/io/yml/yml.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
from glotaran.io import save_dataset
from glotaran.io import save_parameters
from glotaran.model import Model
from glotaran.model import get_megacomplex
from glotaran.parameter import ParameterGroup
from glotaran.project import SavingOptions
from glotaran.project import Scheme
Expand Down Expand Up @@ -66,18 +65,7 @@ def load_model(self, file_name: str) -> Model:
if "megacomplex" not in spec:
raise ValueError("No megacomplex defined in model")

megacomplex_types = {
m["type"]: get_megacomplex(m["type"])
for m in spec["megacomplex"].values()
if "type" in m
}
if default_megacomplex is not None:
megacomplex_types[default_megacomplex] = get_megacomplex(default_megacomplex)
del spec["default-megacomplex"]

return Model.from_dict(
spec, megacomplex_types=megacomplex_types, default_megacomplex_type=default_megacomplex
)
return Model.from_dict(spec, megacomplex_types=None, default_megacomplex_type=None)

def load_parameters(self, file_name: str) -> ParameterGroup:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@

from glotaran.analysis.optimize import optimize
from glotaran.analysis.simulation import simulate
from glotaran.builtin.megacomplexes.decay import DecayMegacomplex
from glotaran.model import Megacomplex
from glotaran.model import Model
from glotaran.parameter import ParameterGroup
from glotaran.project import Scheme
Expand All @@ -28,20 +26,9 @@ class DecayModel(Model):
def from_dict(
cls,
model_dict,
*,
megacomplex_types: dict[str, type[Megacomplex]] | None = None,
default_megacomplex_type: str | None = None,
):
defaults: dict[str, type[Megacomplex]] = {
"decay": DecayMegacomplex,
}
if megacomplex_types is not None:
defaults.update(megacomplex_types)
return super().from_dict(
model_dict,
megacomplex_types=defaults,
default_megacomplex_type=default_megacomplex_type,
)
model_dict = {**model_dict, "default-megacomplex": "decay"}
return super().from_dict(model_dict)


class OneComponentOneChannel:
Expand Down
24 changes: 21 additions & 3 deletions glotaran/model/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from __future__ import annotations

import copy
from typing import Any
from typing import List
from warnings import warn

Expand All @@ -17,6 +18,7 @@
from glotaran.model.weight import Weight
from glotaran.parameter import Parameter
from glotaran.parameter import ParameterGroup
from glotaran.plugin_system.megacomplex_registration import get_megacomplex
from glotaran.utils.ipython import MarkdownStr

default_model_items = {
Expand Down Expand Up @@ -56,18 +58,34 @@ def __init__(
@classmethod
def from_dict(
cls,
model_dict: dict,
model_dict: dict[str, Any],
*,
megacomplex_types: dict[str, type[Megacomplex]],
megacomplex_types: dict[str, type[Megacomplex]] | None = None,
default_megacomplex_type: str | None = None,
) -> Model:
"""Creates a model from a dictionary.

Parameters
----------
model_dict :
model_dict: dict[str, Any]
Dictionary containing the model.
megacomplex_types: dict[str, type[Megacomplex]] | None
Overwrite 'megacomplex_types' in ``model_dict`` for testing.
default_megacomplex_type: str | None
Overwrite 'default-megacomplex' in ``model_dict`` for testing.
"""
if default_megacomplex_type is None:
default_megacomplex_type = model_dict.get("default-megacomplex")

if megacomplex_types is None:
megacomplex_types = {
m["type"]: get_megacomplex(m["type"])
for m in model_dict["megacomplex"].values()
if "type" in m
}
if default_megacomplex_type is not None:
megacomplex_types[default_megacomplex_type] = get_megacomplex(default_megacomplex_type)
del model_dict["default-megacomplex"]

model = cls(
megacomplex_types=megacomplex_types, default_megacomplex_type=default_megacomplex_type
Expand Down
1 change: 1 addition & 0 deletions glotaran/testing/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Testing framework package for glotaran itself and plugins."""
185 changes: 185 additions & 0 deletions glotaran/testing/plugin_system.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
"""Mock functionality for the plugin system."""
from __future__ import annotations

from contextlib import ExitStack
from contextlib import contextmanager
from typing import TYPE_CHECKING
from unittest import mock

from glotaran.plugin_system.base_registry import __PluginRegistry

if TYPE_CHECKING:
from typing import Generator
from typing import MutableMapping

from glotaran.io.interface import DataIoInterface
from glotaran.io.interface import ProjectIoInterface
from glotaran.model.megacomplex import Megacomplex
from glotaran.plugin_system.base_registry import _PluginType


@contextmanager
def _monkeypatch_plugin_registry(
register_name: str,
test_registry: MutableMapping[str, _PluginType] | None = None,
create_new_registry: bool = False,
) -> Generator[None, None, None]:
"""Generic implementation of a plugin system mock context manager.

Parameters
----------
register_name : str
Name of the register which should be patched.
test_registry : MutableMapping[str, _PluginType]
Registry to to update or replace the ``register_name`` registry with.
, by default None
create_new_registry : bool
Whether to update the actual registry or create a new one from ``test_registry``
, by default False

Yields
-------
Generator[None, None, None]
Just to keep the context alive.

See Also
--------
monkeypatch_plugin_registry_megacomplex
monkeypatch_plugin_registry_data_io
monkeypatch_plugin_registry_project_io
"""
if test_registry is not None:
initila_plugins = (
__PluginRegistry.__dict__[register_name]
if not create_new_registry
else {}
)

with mock.patch.object(
__PluginRegistry, register_name, {**initila_plugins, **test_registry}
):
yield
else:
yield


@contextmanager
def monkeypatch_plugin_registry_megacomplex(
test_megacomplex: MutableMapping[str, type[Megacomplex]] | None = None,
create_new_registry: bool = False,
) -> Generator[None, None, None]:
"""Monkeypatch the :class:`Megacomplex` registry.

Parameters
----------
test_megacomplex : MutableMapping[str, type[Megacomplex]], optional
Registry to to update or replace the ``Megacomplex`` registry with.
, by default None
create_new_registry : bool
Whether to update the actual registry or create a new one from ``test_megacomplex``
, by default False

Yields
-------
Generator[None, None, None]
Just to keep the context alive.
"""
with _monkeypatch_plugin_registry("megacomplex", test_megacomplex, create_new_registry):
yield


@contextmanager
def monkeypatch_plugin_registry_data_io(
test_data_io: MutableMapping[str, DataIoInterface] | None = None,
create_new_registry: bool = False,
) -> Generator[None, None, None]:
"""Monkeypatch the :class:`DataIoInterface` registry.

Parameters
----------
test_data_io : MutableMapping[str, DataIoInterface], optional
Registry to to update or replace the ``DataIoInterface`` registry with.
, by default None
create_new_registry : bool
Whether to update the actual registry or create a new one from ``test_data_io``
, by default False

Yields
-------
Generator[None, None, None]
Just to keep the context alive.
"""
with _monkeypatch_plugin_registry("data_io", test_data_io, create_new_registry):
yield


@contextmanager
def monkeypatch_plugin_registry_project_io(
test_project_io: MutableMapping[str, ProjectIoInterface] | None = None,
create_new_registry: bool = False,
) -> Generator[None, None, None]:
"""Monkeypatch the :class:`ProjectIoInterface` registry.

Parameters
----------
test_project_io : MutableMapping[str, ProjectIoInterface], optional
Registry to to update or replace the ``ProjectIoInterface`` registry with.
, by default None
create_new_registry : bool
Whether to update the actual registry or create a new one from ``test_data_io``
, by default False

Yields
-------
Generator[None, None, None]
Just to keep the context alive.
"""
with _monkeypatch_plugin_registry("project_io", test_project_io, create_new_registry):
yield


@contextmanager
def monkeypatch_plugin_registry_full(
test_megacomplex: MutableMapping[str, type[Megacomplex]] | None = None,
test_data_io: MutableMapping[str, DataIoInterface] | None = None,
test_project_io: MutableMapping[str, ProjectIoInterface] | None = None,
create_new_registry: bool = False,
) -> Generator[None, None, None]:
"""[summary]

Parameters
----------
test_megacomplex : MutableMapping[str, type[Megacomplex]], optional
Registry to to update or replace the ``Megacomplex`` registry with.
, by default None
test_data_io : MutableMapping[str, DataIoInterface], optional
Registry to to update or replace the ``DataIoInterface`` registry with.
, by default None
test_project_io : MutableMapping[str, ProjectIoInterface], optional
Registry to to update or replace the ``ProjectIoInterface`` registry with.
, by default None
create_new_registry : bool
Whether to update the actual registry or create a new one from the arguments.
, by default False

Yields
-------
Generator[None, None, None]
Just keeps all context manager alive

See Also
--------
monkeypatch_plugin_registry_megacomplex
monkeypatch_plugin_registry_data_io
monkeypatch_plugin_registry_project_io
"""
context_managers = [
monkeypatch_plugin_registry_megacomplex(test_megacomplex, create_new_registry),
monkeypatch_plugin_registry_data_io(test_data_io, create_new_registry),
monkeypatch_plugin_registry_project_io(test_project_io, create_new_registry),
]

with ExitStack() as stack:
for context_manager in context_managers:
stack.enter_context(context_manager)
yield
Loading