From f5ae0b656363dbe7362ed2e1919a4f1e180599de Mon Sep 17 00:00:00 2001 From: Steve Bachmeier <23350991+stevebachmeier@users.noreply.github.com> Date: Wed, 11 Dec 2024 11:18:38 -0700 Subject: [PATCH] fix mypy errors: component.py (#549) --- CHANGELOG.rst | 4 + pyproject.toml | 1 - src/vivarium/component.py | 89 ++++++++++++----------- src/vivarium/framework/logging/manager.py | 5 +- tests/framework/test_engine.py | 2 +- 5 files changed, 55 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 97c71cac..6e873123 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,7 @@ +**TBD/TBD/TBD** + + - Type-hinting: Fix mypy errors in vivarium/component.py + **3.2.4 - 12/03/24** - Fix type hints for pandas groupby objects diff --git a/pyproject.toml b/pyproject.toml index dcb48a96..2dfd433f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,6 @@ exclude = [ # You will need to remove the mypy: ignore-errors comment from the file heading as well 'docs/source/conf.py', 'setup.py', - 'src/vivarium/component.py', 'src/vivarium/examples/boids/forces.py', 'src/vivarium/examples/boids/movement.py', 'src/vivarium/examples/boids/neighbors.py', diff --git a/src/vivarium/component.py b/src/vivarium/component.py index 818bd174..9f5882e8 100644 --- a/src/vivarium/component.py +++ b/src/vivarium/component.py @@ -1,4 +1,3 @@ -# mypy: ignore-errors """ ========= Component @@ -18,16 +17,17 @@ from importlib import import_module from inspect import signature from numbers import Number -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, cast import pandas as pd from layered_config_tree import ConfigurationError, LayeredConfigTree -from loguru._logger import Logger from vivarium.framework.artifact import ArtifactException from vivarium.framework.population import PopulationError if TYPE_CHECKING: + import loguru + from vivarium.framework.engine import Builder from vivarium.framework.event import Event from vivarium.framework.lookup import LookupTable @@ -92,7 +92,24 @@ class Component(ABC): component. An empty dictionary indicates no managed configurations. """ - def __repr__(self): + def __init__(self) -> None: + """Initializes a new instance of the Component class. + + This method is the initializer for the Component class. It initializes + logger of type Logger and population_view of type PopulationView to None. + These attributes will be fully initialized in the setup_component method + of this class. + """ + self._repr: str = "" + self._name: str = "" + self._sub_components: list["Component"] = [] + self.logger: loguru.Logger | None = None + self.get_value_columns: Callable[[str | pd.DataFrame], list[str]] | None = None + self.configuration: LayeredConfigTree | None = None + self._population_view: PopulationView | None = None + self.lookup_tables: dict[str, LookupTable] = {} + + def __repr__(self) -> str: """Returns a string representation of the __init__ call made to create this object. @@ -111,16 +128,17 @@ def __repr__(self): object. """ if not self._repr: - args = [ - f"{name}={value.__repr__() if isinstance(value, Component) else value}" - for name, value in self.get_initialization_parameters().items() - ] - args = ", ".join(args) + args = ", ".join( + [ + f"{name}={value.__repr__() if isinstance(value, Component) else value}" + for name, value in self.get_initialization_parameters().items() + ] + ) self._repr = f"{type(self).__name__}({args})" return self._repr - def __str__(self): + def __str__(self) -> str: return self._repr ############## @@ -244,15 +262,15 @@ def initialization_requirements( return [] @property - def population_view_query(self) -> str | None: + def population_view_query(self) -> str: """Provides a query to use when filtering the component's `PopulationView`. Returns ------- A pandas query string for filtering the component's `PopulationView`. - Returns `None` if no filtering is required. + Returns an empty string if no filtering is required. """ - return None + return "" @property def post_setup_priority(self) -> int: @@ -324,23 +342,6 @@ def simulation_end_priority(self) -> int: # Lifecycle methods # ##################### - def __init__(self) -> None: - """Initializes a new instance of the Component class. - - This method is the initializer for the Component class. It initializes - logger of type Logger and population_view of type PopulationView to None. - These attributes will be fully initialized in the setup_component method - of this class. - """ - self._repr: str = "" - self._name: str = "" - self._sub_components: list["Component"] = [] - self.logger: Logger | None = None - self.get_value_columns: Callable[[str | pd.DataFrame], list[str]] | None = None - self.configuration: LayeredConfigTree | None = None - self._population_view: PopulationView | None = None - self.lookup_tables: dict[str, LookupTable] = {} - def setup_component(self, builder: "Builder") -> None: """Sets up the component for a Vivarium simulation. @@ -515,7 +516,7 @@ def get_initialization_parameters(self) -> dict[str, Any]: """ return { parameter_name: getattr(self, parameter_name) - for parameter_name in signature(self.__init__).parameters + for parameter_name in signature(self.__init__).parameters # type: ignore[misc] if hasattr(self, parameter_name) } @@ -538,7 +539,7 @@ def get_configuration(self, builder: "Builder") -> LayeredConfigTree | None: """ if self.name in builder.configuration: - return builder.configuration[self.name] + return builder.configuration.get_tree(self.name) return None def build_all_lookup_tables(self, builder: "Builder") -> None: @@ -606,7 +607,9 @@ def build_lookup_table( raise ConfigurationError(f"Data '{data}' must be a LookupTableData instance.") if isinstance(data, list): - return builder.lookup.build_table(data, value_columns=list(value_columns)) + return builder.lookup.build_table( + data, value_columns=list(value_columns) if value_columns else () + ) if isinstance(data, pd.DataFrame): duplicated_columns = set(data.columns[data.columns.duplicated()]) if duplicated_columns: @@ -627,11 +630,15 @@ def build_lookup_table( return builder.lookup.build_table(data) def _get_columns( - self, value_columns: Sequence[str] | None, data: float | pd.DataFrame - ) -> tuple[list[str], list[str], list[str]]: + self, value_columns: Sequence[str] | None, data: pd.DataFrame + ) -> tuple[Sequence[str], list[str], list[str]]: all_columns = list(data.columns) if value_columns is None: - value_columns = self.get_value_columns(data) + # NOTE: self.get_value_columns cannot be None at this point of the call stack + value_column_getter = cast( + Callable[[str | pd.DataFrame], list[str]], self.get_value_columns + ) + value_columns = value_column_getter(data) potential_parameter_columns = [ str(col).removesuffix("_start") @@ -685,9 +692,9 @@ def get_data(self, builder: Builder, data_source: DataInput) -> Any: module, method = data_source.split("::") try: if module == "self": - data_source = getattr(self, method) + data_source_callable = getattr(self, method) else: - data_source = getattr(import_module(module), method) + data_source_callable = getattr(import_module(module), method) except ModuleNotFoundError: raise ConfigurationError(f"Unable to find module '{module}'.") except AttributeError: @@ -697,7 +704,7 @@ def get_data(self, builder: Builder, data_source: DataInput) -> Any: raise ConfigurationError( f"There is no method '{method}' for the {module_string}." ) - data = data_source(builder) + data = data_source_callable(builder) else: try: data = builder.data.load(data_source) @@ -705,7 +712,7 @@ def get_data(self, builder: Builder, data_source: DataInput) -> Any: raise ConfigurationError( f"Failed to find key '{data_source}' in artifact." ) - elif isinstance(data_source, Callable): + elif callable(data_source): data = data_source(builder) else: data = data_source @@ -791,7 +798,7 @@ def _register_simulant_initializer(self, builder: Builder) -> None: if type(self).on_initialize_simulants != Component.on_initialize_simulants: builder.population.initializes_simulants( - self, creates_columns=self.columns_created, **initialization_requirements + self, creates_columns=self.columns_created, **initialization_requirements # type: ignore[arg-type] ) def _register_time_step_prepare_listener(self, builder: "Builder") -> None: diff --git a/src/vivarium/framework/logging/manager.py b/src/vivarium/framework/logging/manager.py index 0e442761..64b15b57 100644 --- a/src/vivarium/framework/logging/manager.py +++ b/src/vivarium/framework/logging/manager.py @@ -7,7 +7,6 @@ from __future__ import annotations import loguru -from loguru import logger from vivarium.framework.logging.utilities import configure_logging_to_terminal from vivarium.manager import Interface, Manager @@ -38,7 +37,7 @@ def _terminal_logging_not_configured() -> bool: # fragile since it depends on a loguru's internals as well as the stability of code # paths in vivarium, but both are quite stable at this point, so I think it's pretty, # low risk. - return 1 not in logger._core.handlers # type: ignore[attr-defined] + return 1 not in loguru.logger._core.handlers # type: ignore[attr-defined] @property def name(self) -> str: @@ -48,7 +47,7 @@ def get_logger(self, component_name: str | None = None) -> loguru.Logger: bind_args = {"simulation": self._simulation_name} if component_name: bind_args["component"] = component_name - return logger.bind(**bind_args) + return loguru.logger.bind(**bind_args) class LoggingInterface(Interface): diff --git a/tests/framework/test_engine.py b/tests/framework/test_engine.py index dd888ac1..37025db5 100644 --- a/tests/framework/test_engine.py +++ b/tests/framework/test_engine.py @@ -62,7 +62,7 @@ def components(): @pytest.fixture def log(mocker): - return mocker.patch("vivarium.framework.logging.manager.logger") + return mocker.patch("vivarium.framework.logging.manager.loguru.logger") def test_simulation_with_non_components(SimulationContext, components: list[Component]):