Repository: vanderschaarlab/temporai (language: python)

Issue:
Embrace mypy strict typing



Discussion / hints:

Fix patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 5e0b4034..731a42ec 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,14 +1,22 @@
-# exclude: '^docs/conf.py' # If need global exclude.
+# If need global exclude:
+# exclude: "^docs/conf.py"
+
+# NOTE:
+# Where excluding "docs/user_guide", that is because these are auto-generated files.
 
 repos:
 - repo: https://github.com/pre-commit/pre-commit-hooks
   rev: v4.4.0
   hooks:
   - id: trailing-whitespace
-    args: ['--markdown-linebreak-ext=md']
-    exclude: 'docs/user_guide'  # Auto-generated, leave as is.
+    args: [
+      "--markdown-linebreak-ext=md",
+    ]
+    exclude: "docs/user_guide"
   - id: check-added-large-files
-    args: ['--maxkb=1000']
+    args: [
+      "--maxkb=1000",
+    ]
   - id: check-ast
   - id: check-case-conflict
   - id: check-json
@@ -19,14 +27,20 @@ repos:
   - id: check-executables-have-shebangs
   - id: debug-statements
   - id: end-of-file-fixer
-    exclude: 'docs/user_guide'  # Auto-generated, leave as is.
+    exclude: "docs/user_guide"
   - id: pretty-format-json
-    args: ['--autofix', '--indent=4', '--no-sort-keys']
-    exclude: '.*.ipynb'  # ipynb may get treated as JSON, don't want that.
+    args: [
+      "--autofix",
+      "--indent=4",
+      "--no-sort-keys",
+    ]
+    exclude: ".*.ipynb"  # Do not want .ipynb to be treated as JSON.
   - id: requirements-txt-fixer
     exclude: docs/requirements.txt
   - id: mixed-line-ending
-    args: ['--fix=auto']
+    args: [
+      "--fix=auto",
+    ]
 
 - repo: https://github.com/pycqa/isort
   rev: 5.12.0
@@ -38,32 +52,55 @@ repos:
   hooks:
   - id: black-jupyter
     language_version: python3
-    exclude: 'docs/user_guide'  # Auto-generated, leave as is.
+    exclude: "docs/user_guide"
 
 # If like to embrace black styles even in the docs:
 - repo: https://github.com/asottile/blacken-docs
   rev: 1.16.0
   hooks:
   - id: blacken-docs
-    additional_dependencies: [black]
-    exclude: 'docs/user_guide'  # Auto-generated, leave as is.
+    additional_dependencies: [
+      "black",
+    ]
+    exclude: "docs/user_guide"
 
 - repo: https://github.com/PyCQA/flake8
   rev: 6.1.0
   hooks:
   - id: flake8
 
--   repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.5.1
-    hooks:
-    -   id: mypy
-        files: "^src/"
-        args: [--config-file=mypy.ini]
-        additional_dependencies: [pydantic>=2]
+- repo: https://github.com/pre-commit/mirrors-mypy
+  rev: v1.5.1
+  hooks:
+  - id: mypy
+    files: "^src/"
+    args: [
+      "--config-file=mypy.ini",
+    ]
+    # NOTE:
+    # Re. additional **typed dependencies** and pre-commit mypy: https://stackoverflow.com/a/74291151.
+    # The list of typed dependencies below needs to be kept up-to-date with `setup.cfg`  for mypy errors to match
+    # the development environment. The typical "mismatched" mypy error that happens if these are not kept in sync is:
+    # `error: Unused "type: ignore" comment  [unused-ignore]`.
+    additional_dependencies: [
+      "hydra-core >=1.3",
+      "hyperimpute >= 0.1.17",
+      "loguru",
+      "pandera >= 0.17.0",
+      "pydantic >= 2",
+      "torch",
+      "tsai",
+    ]
 
 - repo: https://github.com/PyCQA/bandit
   rev: 1.7.5
   hooks:
   - id: bandit
-    args: ["-c", "pyproject.toml", "-q"]
-    additional_dependencies: ["bandit[toml]"]
+    args: [
+      "-c",
+      "pyproject.toml",
+      "-q"
+    ]
+    additional_dependencies: [
+      "bandit[toml]",
+    ]
diff --git a/docs/assets/Conceptual.png b/docs/assets/Conceptual.png
index 4050ce52..9a594ecf 100644
Binary files a/docs/assets/Conceptual.png and b/docs/assets/Conceptual.png differ
diff --git a/docs/assets/Pipeline.png b/docs/assets/Pipeline.png
deleted file mode 100644
index 180f9888..00000000
Binary files a/docs/assets/Pipeline.png and /dev/null differ
diff --git a/docs/user_guide/extending/tutorial01_custom_method.ipynb b/docs/user_guide/extending/tutorial01_custom_method.ipynb
index 8dbe3b9f..ffbe8456 100644
--- a/docs/user_guide/extending/tutorial01_custom_method.ipynb
+++ b/docs/user_guide/extending/tutorial01_custom_method.ipynb
@@ -312,21 +312,21 @@
     "\n",
     "class MyPlugin(BaseOneOffClassifier):\n",
     "    # The initializer:\n",
-    "    def __init__(self, **params) -> None:\n",
+    "    def __init__(self, **params: Any) -> None:\n",
     "        ...\n",
     "\n",
     "    # The _fit implementation.\n",
     "    def _fit(self, data: dataset.BaseDataset, *args, **kwargs):\n",
     "        ...\n",
     "\n",
-    "    def _predict(self, data: dataset.PredictiveDataset, *args, **kwargs) -> samples.StaticSamples:\n",
+    "    def _predict(self, data: dataset.PredictiveDataset, *args: Any, **kwargs: Any) -> samples.StaticSamples:\n",
     "        ...\n",
     "\n",
-    "    def _predict_proba(self, data: dataset.PredictiveDataset, *args, **kwargs) -> samples.StaticSamples:\n",
+    "    def _predict_proba(self, data: dataset.PredictiveDataset, *args: Any, **kwargs: Any) -> samples.StaticSamples:\n",
     "        ...\n",
     "    \n",
     "    @staticmethod\n",
-    "    def hyperparameter_space(*args, **kwargs):\n",
+    "    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:\n",
     "        # This method is not currently used in TemporAI (it will be used once AutoML component is implemented).\n",
     "        # For now, you may just return an empty list.\n",
     "        ...\n",
@@ -380,7 +380,7 @@
     "    # 2. Set the `ParamsDefinition` class variable in your plugin to this dataclass.\n",
     "    ParamsDefinition = MyPluginParams\n",
     "    \n",
-    "    def __init__(self, **params) -> None:\n",
+    "    def __init__(self, **params: Any) -> None:\n",
     "        # 3. Call the parent __init__ as so.\n",
     "        super().__init__(**params)\n",
     "\n",
@@ -411,10 +411,12 @@
    "outputs": [],
    "source": [
     "import dataclasses\n",
+    "from typing import Any, List\n",
     "\n",
     "import numpy as np\n",
     "\n",
     "from tempor.core.plugins import register_plugin\n",
+    "from tempor.methods.core import Params\n",
     "from tempor.data import dataset, samples\n",
     "from tempor.methods.prediction.one_off.classification import BaseOneOffClassifier\n",
     "\n",
@@ -436,7 +438,7 @@
     "        \"\"\"Does nothing.\"\"\"\n",
     "        return self  # Fit method needs to return `self`.\n",
     "\n",
-    "    def _predict(self, data: dataset.PredictiveDataset, *args, **kwargs) -> samples.StaticSamples:\n",
+    "    def _predict(self, data: dataset.PredictiveDataset, *args: Any, **kwargs: Any) -> samples.StaticSamples:\n",
     "        \"\"\"Always returns 1\"\"\"\n",
     "\n",
     "        assert data.predictive.targets is not None\n",
@@ -444,7 +446,7 @@
     "\n",
     "        return samples.StaticSamples.from_numpy(preds, dtype=int)\n",
     "\n",
-    "    def _predict_proba(self, data: dataset.PredictiveDataset, *args, **kwargs) -> samples.StaticSamples:\n",
+    "    def _predict_proba(self, data: dataset.PredictiveDataset, *args: Any, **kwargs: Any) -> samples.StaticSamples:\n",
     "        \"\"\"Always returns 1.0\"\"\"\n",
     "\n",
     "        assert data.predictive.targets is not None\n",
@@ -453,7 +455,7 @@
     "        return samples.StaticSamples.from_numpy(preds, dtype=float)\n",
     "\n",
     "    @staticmethod\n",
-    "    def hyperparameter_space(*args, **kwargs):\n",
+    "    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:\n",
     "        return []"
    ]
   },
diff --git a/docs/user_guide/extending/tutorial03_custom_datasource.ipynb b/docs/user_guide/extending/tutorial03_custom_datasource.ipynb
index d8681589..4a2efe63 100644
--- a/docs/user_guide/extending/tutorial03_custom_datasource.ipynb
+++ b/docs/user_guide/extending/tutorial03_custom_datasource.ipynb
@@ -242,6 +242,8 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "from typing import Any\n",
+    "\n",
     "import numpy as np\n",
     "\n",
     "from tempor.data.dataset import OneOffPredictionDataset\n",
@@ -251,7 +253,7 @@
     "\n",
     "@register_plugin(name=\"my_datasource\", category=\"prediction.one_off\", plugin_type=\"datasource\")\n",
     "class MyDataSource(OneOffPredictionDataSource):\n",
-    "    def __init__(self, random_seed: int = 123, **kwargs) -> None:\n",
+    "    def __init__(self, random_seed: int = 123, **kwargs: Any) -> None:\n",
     "        super().__init__(**kwargs)\n",
     "        self.random_seed = random_seed\n",
     "\n",
diff --git a/mypy.ini b/mypy.ini
index 8bc52109..f6b9f5ac 100644
--- a/mypy.ini
+++ b/mypy.ini
@@ -1,16 +1,41 @@
 [mypy]
+
+# -- Eclude list ---
 exclude = (?x)(
     conftest\.py$
     | ^assets/
     | ^\.dev/
     | ^dist/
     | ^docs/
+    | ^tests/
   )
-follow_imports = silent
-ignore_missing_imports = True
-plugins = pydantic.mypy
-show_column_numbers = True
-no_pretty = True
+
+# -- MyPy arguments --
+strict = True
 check_untyped_defs = True
+disallow_incomplete_defs = True
+disallow_untyped_calls = True
+disallow_untyped_defs = True
+ignore_missing_imports = True
+implicit_optional = False
 install_types = True
 non_interactive = True
+plugins = pydantic.mypy
+scripts_are_modules = True
+show_column_numbers = True
+strict_equality = True
+warn_redundant_casts = True
+warn_unreachable = True
+warn_unused_ignores = True
+
+# -- Disabled error codes ---
+# NOTE:
+# May wish to enable these in the future.
+# Reasoning for disabling for now:
+# type-arg:
+#   triggers on `np.ndarray`, which we do not want to always specify types of.
+# no-any-return:
+#   triggers in many situations, for instance when mypy cannot correctly infer the return type of a `@staticmethod`.
+#   and that propagates to then incorrectly assume that we are returning `Any` when we are not.
+#   See https://github.com/python/mypy/issues/3482 and https://github.com/python/mypy/issues/3482
+disable_error_code = type-arg, no-any-return
diff --git a/pyproject.toml b/pyproject.toml
index 0d6df8b8..71e4be6b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -65,3 +65,24 @@ add-ignore = [
     "D415",
     "D202"
 ]
+
+# Configure pyright.
+# May wish to add to pre-commit eventually.
+[tool.pyright]
+exclude = [
+    # Defaults:
+    "**/node_modules",
+    "**/__pycache__",
+    "**/.*",
+    # Custom:
+    ".dev",
+    "**/logs",
+    "outputs",
+    "data",
+    "tutorials/*/data",
+    "tutorials/*/logs",
+    "codecov",
+    "workspace",
+    # Exclude tests:
+    "tests",
+]
diff --git a/src/hydra_plugins/__init__.py b/src/hydra_plugins/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/tempor/automl/pipeline_selector.py b/src/tempor/automl/pipeline_selector.py
index 888d3ea5..867ccb8a 100644
--- a/src/tempor/automl/pipeline_selector.py
+++ b/src/tempor/automl/pipeline_selector.py
@@ -130,9 +130,9 @@ def _generate_candidates_param(candidates: List[Type[BaseEstimator]]) -> Categor
 
     def hyperparameter_space(
         self,
-        *args,
+        *args: Any,
         override: Optional[List[Params]] = None,  # NOTE: Hyperparameter override applies to predictor only.
-        **kwargs,
+        **kwargs: Any,
     ) -> List[Params]:
         """The customized ``hyperparameter_space`` implementation. The hyperparameter space is built up of the
         hyperparameters of each pipeline steps (the preprocessors, and the final predictive step). ``CategoricalParams``
@@ -140,8 +140,12 @@ def hyperparameter_space(
         Parameter names are customized to be able to differentiate pipeline stages (see ``format_hps_names``).
 
         Args:
+            args (Any):
+                Arguments to be passed on to the ``hyperparameter_space`` methods of the pipeline steps.
             override (Optional[List[Params]], optional):
                 Hyperparameter space override for the final predictive step of the pipeline. Defaults to `None`.
+            kwargs (Any):
+                Keyword arguments to be passed on to the ``hyperparameter_space`` methods of the pipeline steps.
 
         Returns:
             List[Params]: Returned hyperparameter space.
diff --git a/src/tempor/automl/seeker.py b/src/tempor/automl/seeker.py
index d66304cb..c05dd400 100644
--- a/src/tempor/automl/seeker.py
+++ b/src/tempor/automl/seeker.py
@@ -13,12 +13,12 @@
 
 from tempor import plugin_loader
 from tempor.benchmarks import evaluation
+from tempor.core import pydantic_utils
 from tempor.core.types import PredictiveTaskType
 from tempor.data import data_typing
 from tempor.data.dataset import PredictiveDataset, TimeToEventAnalysisDataset
 from tempor.log import logger
-from tempor.methods.core import BasePredictor
-from tempor.methods.core._params import Params
+from tempor.methods.core import BasePredictor, Params
 
 from ._types import AutoMLCompatibleEstimator, OptimDirection
 from .pipeline_selector import (
@@ -103,8 +103,8 @@ def evaluation_callback_dispatch(
     horizon: Optional[data_typing.TimeIndex],
     raise_exceptions: bool,
     silence_warnings: bool,
-    *args,
-    **kwargs,
+    *args: Any,
+    **kwargs: Any,
 ) -> float:
     """Perform evaluation of ``estimator`` (of task type ``task_type``) on ``dataset``, using the appropriate
     evaluation function from the `tempor.benchmarks.evaluation` module.
@@ -129,6 +129,10 @@ def evaluation_callback_dispatch(
             terminated. Otherwise the exception will be ignored and a dummy value returned.
         silence_warnings (bool, optional):
             Whether to silence warnings raised. Defaults to `False`.
+        args (Any):
+            Positional arguments to pass to the estimator constructor.
+        kwargs (Any):
+            Keyword arguments to pass to the estimator constructor.
 
     Returns:
         float: The mean evaluation metric across the cross-validation folds.
@@ -187,8 +191,8 @@ def evaluation_callback_dispatch(
 
 
 class BaseSeeker(abc.ABC):
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
-    def __init__(
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
+    def __init__(  # pylint: disable=unused-argument
         self,
         study_name: str,
         task_type: PredictiveTaskType,
@@ -211,7 +215,7 @@ def __init__(
         custom_tuner: Optional[BaseTuner] = None,
         raise_exceptions: bool = True,
         silence_warnings: bool = False,
-        **kwargs,  # pylint: disable=unused-argument
+        **kwargs: Any,
     ) -> None:
         """The base class for an AutoML Seeker, to be derived from by concrete implementations. Provides an AutoML
         interface, in particular, the ``search`` method.
@@ -361,7 +365,7 @@ def _create_estimator_with_hps(
         """
         ...  # pylint: disable=unnecessary-ellipsis
 
-    def _set_up_tuners(self):
+    def _set_up_tuners(self) -> None:
         logger.info(f"Setting up estimators and tuners for study {self.study_name}.")
         for estimator_name, estimator_def in zip(self.estimator_names, self.estimator_defs):
             # Set up estimator.
@@ -480,7 +484,7 @@ def search(self) -> Tuple[List[BasePredictor], List[float]]:
 
 
 class MethodSeeker(BaseSeeker):
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def __init__(
         self,
         study_name: str,
@@ -503,7 +507,7 @@ def __init__(
         custom_tuner: Optional[BaseTuner] = None,
         raise_exceptions: bool = True,
         silence_warnings: bool = False,
-        **kwargs,
+        **kwargs: Any,
     ) -> None:
         """An AutoML seeker which will search the hyperparameter space of each of the predictor estimators defined in
         ``estimator_names`` for the ``task_type`` task setting.
@@ -548,6 +552,8 @@ def __init__(
                 See `~tempor.automl.seeker.BaseSeeker`.
             silence_warnings (bool, optional):
                 See `~tempor.automl.seeker.BaseSeeker`.
+            kwargs (Any):
+                See `~tempor.automl.seeker.BaseSeeker`.
         """
         estimator_defs = estimator_names
 
@@ -589,7 +595,7 @@ def _create_estimator_with_hps(
 
 
 class PipelineSeeker(BaseSeeker):
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def __init__(
         self,
         study_name: str,
@@ -616,7 +622,7 @@ def __init__(
         custom_tuner: Optional[BaseTuner] = None,
         raise_exceptions: bool = True,
         silence_warnings: bool = False,
-        **kwargs,
+        **kwargs: Any,
     ) -> None:
         """An AutoML seeker which will sample pipelines comprised of:
             - A static imputer (if at lease one candidate in ``static_imputers`` provided)
@@ -683,6 +689,8 @@ def __init__(
                 See `~tempor.automl.seeker.BaseSeeker`.
             silence_warnings (bool, optional):
                 See `~tempor.automl.seeker.BaseSeeker`.
+            kwargs (Any):
+                See `~tempor.automl.seeker.BaseSeeker`.
         """
         # Define estimator definitions:
         estimator_defs = []
@@ -768,8 +776,9 @@ def _create_estimator_with_hps(
         logger.info(f"Selected score {score} for {name} with hyperparameters:\n{hps}")
 
         pipe = estimator_cls.pipeline_from_hps(hps)
+        pipe_as_predictor = cast(BasePredictor, pipe)
 
-        if not isinstance(pipe, BasePredictor):  # pragma: no cover
+        if not isinstance(pipe_as_predictor, BasePredictor):  # pragma: no cover
             # Should not end up here.
             raise RuntimeError(f"Pipeline was not a subclass of {BasePredictor.__name__}")
-        return pipe
+        return pipe_as_predictor
diff --git a/src/tempor/automl/tuner.py b/src/tempor/automl/tuner.py
index d0dd79de..bdb5822a 100644
--- a/src/tempor/automl/tuner.py
+++ b/src/tempor/automl/tuner.py
@@ -2,16 +2,17 @@
 
 import abc
 import copy
-from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type, Union
+from typing import Any, Dict, List, Optional, Tuple, Type, Union, cast
 
 import optuna
 import pydantic
 from typing_extensions import Protocol, runtime_checkable
 
+from tempor.core import pydantic_utils
 from tempor.data.dataset import PredictiveDataset
 from tempor.log import logger
+from tempor.methods.core import Params
 from tempor.methods.core._base_predictor import BasePredictor
-from tempor.methods.core._params import Params
 
 from ._types import AutoMLCompatibleEstimator, OptimDirection
 from .pipeline_selector import PipelineSelector
@@ -40,12 +41,12 @@ def __call__(
 
 
 class BaseTuner(abc.ABC):
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
-    def __init__(
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
+    def __init__(  # pylint: disable=unused-argument
         self,
         study_name: str,
         direction: OptimDirection,
-        **kwargs,  # pylint: disable=unused-argument
+        **kwargs: Any,
     ):
         """Base hyperparameter tuner from which tuner implementations should derive. Defines the initializer and the
         `tune` method.
@@ -67,7 +68,7 @@ def tune(
         evaluation_callback: EvaluationCallback,
         override_hp_space: Optional[List[Params]] = None,
         compute_baseline_score: bool = True,
-        **kwargs,
+        **kwargs: Any,
     ) -> Tuple[List[float], List[Dict]]:  # pragma: no cover
         """Run the hyperparameter tuner and return scores and chosen hyperparameters.
 
@@ -95,7 +96,7 @@ def tune(
 
 
 class OptunaTuner(BaseTuner):
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def __init__(
         self,
         study_name: str,
@@ -105,7 +106,7 @@ def __init__(
         study_storage: Optional[Union[str, optuna.storages.BaseStorage]] = None,
         study_pruner: Optional[optuna.pruners.BasePruner] = None,
         study_load_if_exists: bool = False,
-        **kwargs,
+        **kwargs: Any,
     ):
         """Hyper parameter tuning (optimization) helper for an `optuna.study.Study` using any
         `optuna.sampler.BaseSampler`.
@@ -127,6 +128,7 @@ def __init__(
         super().__init__(
             study_name=study_name,
             direction=direction,
+            **kwargs,
         )
 
         self.sampler = study_sampler
@@ -161,7 +163,7 @@ def tune(
         override_hp_space: Optional[List[Params]] = None,
         compute_baseline_score: bool = True,
         optimize_kwargs: Optional[Dict[str, Any]] = None,
-        **kwargs,
+        **kwargs: Any,
     ) -> Tuple[List[float], List[Dict]]:
         """Run the hyperparameter tuner and return scores and chosen hyperparameters.
 
@@ -217,9 +219,7 @@ def objective(trial: optuna.Trial) -> float:
                 pipe_cls, pipe_hp_dict = estimator.pipeline_class_from_hps(hps)
                 hps = dict(plugin_params=pipe_hp_dict)
                 name = pipe_cls.pipeline_seq()
-                if TYPE_CHECKING:  # pragma: no cover
-                    assert issubclass(pipe_cls, BasePredictor)  # nosec B101
-                estimator_for_eval = pipe_cls
+                estimator_for_eval = cast(Type[BasePredictor], pipe_cls)
             else:
                 estimator_for_eval = estimator
                 name = estimator_for_eval.__name__
diff --git a/src/tempor/benchmarks/benchmark.py b/src/tempor/benchmarks/benchmark.py
index 20a0cc6a..4babe101 100644
--- a/src/tempor/benchmarks/benchmark.py
+++ b/src/tempor/benchmarks/benchmark.py
@@ -7,6 +7,7 @@
 import seaborn as sns
 from packaging.version import Version
 
+from tempor.core import pydantic_utils
 from tempor.core.types import PredictiveTaskType
 from tempor.data import data_typing, dataset
 from tempor.log import logger as log
@@ -25,7 +26,7 @@ def print_score(mean: pd.Series, std: pd.Series) -> pd.Series:
     return mean + " +/- " + std
 
 
-@pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+@pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
 def benchmark_models(
     task_type: PredictiveTaskType,
     tests: List[Tuple[str, Any]],  # [ ( Test name, Model to evaluate (unfitted) ), ... ]
@@ -101,10 +102,10 @@ def benchmark_models(
 
         scores = evaluator(
             plugin,
-            data=data,  # type: ignore
+            data=data,
             n_splits=n_splits,
             random_state=random_state,
-            horizons=horizons,  # type: ignore
+            horizons=horizons,
             raise_exceptions=raise_exceptions,
             silence_warnings=silence_warnings,
         )
@@ -132,7 +133,7 @@ def benchmark_models(
     return aggr, results
 
 
-@pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+@pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
 def visualize_benchmark(results: Dict[str, pd.DataFrame], palette: str = "viridis", plot_block: bool = True) -> Any:
     # Pre-format DF for plotting.
     for k, v in results.items():
@@ -157,7 +158,7 @@ def visualize_benchmark(results: Dict[str, pd.DataFrame], palette: str = "viridi
                 hue="method",
                 yerr=err.loc[metric, :],
             )
-        except ValueError as ex:
+        except ValueError as ex:  # pragma: no cover
             if "'yerr'" in str(ex) and Version(sns.__version__) >= Version("0.13.0"):  # pragma: no cover
                 # Known issue with seaborn 0.13.0+. If the y values are non-unique, it seems to expect the yerr values
                 # only as many as there are unique y values. We hence remap using numpy.unique with index return.
diff --git a/src/tempor/benchmarks/evaluation.py b/src/tempor/benchmarks/evaluation.py
index aa50d11d..90390b66 100644
--- a/src/tempor/benchmarks/evaluation.py
+++ b/src/tempor/benchmarks/evaluation.py
@@ -11,6 +11,7 @@
 import sklearn.model_selection
 from typing_extensions import Literal, get_args
 
+from tempor.core import pydantic_utils
 from tempor.data import data_typing, dataset, samples
 from tempor.log import logger
 from tempor.models.utils import enable_reproducibility
@@ -174,7 +175,7 @@ class _InternalScores(pydantic.BaseModel):
     model_config = pydantic.ConfigDict(arbitrary_types_allowed=True)
 
 
-@pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+@pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
 def _postprocess_results(results: _InternalScores) -> pd.DataFrame:
     output = pd.DataFrame([], columns=output_metrics)
 
@@ -218,7 +219,7 @@ def _postprocess_results(results: _InternalScores) -> pd.DataFrame:
 
 
 class ClassifierMetrics:
-    @pydantic.validate_arguments  # type: ignore [operator]
+    @pydantic_utils.validate_arguments
     def __init__(
         self,
         metric: Union[ClassifierSupportedMetric, Sequence[ClassifierSupportedMetric]] = classifier_supported_metrics,
@@ -333,7 +334,7 @@ def average_precision_score(self, y_test: np.ndarray, y_pred_proba: np.ndarray)
         return utils.evaluate_auc_multiclass(y_test, y_pred_proba)[1]
 
 
-@pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+@pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
 def evaluate_prediction_oneoff_classifier(  # pylint: disable=unused-argument
     estimator: Any,
     data: dataset.PredictiveDataset,
@@ -422,7 +423,7 @@ def evaluate_prediction_oneoff_classifier(  # pylint: disable=unused-argument
     return _postprocess_results(results)
 
 
-@pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+@pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
 def evaluate_prediction_oneoff_regressor(  # pylint: disable=unused-argument
     estimator: Any,
     data: dataset.PredictiveDataset,
@@ -566,7 +567,7 @@ def _compute_time_to_event_metric(
                 f"but more than one event features found in {name}"
             )
     try:
-        float(horizons[0])  # type: ignore
+        float(horizons[0])  # pyright: ignore
     except (TypeError, ValueError) as e:
         raise ValueError("Currently only int or float time horizons supported.") from e
     horizons = cast(List[float], horizons)
@@ -583,7 +584,7 @@ def _compute_time_to_event_metric(
     return avg_metric
 
 
-@pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+@pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
 def evaluate_time_to_event(  # pylint: disable=unused-argument
     estimator: Any,
     data: dataset.TimeToEventAnalysisDataset,
diff --git a/src/tempor/benchmarks/metrics.py b/src/tempor/benchmarks/metrics.py
index 0b8c4a1c..2349b8ca 100644
--- a/src/tempor/benchmarks/metrics.py
+++ b/src/tempor/benchmarks/metrics.py
@@ -1,4 +1,4 @@
-from typing import Any, Optional, Tuple
+from typing import Any, Dict, Optional, Tuple, Union
 
 import numpy as np
 import sklearn
@@ -16,7 +16,7 @@
     _has_input_name = False
 
 
-def check_y_survival(y_or_event: np.ndarray, *args, allow_all_censored=False) -> Tuple[np.ndarray, ...]:
+def check_y_survival(y_or_event: np.ndarray, *args: Any, allow_all_censored: bool = False) -> Tuple[np.ndarray, ...]:
     """Check that array correctly represents an outcome for survival analysis.
 
     Args:
@@ -76,11 +76,11 @@ def check_y_survival(y_or_event: np.ndarray, *args, allow_all_censored=False) ->
     return tuple(return_val)
 
 
-def _check_estimate_1d(estimate, test_time):
+def _check_estimate_1d(estimate: np.ndarray, test_time: np.ndarray) -> np.ndarray:
     estimate = sklearn.utils.check_array(
         estimate,
         ensure_2d=False,
-        **dict(input_name="estimate") if _has_input_name else dict(),
+        **dict(input_name="estimate") if _has_input_name else dict(),  # pyright: ignore
     )
     if estimate.ndim != 1:
         raise ValueError("Expected 1D array, got {:d}D array instead:\narray={}.\n".format(estimate.ndim, estimate))
@@ -88,12 +88,12 @@ def _check_estimate_1d(estimate, test_time):
     return estimate
 
 
-def _check_times(test_time, times):
+def _check_times(test_time: np.ndarray, times: np.ndarray) -> np.ndarray:
     times = sklearn.utils.check_array(
         np.atleast_1d(times),
         ensure_2d=False,
         dtype=test_time.dtype,
-        **dict(input_name="times") if _has_input_name else dict(),
+        **dict(input_name="times") if _has_input_name else dict(),  # pyright: ignore
     )
     times = np.unique(times)
 
@@ -105,13 +105,18 @@ def _check_times(test_time, times):
     return times
 
 
-def _check_estimate_2d(estimate, test_time, time_points, estimator):
+def _check_estimate_2d(
+    estimate: np.ndarray,
+    test_time: np.ndarray,
+    time_points: np.ndarray,
+    estimator: Union[sklearn.base.BaseEstimator, str],
+) -> Tuple[np.ndarray, np.ndarray]:
     estimate = sklearn.utils.check_array(
         estimate,
         ensure_2d=False,
         allow_nd=False,
         estimator=estimator,
-        **dict(input_name="estimate") if _has_input_name else dict(),
+        **dict(input_name="estimate") if _has_input_name else dict(),  # pyright: ignore
     )
     time_points = _check_times(test_time, time_points)
     sklearn.utils.check_consistent_length(test_time, estimate)
@@ -330,10 +335,10 @@ def kaplan_meier_estimator(
     return uniq_times, y
 
 
-class SurvivalFunctionEstimator(sklearn.base.BaseEstimator):
+class SurvivalFunctionEstimator(sklearn.base.BaseEstimator):  # type: ignore [misc]
     """Kaplan-Meier estimate of the survival function."""
 
-    def __init__(self):
+    def __init__(self) -> None:
         pass
 
     def fit(self, y: np.ndarray) -> Self:
@@ -370,7 +375,7 @@ def predict_proba(self, time: np.ndarray) -> np.ndarray:
             time,
             ensure_2d=False,
             estimator=self,
-            **dict(input_name="estimate") if _has_input_name else dict(),
+            **dict(input_name="estimate") if _has_input_name else dict(),  # pyright: ignore
         )
 
         # K-M is undefined if estimate at last time point is non-zero.
@@ -399,7 +404,7 @@ def predict_proba(self, time: np.ndarray) -> np.ndarray:
 class CensoringDistributionEstimator(SurvivalFunctionEstimator):
     """Kaplan-Meier estimator for the censoring distribution."""
 
-    def fit(self, y: np.ndarray):
+    def fit(self, y: np.ndarray) -> Self:
         """Estimate censoring distribution from training data.
 
         Args:
@@ -446,10 +451,12 @@ def predict_ipcw(self, y: np.ndarray) -> np.ndarray:
         return weights
 
 
-def _get_comparable(event_indicator, event_time, order):
+def _get_comparable(
+    event_indicator: np.ndarray, event_time: np.ndarray, order: np.ndarray
+) -> Tuple[Dict[int, np.ndarray], int]:
     n_samples = len(event_time)
-    tied_time = 0
-    comparable = {}
+    tied_time: int = 0
+    comparable: Dict[int, np.ndarray] = dict()
     i = 0
     while i < n_samples - 1:
         time_i = event_time[order[i]]
@@ -475,7 +482,11 @@ def _get_comparable(event_indicator, event_time, order):
 
 
 def _estimate_concordance_index(
-    event_indicator, event_time, estimate, weights, tied_tol=1e-8
+    event_indicator: np.ndarray,
+    event_time: np.ndarray,
+    estimate: np.ndarray,
+    weights: np.ndarray,
+    tied_tol: float = 1e-8,
 ) -> Tuple[float, int, int, int, int]:
     order = np.argsort(event_time)
 
@@ -709,7 +720,7 @@ def concordance_index_ipcw(
         mask = test_time < tau
         survival_test = survival_test[mask]
 
-    estimate = _check_estimate_1d(estimate, test_time)
+    estimate_arr = _check_estimate_1d(estimate, test_time)
 
     cens = CensoringDistributionEstimator()
     cens.fit(survival_train)
@@ -717,10 +728,10 @@ def concordance_index_ipcw(
     if tau is None:
         ipcw = ipcw_test
     else:
-        ipcw = np.empty(estimate.shape[0], dtype=ipcw_test.dtype)
+        ipcw = np.empty(estimate_arr.shape[0], dtype=ipcw_test.dtype)
         ipcw[mask] = ipcw_test  # pyright: ignore
         ipcw[~mask] = 0  # pyright: ignore
 
     w = np.square(ipcw)
 
-    return _estimate_concordance_index(test_event, test_time, estimate, w, tied_tol)
+    return _estimate_concordance_index(test_event, test_time, estimate_arr, w, tied_tol)
diff --git a/src/tempor/benchmarks/utils.py b/src/tempor/benchmarks/utils.py
index bb387260..d2542de7 100644
--- a/src/tempor/benchmarks/utils.py
+++ b/src/tempor/benchmarks/utils.py
@@ -1,4 +1,4 @@
-from typing import Tuple
+from typing import Tuple, cast
 
 import numpy as np
 from sklearn.metrics import auc, average_precision_score, precision_recall_curve, roc_auc_score, roc_curve
@@ -47,7 +47,7 @@ def evaluate_auc_multiclass(
         average_precision = dict()
         roc_auc: dict = dict()
 
-        y_test = label_binarize(y_test, classes=classes)  # type: ignore
+        y_test = cast(np.ndarray, label_binarize(y_test, classes=classes, sparse_output=False))
 
         fpr["micro"], tpr["micro"], _ = roc_curve(y_test.ravel(), y_pred_proba_tmp.ravel())
         roc_auc["micro"] = auc(fpr["micro"], tpr["micro"])
diff --git a/src/tempor/config/__init__.py b/src/tempor/config/__init__.py
index b99c200c..306fabc2 100644
--- a/src/tempor/config/__init__.py
+++ b/src/tempor/config/__init__.py
@@ -38,7 +38,7 @@ class TemporConfig:
     logging: LoggingConfig
     working_directory: str = omegaconf.MISSING
 
-    def get_working_dir(self):
+    def get_working_dir(self) -> str:
         if self.working_directory.startswith("$PWD"):
             return self.working_directory.replace("$PWD", os.getcwd(), 1)
         elif self.working_directory.startswith("~"):
diff --git a/src/tempor/core/plugins/__init__.py b/src/tempor/core/plugins/__init__.py
index c17cc066..402842ab 100644
--- a/src/tempor/core/plugins/__init__.py
+++ b/src/tempor/core/plugins/__init__.py
@@ -5,7 +5,7 @@
 import os
 import os.path
 import sys
-from typing import TYPE_CHECKING, Any, Callable, ClassVar, Dict, List, Optional, Tuple, Type, TypeVar, Union, overload
+from typing import Any, Callable, ClassVar, Dict, List, Optional, Tuple, Type, TypeVar, Union, cast, overload
 
 from typing_extensions import ParamSpec
 
@@ -265,7 +265,7 @@ def register_plugin_category(
     PLUGIN_CATEGORY_REGISTRY[category_fqn] = expected_class
 
 
-def _check_same_class(class_1, class_2) -> bool:
+def _check_same_class(class_1: Type, class_2: Type) -> bool:
     # To avoid raising "already registered" error when a certain plugin class is being reimported.
     # Not a bullet proof check but should suffice for practical purposes.
     return (
@@ -273,7 +273,11 @@ def _check_same_class(class_1, class_2) -> bool:
     )
 
 
-def register_plugin(name: str, category: plugin_typing.PluginCategory, plugin_type: plugin_typing.PluginTypeArg = None):
+def register_plugin(
+    name: str,
+    category: plugin_typing.PluginCategory,
+    plugin_type: plugin_typing.PluginTypeArg = None,
+) -> Callable[[Callable[P, T]], Callable[P, T]]:
     """A decorator to register a plugin class. If ``plugin_type`` is provided, this will also be assigned,
     otherwise the default plugin type will be used. The ``category`` must have already been registered with
     ``@register_plugin_category`` before this can be used to register a plugin.
@@ -296,40 +300,38 @@ def _class_decorator(cls: Callable[P, T]) -> Callable[P, T]:
         #     * https://stackoverflow.com/a/74080156
         #     * https://docs.python.org/3/library/typing.html#typing.ParamSpec
 
-        if TYPE_CHECKING:  # pragma: no cover
-            # Note that cls is in fact `Type[Plugin]`, but this allows to
-            # silence static type-checker warnings inside this function.
-            assert isinstance(cls, Plugin)  # nosec B101
+        # Cast to Type[Plugin], which is the actual expected type, such that static type checking works here.
+        cls_ = cast(Type[Plugin], cls)
 
-        logger.debug(f"Registering plugin of class {cls}")
-        cls.name = name
-        cls.category = category
+        logger.debug(f"Registering plugin of class {cls_}")
+        cls_.name = name
+        cls_.category = category
 
         _plugin_type = parse_plugin_type(plugin_type)
-        cls.plugin_type = _plugin_type
+        cls_.plugin_type = _plugin_type
 
         category_fqn = create_fqn(suffix=category, plugin_type=_plugin_type)
 
         if category_fqn not in PLUGIN_CATEGORY_REGISTRY:
             raise TypeError(
-                f"Found plugin category '{cls.category}' under plugin type '{cls.plugin_type}' which "
+                f"Found plugin category '{cls_.category}' under plugin type '{cls_.plugin_type}' which "
                 f"has not been registered with `@{register_plugin_category.__name__}`"
             )
-        if not issubclass(cls, Plugin):
-            raise TypeError(f"Expected plugin class `{cls.__name__}` to be a subclass of `{Plugin}` but was `{cls}`")
-        if not issubclass(cls, PLUGIN_CATEGORY_REGISTRY[category_fqn]):
+        if not issubclass(cls_, Plugin):
+            raise TypeError(f"Expected plugin class `{cls_.__name__}` to be a subclass of `{Plugin}` but was `{cls_}`")
+        if not issubclass(cls_, PLUGIN_CATEGORY_REGISTRY[category_fqn]):
             raise TypeError(
-                f"Expected plugin class `{cls.__name__}` to be a subclass of "
-                f"`{PLUGIN_CATEGORY_REGISTRY[category_fqn]}` but was `{cls}`"
+                f"Expected plugin class `{cls_.__name__}` to be a subclass of "
+                f"`{PLUGIN_CATEGORY_REGISTRY[category_fqn]}` but was `{cls_}`"
             )
         # pylint: disable-next=protected-access
-        if cls._fqn() in PLUGIN_REGISTRY:
+        if cls_._fqn() in PLUGIN_REGISTRY:
             # pylint: disable-next=protected-access
-            if not _check_same_class(cls, PLUGIN_REGISTRY[cls._fqn()]):
+            if not _check_same_class(cls_, PLUGIN_REGISTRY[cls_._fqn()]):
                 raise TypeError(
                     # pylint: disable-next=protected-access
-                    f"Plugin (plugin type '{cls.plugin_type}') with full name '{cls.full_name()}' has already been "
-                    f"registered (as class `{PLUGIN_REGISTRY[cls._fqn()]}`)"
+                    f"Plugin (plugin type '{cls_.plugin_type}') with full name '{cls_.full_name()}' has already been "
+                    f"registered (as class `{PLUGIN_REGISTRY[cls_._fqn()]}`)"
                 )
             else:
                 # The same class is being reimported, do not raise error.
@@ -337,10 +339,10 @@ def _class_decorator(cls: Callable[P, T]) -> Callable[P, T]:
         for existing_cls in PLUGIN_REGISTRY.values():
             # Cannot have the same plugin name (not just fqn), as this is not supported by Pipeline.
             # TODO: Fix this - make non-unique name work with pipeline.
-            if cls.name == existing_cls.name:
-                if not _check_same_class(cls, existing_cls):
+            if cls_.name == existing_cls.name:
+                if not _check_same_class(cls_, existing_cls):
                     raise TypeError(
-                        f"Plugin (plugin type '{cls.plugin_type}') with name '{cls.name}' has already been "
+                        f"Plugin (plugin type '{cls_.plugin_type}') with name '{cls_.name}' has already been "
                         f"registered (as class `{existing_cls}`). Must use a unique plugin name."
                     )
                 else:  # pragma: no cover
@@ -350,9 +352,10 @@ def _class_decorator(cls: Callable[P, T]) -> Callable[P, T]:
                     pass
 
         # pylint: disable-next=protected-access
-        PLUGIN_REGISTRY[cls._fqn()] = cls
+        PLUGIN_REGISTRY[cls_._fqn()] = cls_
 
-        return cls
+        # Cast back to Callable[P, T] (see note at the top of function).
+        return cast(Callable[P, T], cls_)
 
     return _class_decorator
 
@@ -364,7 +367,7 @@ class PluginLoader:
     def __init__(self) -> None:
         self._refresh()
 
-    def _refresh(self):
+    def _refresh(self) -> None:
         # Internal method to refresh plugin registries.
 
         self._plugin_registry: Dict[str, Type[Plugin]] = PLUGIN_REGISTRY
@@ -387,7 +390,13 @@ def _refresh(self):
             )
         self._plugin_class_by_category_nested = class_by_category_nested
 
-    def _handle_all_plugin_types_case(self, pt: plugin_typing.PluginTypeArg, method: Callable, *args, **kwargs) -> Dict:
+    def _handle_all_plugin_types_case(
+        self,
+        pt: plugin_typing.PluginTypeArg,
+        method: Callable,
+        *args: Any,
+        **kwargs: Any,
+    ) -> Dict:
         # If ``pt`` (plugin type) is "all", will call ``method`` for all plugin types and return a nested dictionary.
         # Otherwise, just calls ``method`` and return what it returns.
         # In either case, plugin type value(s) will be passed to ``method`` by ``plugin_type`` kwarg.
@@ -514,13 +523,13 @@ def list_plugin_types(self) -> List[plugin_typing.PluginType]:
             [get_plugin_type_from_fqn(fqn) for fqn in self._plugin_registry.keys()]
         )
 
-    def _raise_plugin_does_not_exist_error(self, fqn: str):
+    def _raise_plugin_does_not_exist_error(self, fqn: str) -> None:
         plugin_type = get_plugin_type_from_fqn(fqn)
         plugin_full_name = remove_plugin_type_from_fqn(fqn)
         if fqn not in self._plugin_registry:
             raise ValueError(f"Plugin '{plugin_full_name}' (plugin type: {plugin_type}) does not exist.")
 
-    def _handle_get_args_kwargs(self, args, kwargs) -> Tuple[Any, Tuple, Dict]:
+    def _handle_get_args_kwargs(self, args: Tuple, kwargs: Dict[str, Any]) -> Tuple[Any, Tuple, Dict]:
         # "Pop" the `plugin_type` argument if such is found in args (position 0) or kwargs.
         # If appears to be provided in both ways, prefer the value from kwargs and leave the string in args as is.
         # If not, `plugin_type` will fall back to its default value.
@@ -533,7 +542,7 @@ def _handle_get_args_kwargs(self, args, kwargs) -> Tuple[Any, Tuple, Dict]:
 
     # Explicitly listing all the overloads for clarity of documentation.
     @overload
-    def get(self, name: plugin_typing.PluginFullName, *args, **kwargs) -> Type:
+    def get(self, name: plugin_typing.PluginFullName, *args: Any, **kwargs: Any) -> Type:
         ...  # pragma: co cover
 
     @overload
@@ -541,8 +550,8 @@ def get(  # type: ignore [misc]
         self,
         name: plugin_typing.PluginFullName,
         plugin_type: plugin_typing.PluginTypeArg,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Type:
         ...  # pragma: co cover
 
@@ -550,13 +559,13 @@ def get(  # type: ignore [misc]
     def get(  # type: ignore [misc]
         self,
         name: plugin_typing.PluginFullName,
-        *args,
+        *args: Any,
         plugin_type: plugin_typing.PluginTypeArg = None,
-        **kwargs,
+        **kwargs: Any,
     ) -> Type:
         ...  # pragma: co cover
 
-    def get(self, name: plugin_typing.PluginFullName, *args, **kwargs) -> Any:
+    def get(self, name: plugin_typing.PluginFullName, *args: Any, **kwargs: Any) -> Any:
         """Get a plugin by its full name (including category, i.e. of form
         ``'my_category.my_subcategory.my_plugin'``). Use ``*args`` and ``**kwargs`` to pass arguments to
         the plugin initializer. The returned object is an instance of the plugin class. If the plugin is not of the
@@ -591,6 +600,13 @@ def get(self, name: plugin_typing.PluginFullName, *args, **kwargs) -> Any:
         Args:
             name (~tempor.core.plugin.plugin_typing.PluginFullName):
                 Plugin full name including all (sub)categories, of form ``'my_category.my_subcategory.my_plugin'``
+            args (Any):
+                Arguments to pass to the plugin initializer.
+            plugin_type (~tempor.core.plugin.plugin_typing.PluginTypeArg, optional):
+                Plugin type. If left as `None`, default plugin type is assumed. ``plugin_type`` must correctly \
+                correspond to the category implied by plugin full name. Defaults to `None`.
+            kwargs (Any):
+                Keyword arguments to pass to the plugin initializer.
 
         Returns:
             Any: The plugin instance initialised with ``*args`` and ``**kwargs`` as provided.
diff --git a/src/tempor/core/pydantic_utils.py b/src/tempor/core/pydantic_utils.py
index 0b572701..4ebc98cb 100644
--- a/src/tempor/core/pydantic_utils.py
+++ b/src/tempor/core/pydantic_utils.py
@@ -1,7 +1,8 @@
-from typing import Any, Type
+from typing import Any, Callable, Type, TypeVar, cast
 
 import pydantic
 from packaging.version import Version
+from typing_extensions import ParamSpec
 
 # Currently unused.
 # def exclusive_args(
@@ -64,3 +65,24 @@ class MyDataclass:
     else:
         pydantic_dataclass = PYDANTIC_DATACLASS_WORKAROUND_DICT[name]
     return pydantic_dataclass
+
+
+P = ParamSpec("P")
+T = TypeVar("T")
+
+
+def validate_arguments(*args: Any, **kwargs: Any) -> Callable[[Callable[P, T]], Callable[P, T]]:
+    """Uses the ``Callable[P, T]`` approach to type the pydantic ``validate_arguments`` decorator. Helps `mypy` to
+    correctly understand typing of functions that are decorated by this.
+
+    See:
+    - https://stackoverflow.com/a/74080156
+    - https://docs.python.org/3/library/typing.html#typing.ParamSpec
+
+    Returns:
+        Callable[[Callable[P, T]], Callable[P, T]]: The updated ``pydantic.validate_arguments`` decorator.
+    """
+    return cast(
+        Callable[P, T],  # type: ignore [valid-type, misc]
+        pydantic.validate_arguments(*args, **kwargs),  # type: ignore [operator]
+    )
diff --git a/src/tempor/core/utils.py b/src/tempor/core/utils.py
index 78aa177e..9b1d9c4b 100644
--- a/src/tempor/core/utils.py
+++ b/src/tempor/core/utils.py
@@ -3,7 +3,7 @@
 from typing_extensions import Literal, get_args
 
 
-def get_class_full_name(o: object):
+def get_class_full_name(o: object) -> str:
     # See: https://stackoverflow.com/a/2020083
     class_ = o.__class__
     module = class_.__module__
@@ -36,7 +36,7 @@ def is_iterable(o: object) -> bool:
 
 def ensure_literal_matches_dict_keys(
     literal: Any, d: Dict[str, Any], literal_name: str = "literal", dict_name: str = "dictionary"
-):
+) -> None:
     lits = set(get_args(literal))
     keys = set(d.keys())
     if lits != keys:
diff --git a/src/tempor/data/dataset.py b/src/tempor/data/dataset.py
index cebbe86f..9cdb5e27 100644
--- a/src/tempor/data/dataset.py
+++ b/src/tempor/data/dataset.py
@@ -4,6 +4,7 @@
 import dataclasses
 from typing import Any, ClassVar, Generator, Optional, Tuple, Union
 
+import numpy as np
 import rich.pretty
 import sklearn.model_selection
 from typing_extensions import Self
@@ -66,7 +67,7 @@ def __init__(
         static: Optional[data_typing.DataContainer] = None,
         targets: Optional[data_typing.DataContainer] = None,
         treatments: Optional[data_typing.DataContainer] = None,
-        **kwargs,
+        **kwargs: Any,
     ) -> None:
         """Abstract base class representing a dataset used by TemporAI.
 
@@ -89,6 +90,8 @@ def __init__(
                 Data representing treatment (intervention) feature(s) of the samples. Will be initialized as
                 ``{TimeSeries,Static,Event}Samples`` depending on problem setting in the derived class.
                 Defaults to `None`.
+            kwargs (Any):
+                Additional keyword arguments to be passed to the derived class's ``_init_predictive`` method.
         """
         self._time_series = samples.TimeSeriesSamples(time_series)
         self._static = samples.StaticSamples(static) if static is not None else None
@@ -97,7 +100,7 @@ def __init__(
 
         self.validate()
 
-    def __rich_repr__(self):
+    def __rich_repr__(self) -> Generator:
         yield "time_series", RichReprStrPassthrough(self.time_series.short_repr())
         if self.static is not None:
             yield "static", RichReprStrPassthrough(self.static.short_repr())
@@ -112,7 +115,7 @@ def _init_predictive(
         self,
         targets: Optional[data_typing.DataContainer],
         treatments: Optional[data_typing.DataContainer],
-        **kwargs,
+        **kwargs: Any,
     ) -> None:  # pragma: no cover
         """A method to initialize ``self.predictive`` in derived classes."""
         ...
@@ -192,11 +195,11 @@ def __getitem__(self, key: data_typing.GetItemKey) -> Self:
     def train_test_split(
         self,
         *,
-        test_size=None,
-        train_size=None,
-        random_state=None,
-        shuffle=True,
-        stratify=None,
+        test_size: Optional[float] = None,
+        train_size: Optional[float] = None,
+        random_state: Union[int, np.random.RandomState, None] = None,  # pylint: disable=no-member
+        shuffle: bool = True,
+        stratify: Any = None,
     ) -> Tuple[Self, Self]:
         """Split `Dataset` into train and test sets.
 
@@ -220,7 +223,7 @@ def train_test_split(
     def split(
         self,
         splitter: Splitter,
-        **kwargs,
+        **kwargs: Any,
     ) -> Generator[Tuple[Self, Self], None, None]:
         """Generate dataset splits according to the scikit-learn ``splitter`` (`~tempor.data.dataset.Splitter`).
         The ``kwargs`` are passed to the underlying splitter's ``split`` method.
@@ -235,6 +238,7 @@ def split(
 
         Args:
             splitter (Splitter): A `sklearn` splitter.
+            kwargs (Any): Additional keyword arguments to be passed to the ``splitter``'s ``split`` method.
 
         Yields:
             Generator[Tuple[Self, Self], None, None]: ``(dataset_train, dataset_test)`` for each split.
@@ -255,7 +259,7 @@ def __init__(
         static: Optional[data_typing.DataContainer] = None,
         targets: Optional[data_typing.DataContainer] = None,
         treatments: Optional[data_typing.DataContainer] = None,
-        **kwargs,
+        **kwargs: Any,
     ) -> None:
         """A :class:`BaseDataset` subclass for a dataset that does not contain any predictive data
         (``targets`` or ``treatments``).
@@ -266,7 +270,7 @@ def _init_predictive(
         self,
         targets: Optional[data_typing.DataContainer],
         treatments: Optional[data_typing.DataContainer],
-        **kwargs,
+        **kwargs: Any,
     ) -> None:
         if targets is not None:
             raise ValueError(f"`targets` must not be set for a {self.__class__.__name__}.")
@@ -293,7 +297,7 @@ def __init__(
         targets: Optional[data_typing.DataContainer],
         static: Optional[data_typing.DataContainer] = None,
         treatments: Optional[data_typing.DataContainer] = None,
-        **kwargs,
+        **kwargs: Any,
     ) -> None:
         """A :class:`BaseDataset` subclass for a dataset that can contain predictive data
         (``targets`` or ``treatments``).
@@ -319,7 +323,7 @@ def __init__(
         targets: Optional[data_typing.DataContainer],
         static: Optional[data_typing.DataContainer] = None,
         treatments: Optional[data_typing.DataContainer] = None,
-        **kwargs,
+        **kwargs: Any,
     ) -> None:
         """A :class:`PredictiveDataset` subclass for the one-off prediction problem setting,
         see :class:`BaseDataset` docs.
@@ -332,7 +336,7 @@ def _init_predictive(
         self,
         targets: Optional[data_typing.DataContainer],
         treatments: Optional[data_typing.DataContainer],
-        **kwargs,
+        **kwargs: Any,
     ) -> None:
         if targets is None:
             logger.debug(
@@ -365,7 +369,7 @@ def __init__(
         targets: Optional[data_typing.DataContainer],
         static: Optional[data_typing.DataContainer] = None,
         treatments: Optional[data_typing.DataContainer] = None,
-        **kwargs,
+        **kwargs: Any,
     ) -> None:
         """A :class:`PredictiveDataset` subclass for the temporal prediction problem setting,
         see :class:`BaseDataset` docs.
@@ -378,7 +382,7 @@ def _init_predictive(
         self,
         targets: Optional[data_typing.DataContainer],
         treatments: Optional[data_typing.DataContainer],
-        **kwargs,
+        **kwargs: Any,
     ) -> None:
         if targets is None:
             logger.debug(
@@ -413,7 +417,7 @@ def __init__(
         targets: Optional[data_typing.DataContainer],
         static: Optional[data_typing.DataContainer] = None,
         treatments: Optional[data_typing.DataContainer] = None,
-        **kwargs,
+        **kwargs: Any,
     ) -> None:
         """A :class:`PredictiveDataset` subclass for the time-to-event analysis problem setting,
         see :class:`BaseDataset` docs.
@@ -426,7 +430,7 @@ def _init_predictive(
         self,
         targets: Optional[data_typing.DataContainer],
         treatments: Optional[data_typing.DataContainer],
-        **kwargs,
+        **kwargs: Any,
     ) -> None:
         if targets is None:
             logger.debug(
@@ -461,7 +465,7 @@ def __init__(
         targets: Optional[data_typing.DataContainer],
         treatments: data_typing.DataContainer,
         static: Optional[data_typing.DataContainer] = None,
-        **kwargs,
+        **kwargs: Any,
     ) -> None:
         """A :class:`PredictiveDataset` subclass for the one-off treatment effects problem setting,
         see :class:`BaseDataset` docs.
@@ -475,7 +479,7 @@ def _init_predictive(
         self,
         targets: Optional[data_typing.DataContainer],
         treatments: Optional[data_typing.DataContainer],
-        **kwargs,
+        **kwargs: Any,
     ) -> None:
         if targets is None:
             logger.debug(
@@ -518,7 +522,7 @@ def __init__(
         targets: Optional[data_typing.DataContainer],
         treatments: data_typing.DataContainer,
         static: Optional[data_typing.DataContainer] = None,
-        **kwargs,
+        **kwargs: Any,
     ) -> None:
         """A :class:`PredictiveDataset` subclass for the temporal treatment effects problem setting,
         see :class:`BaseDataset` docs.
@@ -532,7 +536,7 @@ def _init_predictive(
         self,
         targets: Optional[data_typing.DataContainer],
         treatments: Optional[data_typing.DataContainer],
-        **kwargs,
+        **kwargs: Any,
     ) -> None:
         if targets is None:
             logger.debug(
diff --git a/src/tempor/data/pandera_utils.py b/src/tempor/data/pandera_utils.py
index 24047b46..d041c02a 100644
--- a/src/tempor/data/pandera_utils.py
+++ b/src/tempor/data/pandera_utils.py
@@ -1,4 +1,4 @@
-from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Tuple, Union
+from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, NoReturn, Optional, Tuple, Type, Union, cast
 
 import numpy as np
 import pandas as pd
@@ -84,37 +84,37 @@ def _get_pa_init_args(pa_object: Any, param_names: List[str]) -> Dict[str, Any]:
     return items
 
 
-def update_schema(schema: pa.DataFrameSchema, **kwargs) -> pa.DataFrameSchema:
+def update_schema(schema: pa.DataFrameSchema, **kwargs: Any) -> pa.DataFrameSchema:
     items = _get_pa_init_args(schema, param_names=_PA_DF_SCHEMA_INIT_PARAMETERS)
     items.update(kwargs)
     return pa.DataFrameSchema(**items)
 
 
-def update_index(index: pa.Index, **kwargs) -> pa.Index:
+def update_index(index: pa.Index, **kwargs: Any) -> pa.Index:
     items = _get_pa_init_args(index, param_names=_PA_INDEX_INIT_PARAMETERS)
     items.update(kwargs)
     return pa.Index(**items)
 
 
-def update_multiindex(multi_index: pa.MultiIndex, **kwargs) -> pa.MultiIndex:
+def update_multiindex(multi_index: pa.MultiIndex, **kwargs: Any) -> pa.MultiIndex:
     items = _get_pa_init_args(multi_index, param_names=_PA_MULTI_INDEX_INIT_PARAMETERS)
     items.update(kwargs)
     return pa.MultiIndex(**items)
 
 
 PA_DTYPE_MAP: Dict[data_typing.Dtype, pa.DataType] = {
-    bool: pa.Bool(),
-    int: pa.Int(),
-    float: pa.Float(),
-    str: pa.String(),
+    bool: pa.Bool(),  # type: ignore [no-untyped-call]
+    int: pa.Int(),  # type: ignore [no-untyped-call]
+    float: pa.Float(),  # type: ignore [no-untyped-call]
+    str: pa.String(),  # type: ignore [no-untyped-call]
     "category": pa.Category(),
-    "datetime": pa.DateTime(),
+    "datetime": pa.DateTime(),  # type: ignore [no-untyped-call]
 }
 """A mapping from dtype specified as :obj:`~tempor.data.data_typing.Dtype` to a `pandera.DataType`.
 """
 
 
-def get_pa_dtypes(dtypes: Iterable[data_typing.Dtype]) -> List[pa.DataType]:
+def get_pa_dtypes(dtypes: Iterable[Union[data_typing.Dtype, pa.DataType]]) -> List[pa.DataType]:
     """Return a list of `pandera.DataType` corresponding to ``dtypes``. Raises `KeyError` If not found."""
     pa_dtypes_ = []
     for dt in dtypes:
@@ -134,10 +134,10 @@ def get_pa_dtypes(dtypes: Iterable[data_typing.Dtype]) -> List[pa.DataType]:
         # Allow for different bits of Int and Float, rather than just what pandera defaults to.
         # Note: especially important on Windows, as pandera ends up with a different bit datatype on Windows than
         # on other systems. See: https://github.com/unionai-oss/pandera/issues/726.
-        if dt_add == pa.Int():
-            pa_dtypes_.extend([pa.Int8(), pa.Int16(), pa.Int32(), pa.Int64()])
-        if dt_add == pa.Float():
-            pa_dtypes_.extend([pa.Float16(), pa.Float32(), pa.Float64()])
+        if dt_add == pa.Int():  # type: ignore [no-untyped-call]
+            pa_dtypes_.extend([pa.Int8(), pa.Int16(), pa.Int32(), pa.Int64()])  # type: ignore [no-untyped-call]
+        if dt_add == pa.Float():  # type: ignore [no-untyped-call]
+            pa_dtypes_.extend([pa.Float16(), pa.Float32(), pa.Float64()])  # type: ignore [no-untyped-call]
 
     return list(set(pa_dtypes_))
 
@@ -162,14 +162,14 @@ class UnionDtype(pd_engine.DataType):
     """The string representation of the data type used for `repr`."""
 
     @classmethod
-    def __class_getitem__(cls, item):
+    def __class_getitem__(cls: Type, item: Iterable[Union[data_typing.Dtype, pa.DataType]]) -> Callable:
         """Allows for setting union types like ``UnionDtype[dtype, ...]``.
 
         Acceptable ``dtype`` s are: `pandera.DataType` (as a class or instance) or the keys of
         `~tempor.data.pandera_utils.PA_DTYPE_MAP`.
         """
         if not tempor.core.utils.is_iterable(item):
-            item = [item]
+            item = [item]  # type: ignore [list-item]
         union_dtypes = get_pa_dtypes(item)
         union_dtypes = sorted(union_dtypes, key=str)  # For consistency: `item` can get captured in random order.
         repr_union_dtypes = str([str(t) for t in union_dtypes]).replace("'", "")
@@ -188,7 +188,7 @@ def __repr__(self) -> str:
     def check(
         self,
         pandera_dtype: pa_dtypes.DataType,
-        data_container=None,
+        data_container: Any = None,
     ) -> Union[bool, Iterable[bool]]:
         """Checks whether the ``pandera_dtype`` and optionally ``data_container`` satisfy at least one the union's
         ``union_dtypes``.
@@ -196,12 +196,12 @@ def check(
         Args:
             pandera_dtype (pa_dtypes.DataType):
                 The data type received as part of the check/validation.
-            data_container (PandasObject, optional):
+            data_container (Any):
                 The data container received as part of the check/validation. Defaults to `None`.
 
         Returns:
             Union[bool, Iterable[bool]]:
-                A `bool` stating whether the data type is satisfied, or an iterable thereof\
+                A `bool` stating whether the data type is satisfied, or an iterable thereof \
                 (for each item in the ``data_container``).
         """
         for union_dtype in self.union_dtypes:
@@ -225,15 +225,13 @@ def check(
         else:
             return np.full_like(data_container, False, dtype=bool)
 
-    def coerce(self, data_container):
+    def coerce(self, data_container: Any) -> NoReturn:
         """The ``coerce`` method is not supported and will throw a `NotImplementedError`."""
         raise NotImplementedError(f"`coerce` not supported by {self.__class__.__name__}")
 
 
-def init_schema(data: pd.DataFrame, **kwargs) -> pa.DataFrameSchema:
-    schema = pa.infer_schema(data)
-    if TYPE_CHECKING:  # pragma: no cover
-        assert isinstance(schema, pa.DataFrameSchema)  # nosec B101
+def init_schema(data: pd.DataFrame, **kwargs: Any) -> pa.DataFrameSchema:
+    schema = cast(pa.DataFrameSchema, pa.infer_schema(data))
     schema = update_schema(schema, **kwargs)
     return schema
 
@@ -260,7 +258,7 @@ def add_regex_column_checks(
                 dtype=dtype,
                 nullable=nullable,
                 regex=True,
-                checks=checks_list,  # type: ignore
+                checks=checks_list,
             )
         }
     )
diff --git a/src/tempor/data/predictive.py b/src/tempor/data/predictive.py
index a0bfe031..90bc1bbf 100644
--- a/src/tempor/data/predictive.py
+++ b/src/tempor/data/predictive.py
@@ -1,5 +1,5 @@
 import abc
-from typing import TYPE_CHECKING, Any, Optional
+from typing import TYPE_CHECKING, Any, Generator, Optional
 
 import rich.pretty
 
@@ -20,12 +20,12 @@ class PredictiveTaskData(abc.ABC):
     def predictive_task(self) -> data_typing.PredictiveTask:  # pragma: no cover
         ...
 
-    def __init__(
+    def __init__(  # pylint: disable=unused-argument
         self,
         parent_dataset: "PredictiveDataset",
         targets: Any,
         treatments: Optional[Any],
-        **kwargs,  # pylint: disable=unused-argument
+        **kwargs: Any,
     ) -> None:  # pragma: no cover
         self.parent_dataset = parent_dataset
         # ^ In order to be able to call parent dataset's `validate` method in the targets/treatments property setters.
@@ -33,7 +33,7 @@ def __init__(
         self._targets = targets
         self._treatments = treatments
 
-    def __rich_repr__(self):
+    def __rich_repr__(self) -> Generator:
         if self.targets is not None:
             yield "targets", RichReprStrPassthrough(self.targets.short_repr())
         else:
@@ -79,7 +79,7 @@ def predictive_task(self) -> data_typing.PredictiveTask:
         return data_typing.PredictiveTask.ONE_OFF_PREDICTION
 
     def __init__(
-        self, parent_dataset: "PredictiveDataset", targets: Optional[data_typing.DataContainer], **kwargs
+        self, parent_dataset: "PredictiveDataset", targets: Optional[data_typing.DataContainer], **kwargs: Any
     ) -> None:
         super().__init__(parent_dataset=parent_dataset, targets=targets, treatments=None)
         self._targets = samples.StaticSamples(targets) if targets is not None else None
@@ -97,7 +97,7 @@ def predictive_task(self) -> data_typing.PredictiveTask:
         return data_typing.PredictiveTask.TEMPORAL_PREDICTION
 
     def __init__(
-        self, parent_dataset: "PredictiveDataset", targets: Optional[data_typing.DataContainer], **kwargs
+        self, parent_dataset: "PredictiveDataset", targets: Optional[data_typing.DataContainer], **kwargs: Any
     ) -> None:
         super().__init__(parent_dataset=parent_dataset, targets=targets, treatments=None)
         self._targets = samples.TimeSeriesSamples(targets) if targets is not None else None
@@ -118,7 +118,7 @@ def predictive_task(self) -> data_typing.PredictiveTask:
         return data_typing.PredictiveTask.TIME_TO_EVENT_ANALYSIS
 
     def __init__(
-        self, parent_dataset: "PredictiveDataset", targets: Optional[data_typing.DataContainer], **kwargs
+        self, parent_dataset: "PredictiveDataset", targets: Optional[data_typing.DataContainer], **kwargs: Any
     ) -> None:
         super().__init__(parent_dataset=parent_dataset, targets=targets, treatments=None)
         self._targets = samples.EventSamples(targets) if targets is not None else None
@@ -143,7 +143,7 @@ def __init__(
         parent_dataset: "PredictiveDataset",
         targets: Optional[data_typing.DataContainer],
         treatments: data_typing.DataContainer,
-        **kwargs,
+        **kwargs: Any,
     ) -> None:
         super().__init__(parent_dataset=parent_dataset, targets=targets, treatments=treatments)
         self._targets = samples.TimeSeriesSamples(targets) if targets is not None else None
@@ -165,7 +165,7 @@ def __init__(
         parent_dataset: "PredictiveDataset",
         targets: Optional[data_typing.DataContainer],
         treatments: data_typing.DataContainer,
-        **kwargs,
+        **kwargs: Any,
     ) -> None:
         super().__init__(parent_dataset=parent_dataset, targets=targets, treatments=treatments)
         self._targets = samples.TimeSeriesSamples(targets) if targets is not None else None
diff --git a/src/tempor/data/samples.py b/src/tempor/data/samples.py
index 8d114bc1..ad988047 100644
--- a/src/tempor/data/samples.py
+++ b/src/tempor/data/samples.py
@@ -4,7 +4,7 @@
 
 import abc
 import contextlib
-from typing import TYPE_CHECKING, Any, List, Optional, Tuple
+from typing import TYPE_CHECKING, Any, Generator, List, Optional, Tuple
 
 import numpy as np
 import pandas as pd
@@ -14,6 +14,7 @@
 from typing_extensions import Self
 
 import tempor.exc
+from tempor.core import pydantic_utils
 from tempor.log import log_helpers, logger
 
 from . import data_typing, pandera_utils, utils
@@ -31,7 +32,7 @@ def modality(self) -> data_typing.DataModality:  # pragma: no cover
     def __init__(
         self,
         data: data_typing.DataContainer,  # pylint: disable=unused-argument
-        **kwargs,
+        **kwargs: Any,
     ) -> None:  # pragma: no cover
         if "_skip_validate" not in kwargs:
             # For efficiency, pass `_skip_validate` internally (e.g. in `__getitem__`)
@@ -78,7 +79,7 @@ def from_numpy(
         *,
         sample_index: Optional[data_typing.SampleIndex] = None,
         feature_index: Optional[data_typing.FeatureIndex] = None,
-        **kwargs,
+        **kwargs: Any,
     ) -> "DataSamples":  # pragma: no cover
         """Create :class:`DataSamples` from `numpy.ndarray`.
 
@@ -99,17 +100,17 @@ def from_numpy(
 
     @staticmethod
     @abc.abstractmethod
-    def from_dataframe(dataframe: pd.DataFrame, **kwargs) -> "DataSamples":  # pragma: no cover
+    def from_dataframe(dataframe: pd.DataFrame, **kwargs: Any) -> "DataSamples":  # pragma: no cover
         """Create :class:`DataSamples` from `pandas.DataFrame`."""
         ...
 
     @abc.abstractmethod
-    def numpy(self, **kwargs) -> np.ndarray:  # pragma: no cover
+    def numpy(self, **kwargs: Any) -> np.ndarray:  # pragma: no cover
         """Return `numpy.ndarray` representation of the data."""
         ...
 
     @abc.abstractmethod
-    def dataframe(self, **kwargs) -> pd.DataFrame:  # pragma: no cover
+    def dataframe(self, **kwargs: Any) -> pd.DataFrame:  # pragma: no cover
         """Return `pandas.DataFrame` representation of the data."""
         ...
 
@@ -161,14 +162,14 @@ class StaticSamples(DataSamples):
     _data: pd.DataFrame
     _schema: pa.DataFrameSchema
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def __init__(
         self,
         data: data_typing.DataContainer,
         *,
         sample_index: Optional[data_typing.SampleIndex] = None,
         feature_index: Optional[data_typing.FeatureIndex] = None,
-        **kwargs,
+        **kwargs: Any,
     ) -> None:
         """Create a :class:`StaticSamples` object from the ``data``.
 
@@ -239,7 +240,7 @@ def _validate(self) -> None:
         self._schema = schema
 
     @staticmethod
-    def from_dataframe(dataframe: pd.DataFrame, **kwargs) -> "StaticSamples":
+    def from_dataframe(dataframe: pd.DataFrame, **kwargs: Any) -> "StaticSamples":
         return StaticSamples(dataframe, **kwargs)
 
     @staticmethod
@@ -248,7 +249,7 @@ def from_numpy(
         *,
         sample_index: Optional[data_typing.SampleIndex] = None,
         feature_index: Optional[data_typing.FeatureIndex] = None,
-        **kwargs,
+        **kwargs: Any,
     ) -> "StaticSamples":
         return StaticSamples(array, sample_index=sample_index, feature_index=feature_index, **kwargs)
 
@@ -258,7 +259,7 @@ def _array_to_df(
         *,
         sample_index: Optional[data_typing.SampleIndex] = None,
         feature_index: Optional[data_typing.FeatureIndex] = None,
-        **kwargs,
+        **kwargs: Any,
     ) -> pd.DataFrame:
         if sample_index is None:
             sample_index = _array_default_sample_index(array)  # pyright: ignore
@@ -266,10 +267,10 @@ def _array_to_df(
             feature_index = _array_default_feature_index(array)
         return pd.DataFrame(data=array, index=sample_index, columns=feature_index, **kwargs)
 
-    def numpy(self, **kwargs) -> np.ndarray:
+    def numpy(self, **kwargs: Any) -> np.ndarray:
         return self._data.to_numpy()
 
-    def dataframe(self, **kwargs) -> pd.DataFrame:
+    def dataframe(self, **kwargs: Any) -> pd.DataFrame:
         return self._data
 
     def sample_index(self) -> data_typing.SampleIndex:
@@ -288,14 +289,14 @@ def short_repr(self) -> str:
 
     def __getitem__(self, key: data_typing.GetItemKey) -> Self:
         key_ = utils.ensure_pd_iloc_key_returns_df(key)
-        return StaticSamples(  # type: ignore[return-value]
+        return StaticSamples(  # type: ignore [return-value]
             self._data.iloc[key_, :],  # pyright: ignore
             _skip_validate=True,
         )
 
 
 @contextlib.contextmanager
-def workaround_pandera_pd2_1_0_multiindex_compatibility(schema: pa.DataFrameSchema, data: pd.DataFrame):
+def workaround_pandera_pd2_1_0_multiindex_compatibility(schema: pa.DataFrameSchema, data: pd.DataFrame) -> Generator:
     """A version compatibility issue exists between pandera and pandas 2.1.0, as reported here:
     https://github.com/unionai-oss/pandera/issues/1328
 
@@ -317,7 +318,11 @@ def problem_versions() -> bool:  # pragma: no cover
             "Columns with duplicate values are not supported in stack" in str(ex)
         ):  # pragma: no cover
             cols = data.index.names
-            raise pa.errors.SchemaError(schema=schema, data=data, message=f"columns {cols} not unique")
+            raise pa.errors.SchemaError(  # type: ignore [no-untyped-call]
+                schema=schema,
+                data=data,
+                message=f"columns {cols} not unique",
+            )
         else:  # pragma: no cover
             raise
 
@@ -333,7 +338,7 @@ class TimeSeriesSamples(DataSamples):
     def modality(self) -> data_typing.DataModality:
         return data_typing.DataModality.TIME_SERIES
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def __init__(
         self,
         data: data_typing.DataContainer,
@@ -342,7 +347,7 @@ def __init__(
         sample_index: Optional[data_typing.SampleIndex] = None,
         time_indexes: Optional[data_typing.TimeIndexList] = None,
         feature_index: Optional[data_typing.FeatureIndex] = None,
-        **kwargs,
+        **kwargs: Any,
     ) -> None:
         """Create a :class:`TimeSeriesSamples` object from the ``data``.
 
@@ -442,7 +447,7 @@ def _validate(self) -> None:
         # - Time index float / int expected non-negative values.
 
     @staticmethod
-    def from_dataframe(dataframe: pd.DataFrame, **kwargs) -> "TimeSeriesSamples":
+    def from_dataframe(dataframe: pd.DataFrame, **kwargs: Any) -> "TimeSeriesSamples":
         return TimeSeriesSamples(dataframe, **kwargs)
 
     @staticmethod
@@ -453,7 +458,7 @@ def from_numpy(
         sample_index: Optional[data_typing.SampleIndex] = None,
         time_indexes: Optional[data_typing.TimeIndexList] = None,
         feature_index: Optional[data_typing.FeatureIndex] = None,
-        **kwargs,
+        **kwargs: Any,
     ) -> "TimeSeriesSamples":
         return TimeSeriesSamples(
             array,
@@ -465,21 +470,21 @@ def from_numpy(
         )
 
     @staticmethod
-    def _array_to_df(
+    def _array_to_df(  # pylint: disable=unused-argument
         array: np.ndarray,
         *,
         padding_indicator: Any,
         sample_index: Optional[data_typing.SampleIndex] = None,
         time_indexes: Optional[data_typing.TimeIndexList] = None,
         feature_index: Optional[data_typing.FeatureIndex] = None,
-        **kwargs,  # pylint: disable=unused-argument
+        **kwargs: Any,
     ) -> pd.DataFrame:
         if sample_index is None:
             sample_index = _array_default_sample_index(array)  # pyright: ignore
         if feature_index is None:
             feature_index = _array_default_feature_index(array)
         if time_indexes is None:
-            time_indexes = _array_default_time_indexes(array, padding_indicator)  # type: ignore
+            time_indexes = _array_default_time_indexes(array, padding_indicator)
         if TYPE_CHECKING:  # pragma: no cover
             assert sample_index is not None and feature_index is not None and time_indexes is not None  # nosec B101
         return utils.array3d_to_multiindex_timeseries_dataframe(
@@ -490,12 +495,12 @@ def _array_to_df(
             padding_indicator=padding_indicator,
         )
 
-    def numpy(self, *, padding_indicator: Any = DATA_SETTINGS.default_padding_indicator, **kwargs) -> np.ndarray:
+    def numpy(self, *, padding_indicator: Any = DATA_SETTINGS.default_padding_indicator, **kwargs: Any) -> np.ndarray:
         return utils.multiindex_timeseries_dataframe_to_array3d(
             df=self._data, padding_indicator=padding_indicator, max_timesteps=None
         )
 
-    def dataframe(self, **kwargs) -> pd.DataFrame:
+    def dataframe(self, **kwargs: Any) -> pd.DataFrame:
         return self._data
 
     def sample_index(self) -> data_typing.SampleIndex:
@@ -586,7 +591,7 @@ def __getitem__(self, key: data_typing.GetItemKey) -> Self:
         key_ = utils.ensure_pd_iloc_key_returns_df(key)
         sample_index = utils.get_df_index_level0_unique(self._data)
         selected = list(sample_index[key_])  # pyright: ignore
-        return TimeSeriesSamples(  # type: ignore[return-value]
+        return TimeSeriesSamples(  # type: ignore [return-value]
             self._data.loc[(selected, slice(None)), :],  # pyright: ignore
             _skip_validate=True,
         )
@@ -604,14 +609,14 @@ class EventSamples(DataSamples):
     def modality(self) -> data_typing.DataModality:
         return data_typing.DataModality.EVENT
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def __init__(
         self,
         data: data_typing.DataContainer,
         *,
         sample_index: Optional[data_typing.SampleIndex] = None,
         feature_index: Optional[data_typing.FeatureIndex] = None,
-        **kwargs,
+        **kwargs: Any,
     ) -> None:
         """Create an :class:`EventSamples` object from the ``data``.
 
@@ -698,7 +703,7 @@ def _validate(self) -> None:
         self._schema = schema
 
     @staticmethod
-    def from_dataframe(dataframe: pd.DataFrame, **kwargs) -> "EventSamples":
+    def from_dataframe(dataframe: pd.DataFrame, **kwargs: Any) -> "EventSamples":
         return EventSamples(dataframe, **kwargs)
 
     @staticmethod
@@ -707,7 +712,7 @@ def from_numpy(
         *,
         sample_index: Optional[data_typing.SampleIndex] = None,
         feature_index: Optional[data_typing.FeatureIndex] = None,
-        **kwargs,
+        **kwargs: Any,
     ) -> "EventSamples":
         return EventSamples(array, sample_index=sample_index, feature_index=feature_index, **kwargs)
 
@@ -717,7 +722,7 @@ def _array_to_df(
         *,
         sample_index: Optional[data_typing.SampleIndex] = None,
         feature_index: Optional[data_typing.FeatureIndex] = None,
-        **kwargs,
+        **kwargs: Any,
     ) -> pd.DataFrame:
         if sample_index is None:
             sample_index = _array_default_sample_index(array)  # pyright: ignore
@@ -725,11 +730,11 @@ def _array_to_df(
             feature_index = _array_default_feature_index(array)
         return pd.DataFrame(data=array, index=sample_index, columns=feature_index, **kwargs)
 
-    def numpy(self, **kwargs) -> np.ndarray:
+    def numpy(self, **kwargs: Any) -> np.ndarray:
         # TODO: May want at option to return a scikit-survive -style array.
         return self._data.to_numpy()
 
-    def dataframe(self, **kwargs) -> pd.DataFrame:
+    def dataframe(self, **kwargs: Any) -> pd.DataFrame:
         return self._data
 
     def sample_index(self) -> data_typing.SampleIndex:
@@ -743,7 +748,7 @@ def num_samples(self) -> int:
     def num_features(self) -> int:
         return self._data.shape[1]
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def split(self, time_feature_suffix: str = _DEFAULT_EVENTS_TIME_FEATURE_SUFFIX) -> pd.DataFrame:
         """Return a `pandas.DataFrame` where the time component of each event feature has been split off to its own
         column. The new columns that contain the times will be named ``"<original column name><time_feature_suffix>"``
@@ -793,7 +798,7 @@ def short_repr(self) -> str:
 
     def __getitem__(self, key: data_typing.GetItemKey) -> Self:
         key_ = utils.ensure_pd_iloc_key_returns_df(key)
-        return EventSamples(  # type: ignore[return-value]
+        return EventSamples(  # type: ignore [return-value]
             self._data.iloc[key_, :],  # pyright: ignore
             _skip_validate=True,
         )
diff --git a/src/tempor/data/utils.py b/src/tempor/data/utils.py
index 762a8afd..54fa8140 100644
--- a/src/tempor/data/utils.py
+++ b/src/tempor/data/utils.py
@@ -1,6 +1,6 @@
 import dataclasses
 import itertools
-from typing import Any, ClassVar, List, Optional, Sequence, Tuple, Union
+from typing import Any, ClassVar, Iterable, List, Optional, Sequence, Tuple, Union
 
 import numpy as np
 import pandas as pd
@@ -8,6 +8,7 @@
 from packaging.version import Version
 
 import tempor.core.utils
+from tempor.core import pydantic_utils
 
 from . import data_typing, settings
 
@@ -45,7 +46,7 @@ def get_df_index_level0_unique(df: pd.DataFrame) -> pd.Index:
 # --- Multiindex timeseries dataframe --> 3D numpy array. ---
 
 
-@pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+@pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
 def multiindex_timeseries_dataframe_to_array3d(
     df: pd.DataFrame, *, padding_indicator: Any, max_timesteps: Optional[int] = None
 ) -> np.ndarray:
@@ -169,7 +170,7 @@ def get_array1d_length_until_padding(array: np.ndarray, padding_indicator: Any =
         return len(array)
 
 
-def validate_timeseries_array3d(array: np.ndarray, padding_indicator: Any = None):
+def validate_timeseries_array3d(array: np.ndarray, padding_indicator: Any = None) -> None:
     """Check if 3D ``array`` representing timeseries satisfies the blow criteria, otherwise raise `ValueError`:
     - 3 dimensions,
     - Dimension 2 not of size 0,
@@ -271,7 +272,7 @@ def make_sample_time_index_tuples(
     return pairs  # type: ignore
 
 
-@pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+@pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
 def array3d_to_multiindex_timeseries_dataframe(
     array: np.ndarray,
     *,
@@ -319,7 +320,7 @@ def array3d_to_multiindex_timeseries_dataframe(
 # --- List of dataframes --> Multiindex timeseries dataframe. ---
 
 
-@pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+@pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
 def list_of_dataframes_to_multiindex_timeseries_dataframe(
     list_of_dataframes: List[pd.DataFrame],
     *,
@@ -380,7 +381,7 @@ def multiindex_timeseries_dataframe_to_list_of_dataframes(df: pd.DataFrame) -> L
 # --- [(event_times, event_values), ...] --> DataFrame compatible with EventSamples. ---
 
 
-@pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+@pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
 def event_time_value_pairs_to_event_dataframe(
     event_time_value_pairs: Sequence[Tuple[data_typing.TimeIndex, List[bool]]],
     sample_index: data_typing.SampleIndex,
@@ -415,7 +416,7 @@ def event_time_value_pairs_to_event_dataframe(
     Returns:
         pd.DataFrame: `pandas.DataFrame` compatible with `EventSamples`.
     """
-    series_list = [pd.Series(zip(t, v)) for t, v in event_time_value_pairs]
+    series_list = [pd.Series(zip(t, v)) for t, v in event_time_value_pairs]  # pyright: ignore
     data_input = pd.DataFrame(data=series_list).T.to_numpy()
     df = pd.DataFrame(data=data_input, index=sample_index)
     if feature_index is not None:
@@ -426,7 +427,7 @@ def event_time_value_pairs_to_event_dataframe(
 # --- Date-time time index -related ---
 
 
-@pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+@pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
 def datetime_time_index_to_float(time_index: Union[data_typing.TimeIndex, pd.Index, pd.Series]) -> np.ndarray:
     """Convert a date-time ``time_index`` to floats. The conversion is done by calling
     ``<time_index as a numpy array>.astype(float)``.
@@ -449,7 +450,7 @@ def datetime_time_index_to_float(time_index: Union[data_typing.TimeIndex, pd.Ind
 # --- Miscellaneous. ---
 
 
-def ensure_pd_iloc_key_returns_df(key):
+def ensure_pd_iloc_key_returns_df(key: Any) -> Union[Iterable, slice]:
     if isinstance(key, slice):
         key_: Any = key
     elif tempor.core.utils.is_iterable(key):
diff --git a/src/tempor/datasources/datasource.py b/src/tempor/datasources/datasource.py
index 2ba62fab..11c80232 100644
--- a/src/tempor/datasources/datasource.py
+++ b/src/tempor/datasources/datasource.py
@@ -2,7 +2,7 @@
 
 import abc
 import os
-from typing import ClassVar, Optional, Type
+from typing import Any, ClassVar, Optional, Type
 
 import tempor
 from tempor.core import plugins
@@ -26,7 +26,7 @@ class DataSource(plugins.Plugin, abc.ABC):
     It will be ``< tempor -> config -> working_directory > / data``.
     """
 
-    def __init__(self, **kwargs) -> None:  # pylint: disable=unused-argument
+    def __init__(self, **kwargs: Any) -> None:  # pylint: disable=unused-argument
         """Initializer for `DataSource`.
 
         Args:
@@ -86,7 +86,7 @@ def predictive_task(self) -> data_typing.PredictiveTask:  # pragma: no cover
         ...
 
     @abc.abstractmethod
-    def load(self, **kwargs) -> dataset.PredictiveDataset:  # pragma: no cover
+    def load(self, **kwargs: Any) -> dataset.PredictiveDataset:  # pragma: no cover
         """The method that should return the loaded `~tempor.data.dataset.DataSet` for the appropriate
         ``predictive_task``.
 
@@ -102,7 +102,7 @@ def predictive_task(self) -> data_typing.PredictiveTask:
         return data_typing.PredictiveTask.ONE_OFF_PREDICTION
 
     @abc.abstractmethod
-    def load(self, **kwargs) -> dataset.OneOffPredictionDataset:  # pragma: no cover
+    def load(self, **kwargs: Any) -> dataset.OneOffPredictionDataset:  # pragma: no cover
         ...
 
 
@@ -112,7 +112,7 @@ def predictive_task(self) -> data_typing.PredictiveTask:
         return data_typing.PredictiveTask.TEMPORAL_PREDICTION
 
     @abc.abstractmethod
-    def load(self, **kwargs) -> dataset.TemporalPredictionDataset:  # pragma: no cover
+    def load(self, **kwargs: Any) -> dataset.TemporalPredictionDataset:  # pragma: no cover
         ...
 
 
@@ -122,7 +122,7 @@ def predictive_task(self) -> data_typing.PredictiveTask:
         return data_typing.PredictiveTask.TIME_TO_EVENT_ANALYSIS
 
     @abc.abstractmethod
-    def load(self, **kwargs) -> dataset.TimeToEventAnalysisDataset:  # pragma: no cover
+    def load(self, **kwargs: Any) -> dataset.TimeToEventAnalysisDataset:  # pragma: no cover
         ...
 
 
@@ -132,7 +132,7 @@ def predictive_task(self) -> data_typing.PredictiveTask:
         return data_typing.PredictiveTask.ONE_OFF_TREATMENT_EFFECTS
 
     @abc.abstractmethod
-    def load(self, **kwargs) -> dataset.OneOffTreatmentEffectsDataset:  # pragma: no cover
+    def load(self, **kwargs: Any) -> dataset.OneOffTreatmentEffectsDataset:  # pragma: no cover
         ...
 
 
@@ -142,5 +142,5 @@ def predictive_task(self) -> data_typing.PredictiveTask:
         return data_typing.PredictiveTask.TEMPORAL_TREATMENT_EFFECTS
 
     @abc.abstractmethod
-    def load(self, **kwargs) -> dataset.TemporalTreatmentEffectsDataset:  # pragma: no cover
+    def load(self, **kwargs: Any) -> dataset.TemporalTreatmentEffectsDataset:  # pragma: no cover
         ...
diff --git a/src/tempor/datasources/prediction/one_off/plugin_google_stocks.py b/src/tempor/datasources/prediction/one_off/plugin_google_stocks.py
index abdcfd96..3893a3e0 100644
--- a/src/tempor/datasources/prediction/one_off/plugin_google_stocks.py
+++ b/src/tempor/datasources/prediction/one_off/plugin_google_stocks.py
@@ -1,5 +1,6 @@
 import io
 from pathlib import Path
+from typing import Any
 
 import numpy as np
 import pandas as pd
@@ -14,7 +15,7 @@
 # TODO: Docstring to explain the dataset.
 @plugins.register_plugin(name="google_stocks", category="prediction.one_off", plugin_type="datasource")
 class GoogleStocksDataSource(datasource.OneOffPredictionDataSource):
-    def __init__(self, seq_len: int = 10, **kwargs) -> None:
+    def __init__(self, seq_len: int = 10, **kwargs: Any) -> None:
         super().__init__(**kwargs)
 
         self.seq_len = seq_len
@@ -28,7 +29,7 @@ def url() -> str:
     def dataset_dir() -> str:
         return str(Path(GoogleStocksDataSource.data_root_dir) / "google_stocks")
 
-    def load(self, **kwargs) -> dataset.OneOffPredictionDataset:
+    def load(self, **kwargs: Any) -> dataset.OneOffPredictionDataset:
         # Load Google Data
         if not self.df_path.exists():
             s = requests.get(self.url(), timeout=5).content
diff --git a/src/tempor/datasources/prediction/one_off/plugin_sine.py b/src/tempor/datasources/prediction/one_off/plugin_sine.py
index 95205739..4922e2b3 100644
--- a/src/tempor/datasources/prediction/one_off/plugin_sine.py
+++ b/src/tempor/datasources/prediction/one_off/plugin_sine.py
@@ -1,3 +1,5 @@
+from typing import Any
+
 import numpy as np
 import pandas as pd
 
@@ -20,7 +22,7 @@ def __init__(
         static_scale: float = 1.0,
         ts_scale: float = 1.0,
         random_state: int = 42,
-        **kwargs,
+        **kwargs: Any,
     ) -> None:
         """Sine data generation.
 
@@ -68,7 +70,7 @@ def url() -> None:
     def dataset_dir() -> None:
         return None
 
-    def load(self, **kwargs) -> dataset.OneOffPredictionDataset:
+    def load(self, **kwargs: Any) -> dataset.OneOffPredictionDataset:
         # Initialize the output.
         np.random.seed(self.random_state)
 
diff --git a/src/tempor/datasources/prediction/temporal/plugin_dummy_prediction.py b/src/tempor/datasources/prediction/temporal/plugin_dummy_prediction.py
index 2e61c612..e1ed5b7a 100644
--- a/src/tempor/datasources/prediction/temporal/plugin_dummy_prediction.py
+++ b/src/tempor/datasources/prediction/temporal/plugin_dummy_prediction.py
@@ -1,4 +1,4 @@
-from typing import Optional, cast
+from typing import Any, Optional, cast
 
 from clairvoyance2.datasets.dummy import dummy_dataset
 
@@ -21,7 +21,7 @@ def __init__(
         temporal_targets_n_features: int = 2,
         temporal_targets_n_categories: Optional[int] = None,
         random_state: int = 12345,
-        **kwargs,
+        **kwargs: Any,
     ) -> None:
         """Dummy data source for temporal prediction tasks; generates a dataset with random data.
 
@@ -68,7 +68,7 @@ def url() -> None:
     def dataset_dir() -> None:
         return None
 
-    def load(self, **kwargs) -> dataset.TemporalPredictionDataset:
+    def load(self, **kwargs: Any) -> dataset.TemporalPredictionDataset:
         clv_dataset = dummy_dataset(
             n_samples=self.n_samples,
             temporal_covariates_n_features=self.temporal_covariates_n_features,
diff --git a/src/tempor/datasources/prediction/temporal/plugin_uci_diabetes.py b/src/tempor/datasources/prediction/temporal/plugin_uci_diabetes.py
index 841db8e9..58226532 100644
--- a/src/tempor/datasources/prediction/temporal/plugin_uci_diabetes.py
+++ b/src/tempor/datasources/prediction/temporal/plugin_uci_diabetes.py
@@ -2,7 +2,7 @@
 import traceback
 import urllib.error
 from pathlib import Path
-from typing import Tuple, cast
+from typing import Any, Tuple, cast
 
 import requests
 from clairvoyance2.datasets.uci import uci_diabetes
@@ -23,7 +23,7 @@ def __init__(
         make_regular: bool = False,
         use_int_index: bool = True,
         targets: Tuple[str, ...] = ("hypoglycemic_symptoms",),
-        **kwargs,
+        **kwargs: Any,
     ) -> None:
         super().__init__(**kwargs)
         self.make_regular = make_regular
@@ -38,7 +38,7 @@ def url() -> str:
     def dataset_dir() -> str:
         return str(Path(UCIDiabetesDataSource.data_root_dir) / "uci_diabetes")
 
-    def load(self, **kwargs) -> dataset.TemporalPredictionDataset:
+    def load(self, **kwargs: Any) -> dataset.TemporalPredictionDataset:
         download_retries = 3
         download_pause_sec = 5
         for retry in range(download_retries):
diff --git a/src/tempor/datasources/time_to_event/plugin_pbc.py b/src/tempor/datasources/time_to_event/plugin_pbc.py
index 88887795..a3941504 100644
--- a/src/tempor/datasources/time_to_event/plugin_pbc.py
+++ b/src/tempor/datasources/time_to_event/plugin_pbc.py
@@ -1,5 +1,6 @@
 import io
 import os
+from typing import Any
 
 import numpy as np
 import pandas as pd
@@ -15,7 +16,7 @@
 # TODO: Docstring to explain the dataset.
 @plugins.register_plugin(name="pbc", category="time_to_event", plugin_type="datasource")
 class PBCDataSource(datasource.TimeToEventAnalysisDataSource):
-    def __init__(self, **kwargs) -> None:
+    def __init__(self, **kwargs: Any) -> None:
         self.datafile_path = os.path.join(self.dataset_dir(), "pbc2.csv")
         super().__init__(**kwargs)
 
@@ -30,7 +31,7 @@ def url() -> str:
             "cf583e598ec9ab92fa5d510a0ca72d46dfe0706f/dsm/datasets/pbc2.csv"
         )
 
-    def load(self, **kwargs) -> dataset.TimeToEventAnalysisDataset:
+    def load(self, **kwargs: Any) -> dataset.TimeToEventAnalysisDataset:
         if os.path.exists(self.datafile_path):
             data = pd.read_csv(self.datafile_path)
         else:
diff --git a/src/tempor/datasources/treatments/one_off/plugin_pkpd.py b/src/tempor/datasources/treatments/one_off/plugin_pkpd.py
index 8126cee7..9cb8476c 100644
--- a/src/tempor/datasources/treatments/one_off/plugin_pkpd.py
+++ b/src/tempor/datasources/treatments/one_off/plugin_pkpd.py
@@ -1,4 +1,4 @@
-from typing import cast
+from typing import Any, cast
 
 from clairvoyance2.datasets.simulated.simple_pkpd import simple_pkpd_dataset
 
@@ -18,7 +18,7 @@ def __init__(
         n_control_samples: int = 20,
         n_treated_samples: int = 20,
         random_state: int = 100,
-        **kwargs,
+        **kwargs: Any,
     ) -> None:
         super().__init__(**kwargs)
 
@@ -36,7 +36,7 @@ def url() -> None:
     def dataset_dir() -> None:
         return None
 
-    def load(self, **kwargs) -> dataset.OneOffTreatmentEffectsDataset:
+    def load(self, **kwargs: Any) -> dataset.OneOffTreatmentEffectsDataset:
         clv_dataset = simple_pkpd_dataset(
             n_timesteps=self.n_timesteps,
             time_index_treatment_event=self.time_index_treatment_event,
diff --git a/src/tempor/datasources/treatments/temporal/plugin_dummy_treatments.py b/src/tempor/datasources/treatments/temporal/plugin_dummy_treatments.py
index ce8289e1..4f4df3c2 100644
--- a/src/tempor/datasources/treatments/temporal/plugin_dummy_treatments.py
+++ b/src/tempor/datasources/treatments/temporal/plugin_dummy_treatments.py
@@ -1,4 +1,4 @@
-from typing import Optional, cast
+from typing import Any, Optional, cast
 
 from clairvoyance2.datasets.dummy import dummy_dataset
 
@@ -23,7 +23,7 @@ def __init__(
         temporal_treatments_n_features: int = 2,
         temporal_treatments_n_categories: Optional[int] = None,
         random_state: int = 12345,
-        **kwargs,
+        **kwargs: Any,
     ) -> None:
         """Dummy data source for temporal treatment effects tasks; generates a dataset with random data.
 
@@ -77,7 +77,7 @@ def url() -> None:
     def dataset_dir() -> None:
         return None
 
-    def load(self, **kwargs) -> dataset.TemporalTreatmentEffectsDataset:
+    def load(self, **kwargs: Any) -> dataset.TemporalTreatmentEffectsDataset:
         clv_dataset = dummy_dataset(
             n_samples=self.n_samples,
             temporal_covariates_n_features=self.temporal_covariates_n_features,
diff --git a/src/tempor/log/__init__.py b/src/tempor/log/__init__.py
index 18de4218..f9a3d03f 100644
--- a/src/tempor/log/__init__.py
+++ b/src/tempor/log/__init__.py
@@ -1,3 +1,3 @@
-from ._custom_logger import logger
+from ._custom_logger import logger  # type: ignore [attr-defined]
 
 __all__ = ["logger"]
diff --git a/src/tempor/log/_custom_logger.py b/src/tempor/log/_custom_logger.py
index 4c089c9b..d2c52586 100644
--- a/src/tempor/log/_custom_logger.py
+++ b/src/tempor/log/_custom_logger.py
@@ -24,7 +24,7 @@
 _log_like_no_newline = _log_like_base.replace(_nl_replace, "")
 
 
-def _log_like_formatter(record) -> str:
+def _log_like_formatter(record: Dict) -> str:
     return _log_like_newline if "\n" in record["message"] else _log_like_no_newline
 
 
@@ -46,12 +46,12 @@ def _log_like_formatter(record) -> str:
 
 
 # Dynamically add print method. ---
-def _print_filter(record):
+def _print_filter(record: Dict) -> bool:
     # A filter to enable special handling of logger.print() case.
     return record["extra"].get("print", False)
 
 
-def _logger_print(message: str):
+def _logger_print(message: str) -> None:
     logger.bind(print=True).opt(depth=1).info(message)
 
 
@@ -59,7 +59,7 @@ def _logger_print(message: str):
 # --- --- ---
 
 
-def _configure_loggers(config: conf.TemporConfig):
+def _configure_loggers(config: conf.TemporConfig) -> None:
     # Reset _LOGGERS, _ADD_CONFIGS
     _this_module._LOGGERS = []  # type: ignore  # pylint: disable=protected-access
     _this_module._ADD_CONFIGS = dict()  # type: ignore  # pylint: disable=protected-access
diff --git a/src/tempor/log/log_helpers.py b/src/tempor/log/log_helpers.py
index 8519ad7e..8bd94228 100644
--- a/src/tempor/log/log_helpers.py
+++ b/src/tempor/log/log_helpers.py
@@ -1,10 +1,11 @@
 from contextlib import contextmanager
+from typing import Generator
 
-from ._custom_logger import logger
+from ._custom_logger import logger  # type: ignore [attr-defined]
 
 
 @contextmanager
-def exc_to_log(message: str = "", reraise: bool = True):
+def exc_to_log(message: str = "", reraise: bool = True) -> Generator:
     """Log `Exception` raised inside this context manager and reraise.
 
     Args:
diff --git a/src/tempor/methods/core/__init__.py b/src/tempor/methods/core/__init__.py
index 68efd205..5853222e 100644
--- a/src/tempor/methods/core/__init__.py
+++ b/src/tempor/methods/core/__init__.py
@@ -3,10 +3,12 @@
 from ._base_estimator import BaseEstimator
 from ._base_predictor import BasePredictor
 from ._base_transformer import BaseTransformer
+from ._params import Params
 
 __all__ = [
     "BaseEstimator",
     "importing",
+    "Params",
     "Plugin",
     "PluginLoader",
     "BasePredictor",
diff --git a/src/tempor/methods/core/_base_estimator.py b/src/tempor/methods/core/_base_estimator.py
index fcc50da2..ed39d9e2 100644
--- a/src/tempor/methods/core/_base_estimator.py
+++ b/src/tempor/methods/core/_base_estimator.py
@@ -1,6 +1,6 @@
 import abc
 import dataclasses
-from typing import TYPE_CHECKING, Any, ClassVar, Dict, List, Optional, Type
+from typing import TYPE_CHECKING, Any, ClassVar, Dict, Generator, List, Optional, Type
 
 import omegaconf
 import optuna
@@ -33,7 +33,7 @@ class _InitArgsValidator(pydantic.BaseModel):
         params_processed: Optional[omegaconf.DictConfig] = None
 
         @pydantic.model_validator(mode="before")
-        def root_checks(cls, values: Dict):  # pylint: disable=no-self-argument
+        def root_checks(cls, values: Dict) -> Dict:  # pylint: disable=no-self-argument
             params = values.get("params", dict())
             ParamsDefinitionClass = values.get("ParamsDefinitionClass")
 
@@ -56,7 +56,7 @@ def root_checks(cls, values: Dict):  # pylint: disable=no-self-argument
 
         model_config = pydantic.ConfigDict(arbitrary_types_allowed=True)
 
-    def __init__(self, **params) -> None:
+    def __init__(self, **params: Any) -> None:
         Plugin.__init__(self)
         self._fitted = False
         args_validator = self._InitArgsValidator(params=params, ParamsDefinitionClass=self.ParamsDefinition)
@@ -70,7 +70,7 @@ def is_fitted(self) -> bool:
         """Check if the model was trained"""
         return self._fitted
 
-    def __rich_repr__(self):
+    def __rich_repr__(self) -> Generator:
         yield "name", self.name
         yield "category", self.category
         yield "plugin_type", self.plugin_type
@@ -79,12 +79,12 @@ def __rich_repr__(self):
     def __repr__(self) -> str:
         return rich.pretty.pretty_repr(self)
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def fit(
         self,
         data: dataset.BaseDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Self:
         if not data.fit_ready:
             raise ValueError(
@@ -98,7 +98,7 @@ def fit(
         return fitted_model
 
     @abc.abstractmethod
-    def _fit(self, data: dataset.BaseDataset, *args, **kwargs) -> Self:  # pragma: no cover
+    def _fit(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Self:  # pragma: no cover
         ...
 
     @staticmethod
diff --git a/src/tempor/methods/core/_base_predictor.py b/src/tempor/methods/core/_base_predictor.py
index a16f0740..b88c5c60 100644
--- a/src/tempor/methods/core/_base_predictor.py
+++ b/src/tempor/methods/core/_base_predictor.py
@@ -3,6 +3,7 @@
 
 import pydantic
 
+from tempor.core import pydantic_utils
 from tempor.data import dataset
 from tempor.log import logger
 
@@ -10,14 +11,14 @@
 
 
 class BasePredictor(estimator.BaseEstimator):
-    def __init__(self, **params) -> None:  # pylint: disable=useless-super-delegation
+    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
         super().__init__(**params)
 
     def predict(
         self,
         data: dataset.PredictiveDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Any:  # TODO: Narrow down output formats later.
         if not self.is_fitted:
             raise ValueError("The model was not fitted, call `fit` first")
@@ -34,8 +35,8 @@ def predict(
     def predict_proba(
         self,
         data: dataset.PredictiveDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Any:  # TODO: Narrow down output formats later.
         if not self.is_fitted:
             raise ValueError("The model was not fitted, call `fit` first")
@@ -52,8 +53,8 @@ def predict_proba(
     def predict_counterfactuals(
         self,
         data: dataset.PredictiveDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Any:  # TODO: Narrow down output formats later.
         if not self.is_fitted:
             raise ValueError("The model was not fitted, call `fit` first")
@@ -68,22 +69,22 @@ def predict_counterfactuals(
         return prediction
 
     # TODO: Add similar methods for predict_{proba,counterfactuals}.
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def fit_predict(
         self,
         data: dataset.PredictiveDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Any:
         self.fit(data, *args, **kwargs)
         return self.predict(data, *args, **kwargs)
 
     @abc.abstractmethod
-    def _predict(self, data: dataset.PredictiveDataset, *args, **kwargs) -> Any:  # pragma: no cover
+    def _predict(self, data: dataset.PredictiveDataset, *args: Any, **kwargs: Any) -> Any:  # pragma: no cover
         ...
 
-    def _predict_proba(self, data: dataset.PredictiveDataset, *args, **kwargs) -> Any:
+    def _predict_proba(self, data: dataset.PredictiveDataset, *args: Any, **kwargs: Any) -> Any:
         raise NotImplementedError("`predict_proba` is supported only for classification tasks")
 
-    def _predict_counterfactuals(self, data: dataset.PredictiveDataset, *args, **kwargs) -> Any:
+    def _predict_counterfactuals(self, data: dataset.PredictiveDataset, *args: Any, **kwargs: Any) -> Any:
         raise NotImplementedError("`predict_counterfactuals` is supported only for treatments tasks")
diff --git a/src/tempor/methods/core/_base_transformer.py b/src/tempor/methods/core/_base_transformer.py
index ed4576b2..68354ab7 100644
--- a/src/tempor/methods/core/_base_transformer.py
+++ b/src/tempor/methods/core/_base_transformer.py
@@ -3,6 +3,7 @@
 
 import pydantic
 
+from tempor.core import pydantic_utils
 from tempor.data import dataset
 from tempor.log import logger
 
@@ -10,30 +11,32 @@
 
 
 class BaseTransformer(estimator.BaseEstimator):
-    def __init__(self, **params) -> None:  # pylint: disable=useless-super-delegation
+    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
         super().__init__(**params)
 
     def transform(
         self,
         data: dataset.BaseDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Any:
         logger.debug(f"Calling _transform() implementation on {self.__class__.__name__}")
         transformed_data = self._transform(data, *args, **kwargs)
 
         return transformed_data
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def fit_transform(
         self,
         data: dataset.BaseDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> dataset.BaseDataset:
         self.fit(data, *args, **kwargs)
         return self.transform(data, *args, **kwargs)
 
     @abc.abstractmethod
-    def _transform(self, data: dataset.BaseDataset, *args, **kwargs) -> dataset.BaseDataset:  # pragma: no cover
+    def _transform(
+        self, data: dataset.BaseDataset, *args: Any, **kwargs: Any
+    ) -> dataset.BaseDataset:  # pragma: no cover
         ...
diff --git a/src/tempor/methods/core/_params.py b/src/tempor/methods/core/_params.py
index 67f43025..087d81e3 100644
--- a/src/tempor/methods/core/_params.py
+++ b/src/tempor/methods/core/_params.py
@@ -1,6 +1,6 @@
 import abc
 import random
-from typing import Any, List, Optional, Tuple
+from typing import Any, Generator, List, Optional, Tuple
 
 import rich.pretty
 from optuna.trial import Trial
@@ -36,7 +36,7 @@ def _sample_optuna_trial(self, trial: Trial) -> Any:  # pragma: no cover
     def _sample_default(self) -> Any:  # pragma: no cover
         ...
 
-    def __rich_repr__(self):
+    def __rich_repr__(self) -> Generator:
         yield "name", self.name
         yield "bounds", self.bounds
 
@@ -61,7 +61,7 @@ def _sample_optuna_trial(self, trial: Trial) -> Any:
     def _sample_default(self) -> Any:
         return random.SystemRandom().choice(self.choices)
 
-    def __rich_repr__(self):
+    def __rich_repr__(self) -> Generator:
         yield "name", self.name
         yield "choices", self.choices
 
@@ -87,7 +87,7 @@ def _sample_optuna_trial(self, trial: Trial) -> Any:
     def _sample_default(self) -> Any:
         return random.uniform(self.low, self.high)  # nosec
 
-    def __rich_repr__(self):
+    def __rich_repr__(self) -> Generator:
         yield "name", self.name
         yield "low", self.low
         yield "high", self.high
@@ -117,7 +117,7 @@ def _sample_optuna_trial(self, trial: Trial) -> Any:
     def _sample_default(self) -> Any:
         return random.SystemRandom().choice(self.choices)
 
-    def __rich_repr__(self):
+    def __rich_repr__(self) -> Generator:
         yield "name", self.name
         yield "low", self.low
         yield "high", self.high
diff --git a/src/tempor/methods/pipeline/__init__.py b/src/tempor/methods/pipeline/__init__.py
index a4774295..580a07b7 100644
--- a/src/tempor/methods/pipeline/__init__.py
+++ b/src/tempor/methods/pipeline/__init__.py
@@ -1,5 +1,5 @@
 import abc
-from typing import Any, Dict, List, NoReturn, Optional, Tuple, Type
+from typing import Any, Dict, Generator, List, NoReturn, Optional, Tuple, Type
 
 import omegaconf
 import rich.pretty
@@ -8,7 +8,7 @@
 from tempor import plugin_loader
 from tempor.data import dataset
 from tempor.log import logger
-from tempor.methods.core._params import Params
+from tempor.methods.core import Params
 from tempor.methods.prediction.one_off.classification import BaseOneOffClassifier
 from tempor.methods.prediction.one_off.regression import BaseOneOffRegressor
 from tempor.methods.prediction.temporal.classification import BaseTemporalClassifier
@@ -49,7 +49,7 @@ class PipelineBase:
     plugin_types: List[Type]
     """A list of types denoting the class of each step in the pipeline."""
 
-    def __init__(self, plugin_params: Optional[Dict[str, Dict]] = None, **kwargs) -> None:  # pragma: no cover
+    def __init__(self, plugin_params: Optional[Dict[str, Dict]] = None, **kwargs: Any) -> None:  # pragma: no cover
         """Instantiate the pipeline, (optionally) providing initialization parameters for constituent step plugins.
 
         Note:
@@ -171,7 +171,7 @@ def params(self) -> Dict[str, omegaconf.DictConfig]:
             out[p.name] = p.params
         return out
 
-    def __rich_repr__(self):
+    def __rich_repr__(self) -> Generator:
         yield "pipeline_seq", self.pipeline_seq()
         yield "predictor_category", self.predictor_category
         yield "params", {k: omegaconf.OmegaConf.to_container(v) for k, v in self.params.items()}
@@ -188,7 +188,7 @@ def prepend_base(base: Type, bases: List[Type]) -> List[Type]:
     return bases_final
 
 
-def raise_not_implemented(*args, **kwargs) -> NoReturn:
+def raise_not_implemented(*args: Any, **kwargs: Any) -> NoReturn:
     raise NotImplementedError("The `{_fit/predict/...}` methods are not implemented for the pipelines")
 
 
@@ -236,7 +236,7 @@ def __new__(
         return super().__new__(cls, __name, bases, __namespace, **kwds)
 
     @staticmethod
-    def parse_bases(bases: Tuple[type, ...], plugins: Tuple[Type, ...]):
+    def parse_bases(bases: Tuple[type, ...], plugins: Tuple[Type, ...]) -> Tuple[type, ...]:
         bases_final: List[Type] = list(bases)
 
         if len(plugins) > 0:
diff --git a/src/tempor/methods/pipeline/generators.py b/src/tempor/methods/pipeline/generators.py
index 854cc24a..3ab9e7bc 100644
--- a/src/tempor/methods/pipeline/generators.py
+++ b/src/tempor/methods/pipeline/generators.py
@@ -3,7 +3,7 @@
 from tempor.data import dataset
 
 if TYPE_CHECKING:  # pragma: no cover
-    from tempor.methods.core._params import Params
+    from tempor.methods.core import Params
 
 
 def _generate_pipeline_seq_impl(plugins: Tuple[Type, ...]) -> Callable:
diff --git a/src/tempor/methods/prediction/one_off/classification/__init__.py b/src/tempor/methods/prediction/one_off/classification/__init__.py
index 88d90c6b..b6a8b724 100644
--- a/src/tempor/methods/prediction/one_off/classification/__init__.py
+++ b/src/tempor/methods/prediction/one_off/classification/__init__.py
@@ -1,16 +1,16 @@
 import abc
-from typing import Tuple
+from typing import Any, Tuple
 
 import numpy as np
 import pydantic
 from typing_extensions import Self
 
 import tempor.methods.core as methods_core
-from tempor.core import plugins
+from tempor.core import plugins, pydantic_utils
 from tempor.data import dataset, samples
 
 
-def check_data_class(data):
+def check_data_class(data: Any) -> None:
     if not isinstance(data, dataset.OneOffPredictionDataset):
         raise TypeError(
             "Expected `data` passed to a one-off classification estimator to be "
@@ -19,41 +19,46 @@ def check_data_class(data):
 
 
 class BaseOneOffClassifier(methods_core.BasePredictor):
-    def __init__(self, **params) -> None:  # pylint: disable=useless-super-delegation
+    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
         super().__init__(**params)
 
-    def fit(self, data: dataset.BaseDataset, *args, **kwargs) -> Self:
+    def fit(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Self:
         check_data_class(data)
         super().fit(data, *args, **kwargs)
         return self
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def predict(
         self,
         data: dataset.PredictiveDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> samples.StaticSamples:
         check_data_class(data)
         return super().predict(data, *args, **kwargs)
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def predict_proba(
         self,
         data: dataset.PredictiveDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> samples.StaticSamples:
         check_data_class(data)
         return super().predict_proba(data, *args, **kwargs)
 
     @abc.abstractmethod
-    def _predict(self, data: dataset.PredictiveDataset, *args, **kwargs) -> samples.StaticSamples:  # pragma: no cover
+    def _predict(
+        self,
+        data: dataset.PredictiveDataset,
+        *args: Any,
+        **kwargs: Any,
+    ) -> samples.StaticSamples:  # pragma: no cover
         ...
 
     @abc.abstractmethod
     def _predict_proba(
-        self, data: dataset.PredictiveDataset, *args, **kwargs
+        self, data: dataset.PredictiveDataset, *args: Any, **kwargs: Any
     ) -> samples.StaticSamples:  # pragma: no cover
         ...
 
diff --git a/src/tempor/methods/prediction/one_off/classification/plugin_cde_classifier.py b/src/tempor/methods/prediction/one_off/classification/plugin_cde_classifier.py
index 1f4c2410..427a3088 100644
--- a/src/tempor/methods/prediction/one_off/classification/plugin_cde_classifier.py
+++ b/src/tempor/methods/prediction/one_off/classification/plugin_cde_classifier.py
@@ -1,11 +1,12 @@
 import dataclasses
-from typing import Optional
+from typing import Any, List, Optional
 
 import numpy as np
 from typing_extensions import Self
 
 from tempor.core import plugins
 from tempor.data import dataset, samples
+from tempor.methods.core import Params
 from tempor.methods.core._params import CategoricalParams, FloatParams, IntegerParams
 from tempor.methods.prediction.one_off.classification import BaseOneOffClassifier
 from tempor.models import utils as model_utils
@@ -64,7 +65,7 @@ class CDEClassifier(BaseOneOffClassifier):
     ParamsDefinition = CDEClassifierParams
     params: CDEClassifierParams  # type: ignore
 
-    def __init__(self, **params) -> None:
+    def __init__(self, **params: Any) -> None:
         """Neural Controlled Differential Equations for Irregular Time Series.
 
         Args:
@@ -100,8 +101,8 @@ def __init__(self, **params) -> None:
     def _fit(
         self,
         data: dataset.BaseDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Self:
         static, temporal, observation_times, outcome = self._unpack_dataset(data)
         outcome = outcome.squeeze()
@@ -142,8 +143,8 @@ def _fit(
     def _predict(
         self,
         data: dataset.PredictiveDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> samples.StaticSamples:
         if self.model is None:
             raise RuntimeError("Fit the model first")
@@ -158,8 +159,8 @@ def _predict(
     def _predict_proba(
         self,
         data: dataset.PredictiveDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> samples.StaticSamples:
         if self.model is None:
             raise RuntimeError("Fit the model first")
@@ -171,7 +172,7 @@ def _predict_proba(
         return samples.StaticSamples.from_numpy(preds)
 
     @staticmethod
-    def hyperparameter_space(*args, **kwargs):
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
         return [
             IntegerParams(name="n_units_hidden", low=100, high=1000),
             IntegerParams(name="n_layers_hidden", low=1, high=5),
diff --git a/src/tempor/methods/prediction/one_off/classification/plugin_laplace_classifier.py b/src/tempor/methods/prediction/one_off/classification/plugin_laplace_classifier.py
index db112179..36f3ff69 100644
--- a/src/tempor/methods/prediction/one_off/classification/plugin_laplace_classifier.py
+++ b/src/tempor/methods/prediction/one_off/classification/plugin_laplace_classifier.py
@@ -1,11 +1,12 @@
 import dataclasses
-from typing import Optional
+from typing import Any, List, Optional
 
 import numpy as np
 from typing_extensions import Self
 
 from tempor.core import plugins
 from tempor.data import dataset, samples
+from tempor.methods.core import Params
 from tempor.methods.core._params import CategoricalParams, FloatParams, IntegerParams
 from tempor.methods.prediction.one_off.classification import BaseOneOffClassifier
 from tempor.models import utils as model_utils
@@ -64,7 +65,7 @@ class LaplaceODEClassifier(BaseOneOffClassifier):
     ParamsDefinition = LaplaceODEClassifierParams
     params: LaplaceODEClassifierParams  # type: ignore
 
-    def __init__(self, **params) -> None:
+    def __init__(self, **params: Any) -> None:
         """Inverse Laplace Transform (ILT) algorithms implemented in PyTorch.
         Backpropagation through differential equation (DE) solutions in the Laplace domain is supported using the
         Riemann stereographic projection for better global representation of the complex Laplace domain.
@@ -104,8 +105,8 @@ def __init__(self, **params) -> None:
     def _fit(
         self,
         data: dataset.BaseDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Self:
         static, temporal, observation_times, outcome = self._unpack_dataset(data)
         outcome = outcome.squeeze()
@@ -145,8 +146,8 @@ def _fit(
     def _predict(
         self,
         data: dataset.PredictiveDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> samples.StaticSamples:
         if self.model is None:
             raise RuntimeError("Fit the model first")
@@ -161,8 +162,8 @@ def _predict(
     def _predict_proba(
         self,
         data: dataset.PredictiveDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> samples.StaticSamples:
         if self.model is None:
             raise RuntimeError("Fit the model first")
@@ -174,7 +175,7 @@ def _predict_proba(
         return samples.StaticSamples.from_numpy(preds)
 
     @staticmethod
-    def hyperparameter_space(*args, **kwargs):
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
         return [
             IntegerParams(name="n_units_hidden", low=100, high=1000),
             IntegerParams(name="n_layers_hidden", low=1, high=5),
diff --git a/src/tempor/methods/prediction/one_off/classification/plugin_nn_classifier.py b/src/tempor/methods/prediction/one_off/classification/plugin_nn_classifier.py
index 295df1fc..68389483 100644
--- a/src/tempor/methods/prediction/one_off/classification/plugin_nn_classifier.py
+++ b/src/tempor/methods/prediction/one_off/classification/plugin_nn_classifier.py
@@ -1,11 +1,12 @@
 import dataclasses
-from typing import TYPE_CHECKING, Optional
+from typing import TYPE_CHECKING, Any, List, Optional
 
 import numpy as np
 from typing_extensions import Self, get_args
 
 from tempor.core import plugins
 from tempor.data import dataset, samples
+from tempor.methods.core import Params
 from tempor.methods.core._params import CategoricalParams, FloatParams, IntegerParams
 from tempor.methods.prediction.one_off.classification import BaseOneOffClassifier
 from tempor.models import utils as model_utils
@@ -62,7 +63,7 @@ class NeuralNetClassifier(BaseOneOffClassifier):
     ParamsDefinition = NeuralNetClassifierParams
     params: NeuralNetClassifierParams  # type: ignore
 
-    def __init__(self, **params) -> None:
+    def __init__(self, **params: Any) -> None:
         """Neural-net classifier.
 
         Args:
@@ -94,8 +95,8 @@ def __init__(self, **params) -> None:
     def _fit(
         self,
         data: dataset.BaseDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Self:
         static, temporal, observation_times, outcome = self._unpack_dataset(data)
         outcome = outcome.squeeze()
@@ -138,8 +139,8 @@ def _fit(
     def _predict(
         self,
         data: dataset.PredictiveDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> samples.StaticSamples:
         if self.model is None:
             raise RuntimeError("Fit the model first")
@@ -154,8 +155,8 @@ def _predict(
     def _predict_proba(
         self,
         data: dataset.PredictiveDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> samples.StaticSamples:
         if self.model is None:
             raise RuntimeError("Fit the model first")
@@ -167,7 +168,7 @@ def _predict_proba(
         return samples.StaticSamples.from_numpy(preds)
 
     @staticmethod
-    def hyperparameter_space(*args, **kwargs):
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
         return [
             IntegerParams(name="n_static_units_hidden", low=100, high=1000),
             IntegerParams(name="n_static_layers_hidden", low=1, high=5),
diff --git a/src/tempor/methods/prediction/one_off/classification/plugin_ode_classifier.py b/src/tempor/methods/prediction/one_off/classification/plugin_ode_classifier.py
index 0ef90ccd..ce834689 100644
--- a/src/tempor/methods/prediction/one_off/classification/plugin_ode_classifier.py
+++ b/src/tempor/methods/prediction/one_off/classification/plugin_ode_classifier.py
@@ -1,11 +1,12 @@
 import dataclasses
-from typing import Optional
+from typing import Any, List, Optional
 
 import numpy as np
 from typing_extensions import Self
 
 from tempor.core import plugins
 from tempor.data import dataset, samples
+from tempor.methods.core import Params
 from tempor.methods.core._params import CategoricalParams, FloatParams, IntegerParams
 from tempor.methods.prediction.one_off.classification import BaseOneOffClassifier
 from tempor.models import utils as model_utils
@@ -64,7 +65,7 @@ class ODEClassifier(BaseOneOffClassifier):
     ParamsDefinition = ODEClassifierParams
     params: ODEClassifierParams  # type: ignore
 
-    def __init__(self, **params) -> None:
+    def __init__(self, **params: Any) -> None:
         """Classifier based on ordinary differential equation (ODE) solvers.
 
         Args:
@@ -100,8 +101,8 @@ def __init__(self, **params) -> None:
     def _fit(
         self,
         data: dataset.BaseDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Self:
         static, temporal, observation_times, outcome = self._unpack_dataset(data)
         outcome = outcome.squeeze()
@@ -142,8 +143,8 @@ def _fit(
     def _predict(
         self,
         data: dataset.PredictiveDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> samples.StaticSamples:
         if self.model is None:
             raise RuntimeError("Fit the model first")
@@ -158,8 +159,8 @@ def _predict(
     def _predict_proba(
         self,
         data: dataset.PredictiveDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> samples.StaticSamples:
         if self.model is None:
             raise RuntimeError("Fit the model first")
@@ -171,7 +172,7 @@ def _predict_proba(
         return samples.StaticSamples.from_numpy(preds)
 
     @staticmethod
-    def hyperparameter_space(*args, **kwargs):
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
         return [
             IntegerParams(name="n_units_hidden", low=100, high=1000),
             IntegerParams(name="n_layers_hidden", low=1, high=5),
diff --git a/src/tempor/methods/prediction/one_off/regression/__init__.py b/src/tempor/methods/prediction/one_off/regression/__init__.py
index eca5d012..c3bc73bc 100644
--- a/src/tempor/methods/prediction/one_off/regression/__init__.py
+++ b/src/tempor/methods/prediction/one_off/regression/__init__.py
@@ -1,16 +1,16 @@
 import abc
-from typing import Tuple
+from typing import Any, Tuple
 
 import numpy as np
 import pydantic
 from typing_extensions import Self
 
 import tempor.methods.core as methods_core
-from tempor.core import plugins
+from tempor.core import plugins, pydantic_utils
 from tempor.data import dataset, samples
 
 
-def check_data_class(data):
+def check_data_class(data: Any) -> None:
     if not isinstance(data, dataset.OneOffPredictionDataset):
         raise TypeError(
             "Expected `data` passed to a one-off regression estimator to be "
@@ -19,26 +19,28 @@ def check_data_class(data):
 
 
 class BaseOneOffRegressor(methods_core.BasePredictor):
-    def __init__(self, **params) -> None:  # pylint: disable=useless-super-delegation
+    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
         super().__init__(**params)
 
-    def fit(self, data: dataset.BaseDataset, *args, **kwargs) -> Self:
+    def fit(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Self:
         check_data_class(data)
         super().fit(data, *args, **kwargs)
         return self
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def predict(
         self,
         data: dataset.PredictiveDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> samples.StaticSamples:
         check_data_class(data)
         return super().predict(data, *args, **kwargs)
 
     @abc.abstractmethod
-    def _predict(self, data: dataset.PredictiveDataset, *args, **kwargs) -> samples.StaticSamples:  # pragma: no cover
+    def _predict(
+        self, data: dataset.PredictiveDataset, *args: Any, **kwargs: Any
+    ) -> samples.StaticSamples:  # pragma: no cover
         ...
 
     def _unpack_dataset(self, data: dataset.BaseDataset) -> Tuple:
diff --git a/src/tempor/methods/prediction/one_off/regression/plugin_cde_regressor.py b/src/tempor/methods/prediction/one_off/regression/plugin_cde_regressor.py
index 14618a1a..8e8f12d2 100644
--- a/src/tempor/methods/prediction/one_off/regression/plugin_cde_regressor.py
+++ b/src/tempor/methods/prediction/one_off/regression/plugin_cde_regressor.py
@@ -1,10 +1,11 @@
 import dataclasses
-from typing import Optional
+from typing import Any, List, Optional
 
 from typing_extensions import Self
 
 from tempor.core import plugins
 from tempor.data import dataset, samples
+from tempor.methods.core import Params
 from tempor.methods.core._params import CategoricalParams, FloatParams, IntegerParams
 from tempor.methods.prediction.one_off.regression import BaseOneOffRegressor
 from tempor.models import utils as model_utils
@@ -63,7 +64,7 @@ class CDERegressor(BaseOneOffRegressor):
     ParamsDefinition = CDERegressorParams
     params: CDERegressorParams  # type: ignore
 
-    def __init__(self, **params) -> None:
+    def __init__(self, **params: Any) -> None:
         """Neural Controlled Differential Equations for Irregular Time Series.
 
         Args:
@@ -99,8 +100,8 @@ def __init__(self, **params) -> None:
     def _fit(
         self,
         data: dataset.BaseDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Self:
         static, temporal, observation_times, outcome = self._unpack_dataset(data)
         outcome = outcome.squeeze()
@@ -139,8 +140,8 @@ def _fit(
     def _predict(
         self,
         data: dataset.PredictiveDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> samples.StaticSamples:
         if self.model is None:
             raise RuntimeError("Fit the model first")
@@ -153,7 +154,7 @@ def _predict(
         return samples.StaticSamples.from_numpy(preds)
 
     @staticmethod
-    def hyperparameter_space(*args, **kwargs):
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
         return [
             IntegerParams(name="n_units_hidden", low=100, high=1000),
             IntegerParams(name="n_layers_hidden", low=1, high=5),
diff --git a/src/tempor/methods/prediction/one_off/regression/plugin_laplace_regressor.py b/src/tempor/methods/prediction/one_off/regression/plugin_laplace_regressor.py
index fb3ca42b..874bcddc 100644
--- a/src/tempor/methods/prediction/one_off/regression/plugin_laplace_regressor.py
+++ b/src/tempor/methods/prediction/one_off/regression/plugin_laplace_regressor.py
@@ -1,10 +1,11 @@
 import dataclasses
-from typing import Optional
+from typing import Any, List, Optional
 
 from typing_extensions import Self
 
 from tempor.core import plugins
 from tempor.data import dataset, samples
+from tempor.methods.core import Params
 from tempor.methods.core._params import CategoricalParams, FloatParams, IntegerParams
 from tempor.methods.prediction.one_off.regression import BaseOneOffRegressor
 from tempor.models import utils as model_utils
@@ -63,7 +64,7 @@ class LaplaceODERegressor(BaseOneOffRegressor):
     ParamsDefinition = LaplaceODERegressorParams
     params: LaplaceODERegressorParams  # type: ignore
 
-    def __init__(self, **params) -> None:
+    def __init__(self, **params: Any) -> None:
         """Inverse Laplace Transform (ILT) algorithms implemented in PyTorch.
         Backpropagation through differential equation (DE) solutions in the Laplace domain is supported using the
         Riemann stereographic projection for better global representation of the complex Laplace domain.
@@ -103,8 +104,8 @@ def __init__(self, **params) -> None:
     def _fit(
         self,
         data: dataset.BaseDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Self:
         static, temporal, observation_times, outcome = self._unpack_dataset(data)
         outcome = outcome.squeeze()
@@ -142,8 +143,8 @@ def _fit(
     def _predict(
         self,
         data: dataset.PredictiveDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> samples.StaticSamples:
         if self.model is None:
             raise RuntimeError("Fit the model first")
@@ -156,7 +157,7 @@ def _predict(
         return samples.StaticSamples.from_numpy(preds)
 
     @staticmethod
-    def hyperparameter_space(*args, **kwargs):
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
         return [
             IntegerParams(name="n_units_hidden", low=100, high=1000),
             IntegerParams(name="n_layers_hidden", low=1, high=5),
diff --git a/src/tempor/methods/prediction/one_off/regression/plugin_nn_regressor.py b/src/tempor/methods/prediction/one_off/regression/plugin_nn_regressor.py
index 5af27610..0de85d60 100644
--- a/src/tempor/methods/prediction/one_off/regression/plugin_nn_regressor.py
+++ b/src/tempor/methods/prediction/one_off/regression/plugin_nn_regressor.py
@@ -1,10 +1,11 @@
 import dataclasses
-from typing import TYPE_CHECKING, Optional
+from typing import TYPE_CHECKING, Any, List, Optional
 
 from typing_extensions import Self, get_args
 
 from tempor.core import plugins
 from tempor.data import dataset, samples
+from tempor.methods.core import Params
 from tempor.methods.core._params import CategoricalParams, FloatParams, IntegerParams
 from tempor.methods.prediction.one_off.regression import BaseOneOffRegressor
 from tempor.models import utils as model_utils
@@ -61,7 +62,7 @@ class NeuralNetRegressor(BaseOneOffRegressor):
     ParamsDefinition = NeuralNetRegressorParams
     params: NeuralNetRegressorParams  # type: ignore
 
-    def __init__(self, **params) -> None:
+    def __init__(self, **params: Any) -> None:
         """Neural-net regressor.
 
         Args:
@@ -93,8 +94,8 @@ def __init__(self, **params) -> None:
     def _fit(
         self,
         data: dataset.BaseDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Self:
         static, temporal, observation_times, outcome = self._unpack_dataset(data)
         outcome = outcome.squeeze()
@@ -135,8 +136,8 @@ def _fit(
     def _predict(
         self,
         data: dataset.PredictiveDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> samples.StaticSamples:
         if self.model is None:
             raise RuntimeError("Fit the model first")
@@ -148,7 +149,7 @@ def _predict(
         return samples.StaticSamples.from_numpy(preds)
 
     @staticmethod
-    def hyperparameter_space(*args, **kwargs):
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
         return [
             IntegerParams(name="n_static_units_hidden", low=100, high=1000),
             IntegerParams(name="n_static_layers_hidden", low=1, high=5),
diff --git a/src/tempor/methods/prediction/one_off/regression/plugin_ode_regressor.py b/src/tempor/methods/prediction/one_off/regression/plugin_ode_regressor.py
index d4efd5b9..f82fb70b 100644
--- a/src/tempor/methods/prediction/one_off/regression/plugin_ode_regressor.py
+++ b/src/tempor/methods/prediction/one_off/regression/plugin_ode_regressor.py
@@ -1,10 +1,11 @@
 import dataclasses
-from typing import Optional
+from typing import Any, List, Optional
 
 from typing_extensions import Self
 
 from tempor.core import plugins
 from tempor.data import dataset, samples
+from tempor.methods.core import Params
 from tempor.methods.core._params import CategoricalParams, FloatParams, IntegerParams
 from tempor.methods.prediction.one_off.regression import BaseOneOffRegressor
 from tempor.models import utils as model_utils
@@ -63,7 +64,7 @@ class ODERegressor(BaseOneOffRegressor):
     ParamsDefinition = ODERegressorParams
     params: ODERegressorParams  # type: ignore
 
-    def __init__(self, **params) -> None:
+    def __init__(self, **params: Any) -> None:
         """Regressor based on ordinary differential equation (ODE) solvers.
 
         Args:
@@ -99,8 +100,8 @@ def __init__(self, **params) -> None:
     def _fit(
         self,
         data: dataset.BaseDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Self:
         static, temporal, observation_times, outcome = self._unpack_dataset(data)
         outcome = outcome.squeeze()
@@ -139,8 +140,8 @@ def _fit(
     def _predict(
         self,
         data: dataset.PredictiveDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> samples.StaticSamples:
         if self.model is None:
             raise RuntimeError("Fit the model first")
@@ -153,7 +154,7 @@ def _predict(
         return samples.StaticSamples.from_numpy(preds)
 
     @staticmethod
-    def hyperparameter_space(*args, **kwargs):
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
         return [
             IntegerParams(name="n_units_hidden", low=100, high=1000),
             IntegerParams(name="n_layers_hidden", low=1, high=5),
diff --git a/src/tempor/methods/prediction/temporal/classification/__init__.py b/src/tempor/methods/prediction/temporal/classification/__init__.py
index f3a8196b..f2c61da8 100644
--- a/src/tempor/methods/prediction/temporal/classification/__init__.py
+++ b/src/tempor/methods/prediction/temporal/classification/__init__.py
@@ -1,14 +1,15 @@
 import abc
+from typing import Any
 
 import pydantic
 from typing_extensions import Self
 
 import tempor.methods.core as methods_core
-from tempor.core import plugins
+from tempor.core import plugins, pydantic_utils
 from tempor.data import dataset, samples
 
 
-def check_data_class(data):
+def check_data_class(data: Any) -> None:
     if not isinstance(data, dataset.TemporalPredictionDataset):
         raise TypeError(
             "Expected `data` passed to a temporal classification estimator to be "
@@ -17,47 +18,47 @@ def check_data_class(data):
 
 
 class BaseTemporalClassifier(methods_core.BasePredictor):
-    def __init__(self, **params) -> None:  # pylint: disable=useless-super-delegation
+    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
         super().__init__(**params)
 
-    def fit(self, data: dataset.BaseDataset, *args, **kwargs) -> Self:
+    def fit(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Self:
         check_data_class(data)
         super().fit(data, *args, **kwargs)
         return self
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
-    def predict(  # type: ignore[override]  # pylint: disable=arguments-differ
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
+    def predict(  # type: ignore [override]  # pylint: disable=arguments-differ
         self,
         data: dataset.PredictiveDataset,
         n_future_steps: int,
-        *args,
+        *args: Any,
         time_delta: int = 1,
-        **kwargs,
+        **kwargs: Any,
     ) -> samples.TimeSeriesSamples:
         check_data_class(data)
         return super().predict(data, n_future_steps, *args, time_delta=time_delta, **kwargs)
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
-    def predict_proba(  # type: ignore[override]  # pylint: disable=arguments-differ
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
+    def predict_proba(  # type: ignore [override]  # pylint: disable=arguments-differ
         self,
         data: dataset.PredictiveDataset,
         n_future_steps: int,
-        *args,
+        *args: Any,
         time_delta: int = 1,
-        **kwargs,
+        **kwargs: Any,
     ) -> samples.TimeSeriesSamples:
         check_data_class(data)
         return super().predict_proba(data, n_future_steps, *args, time_delta=time_delta, **kwargs)
 
     @abc.abstractmethod
     def _predict(  # type: ignore[override]  # pylint: disable=arguments-differ
-        self, data: dataset.PredictiveDataset, n_future_steps: int, *args, time_delta: int = 1, **kwargs
+        self, data: dataset.PredictiveDataset, n_future_steps: int, *args: Any, time_delta: int = 1, **kwargs: Any
     ) -> samples.TimeSeriesSamples:  # pragma: no cover
         ...
 
     @abc.abstractmethod
     def _predict_proba(  # type: ignore[override]  # pylint: disable=arguments-differ
-        self, data: dataset.PredictiveDataset, n_future_steps: int, *args, time_delta: int = 1, **kwargs
+        self, data: dataset.PredictiveDataset, n_future_steps: int, *args: Any, time_delta: int = 1, **kwargs: Any
     ) -> samples.TimeSeriesSamples:  # pragma: no cover
         ...
 
diff --git a/src/tempor/methods/prediction/temporal/classification/plugin_seq2seq_classifier.py b/src/tempor/methods/prediction/temporal/classification/plugin_seq2seq_classifier.py
index 70ccf8dc..12445606 100644
--- a/src/tempor/methods/prediction/temporal/classification/plugin_seq2seq_classifier.py
+++ b/src/tempor/methods/prediction/temporal/classification/plugin_seq2seq_classifier.py
@@ -9,6 +9,7 @@
 from tempor.core import plugins
 from tempor.data import dataset, samples
 from tempor.data.clv2conv import _from_clv2_time_series, tempor_dataset_to_clairvoyance2_dataset
+from tempor.methods.core import Params
 from tempor.methods.core._params import CategoricalParams, FloatParams, IntegerParams
 from tempor.methods.prediction.temporal.classification import BaseTemporalClassifier
 
@@ -56,7 +57,7 @@ class Seq2seqClassifier(BaseTemporalClassifier):
 
     def __init__(
         self,
-        **params,
+        **params: Any,
     ) -> None:
         """Seq2seq classifier.
 
@@ -97,8 +98,8 @@ def __init__(
     def _fit(
         self,
         data: dataset.BaseDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Self:
         cl_dataset = tempor_dataset_to_clairvoyance2_dataset(data)
         self.model.fit(cl_dataset)
@@ -108,9 +109,9 @@ def _predict(  # type: ignore[override]
         self,
         data: dataset.PredictiveDataset,
         n_future_steps: int,
-        *args,
+        *args: Any,
         time_delta: int = 1,
-        **kwargs,
+        **kwargs: Any,
     ) -> samples.TimeSeriesSamples:
         if self.model is None:
             raise RuntimeError("Fit the model first")
@@ -129,13 +130,13 @@ def _predict(  # type: ignore[override]
     def _predict_proba(
         self,
         data: dataset.PredictiveDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> samples.TimeSeriesSamples:
         raise NotImplementedError("Not currently supported")
 
     @staticmethod
-    def hyperparameter_space(*args, **kwargs):
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
         return [
             IntegerParams(name="encoder_hidden_size", low=10, high=500),
             IntegerParams(name="encoder_num_layers", low=1, high=10),
diff --git a/src/tempor/methods/prediction/temporal/regression/__init__.py b/src/tempor/methods/prediction/temporal/regression/__init__.py
index 57b669d7..a812f74c 100644
--- a/src/tempor/methods/prediction/temporal/regression/__init__.py
+++ b/src/tempor/methods/prediction/temporal/regression/__init__.py
@@ -1,14 +1,15 @@
 import abc
+from typing import Any
 
 import pydantic
 from typing_extensions import Self
 
 import tempor.methods.core as methods_core
-from tempor.core import plugins
+from tempor.core import plugins, pydantic_utils
 from tempor.data import dataset, samples
 
 
-def check_data_class(data):
+def check_data_class(data: Any) -> None:
     if not isinstance(data, dataset.TemporalPredictionDataset):
         raise TypeError(
             "Expected `data` passed to a temporal regression estimator to be "
@@ -17,29 +18,29 @@ def check_data_class(data):
 
 
 class BaseTemporalRegressor(methods_core.BasePredictor):
-    def __init__(self, **params) -> None:  # pylint: disable=useless-super-delegation
+    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
         super().__init__(**params)
 
-    def fit(self, data: dataset.BaseDataset, *args, **kwargs) -> Self:
+    def fit(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Self:
         check_data_class(data)
         super().fit(data, *args, **kwargs)
         return self
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
-    def predict(  # type: ignore[override]  # pylint: disable=arguments-differ
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
+    def predict(  # type: ignore [override] # pylint: disable=arguments-differ
         self,
         data: dataset.PredictiveDataset,
         n_future_steps: int,
-        *args,
+        *args: Any,
         time_delta: int = 1,
-        **kwargs,
+        **kwargs: Any,
     ) -> samples.TimeSeriesSamples:
         check_data_class(data)
         return super().predict(data, n_future_steps, *args, time_delta=time_delta, **kwargs)
 
     @abc.abstractmethod
     def _predict(  # type: ignore[override]  # pylint: disable=arguments-differ
-        self, data: dataset.PredictiveDataset, n_future_steps: int, *args, time_delta: int = 1, **kwargs
+        self, data: dataset.PredictiveDataset, n_future_steps: int, *args: Any, time_delta: int = 1, **kwargs: Any
     ) -> samples.TimeSeriesSamples:  # pragma: no cover
         ...
 
diff --git a/src/tempor/methods/prediction/temporal/regression/plugin_seq2seq_regressor.py b/src/tempor/methods/prediction/temporal/regression/plugin_seq2seq_regressor.py
index 60bb297d..6d0ed1d4 100644
--- a/src/tempor/methods/prediction/temporal/regression/plugin_seq2seq_regressor.py
+++ b/src/tempor/methods/prediction/temporal/regression/plugin_seq2seq_regressor.py
@@ -9,6 +9,7 @@
 from tempor.core import plugins
 from tempor.data import dataset, samples
 from tempor.data.clv2conv import _from_clv2_time_series, tempor_dataset_to_clairvoyance2_dataset
+from tempor.methods.core import Params
 from tempor.methods.core._params import CategoricalParams, FloatParams, IntegerParams
 from tempor.methods.prediction.temporal.regression import BaseTemporalRegressor
 
@@ -56,7 +57,7 @@ class Seq2seqRegressor(BaseTemporalRegressor):
 
     def __init__(
         self,
-        **params,
+        **params: Any,
     ) -> None:
         """Seq2seq regressor.
 
@@ -97,8 +98,8 @@ def __init__(
     def _fit(
         self,
         data: dataset.BaseDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Self:
         cl_dataset = tempor_dataset_to_clairvoyance2_dataset(data)
         self.model.fit(cl_dataset)
@@ -108,9 +109,9 @@ def _predict(  # type: ignore[override]
         self,
         data: dataset.PredictiveDataset,
         n_future_steps: int,
-        *args,
+        *args: Any,
         time_delta: int = 1,
-        **kwargs,
+        **kwargs: Any,
     ) -> samples.TimeSeriesSamples:
         if self.model is None:
             raise RuntimeError("Fit the model first")
@@ -127,7 +128,7 @@ def _predict(  # type: ignore[override]
         return samples.TimeSeriesSamples.from_dataframe(preds)
 
     @staticmethod
-    def hyperparameter_space(*args, **kwargs):
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
         return [
             IntegerParams(name="encoder_hidden_size", low=10, high=500),
             IntegerParams(name="encoder_num_layers", low=1, high=10),
diff --git a/src/tempor/methods/preprocessing/encoding/_base.py b/src/tempor/methods/preprocessing/encoding/_base.py
index 16a65439..28314101 100644
--- a/src/tempor/methods/preprocessing/encoding/_base.py
+++ b/src/tempor/methods/preprocessing/encoding/_base.py
@@ -1,6 +1,8 @@
+from typing import Any
+
 import tempor.methods.core as methods_core
 
 
 class BaseEncoder(methods_core.BaseTransformer):
-    def __init__(self, **params) -> None:  # pylint: disable=useless-super-delegation
+    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
         super().__init__(**params)
diff --git a/src/tempor/methods/preprocessing/encoding/static/__init__.py b/src/tempor/methods/preprocessing/encoding/static/__init__.py
index 73806469..ff4af9a5 100644
--- a/src/tempor/methods/preprocessing/encoding/static/__init__.py
+++ b/src/tempor/methods/preprocessing/encoding/static/__init__.py
@@ -6,6 +6,6 @@
 
 plugins.importing.import_plugins(__file__)
 
-__all__ = [
+__all__ = [  # pyright: ignore
     *plugins.importing.gather_modules_names(__file__),
 ]
diff --git a/src/tempor/methods/preprocessing/encoding/static/plugin_static_onehot_encoder.py b/src/tempor/methods/preprocessing/encoding/static/plugin_static_onehot_encoder.py
index e3b6ba48..57106405 100644
--- a/src/tempor/methods/preprocessing/encoding/static/plugin_static_onehot_encoder.py
+++ b/src/tempor/methods/preprocessing/encoding/static/plugin_static_onehot_encoder.py
@@ -11,6 +11,7 @@
 from tempor.data import dataset
 from tempor.data.data_typing import FeatureIndex
 from tempor.data.samples import StaticSamples
+from tempor.methods.core import Params
 from tempor.methods.core._params import CategoricalParams, FloatParams
 from tempor.methods.preprocessing.encoding._base import BaseEncoder
 
@@ -52,7 +53,7 @@ class StaticOneHotEncoder(BaseEncoder):
     ParamsDefinition = StaticOneHotEncoderParams
     params: StaticOneHotEncoderParams  # type: ignore
 
-    def __init__(self, **params) -> None:
+    def __init__(self, **params: Any) -> None:
         """One-hot encoding for the static data.
 
         See `sklearn.preprocessing.OneHotEncoder` for details.
@@ -112,8 +113,8 @@ def __init__(self, **params) -> None:
     def _fit(
         self,
         data: dataset.BaseDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Self:
         if data.static is None:
             return self
@@ -126,12 +127,12 @@ def _fit(
         self.model.fit(df_to_use)
         return self
 
-    def _transform(self, data: dataset.BaseDataset, *args, **kwargs) -> dataset.BaseDataset:
+    def _transform(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> dataset.BaseDataset:
         if data.static is None:
             return data
 
         df_to_encode = data.static.dataframe()[self.features]
-        encoded_arr = self.model.transform(df_to_encode)
+        encoded_arr = self.model.transform(df_to_encode)  # pyright: ignore
         encoded_col_names = self.model.get_feature_names_out()
 
         # Drop old columns.
@@ -146,7 +147,7 @@ def _transform(self, data: dataset.BaseDataset, *args, **kwargs) -> dataset.Base
         return data
 
     @staticmethod
-    def hyperparameter_space(*args, **kwargs):
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
         return [
             CategoricalParams("drop", ["first", "if_binary"]),
             CategoricalParams("handle_unknown", ["error", "ignore", "infrequent_if_exist"]),
diff --git a/src/tempor/methods/preprocessing/encoding/temporal/__init__.py b/src/tempor/methods/preprocessing/encoding/temporal/__init__.py
index 846155bf..52753807 100644
--- a/src/tempor/methods/preprocessing/encoding/temporal/__init__.py
+++ b/src/tempor/methods/preprocessing/encoding/temporal/__init__.py
@@ -6,6 +6,6 @@
 
 plugins.importing.import_plugins(__file__)
 
-__all__ = [
+__all__ = [  # pyright: ignore
     *plugins.importing.gather_modules_names(__file__),
 ]
diff --git a/src/tempor/methods/preprocessing/encoding/temporal/plugin_ts_onehot_encoder.py b/src/tempor/methods/preprocessing/encoding/temporal/plugin_ts_onehot_encoder.py
index 30e0f0b2..3e1a378a 100644
--- a/src/tempor/methods/preprocessing/encoding/temporal/plugin_ts_onehot_encoder.py
+++ b/src/tempor/methods/preprocessing/encoding/temporal/plugin_ts_onehot_encoder.py
@@ -11,6 +11,7 @@
 from tempor.data import dataset
 from tempor.data.data_typing import FeatureIndex
 from tempor.data.samples import TimeSeriesSamples
+from tempor.methods.core import Params
 from tempor.methods.core._params import CategoricalParams, FloatParams
 from tempor.methods.preprocessing.encoding._base import BaseEncoder
 
@@ -50,7 +51,7 @@ class TimeSeriesOneHotEncoder(BaseEncoder):
     ParamsDefinition = TimeSeriesOneHotEncoderParams
     params: TimeSeriesOneHotEncoderParams  # type: ignore
 
-    def __init__(self, **params) -> None:
+    def __init__(self, **params: Any) -> None:
         """One-hot encoding for the time series data.
 
         See `sklearn.preprocessing.OneHotEncoder` for details.
@@ -110,8 +111,8 @@ def __init__(self, **params) -> None:
     def _fit(
         self,
         data: dataset.BaseDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Self:
         df_to_use = data.time_series.dataframe()
         if self.features is None:
@@ -121,9 +122,9 @@ def _fit(
         self.model.fit(df_to_use)
         return self
 
-    def _transform(self, data: dataset.BaseDataset, *args, **kwargs) -> dataset.BaseDataset:
+    def _transform(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> dataset.BaseDataset:
         df_to_encode = data.time_series.dataframe()[self.features]
-        encoded_arr = self.model.transform(df_to_encode)
+        encoded_arr = self.model.transform(df_to_encode)  # pyright: ignore
         encoded_col_names = self.model.get_feature_names_out()
 
         # Drop old columns.
@@ -138,7 +139,7 @@ def _transform(self, data: dataset.BaseDataset, *args, **kwargs) -> dataset.Base
         return data
 
     @staticmethod
-    def hyperparameter_space(*args, **kwargs):
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
         return [
             CategoricalParams("drop", ["first", "if_binary"]),
             CategoricalParams("handle_unknown", ["error", "ignore", "infrequent_if_exist"]),
diff --git a/src/tempor/methods/preprocessing/imputation/_base.py b/src/tempor/methods/preprocessing/imputation/_base.py
index b431fecc..0a02130a 100644
--- a/src/tempor/methods/preprocessing/imputation/_base.py
+++ b/src/tempor/methods/preprocessing/imputation/_base.py
@@ -1,3 +1,5 @@
+from typing import Any
+
 from typing_extensions import Literal
 
 import tempor.methods.core as methods_core
@@ -22,5 +24,5 @@
 
 
 class BaseImputer(methods_core.BaseTransformer):
-    def __init__(self, **params) -> None:  # pylint: disable=useless-super-delegation
+    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
         super().__init__(**params)
diff --git a/src/tempor/methods/preprocessing/imputation/hyperimpute_utils.py b/src/tempor/methods/preprocessing/imputation/hyperimpute_utils.py
index 95a03b19..e73aa7fb 100644
--- a/src/tempor/methods/preprocessing/imputation/hyperimpute_utils.py
+++ b/src/tempor/methods/preprocessing/imputation/hyperimpute_utils.py
@@ -1,8 +1,9 @@
 import contextlib
+from typing import Generator
 
 
 @contextlib.contextmanager
-def monkeypatch_hyperimpute_logger():
+def monkeypatch_hyperimpute_logger() -> Generator:
     """In `hyperimpute`, at least as of version ``0.1.17``, the following call in `hyperimpute.logger` causes a
     conflict with `loguru` logger as used in TemporAI. To circumvent this problem, this context manager monkeypatches
     `loguru` ``logger.remove`` call with a no-op. To be used around `hyperimpute` imports.
@@ -18,7 +19,7 @@ def monkeypatch_hyperimpute_logger():
 
     original_remove = logger.remove
 
-    def monkeypatched_remove():
+    def monkeypatched_remove() -> None:
         pass
 
     logger.remove = monkeypatched_remove  # type: ignore
diff --git a/src/tempor/methods/preprocessing/imputation/static/plugin_static_tabular_imputer.py b/src/tempor/methods/preprocessing/imputation/static/plugin_static_tabular_imputer.py
index 2c4c4619..7cafa2a9 100644
--- a/src/tempor/methods/preprocessing/imputation/static/plugin_static_tabular_imputer.py
+++ b/src/tempor/methods/preprocessing/imputation/static/plugin_static_tabular_imputer.py
@@ -32,7 +32,7 @@ class StaticTabularImputer(BaseImputer):
     ParamsDefinition = StaticTabularImputerParams
     params: StaticTabularImputerParams  # type: ignore
 
-    def __init__(self, **params) -> None:
+    def __init__(self, **params: Any) -> None:
         """Impute the static covariates using any tabular imputer from the `hyperimpute` library.
 
         Args:
@@ -68,12 +68,12 @@ def __init__(self, **params) -> None:
         self.params.imputer_params["random_state"] = self.params.random_state
         self.imputer = Imputers().get(self.params.imputer, **self.params.imputer_params)
 
-    def _fit(self, data: dataset.BaseDataset, *args, **kwargs) -> Self:
+    def _fit(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Self:
         if data.static is not None:
             self.imputer.fit(data.static.dataframe())
         return self
 
-    def _transform(self, data: dataset.BaseDataset, *args, **kwargs) -> dataset.BaseDataset:
+    def _transform(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> dataset.BaseDataset:
         # Impute static data.
         if data.static is not None:
             static_data = data.static.dataframe()
diff --git a/src/tempor/methods/preprocessing/imputation/temporal/plugin_bfill.py b/src/tempor/methods/preprocessing/imputation/temporal/plugin_bfill.py
index 0e0872ea..e2483ccf 100644
--- a/src/tempor/methods/preprocessing/imputation/temporal/plugin_bfill.py
+++ b/src/tempor/methods/preprocessing/imputation/temporal/plugin_bfill.py
@@ -5,13 +5,13 @@
 from tempor.core import plugins
 from tempor.data import dataset
 from tempor.data.samples import TimeSeriesSamples
-from tempor.methods.core._params import Params
+from tempor.methods.core import Params
 from tempor.methods.preprocessing.imputation._base import BaseImputer
 
 
 @plugins.register_plugin(name="bfill", category="preprocessing.imputation.temporal")
 class BFillImputer(BaseImputer):
-    def __init__(self, **params) -> None:  # pylint: disable=useless-super-delegation
+    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
         """Backward-first Time-Series Imputation.
 
         Note:
@@ -41,10 +41,10 @@ def __init__(self, **params) -> None:  # pylint: disable=useless-super-delegatio
         """
         super().__init__(**params)
 
-    def _fit(self, data: dataset.BaseDataset, *args, **kwargs) -> Self:
+    def _fit(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Self:
         return self
 
-    def _transform(self, data: dataset.BaseDataset, *args, **kwargs) -> dataset.BaseDataset:
+    def _transform(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> dataset.BaseDataset:
         # Impute temporal data.
         sample_ts_index = data.time_series.sample_index()
         imputed_ts = data.time_series.dataframe()
diff --git a/src/tempor/methods/preprocessing/imputation/temporal/plugin_ffill.py b/src/tempor/methods/preprocessing/imputation/temporal/plugin_ffill.py
index 26399d37..81e49f86 100644
--- a/src/tempor/methods/preprocessing/imputation/temporal/plugin_ffill.py
+++ b/src/tempor/methods/preprocessing/imputation/temporal/plugin_ffill.py
@@ -5,13 +5,13 @@
 from tempor.core import plugins
 from tempor.data import dataset
 from tempor.data.samples import TimeSeriesSamples
-from tempor.methods.core._params import Params
+from tempor.methods.core import Params
 from tempor.methods.preprocessing.imputation._base import BaseImputer
 
 
 @plugins.register_plugin(name="ffill", category="preprocessing.imputation.temporal")
 class FFillImputer(BaseImputer):
-    def __init__(self, **params) -> None:  # pylint: disable=useless-super-delegation
+    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
         """Forward-first Time-Series Imputation.
 
         Note:
@@ -41,10 +41,10 @@ def __init__(self, **params) -> None:  # pylint: disable=useless-super-delegatio
         """
         super().__init__(**params)
 
-    def _fit(self, data: dataset.BaseDataset, *args, **kwargs) -> Self:
+    def _fit(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Self:
         return self
 
-    def _transform(self, data: dataset.BaseDataset, *args, **kwargs) -> dataset.BaseDataset:
+    def _transform(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> dataset.BaseDataset:
         # Impute temporal data.
         sample_ts_index = data.time_series.sample_index()
         imputed_ts = data.time_series.dataframe()
diff --git a/src/tempor/methods/preprocessing/imputation/temporal/plugin_ts_tabular_imputer.py b/src/tempor/methods/preprocessing/imputation/temporal/plugin_ts_tabular_imputer.py
index b5358566..b3eebe4c 100644
--- a/src/tempor/methods/preprocessing/imputation/temporal/plugin_ts_tabular_imputer.py
+++ b/src/tempor/methods/preprocessing/imputation/temporal/plugin_ts_tabular_imputer.py
@@ -32,7 +32,7 @@ class TemporalTabularImputer(BaseImputer):
     ParamsDefinition = TemporalTabularImputerParams
     params: TemporalTabularImputerParams  # type: ignore
 
-    def __init__(self, **params) -> None:
+    def __init__(self, **params: Any) -> None:
         """Impute the temporal covariates using any tabular imputer from the `hyperimpute` library.
 
         Note:
@@ -72,11 +72,11 @@ def __init__(self, **params) -> None:
         self.params.imputer_params["random_state"] = self.params.random_state
         self.imputer = Imputers().get(self.params.imputer, **self.params.imputer_params)
 
-    def _fit(self, data: dataset.BaseDataset, *args, **kwargs) -> Self:
+    def _fit(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Self:
         self.imputer.fit(data.time_series.dataframe())
         return self
 
-    def _transform(self, data: dataset.BaseDataset, *args, **kwargs) -> dataset.BaseDataset:
+    def _transform(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> dataset.BaseDataset:
         # Impute temporal data.
         ts_data = data.time_series.dataframe()
         imputed_ts_data = self.imputer.transform(ts_data)
diff --git a/src/tempor/methods/preprocessing/nop/__init__.py b/src/tempor/methods/preprocessing/nop/__init__.py
index 54e358a3..db8bdf5c 100644
--- a/src/tempor/methods/preprocessing/nop/__init__.py
+++ b/src/tempor/methods/preprocessing/nop/__init__.py
@@ -1,30 +1,33 @@
+from typing import Any, List
+
 from typing_extensions import Self
 
 import tempor.methods.core as methods_core
 from tempor.core import plugins
 from tempor.data import dataset
+from tempor.methods.core import Params
 
 plugins.register_plugin_category("preprocessing.nop", methods_core.BaseTransformer)
 
 
 @plugins.register_plugin(name="nop_transformer", category="preprocessing.nop")
 class NopTransformer(methods_core.BaseTransformer):
-    def __init__(self, **params) -> None:  # pylint: disable=useless-super-delegation
+    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
         super().__init__(**params)
 
     def _fit(
         self,
         data: dataset.BaseDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Self:
         return self
 
-    def _transform(self, data: dataset.BaseDataset, *args, **kwargs) -> dataset.BaseDataset:
+    def _transform(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> dataset.BaseDataset:
         return data
 
     @staticmethod
-    def hyperparameter_space(*args, **kwargs):
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
         return []
 
 
diff --git a/src/tempor/methods/preprocessing/scaling/_base.py b/src/tempor/methods/preprocessing/scaling/_base.py
index 0c349748..b1b6c40d 100644
--- a/src/tempor/methods/preprocessing/scaling/_base.py
+++ b/src/tempor/methods/preprocessing/scaling/_base.py
@@ -1,6 +1,8 @@
+from typing import Any
+
 import tempor.methods.core as methods_core
 
 
 class BaseScaler(methods_core.BaseTransformer):
-    def __init__(self, **params) -> None:  # pylint: disable=useless-super-delegation
+    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
         super().__init__(**params)
diff --git a/src/tempor/methods/preprocessing/scaling/static/plugin_static_minmax_scaler.py b/src/tempor/methods/preprocessing/scaling/static/plugin_static_minmax_scaler.py
index bf0c3abf..ccf629bd 100644
--- a/src/tempor/methods/preprocessing/scaling/static/plugin_static_minmax_scaler.py
+++ b/src/tempor/methods/preprocessing/scaling/static/plugin_static_minmax_scaler.py
@@ -1,5 +1,5 @@
 import dataclasses
-from typing import Any, Dict, Tuple
+from typing import Any, Dict, List, Tuple
 
 import pandas as pd
 from sklearn.preprocessing import MinMaxScaler
@@ -8,6 +8,7 @@
 from tempor.core import plugins
 from tempor.data import dataset
 from tempor.data.samples import StaticSamples
+from tempor.methods.core import Params
 from tempor.methods.core._params import CategoricalParams
 from tempor.methods.preprocessing.scaling._base import BaseScaler
 
@@ -29,7 +30,7 @@ class StaticMinMaxScaler(BaseScaler):
     ParamsDefinition = StaticMinMaxScalerParams
     params: StaticMinMaxScalerParams  # type: ignore
 
-    def __init__(self, **params) -> None:
+    def __init__(self, **params: Any) -> None:
         """MinMax scaling for the static data.
 
         Transform the static features by scaling each feature to a given range. This estimator scales and translates
@@ -62,8 +63,8 @@ def __init__(self, **params) -> None:
     def _fit(
         self,
         data: dataset.BaseDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Self:
         if data.static is None:
             return self
@@ -71,7 +72,7 @@ def _fit(
         self.model.fit(data.static.dataframe())
         return self
 
-    def _transform(self, data: dataset.BaseDataset, *args, **kwargs) -> dataset.BaseDataset:
+    def _transform(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> dataset.BaseDataset:
         if data.static is None:
             return data
 
@@ -85,7 +86,7 @@ def _transform(self, data: dataset.BaseDataset, *args, **kwargs) -> dataset.Base
         return data
 
     @staticmethod
-    def hyperparameter_space(*args, **kwargs):
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
         return [
             CategoricalParams("clip", [True, False]),
         ]
diff --git a/src/tempor/methods/preprocessing/scaling/static/plugin_static_standard_scaler.py b/src/tempor/methods/preprocessing/scaling/static/plugin_static_standard_scaler.py
index db3af14b..6b3ed6d3 100644
--- a/src/tempor/methods/preprocessing/scaling/static/plugin_static_standard_scaler.py
+++ b/src/tempor/methods/preprocessing/scaling/static/plugin_static_standard_scaler.py
@@ -1,5 +1,5 @@
 import dataclasses
-from typing import Any, Dict
+from typing import Any, Dict, List
 
 import pandas as pd
 from sklearn.preprocessing import StandardScaler
@@ -8,6 +8,7 @@
 from tempor.core import plugins
 from tempor.data import dataset
 from tempor.data.samples import StaticSamples
+from tempor.methods.core import Params
 from tempor.methods.preprocessing.scaling._base import BaseScaler
 
 
@@ -26,7 +27,7 @@ class StaticStandardScaler(BaseScaler):
     ParamsDefinition = StaticStandardScalerParams
     params: StaticStandardScalerParams  # type: ignore
 
-    def __init__(self, **params) -> None:
+    def __init__(self, **params: Any) -> None:
         """Standard scaling for the static data.
 
         Standardize the static features by removing the mean and scaling to unit variance.
@@ -57,8 +58,8 @@ def __init__(self, **params) -> None:
     def _fit(
         self,
         data: dataset.BaseDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Self:
         if data.static is None:
             return self
@@ -66,7 +67,7 @@ def _fit(
         self.model.fit(data.static.dataframe())
         return self
 
-    def _transform(self, data: dataset.BaseDataset, *args, **kwargs) -> dataset.BaseDataset:
+    def _transform(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> dataset.BaseDataset:
         if data.static is None:
             return data
 
@@ -80,5 +81,5 @@ def _transform(self, data: dataset.BaseDataset, *args, **kwargs) -> dataset.Base
         return data
 
     @staticmethod
-    def hyperparameter_space(*args, **kwargs):
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
         return []
diff --git a/src/tempor/methods/preprocessing/scaling/temporal/plugin_ts_minmax_scaler.py b/src/tempor/methods/preprocessing/scaling/temporal/plugin_ts_minmax_scaler.py
index 852dbe51..40b78a37 100644
--- a/src/tempor/methods/preprocessing/scaling/temporal/plugin_ts_minmax_scaler.py
+++ b/src/tempor/methods/preprocessing/scaling/temporal/plugin_ts_minmax_scaler.py
@@ -1,5 +1,5 @@
 import dataclasses
-from typing import Any, Dict, Tuple
+from typing import Any, Dict, List, Tuple
 
 import pandas as pd
 from sklearn.preprocessing import MinMaxScaler
@@ -8,6 +8,7 @@
 from tempor.core import plugins
 from tempor.data import dataset
 from tempor.data.samples import TimeSeriesSamples
+from tempor.methods.core import Params
 from tempor.methods.core._params import CategoricalParams
 from tempor.methods.preprocessing.scaling._base import BaseScaler
 
@@ -29,7 +30,7 @@ class TimeSeriesMinMaxScaler(BaseScaler):
     ParamsDefinition = TimeSeriesMinMaxScalerParams
     params: TimeSeriesMinMaxScalerParams  # type: ignore
 
-    def __init__(self, **params) -> None:
+    def __init__(self, **params: Any) -> None:
         """MinMax scaling for the time-series data.
 
         Transform the temporal features by scaling each feature to a given range. This estimator scales and translates
@@ -65,13 +66,13 @@ def __init__(self, **params) -> None:
     def _fit(
         self,
         data: dataset.BaseDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Self:
         self.model.fit(data.time_series.dataframe())
         return self
 
-    def _transform(self, data: dataset.BaseDataset, *args, **kwargs) -> dataset.BaseDataset:
+    def _transform(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> dataset.BaseDataset:
         temporal_data = data.time_series.dataframe()
         scaled = pd.DataFrame(self.model.transform(temporal_data))
         scaled.columns = temporal_data.columns
@@ -82,7 +83,7 @@ def _transform(self, data: dataset.BaseDataset, *args, **kwargs) -> dataset.Base
         return data
 
     @staticmethod
-    def hyperparameter_space(*args, **kwargs):
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
         return [
             CategoricalParams("clip", [True, False]),
         ]
diff --git a/src/tempor/methods/preprocessing/scaling/temporal/plugin_ts_standard_scaler.py b/src/tempor/methods/preprocessing/scaling/temporal/plugin_ts_standard_scaler.py
index b5884223..05ef00c2 100644
--- a/src/tempor/methods/preprocessing/scaling/temporal/plugin_ts_standard_scaler.py
+++ b/src/tempor/methods/preprocessing/scaling/temporal/plugin_ts_standard_scaler.py
@@ -1,5 +1,5 @@
 import dataclasses
-from typing import Any, Dict
+from typing import Any, Dict, List
 
 import pandas as pd
 from sklearn.preprocessing import StandardScaler
@@ -8,6 +8,7 @@
 from tempor.core import plugins
 from tempor.data import dataset
 from tempor.data.samples import TimeSeriesSamples
+from tempor.methods.core import Params
 from tempor.methods.preprocessing.scaling._base import BaseScaler
 
 
@@ -26,7 +27,7 @@ class TimeSeriesStandardScaler(BaseScaler):
     ParamsDefinition = TimeSeriesStandardScalerParams
     params: TimeSeriesStandardScalerParams  # type: ignore
 
-    def __init__(self, **params) -> None:
+    def __init__(self, **params: Any) -> None:
         """Standard scaling for the time-series data.
 
         Standardize the temporal features by removing the mean and scaling to unit variance. The time series data
@@ -59,13 +60,13 @@ def __init__(self, **params) -> None:
     def _fit(
         self,
         data: dataset.BaseDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Self:
         self.model.fit(data.time_series.dataframe())
         return self
 
-    def _transform(self, data: dataset.BaseDataset, *args, **kwargs) -> dataset.BaseDataset:
+    def _transform(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> dataset.BaseDataset:
         temporal_data = data.time_series.dataframe()
         scaled = pd.DataFrame(self.model.transform(temporal_data))
         scaled.columns = temporal_data.columns
@@ -76,5 +77,5 @@ def _transform(self, data: dataset.BaseDataset, *args, **kwargs) -> dataset.Base
         return data
 
     @staticmethod
-    def hyperparameter_space(*args, **kwargs):
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
         return []
diff --git a/src/tempor/methods/time_to_event/__init__.py b/src/tempor/methods/time_to_event/__init__.py
index 175b2792..81a5d21d 100644
--- a/src/tempor/methods/time_to_event/__init__.py
+++ b/src/tempor/methods/time_to_event/__init__.py
@@ -5,11 +5,11 @@
 
 import tempor.exc
 import tempor.methods.core as methods_core
-from tempor.core import plugins
+from tempor.core import plugins, pydantic_utils
 from tempor.data import data_typing, dataset, samples
 
 
-def check_data_class(data):
+def check_data_class(data: Any) -> None:
     if not isinstance(data, dataset.TimeToEventAnalysisDataset):
         raise TypeError(
             "Expected `data` passed to a survival analysis estimator to be "
@@ -18,16 +18,20 @@ def check_data_class(data):
 
 
 class BaseTimeToEventAnalysis(methods_core.BasePredictor):
-    def __init__(self, **params) -> None:  # pylint: disable=useless-super-delegation
+    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
         super().__init__(**params)
 
-    def fit(self, data: dataset.BaseDataset, *args, **kwargs) -> Self:
+    def fit(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Self:
         check_data_class(data)
         super().fit(data, *args, **kwargs)
         return self
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
-    def predict(  # type: ignore[override]  # pylint: disable=arguments-differ
+    # NOTE:
+    # It appears that `pydantic.validate_arguments` throws an error when `*args: Any` and `**kwargs: Any` are
+    # specified here for unknown reasons. For now, we just ignore the type checking for these arguments with
+    # `# type: ignore [no-untyped-def]`.
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
+    def predict(  # type: ignore [no-untyped-def, override] # pylint: disable=arguments-differ
         self,
         data: dataset.PredictiveDataset,
         horizons: data_typing.TimeIndex,
@@ -37,14 +41,14 @@ def predict(  # type: ignore[override]  # pylint: disable=arguments-differ
         check_data_class(data)
         return super().predict(data, horizons, *args, **kwargs)
 
-    def predict_proba(self, data: dataset.PredictiveDataset, *args, **kwargs) -> Any:
+    def predict_proba(self, data: dataset.PredictiveDataset, *args: Any, **kwargs: Any) -> Any:
         raise tempor.exc.UnsupportedSetupException(
             "`predict_proba` method is not supported in the time-to-event analysis setting"
         )
 
     @abc.abstractmethod
     def _predict(  # type: ignore[override]  # pylint: disable=arguments-differ
-        self, data: dataset.PredictiveDataset, horizons: data_typing.TimeIndex, *args, **kwargs
+        self, data: dataset.PredictiveDataset, horizons: data_typing.TimeIndex, *args: Any, **kwargs: Any
     ) -> samples.TimeSeriesSamples:  # pragma: no cover
         ...
 
diff --git a/src/tempor/methods/time_to_event/helper_embedding.py b/src/tempor/methods/time_to_event/helper_embedding.py
index 29ea7758..1dcd7dad 100644
--- a/src/tempor/methods/time_to_event/helper_embedding.py
+++ b/src/tempor/methods/time_to_event/helper_embedding.py
@@ -1,5 +1,5 @@
 import abc
-from typing import TYPE_CHECKING, List, Optional, Tuple, cast
+from typing import TYPE_CHECKING, Any, List, Optional, Tuple, cast
 
 import numpy as np
 import pandas as pd
@@ -7,6 +7,7 @@
 
 import tempor.exc
 from tempor.data import data_typing, dataset, samples
+from tempor.methods.core import Params
 from tempor.methods.core._params import CategoricalParams, FloatParams, IntegerParams
 from tempor.models import utils
 from tempor.models.ddh import DynamicDeepHitModel, output_modes, rnn_modes
@@ -70,7 +71,9 @@ def _validate_data(self, data: dataset.TimeToEventAnalysisDataset) -> None:
         #         f"but found timesteps of varying lengths {np.unique(data.time_series.num_timesteps()).tolist()}"
         #     )
 
-    def _convert_data(self, data: dataset.TimeToEventAnalysisDataset):
+    def _convert_data(
+        self, data: dataset.TimeToEventAnalysisDataset
+    ) -> Tuple[Optional[np.ndarray], List[np.ndarray], List[np.ndarray], Optional[np.ndarray], Optional[np.ndarray]]:
         if data.has_static:
             static = data.static.numpy() if data.static is not None else None
         else:
@@ -103,8 +106,8 @@ def prepare_predict(
         self,
         data: dataset.PredictiveDataset,
         horizons: data_typing.TimeIndex,
-        *args,  # pylint: disable=unused-argument
-        **kwargs,
+        *args: Any,  # pylint: disable=unused-argument
+        **kwargs: Any,
     ) -> np.ndarray:
         data = cast(dataset.TimeToEventAnalysisDataset, data)
         self._validate_data(data)
@@ -113,7 +116,7 @@ def prepare_predict(
         return processed_data
 
     @staticmethod
-    def hyperparameter_space(*args, **kwargs):  # pylint: disable=unused-argument
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # pylint: disable=unused-argument
         return [
             IntegerParams(name="n_units_hidden", low=10, high=100, step=10),
             IntegerParams(name="n_layers_hidden", low=1, high=4),
@@ -149,8 +152,8 @@ def __init__(
     def fit(
         self,
         data: dataset.BaseDataset,
-        *args,  # pylint: disable=unused-argument
-        **kwargs,
+        *args: Any,  # pylint: disable=unused-argument
+        **kwargs: Any,
     ) -> Self:
         processed_data, event_times, event_values = self.prepare_fit(data)
 
@@ -168,8 +171,8 @@ def predict(
         self,
         data: dataset.PredictiveDataset,
         horizons: data_typing.TimeIndex,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> samples.TimeSeriesSamples:
         # NOTE: kwargs will be passed to DynamicDeepHitModel.predict_emb().
         # E.g. `batch_size` batch size parameter can be provided this way.
@@ -190,5 +193,5 @@ def predict(
         )
 
     @staticmethod
-    def hyperparameter_space(*args, **kwargs):
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
         return DDHEmbedding.hyperparameter_space(*args, **kwargs)
diff --git a/src/tempor/methods/time_to_event/plugin_ddh.py b/src/tempor/methods/time_to_event/plugin_ddh.py
index 79898f70..67fca4be 100644
--- a/src/tempor/methods/time_to_event/plugin_ddh.py
+++ b/src/tempor/methods/time_to_event/plugin_ddh.py
@@ -1,9 +1,11 @@
 import dataclasses
+from typing import Any, List
 
 from typing_extensions import Self
 
 from tempor.core import plugins
 from tempor.data import data_typing, dataset, samples
+from tempor.methods.core import Params
 from tempor.methods.time_to_event import BaseTimeToEventAnalysis
 from tempor.models.ddh import DynamicDeepHitModel, OutputMode, RnnMode
 
@@ -51,7 +53,7 @@ class DynamicDeepHitTimeToEventAnalysis(BaseTimeToEventAnalysis, DDHEmbedding):
     ParamsDefinition = DynamicDeepHitTimeToEventAnalysisParams
     params: DynamicDeepHitTimeToEventAnalysisParams  # type: ignore
 
-    def __init__(self, **params) -> None:
+    def __init__(self, **params: Any) -> None:
         """Dynamic DeepHit survival analysis model.
 
         Note:
@@ -91,8 +93,8 @@ def __init__(self, **params) -> None:
     def _fit(
         self,
         data: dataset.BaseDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Self:
         processed_data, event_times, event_values = self.prepare_fit(data)
         self.model.fit(processed_data, event_times, event_values)
@@ -102,8 +104,8 @@ def _predict(  # type: ignore[override]
         self,
         data: dataset.PredictiveDataset,
         horizons: data_typing.TimeIndex,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> samples.TimeSeriesSamples:
         # NOTE: kwargs will be passed to DynamicDeepHitModel.predict_risk().
         # E.g. `batch_size` batch size parameter can be provided this way.
@@ -117,5 +119,5 @@ def _predict(  # type: ignore[override]
         )
 
     @staticmethod
-    def hyperparameter_space(*args, **kwargs):
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
         return DDHEmbedding.hyperparameter_space(*args, **kwargs)
diff --git a/src/tempor/methods/time_to_event/plugin_ts_coxph.py b/src/tempor/methods/time_to_event/plugin_ts_coxph.py
index 129d44a7..64b66d02 100644
--- a/src/tempor/methods/time_to_event/plugin_ts_coxph.py
+++ b/src/tempor/methods/time_to_event/plugin_ts_coxph.py
@@ -1,6 +1,6 @@
 import contextlib
 import dataclasses
-from typing import Any, Dict, List, Optional
+from typing import Any, Dict, Generator, List, Optional
 
 import lifelines
 import numpy as np
@@ -11,6 +11,7 @@
 
 from tempor.core import plugins
 from tempor.data import data_typing, dataset, samples
+from tempor.methods.core import Params
 from tempor.methods.core._params import FloatParams
 from tempor.methods.time_to_event import BaseTimeToEventAnalysis
 from tempor.models.ddh import DynamicDeepHitModel, OutputMode, RnnMode
@@ -19,7 +20,7 @@
 
 
 @contextlib.contextmanager
-def monkeypatch_lifelines_pd2_compatibility():
+def monkeypatch_lifelines_pd2_compatibility() -> Generator:
     """lifelines (before 0.27.6) is not compatible with pandas 2.0.0+, due to
     ``TypeError: describe() got an unexpected keyword argument 'datetime_is_numeric'`` thrown by pandas in e.g.
     ``CoxPHFitter.fit``. This monkeypatch fixes this compatibility issue, until the problem is addressed by
@@ -36,7 +37,7 @@ def problem_versions() -> bool:  # pragma: no cover
 
         original_pd_df_describe = pd.DataFrame.describe
 
-        def monkeypatched_describe(*args, **kwargs):
+        def monkeypatched_describe(*args: Any, **kwargs: Any) -> pd.DataFrame:
             # Remove the offending keyword argument (it is no longer needed to pass in).
             kwargs.pop("datetime_is_numeric", None)
             return original_pd_df_describe(*args, **kwargs)
@@ -171,7 +172,7 @@ class CoxPHTimeToEventAnalysis(BaseTimeToEventAnalysis):
     ParamsDefinition = CoxPHTimeToEventAnalysisParams
     params: CoxPHTimeToEventAnalysisParams  # type: ignore
 
-    def __init__(self, **params) -> None:
+    def __init__(self, **params: Any) -> None:
         """CoxPH survival analysis model.
 
         Args:
@@ -208,8 +209,8 @@ def __init__(self, **params) -> None:
     def _fit(
         self,
         data: dataset.BaseDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Self:
         self.model.fit(data, *args, **kwargs)
         return self
@@ -218,13 +219,13 @@ def _predict(  # type: ignore[override]
         self,
         data: dataset.PredictiveDataset,
         horizons: data_typing.TimeIndex,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> samples.TimeSeriesSamples:
         return self.model.predict(data, horizons, *args, **kwargs)
 
     @staticmethod
-    def hyperparameter_space(*args, **kwargs):
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
         return [
             FloatParams(name="coxph_alpha", low=0.05, high=0.1),
             FloatParams(name="coxph_penalizer", low=0, high=0.2),
diff --git a/src/tempor/methods/time_to_event/plugin_ts_xgb.py b/src/tempor/methods/time_to_event/plugin_ts_xgb.py
index f6b88bae..a5d09554 100644
--- a/src/tempor/methods/time_to_event/plugin_ts_xgb.py
+++ b/src/tempor/methods/time_to_event/plugin_ts_xgb.py
@@ -1,6 +1,6 @@
 import contextlib
 import dataclasses
-from typing import Any, List
+from typing import Any, Generator, List
 
 import numpy as np
 import pandas as pd
@@ -12,6 +12,7 @@
 
 from tempor.core import plugins
 from tempor.data import data_typing, dataset, samples
+from tempor.methods.core import Params
 from tempor.methods.core._params import CategoricalParams, FloatParams, IntegerParams
 from tempor.methods.time_to_event import BaseTimeToEventAnalysis
 from tempor.models.constants import DEVICE
@@ -27,7 +28,7 @@
 
 
 @contextlib.contextmanager
-def monkeypatch_xgbse_xgboost2_compatibility():
+def monkeypatch_xgbse_xgboost2_compatibility() -> Generator:
     """There is a bug that occurs in `xgbse` with `xgboost` 2.0+.
 
     ``AttributeError: `best_iteration` is only defined when early stopping is used.`` will be thrown when
@@ -51,13 +52,15 @@ def problem_versions() -> bool:  # pragma: no cover
         original_xgb_booster_best_iteration_fset = xgb.Booster.best_iteration.fset  # type: ignore [attr-defined]
         original_xgb_booster_best_iteration_fdel = xgb.Booster.best_iteration.fdel  # type: ignore [attr-defined]
 
-        def monkeypatched_best_iteration_getter(*args, **kwargs):
+        def monkeypatched_best_iteration_getter(*args: Any, **kwargs: Any) -> int:
             # Use try-catch to fall back to the default returning of 999 as in previous versions of xgboost.
             try:
                 return original_xgb_booster_best_iteration_fget(*args, **kwargs)
             except AttributeError as ex:
                 if "best_iteration" in str(ex):
                     return 999
+                else:
+                    raise
 
         setattr(
             xgb.Booster,
@@ -75,7 +78,11 @@ def monkeypatched_best_iteration_getter(*args, **kwargs):
     finally:
         if problem_versions():  # pragma: no cover
             # Restore `xgb.Booster.best_iteration`.
-            setattr(xgb.Booster, "best_iteration", original_xgb_booster_best_iteration)
+            setattr(
+                xgb.Booster,
+                "best_iteration",
+                original_xgb_booster_best_iteration,  # pyright: ignore  [reportUnboundVariable]
+            )
 
 
 @dataclasses.dataclass
@@ -227,7 +234,7 @@ def __init__(
 
         self.time_points = time_points
 
-    def fit(self, X: pd.DataFrame, T: pd.Series, Y: pd.Series):
+    def fit(self, X: pd.DataFrame, T: pd.Series, Y: pd.Series) -> Self:
         y = convert_to_structured(T, Y)
 
         censored_times = T[Y == 0]
@@ -272,7 +279,7 @@ class XGBTimeToEventAnalysis(BaseTimeToEventAnalysis):
     ParamsDefinition = XGBTimeToEventAnalysisParams
     params: XGBTimeToEventAnalysisParams  # type: ignore
 
-    def __init__(self, **params) -> None:
+    def __init__(self, **params: Any) -> None:
         """XGB survival analysis model.
 
         Args:
@@ -325,8 +332,8 @@ def __init__(self, **params) -> None:
     def _fit(
         self,
         data: dataset.BaseDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Self:
         self.model.fit(data, *args, **kwargs)
         return self
@@ -335,13 +342,13 @@ def _predict(  # type: ignore[override]
         self,
         data: dataset.PredictiveDataset,
         horizons: data_typing.TimeIndex,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> samples.TimeSeriesSamples:
         return self.model.predict(data, horizons, *args, **kwargs)
 
     @staticmethod
-    def hyperparameter_space(*args, **kwargs):
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
         return [
             IntegerParams(name="xgb_max_depth", low=2, high=6),
             IntegerParams(name="xgb_min_child_weight", low=0, high=50),
diff --git a/src/tempor/methods/treatments/one_off/_base.py b/src/tempor/methods/treatments/one_off/_base.py
index cb4ac90d..3a4ec861 100644
--- a/src/tempor/methods/treatments/one_off/_base.py
+++ b/src/tempor/methods/treatments/one_off/_base.py
@@ -1,14 +1,15 @@
 import abc
-from typing import List
+from typing import Any, List
 
 import pydantic
 from typing_extensions import Self
 
 import tempor.methods.core as methods_core
+from tempor.core import pydantic_utils
 from tempor.data import dataset, samples
 
 
-def check_data_class(data):
+def check_data_class(data: Any) -> None:
     if not isinstance(data, dataset.OneOffTreatmentEffectsDataset):
         raise TypeError(
             "Expected `data` passed to a one-off treatment effects estimator to be "
@@ -17,38 +18,42 @@ def check_data_class(data):
 
 
 class BaseOneOffTreatmentEffects(methods_core.BasePredictor):
-    def __init__(self, **params) -> None:  # pylint: disable=useless-super-delegation
+    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
         super().__init__(**params)
 
-    def fit(self, data: dataset.BaseDataset, *args, **kwargs) -> Self:
+    def fit(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Self:
         check_data_class(data)
         super().fit(data, *args, **kwargs)
         return self
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def predict(
         self,
         data: dataset.PredictiveDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> samples.StaticSamples:
         check_data_class(data)
         return super().predict(data, *args, **kwargs)
 
     @abc.abstractmethod
-    def _predict(self, data: dataset.PredictiveDataset, *args, **kwargs) -> samples.StaticSamples:  # pragma: no cover
+    def _predict(
+        self, data: dataset.PredictiveDataset, *args: Any, **kwargs: Any
+    ) -> samples.StaticSamples:  # pragma: no cover
         ...
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def predict_counterfactuals(
         self,
         data: dataset.PredictiveDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> List:
         check_data_class(data)
         return super().predict_counterfactuals(data, *args, **kwargs)
 
     @abc.abstractmethod
-    def _predict_counterfactuals(self, data: dataset.PredictiveDataset, *args, **kwargs) -> List:  # pragma: no cover
+    def _predict_counterfactuals(
+        self, data: dataset.PredictiveDataset, *args: Any, **kwargs: Any
+    ) -> List:  # pragma: no cover
         ...
diff --git a/src/tempor/methods/treatments/one_off/regression/plugin_synctwin_regressor.py b/src/tempor/methods/treatments/one_off/regression/plugin_synctwin_regressor.py
index 4f0c1464..b3089545 100644
--- a/src/tempor/methods/treatments/one_off/regression/plugin_synctwin_regressor.py
+++ b/src/tempor/methods/treatments/one_off/regression/plugin_synctwin_regressor.py
@@ -1,11 +1,12 @@
 import dataclasses
-from typing import List
+from typing import Any, List
 
 from clairvoyance2.treatment_effects.synctwin import SyncTwinRegressor
 
 from tempor.core import plugins
 from tempor.data import dataset, samples
 from tempor.data.clv2conv import tempor_dataset_to_clairvoyance2_dataset
+from tempor.methods.core import Params
 from tempor.methods.core._params import FloatParams, IntegerParams
 from tempor.methods.treatments.one_off._base import BaseOneOffTreatmentEffects
 
@@ -33,7 +34,7 @@ class SyncTwinTreatmentsRegressor(BaseOneOffTreatmentEffects):
 
     def __init__(
         self,
-        **params,
+        **params: Any,
     ) -> None:
         """SyncTwin treatment effects estimation.
 
@@ -61,8 +62,8 @@ def __init__(
     def _fit(
         self,
         data: dataset.BaseDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> "SyncTwinTreatmentsRegressor":  # pyright: ignore
         cl_dataset = tempor_dataset_to_clairvoyance2_dataset(data)
         self.model.fit(cl_dataset)
@@ -72,20 +73,20 @@ def _predict(  # type: ignore[override]  # pylint: disable=arguments-differ
         self,
         data: dataset.PredictiveDataset,
         horizons: List[List[float]],
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> samples.TimeSeriesSamples:
         raise NotImplementedError(
             "SyncTwin implementation does not currently support `predict`, only `predict_counterfactuals`"
         )
 
-    def _predict_counterfactuals(  # type: ignore[override]
+    def _predict_counterfactuals(
         self,
         data: dataset.PredictiveDataset,
         # horizons: SyncTwin can only handle the same time horizon as targets in the data.
         # treatment_scenarios: SyncTwin can only handle the one alternative treatment case.
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> List:
         if self.model is None:
             raise RuntimeError("Fit the model first")
@@ -128,7 +129,7 @@ def _predict_counterfactuals(  # type: ignore[override]
         return counterfactuals
 
     @staticmethod
-    def hyperparameter_space(*args, **kwargs):
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
         return [
             IntegerParams(name="hidden_size", low=10, high=500),
             FloatParams(name="tau", low=0.0, high=2.0),
diff --git a/src/tempor/methods/treatments/temporal/_base.py b/src/tempor/methods/treatments/temporal/_base.py
index 75e977d8..e6711a9a 100644
--- a/src/tempor/methods/treatments/temporal/_base.py
+++ b/src/tempor/methods/treatments/temporal/_base.py
@@ -1,14 +1,15 @@
 import abc
-from typing import List
+from typing import Any, List
 
 import pydantic
 from typing_extensions import Self
 
 import tempor.methods.core as methods_core
+from tempor.core import pydantic_utils
 from tempor.data import dataset, samples
 
 
-def check_data_class(data):
+def check_data_class(data: Any) -> None:
     if not isinstance(data, dataset.TemporalTreatmentEffectsDataset):
         raise TypeError(
             "Expected `data` passed to a temporal treatment effects estimator to be "
@@ -17,40 +18,42 @@ def check_data_class(data):
 
 
 class BaseTemporalTreatmentEffects(methods_core.BasePredictor):
-    def __init__(self, **params) -> None:  # pylint: disable=useless-super-delegation
+    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
         super().__init__(**params)
 
-    def fit(self, data: dataset.BaseDataset, *args, **kwargs) -> Self:
+    def fit(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Self:
         check_data_class(data)
         super().fit(data, *args, **kwargs)
         return self
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def predict(
         self,
         data: dataset.PredictiveDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> samples.TimeSeriesSamples:
         check_data_class(data)
         return super().predict(data, *args, **kwargs)
 
     @abc.abstractmethod
     def _predict(
-        self, data: dataset.PredictiveDataset, *args, **kwargs
+        self, data: dataset.PredictiveDataset, *args: Any, **kwargs: Any
     ) -> samples.TimeSeriesSamples:  # pragma: no cover
         ...
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def predict_counterfactuals(
         self,
         data: dataset.PredictiveDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> List:
         check_data_class(data)
         return super().predict_counterfactuals(data, *args, **kwargs)
 
     @abc.abstractmethod
-    def _predict_counterfactuals(self, data: dataset.PredictiveDataset, *args, **kwargs) -> List:  # pragma: no cover
+    def _predict_counterfactuals(
+        self, data: dataset.PredictiveDataset, *args: Any, **kwargs: Any
+    ) -> List:  # pragma: no cover
         ...
diff --git a/src/tempor/methods/treatments/temporal/classification/plugin_crn_classifier.py b/src/tempor/methods/treatments/temporal/classification/plugin_crn_classifier.py
index 96848d2e..9eb6e02b 100644
--- a/src/tempor/methods/treatments/temporal/classification/plugin_crn_classifier.py
+++ b/src/tempor/methods/treatments/temporal/classification/plugin_crn_classifier.py
@@ -10,6 +10,7 @@
 from tempor.core import plugins
 from tempor.data import dataset, samples
 from tempor.data.clv2conv import _from_clv2_time_series, tempor_dataset_to_clairvoyance2_dataset
+from tempor.methods.core import Params
 from tempor.methods.core._params import CategoricalParams, FloatParams, IntegerParams
 from tempor.methods.treatments.temporal._base import BaseTemporalTreatmentEffects
 
@@ -56,7 +57,7 @@ class CRNTreatmentsClassifier(BaseTemporalTreatmentEffects):
 
     def __init__(
         self,
-        **params,
+        **params: Any,
     ) -> None:
         """Counterfactual Recurrent Network treatment effects model for classification on the outcomes (targets).
 
@@ -82,8 +83,8 @@ def __init__(
     def _fit(
         self,
         data: dataset.BaseDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Self:
         cl_dataset = tempor_dataset_to_clairvoyance2_dataset(data)
 
@@ -98,8 +99,8 @@ def _predict(  # type: ignore[override]  # pylint: disable=arguments-differ
         self,
         data: dataset.PredictiveDataset,
         horizons: List[List[float]],
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> samples.TimeSeriesSamples:
         if self.model is None:
             raise RuntimeError("Fit the model first")
@@ -122,8 +123,8 @@ def _predict_counterfactuals(  # type: ignore[override]  # pylint: disable=argum
         data: dataset.PredictiveDataset,
         horizons: List[List[float]],
         treatment_scenarios: List[List[int]],
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> List:
         if self.model is None:
             raise RuntimeError("Fit the model first")
@@ -164,7 +165,7 @@ def _predict_counterfactuals(  # type: ignore[override]  # pylint: disable=argum
         return counterfactuals
 
     @staticmethod
-    def hyperparameter_space(*args, **kwargs):
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
         return [
             IntegerParams(name="encoder_hidden_size", low=10, high=500),
             IntegerParams(name="encoder_num_layers", low=1, high=10),
diff --git a/src/tempor/methods/treatments/temporal/regression/plugin_crn_regressor.py b/src/tempor/methods/treatments/temporal/regression/plugin_crn_regressor.py
index e41a8ea8..73116a65 100644
--- a/src/tempor/methods/treatments/temporal/regression/plugin_crn_regressor.py
+++ b/src/tempor/methods/treatments/temporal/regression/plugin_crn_regressor.py
@@ -10,6 +10,7 @@
 from tempor.core import plugins
 from tempor.data import dataset, samples
 from tempor.data.clv2conv import _from_clv2_time_series, tempor_dataset_to_clairvoyance2_dataset
+from tempor.methods.core import Params
 from tempor.methods.core._params import CategoricalParams, FloatParams, IntegerParams
 from tempor.methods.treatments.temporal._base import BaseTemporalTreatmentEffects
 
@@ -56,7 +57,7 @@ class CRNTreatmentsRegressor(BaseTemporalTreatmentEffects):
 
     def __init__(
         self,
-        **params,
+        **params: Any,
     ) -> None:
         """Counterfactual Recurrent Network treatment effects model for regression on the outcomes (targets).
 
@@ -86,8 +87,8 @@ def __init__(
     def _fit(
         self,
         data: dataset.BaseDataset,
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> Self:
         cl_dataset = tempor_dataset_to_clairvoyance2_dataset(data)
 
@@ -102,8 +103,8 @@ def _predict(  # type: ignore[override]  # pylint: disable=arguments-differ
         self,
         data: dataset.PredictiveDataset,
         horizons: List[List[float]],
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> samples.TimeSeriesSamples:
         if self.model is None:
             raise RuntimeError("Fit the model first")
@@ -126,8 +127,8 @@ def _predict_counterfactuals(  # type: ignore[override]  # pylint: disable=argum
         data: dataset.PredictiveDataset,
         horizons: List[List[float]],
         treatment_scenarios: List[List[int]],
-        *args,
-        **kwargs,
+        *args: Any,
+        **kwargs: Any,
     ) -> List:
         if self.model is None:
             raise RuntimeError("Fit the model first")
@@ -168,7 +169,7 @@ def _predict_counterfactuals(  # type: ignore[override]  # pylint: disable=argum
         return counterfactuals
 
     @staticmethod
-    def hyperparameter_space(*args, **kwargs):
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
         return [
             IntegerParams(name="encoder_hidden_size", low=10, high=500),
             IntegerParams(name="encoder_num_layers", low=1, high=10),
diff --git a/src/tempor/models/ddh.py b/src/tempor/models/ddh.py
index 2d46a5ca..06e2393b 100644
--- a/src/tempor/models/ddh.py
+++ b/src/tempor/models/ddh.py
@@ -161,10 +161,10 @@ def fit(
 
                 optimizer.zero_grad()
                 loss = self.total_loss(xb, tb, eb)
-                loss.backward()
+                loss.backward()  # type: ignore [no-untyped-call]
 
                 if self.clipping_value > 0:
-                    torch.nn.utils.clip_grad_norm_(  # pyright: ignore [reportPrivateImportUsage]
+                    torch.nn.utils.clip_grad_norm_(  # type: ignore [attr-defined] # pyright: ignore
                         self.model.parameters(),
                         self.clipping_value,
                     )
@@ -454,9 +454,15 @@ def __init__(
         # RNN model for longitudinal data
         self.embedding: nn.Module
         if self.rnn_type == "LSTM":
-            self.embedding = nn.LSTM(input_dim, hidden_rnn, layers_rnn, bias=False, batch_first=True)
+            self.embedding = nn.LSTM(  # type: ignore [no-untyped-call]
+                input_dim,
+                hidden_rnn,
+                layers_rnn,
+                bias=False,
+                batch_first=True,
+            )
         elif self.rnn_type == "RNN":
-            self.embedding = nn.RNN(
+            self.embedding = nn.RNN(  # type: ignore [no-untyped-call]
                 input_dim,
                 hidden_rnn,
                 layers_rnn,
@@ -465,9 +471,20 @@ def __init__(
                 nonlinearity="relu",
             )
         elif self.rnn_type == "GRU":
-            self.embedding = nn.GRU(input_dim, hidden_rnn, layers_rnn, bias=False, batch_first=True)
+            self.embedding = nn.GRU(  # type: ignore [no-untyped-call]
+                input_dim,
+                hidden_rnn,
+                layers_rnn,
+                bias=False,
+                batch_first=True,
+            )
         elif self.rnn_type == "Transformer":
-            self.embedding = TransformerModel(input_dim, hidden_rnn, n_layers_hidden=layers_rnn, dropout=dropout)
+            self.embedding = TransformerModel(
+                input_dim,
+                hidden_rnn,
+                n_layers_hidden=layers_rnn,
+                dropout=dropout,
+            )
         else:
             raise RuntimeError(f"Unknown rnn_type {rnn_type}")
 
diff --git a/src/tempor/models/mlp.py b/src/tempor/models/mlp.py
index 3f3537c8..4bfbba2d 100644
--- a/src/tempor/models/mlp.py
+++ b/src/tempor/models/mlp.py
@@ -6,6 +6,7 @@
 import torch.utils.data
 from torch import nn
 
+from tempor.core import pydantic_utils
 from tempor.log import logger
 from tempor.models import constants, utils
 
@@ -14,7 +15,7 @@
 
 
 class LinearLayer(nn.Module):
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def __init__(
         self,
         n_units_in: int,
@@ -41,13 +42,13 @@ def __init__(
 
         self.model = nn.Sequential(*layers).to(self.device)
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def forward(self, X: torch.Tensor) -> torch.Tensor:
         return self.model(X.float()).to(self.device)  # pylint: disable=not-callable
 
 
 class ResidualLayer(LinearLayer):
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def __init__(
         self,
         n_units_in: int,
@@ -68,7 +69,7 @@ def __init__(
         self.device = device
         self.n_units_out = n_units_out
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def forward(self, X: torch.Tensor) -> torch.Tensor:
         if X.shape[-1] == 0:
             return torch.zeros((*X.shape[:-1], self.n_units_out)).to(self.device)
@@ -94,7 +95,7 @@ def __init__(
             self.activations.append(activation)
             self.activation_lengths.append(length)
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def forward(self, X: torch.Tensor) -> torch.Tensor:
         if X.shape[-1] != np.sum(self.activation_lengths):
             raise RuntimeError(
@@ -113,7 +114,7 @@ def forward(self, X: torch.Tensor) -> torch.Tensor:
 
 
 class MLP(nn.Module):
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def __init__(
         self,
         task_type: constants.ModelTaskType,
@@ -300,7 +301,7 @@ def fit(self, X: np.ndarray, y: np.ndarray) -> "MLP":
 
         return self
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def predict_proba(self, X: np.ndarray) -> np.ndarray:
         if self.task_type != "classification":
             raise ValueError(f"Invalid task type for predict_proba {self.task_type}")
@@ -312,7 +313,7 @@ def predict_proba(self, X: np.ndarray) -> np.ndarray:
 
             return yt.cpu().numpy().squeeze()
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def predict(self, X: np.ndarray) -> np.ndarray:
         with torch.no_grad():
             Xt = self._check_tensor(X)
@@ -331,7 +332,7 @@ def score(self, X: np.ndarray, y: np.ndarray) -> float:
         else:
             return np.mean(np.inner(y - y_pred, y - y_pred) / 2.0)
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def forward(self, X: torch.Tensor) -> torch.Tensor:
         return self.model(X.float())  # pylint: disable=not-callable
 
@@ -352,7 +353,7 @@ def _train_epoch(self, loader: torch.utils.data.DataLoader) -> float:
             batch_loss.backward()
 
             if self.clipping_value > 0:
-                torch.nn.utils.clip_grad_norm_(  # pyright: ignore [reportPrivateImportUsage]
+                torch.nn.utils.clip_grad_norm_(  # type: ignore [attr-defined] # pyright: ignore
                     self.parameters(),
                     self.clipping_value,
                 )
diff --git a/src/tempor/models/samplers.py b/src/tempor/models/samplers.py
index 372e0766..52010734 100644
--- a/src/tempor/models/samplers.py
+++ b/src/tempor/models/samplers.py
@@ -7,6 +7,8 @@
 import torch.utils.data.sampler
 from sklearn.model_selection import train_test_split
 
+from tempor.core import pydantic_utils
+
 
 class BaseSampler(torch.utils.data.sampler.Sampler):
     """DataSampler samples the conditional vector and corresponding data."""
@@ -14,13 +16,13 @@ class BaseSampler(torch.utils.data.sampler.Sampler):
     def get_dataset_conditionals(self) -> Optional[np.ndarray]:  # pragma: no cover
         return None
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def sample_conditional(
         self, batch: int, **kwargs: Any  # pylint: disable=unused-argument
     ) -> Optional[Tuple]:  # pragma: no cover
         return None
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def sample_conditional_for_class(
         self, batch: int, c: int  # pylint: disable=unused-argument
     ) -> Optional[np.ndarray]:  # pragma: no cover
@@ -41,7 +43,7 @@ def train_test(self) -> Tuple:  # pragma: no cover
 class ImbalancedDatasetSampler(BaseSampler):
     """Samples elements randomly from a given list of indices for imbalanced dataset."""
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def __init__(self, labels: List, train_size: float = 0.8) -> None:
         super().__init__(None)
 
diff --git a/src/tempor/models/transformer.py b/src/tempor/models/transformer.py
index 552ef6f6..93a1e0d7 100644
--- a/src/tempor/models/transformer.py
+++ b/src/tempor/models/transformer.py
@@ -71,7 +71,11 @@ def __init__(
             activation=activation,
         )
         encoder_norm = nn.LayerNorm(n_units_hidden)
-        self.transformer_encoder = TransformerEncoder(encoder_layer, n_layers_hidden, norm=encoder_norm)
+        self.transformer_encoder = TransformerEncoder(  # type: ignore [no-untyped-call]
+            encoder_layer,
+            n_layers_hidden,
+            norm=encoder_norm,
+        )
 
         self.model = nn.Sequential(
             Permute(1, 0, 2),  # bs x seq_len x nvars -> seq_len x bs x nvars
diff --git a/src/tempor/models/ts_model.py b/src/tempor/models/ts_model.py
index e3bfaf79..8e8b9ae7 100644
--- a/src/tempor/models/ts_model.py
+++ b/src/tempor/models/ts_model.py
@@ -17,6 +17,7 @@
 from tsai.models.XCM import XCM
 from typing_extensions import Literal
 
+from tempor.core import pydantic_utils
 from tempor.log import logger as log
 from tempor.models import constants
 from tempor.models.constants import DEVICE, ModelTaskType, Nonlin
@@ -41,7 +42,7 @@
 
 
 class TimeSeriesModel(nn.Module):
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def __init__(
         self,
         task_type: ModelTaskType,
@@ -219,7 +220,7 @@ def __init__(
             weight_decay=weight_decay,
         )  # optimize all rnn parameters
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def forward(
         self,
         static_data: torch.Tensor,
@@ -254,7 +255,7 @@ def forward(
 
         return pred
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def predict(
         self,
         static_data: Union[List, np.ndarray],
@@ -286,7 +287,7 @@ def predict(
             else:
                 return yt.cpu().numpy()
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def predict_proba(
         self,
         static_data: Union[List, np.ndarray],
@@ -330,7 +331,7 @@ def score(
         else:
             return np.mean(np.inner(outcome - y_pred, outcome - y_pred) / 2.0)
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def fit(
         self,
         static_data: Union[List, np.ndarray],
@@ -348,7 +349,7 @@ def fit(
 
         return self._train(static_data_t, temporal_data_t, observation_times_t, outcome_t)
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def _train(
         self,
         static_data: List[torch.Tensor],
@@ -403,7 +404,10 @@ def _train_epoch(self, loaders: List[DataLoader]) -> float:
 
                 loss.backward()  # backpropagation, compute gradients
                 if self.clipping_value > 0:
-                    torch.nn.utils.clip_grad_norm_(self.parameters(), self.clipping_value)  # pyright: ignore
+                    torch.nn.utils.clip_grad_norm_(  # type: ignore [attr-defined] # pyright: ignore
+                        self.parameters(),
+                        self.clipping_value,
+                    )
                 self.optimizer.step()  # apply gradients
 
                 losses.append(loss.detach().cpu())
@@ -437,7 +441,7 @@ def dataloader(
         if out_counts.min() > 1:
             stratify = outcome.cpu()
 
-        split: Tuple[torch.Tensor, ...] = train_test_split(  # type: ignore
+        split: Tuple[torch.Tensor, ...] = train_test_split(  # pyright: ignore
             static_data.cpu(),
             temporal_data.cpu(),
             observation_times.cpu(),
@@ -580,7 +584,7 @@ def __init__(
         }
 
         if mode in ["RNN", "LSTM", "GRU"]:
-            self.temporal_layer = temporal_models[mode](**temporal_params)
+            self.temporal_layer = temporal_models[mode](**temporal_params)  # type: ignore [no-untyped-call]
         elif mode == "MLSTM_FCN":
             self.temporal_layer = MLSTM_FCN(
                 c_in=n_temporal_units_in,
@@ -691,7 +695,7 @@ def forward(self, static_data: torch.Tensor, temporal_data: torch.Tensor) -> tor
 
 
 class WindowLinearLayer(nn.Module):
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def __init__(
         self,
         n_static_units_in: int,
@@ -720,7 +724,7 @@ def __init__(
             device=device,
         )
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def forward(self, static_data: torch.Tensor, temporal_data: torch.Tensor) -> torch.Tensor:
         if self.n_static_units_in > 0 and len(static_data) != len(temporal_data):
             raise ValueError("Length mismatch between static and temporal data")
diff --git a/src/tempor/models/ts_ode.py b/src/tempor/models/ts_ode.py
index 593e7efe..d7013956 100644
--- a/src/tempor/models/ts_ode.py
+++ b/src/tempor/models/ts_ode.py
@@ -12,6 +12,7 @@
 from torch.utils.data import DataLoader, TensorDataset, sampler
 from typing_extensions import Literal
 
+from tempor.core import pydantic_utils
 from tempor.log import logger as log
 from tempor.models.constants import DEVICE, ModelTaskType, Nonlin, ODEBackend
 from tempor.models.mlp import MLP
@@ -66,7 +67,7 @@ def __init__(
             nonlin_out=[("tanh", n_units_out)],
         )
 
-    def forward(self, t, z):  # pylint: disable=unused-argument
+    def forward(self, t: torch.Tensor, z: torch.Tensor) -> torch.Tensor:  # pylint: disable=unused-argument
         z = self.model(z)
 
         z = z.view(*z.shape[:-1], self.n_units_hidden, self.n_units_in)
@@ -112,7 +113,7 @@ def __init__(
             nonlin_out=[("tanh", n_units_hidden)],
         )
 
-    def forward(self, t, z):  # pylint: disable=unused-argument
+    def forward(self, t: torch.Tensor, z: torch.Tensor) -> torch.Tensor:  # pylint: disable=unused-argument
         return self.model(z)
 
 
@@ -127,11 +128,11 @@ def __init__(
         device: Any = DEVICE,
     ):
         super(ReverseGRUEncoder, self).__init__()
-        self.gru = nn.GRU(n_units_in, n_units_hidden, 2, batch_first=True)
+        self.gru = nn.GRU(n_units_in, n_units_hidden, 2, batch_first=True)  # type: ignore [no-untyped-call]
         self.linear_out = nn.Linear(n_units_hidden, n_units_latent).to(device)
         nn.init.xavier_uniform_(self.linear_out.weight)
 
-    def forward(self, observed_data: torch.Tensor):
+    def forward(self, observed_data: torch.Tensor) -> torch.Tensor:
         trajs_to_encode = observed_data  # (batch_size, t_observed_dim, observed_dim)
         reversed_trajs_to_encode = torch.flip(trajs_to_encode, (1,))
         out, _ = self.gru(reversed_trajs_to_encode)
@@ -145,12 +146,12 @@ class LaplaceFunc(nn.Module):
 
     def __init__(
         self,
-        s_dim,
-        n_units_out,
-        n_units_latent,
-        n_units_hidden=64,
+        s_dim: int,
+        n_units_out: int,
+        n_units_latent: int,
+        n_units_hidden: int = 64,
         device: Any = DEVICE,
-    ):
+    ) -> None:
         super(LaplaceFunc, self).__init__()
         self.s_dim = s_dim
         self.n_units_out = n_units_out
@@ -172,7 +173,7 @@ def __init__(
 
         self.to(device)
 
-    def forward(self, i):
+    def forward(self, i: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
         out = self.linear_tanh_stack(i.view(-1, self.s_dim * 2 + self.n_units_latent)).view(
             -1, 2 * self.n_units_out, self.s_dim
         )
@@ -458,7 +459,7 @@ def forward(
                 atol=self.atol,
                 rtol=self.rtol,
             )
-            z_T = z_T[1]
+            z_T = z_T[1]  # pyright: ignore
             z_T = z_T[:, -1, :]  # pyright: ignore  # Last time point.
         elif self.backend == "laplace":
             X_emb = self.initial_temporal(temporal_data_ext)
@@ -481,7 +482,7 @@ def forward(
         out = self.output(z_T)
         return out.reshape(-1, *self.output_shape)
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def predict(
         self,
         static_data: Union[List, np.ndarray, torch.Tensor],
@@ -513,7 +514,7 @@ def predict(
             else:
                 return yt.cpu().numpy()
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def predict_proba(
         self,
         static_data: Union[List, np.ndarray, torch.Tensor],
@@ -558,7 +559,7 @@ def score(
         else:
             return np.mean(np.inner(outcome - y_pred, outcome - y_pred) / 2.0)
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def fit(
         self,
         static_data: Union[List, np.ndarray, torch.Tensor],
@@ -576,7 +577,7 @@ def fit(
 
         return self._train(static_data_t, temporal_data_t, observation_times_t, outcome_t)
 
-    @pydantic.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))  # type: ignore [operator]
+    @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def _train(
         self,
         static_data: List[torch.Tensor],
@@ -634,7 +635,10 @@ def _train_epoch(self, loaders: List[DataLoader]) -> float:
 
                 loss.backward()  # backpropagation, compute gradients
                 if self.clipping_value > 0:
-                    torch.nn.utils.clip_grad_norm_(self.parameters(), self.clipping_value)  # pyright: ignore
+                    torch.nn.utils.clip_grad_norm_(  # type: ignore [attr-defined] # pyright: ignore
+                        self.parameters(),
+                        self.clipping_value,
+                    )
                 self.optimizer.step()  # apply gradients
 
                 if torch.isnan(loss):  # pragma: no cover
@@ -673,7 +677,7 @@ def dataloader(
         if out_counts.min() > 1:
             stratify = outcome.cpu()
 
-        split: Tuple[torch.Tensor, ...] = train_test_split(  # type: ignore
+        split: Tuple[torch.Tensor, ...] = train_test_split(  # pyright: ignore
             static_data.cpu(),
             temporal_data.cpu(),
             observation_times.cpu(),
diff --git a/src/tempor/models/utils.py b/src/tempor/models/utils.py
index 38d7a768..e8019615 100644
--- a/src/tempor/models/utils.py
+++ b/src/tempor/models/utils.py
@@ -1,7 +1,7 @@
 import os
 import random
 import warnings
-from typing import Dict, Type, Union
+from typing import Any, Dict, Type, Union
 
 import numpy as np
 import torch
@@ -58,7 +58,7 @@ def enable_reproducibility(
     torch.cuda.manual_seed_all(random_seed)
     # If enabled, force deterministic algorithms:
     if torch_use_deterministic_algorithms:
-        torch.use_deterministic_algorithms(True)
+        torch.use_deterministic_algorithms(True)  # type: ignore [no-untyped-call]
         if warn_cuda_env_vars and torch.version.cuda not in ("", None):
             cuda_version = Version(torch.version.cuda)
             if (cuda_version.major, cuda_version.minor) == (10, 1) and os.environ.get(
@@ -131,7 +131,7 @@ def get_nonlin(name: Nonlin) -> nn.Module:
 tempor.core.utils.ensure_literal_matches_dict_keys(Samp, SAMPLER_MAP, "Samp", "SAMPLER_MAP")
 
 
-def get_sampler(name: Union[Samp, None], **kwargs) -> Union[torch.utils.data.sampler.Sampler, None]:
+def get_sampler(name: Union[Samp, None], **kwargs: Any) -> Union[torch.utils.data.sampler.Sampler, None]:
     try:
         return SAMPLER_MAP[name](**kwargs) if name is not None else None
     except KeyError as e:
diff --git a/tutorials/extending/tutorial01_custom_method.ipynb b/tutorials/extending/tutorial01_custom_method.ipynb
index ea29099c..5ace63fe 100644
--- a/tutorials/extending/tutorial01_custom_method.ipynb
+++ b/tutorials/extending/tutorial01_custom_method.ipynb
@@ -323,21 +323,21 @@
     "\n",
     "class MyPlugin(BaseOneOffClassifier):\n",
     "    # The initializer:\n",
-    "    def __init__(self, **params) -> None:\n",
+    "    def __init__(self, **params: Any) -> None:\n",
     "        ...\n",
     "\n",
     "    # The _fit implementation.\n",
     "    def _fit(self, data: dataset.BaseDataset, *args, **kwargs):\n",
     "        ...\n",
     "\n",
-    "    def _predict(self, data: dataset.PredictiveDataset, *args, **kwargs) -> samples.StaticSamples:\n",
+    "    def _predict(self, data: dataset.PredictiveDataset, *args: Any, **kwargs: Any) -> samples.StaticSamples:\n",
     "        ...\n",
     "\n",
-    "    def _predict_proba(self, data: dataset.PredictiveDataset, *args, **kwargs) -> samples.StaticSamples:\n",
+    "    def _predict_proba(self, data: dataset.PredictiveDataset, *args: Any, **kwargs: Any) -> samples.StaticSamples:\n",
     "        ...\n",
     "    \n",
     "    @staticmethod\n",
-    "    def hyperparameter_space(*args, **kwargs):\n",
+    "    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:\n",
     "        # This method is not currently used in TemporAI (it will be used once AutoML component is implemented).\n",
     "        # For now, you may just return an empty list.\n",
     "        ...\n",
@@ -391,7 +391,7 @@
     "    # 2. Set the `ParamsDefinition` class variable in your plugin to this dataclass.\n",
     "    ParamsDefinition = MyPluginParams\n",
     "    \n",
-    "    def __init__(self, **params) -> None:\n",
+    "    def __init__(self, **params: Any) -> None:\n",
     "        # 3. Call the parent __init__ as so.\n",
     "        super().__init__(**params)\n",
     "\n",
@@ -422,10 +422,12 @@
    "outputs": [],
    "source": [
     "import dataclasses\n",
+    "from typing import Any, List\n",
     "\n",
     "import numpy as np\n",
     "\n",
     "from tempor.core.plugins import register_plugin\n",
+    "from tempor.methods.core import Params\n",
     "from tempor.data import dataset, samples\n",
     "from tempor.methods.prediction.one_off.classification import BaseOneOffClassifier\n",
     "\n",
@@ -447,7 +449,7 @@
     "        \"\"\"Does nothing.\"\"\"\n",
     "        return self  # Fit method needs to return `self`.\n",
     "\n",
-    "    def _predict(self, data: dataset.PredictiveDataset, *args, **kwargs) -> samples.StaticSamples:\n",
+    "    def _predict(self, data: dataset.PredictiveDataset, *args: Any, **kwargs: Any) -> samples.StaticSamples:\n",
     "        \"\"\"Always returns 1\"\"\"\n",
     "\n",
     "        assert data.predictive.targets is not None\n",
@@ -455,7 +457,7 @@
     "\n",
     "        return samples.StaticSamples.from_numpy(preds, dtype=int)\n",
     "\n",
-    "    def _predict_proba(self, data: dataset.PredictiveDataset, *args, **kwargs) -> samples.StaticSamples:\n",
+    "    def _predict_proba(self, data: dataset.PredictiveDataset, *args: Any, **kwargs: Any) -> samples.StaticSamples:\n",
     "        \"\"\"Always returns 1.0\"\"\"\n",
     "\n",
     "        assert data.predictive.targets is not None\n",
@@ -464,7 +466,7 @@
     "        return samples.StaticSamples.from_numpy(preds, dtype=float)\n",
     "\n",
     "    @staticmethod\n",
-    "    def hyperparameter_space(*args, **kwargs):\n",
+    "    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:\n",
     "        return []"
    ]
   },
diff --git a/tutorials/extending/tutorial03_custom_datasource.ipynb b/tutorials/extending/tutorial03_custom_datasource.ipynb
index 790e9323..4bedfb51 100644
--- a/tutorials/extending/tutorial03_custom_datasource.ipynb
+++ b/tutorials/extending/tutorial03_custom_datasource.ipynb
@@ -253,6 +253,8 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "from typing import Any\n",
+    "\n",
     "import numpy as np\n",
     "\n",
     "from tempor.data.dataset import OneOffPredictionDataset\n",
@@ -262,7 +264,7 @@
     "\n",
     "@register_plugin(name=\"my_datasource\", category=\"prediction.one_off\", plugin_type=\"datasource\")\n",
     "class MyDataSource(OneOffPredictionDataSource):\n",
-    "    def __init__(self, random_seed: int = 123, **kwargs) -> None:\n",
+    "    def __init__(self, random_seed: int = 123, **kwargs: Any) -> None:\n",
     "        super().__init__(**kwargs)\n",
     "        self.random_seed = random_seed\n",
     "\n",


Test patch:
diff --git a/tests/methods/prediction/one_off/classification/test_prediction_oneoff_classification_base.py b/tests/methods/prediction/one_off/classification/test_prediction_oneoff_classification_base.py
index 430a9511..49b00a65 100644
--- a/tests/methods/prediction/one_off/classification/test_prediction_oneoff_classification_base.py
+++ b/tests/methods/prediction/one_off/classification/test_prediction_oneoff_classification_base.py
@@ -8,7 +8,7 @@
 
 if TYPE_CHECKING:
     from typing_extensions import Self
-    from tempor.methods.core._params import Params
+    from tempor.methods.core import Params
     from tempor.data.dataset import BaseDataset
     from tempor.data.samples import StaticSamples
 
diff --git a/tests/methods/prediction/one_off/regression/test_prediction_oneoff_regression_base.py b/tests/methods/prediction/one_off/regression/test_prediction_oneoff_regression_base.py
index dda49c0f..d9a0dae4 100644
--- a/tests/methods/prediction/one_off/regression/test_prediction_oneoff_regression_base.py
+++ b/tests/methods/prediction/one_off/regression/test_prediction_oneoff_regression_base.py
@@ -8,7 +8,7 @@
 
 if TYPE_CHECKING:
     from typing_extensions import Self
-    from tempor.methods.core._params import Params
+    from tempor.methods.core import Params
     from tempor.data.dataset import BaseDataset
     from tempor.data.samples import StaticSamples
 
diff --git a/tests/methods/prediction/temporal/classification/test_prediction_temporal_classification_base.py b/tests/methods/prediction/temporal/classification/test_prediction_temporal_classification_base.py
index 1d524133..16247810 100644
--- a/tests/methods/prediction/temporal/classification/test_prediction_temporal_classification_base.py
+++ b/tests/methods/prediction/temporal/classification/test_prediction_temporal_classification_base.py
@@ -7,7 +7,7 @@
 
 if TYPE_CHECKING:
     from typing_extensions import Self
-    from tempor.methods.core._params import Params
+    from tempor.methods.core import Params
     from tempor.data.dataset import BaseDataset, PredictiveDataset
     from tempor.data.samples import TimeSeriesSamples
 
diff --git a/tests/methods/prediction/temporal/regression/test_prediction_temporal_regression_base.py b/tests/methods/prediction/temporal/regression/test_prediction_temporal_regression_base.py
index 143e016b..8f78132e 100644
--- a/tests/methods/prediction/temporal/regression/test_prediction_temporal_regression_base.py
+++ b/tests/methods/prediction/temporal/regression/test_prediction_temporal_regression_base.py
@@ -6,7 +6,7 @@
 
 if TYPE_CHECKING:
     from typing_extensions import Self
-    from tempor.methods.core._params import Params
+    from tempor.methods.core import Params
     from tempor.data.dataset import BaseDataset, PredictiveDataset
     from tempor.data.samples import TimeSeriesSamples
 
diff --git a/tests/methods/preprocessing/encoding/conftest.py b/tests/methods/preprocessing/encoding/conftest.py
index 1c9c479f..a5a87505 100644
--- a/tests/methods/preprocessing/encoding/conftest.py
+++ b/tests/methods/preprocessing/encoding/conftest.py
@@ -21,11 +21,11 @@ def _dummy_data_with_categorical_features_full() -> dataset.OneOffPredictionData
     ).load()
 
     # Add static categorical features.
-    static_df = data.static.dataframe()
+    static_df = data.static.dataframe()  # pyright: ignore
     np.random.seed(777)
     cat1 = pd.Categorical(np.random.choice(["a", "b", "c"], size=(len(static_df),)))
     cat2 = pd.Categorical(np.random.choice(["D", "E"], size=(len(static_df),)))
-    static_df.insert(1, "categorical_feat_1", cat1)
+    static_df.insert(1, "categorical_feat_1", cat1)  # pyright: ignore
     static_df["categorical_feat_2"] = cat2
     data.static = samples.StaticSamples.from_dataframe(static_df)
 
@@ -34,11 +34,11 @@ def _dummy_data_with_categorical_features_full() -> dataset.OneOffPredictionData
     np.random.seed(111)
     cat1 = pd.Categorical(np.random.choice(["p", "q", "r"], size=(len(ts_df),)))
     cat2 = pd.Categorical(np.random.choice(["S", "T"], size=(len(ts_df),)))
-    ts_df.insert(1, "categorical_feat_1", cat1)
+    ts_df.insert(1, "categorical_feat_1", cat1)  # pyright: ignore
     ts_df["categorical_feat_2"] = cat2
     data.time_series = samples.TimeSeriesSamples.from_dataframe(ts_df)
 
-    return data
+    return data  # pyright: ignore
 
 
 @pytest.fixture(scope="function")
diff --git a/tests/methods/time_to_event/test_time_to_event_base.py b/tests/methods/time_to_event/test_time_to_event_base.py
index 7a4276cf..3fc9fe3f 100644
--- a/tests/methods/time_to_event/test_time_to_event_base.py
+++ b/tests/methods/time_to_event/test_time_to_event_base.py
@@ -8,7 +8,7 @@
 
 if TYPE_CHECKING:
     from typing_extensions import Self
-    from tempor.methods.core._params import Params
+    from tempor.methods.core import Params
     from tempor.data.dataset import BaseDataset, PredictiveDataset
     from tempor.data.samples import TimeSeriesSamples
 
diff --git a/tests/methods/treatments/one_off/test_treatments_oneoff_base.py b/tests/methods/treatments/one_off/test_treatments_oneoff_base.py
index e1f8d1c0..651ca7cf 100644
--- a/tests/methods/treatments/one_off/test_treatments_oneoff_base.py
+++ b/tests/methods/treatments/one_off/test_treatments_oneoff_base.py
@@ -7,7 +7,7 @@
 
 if TYPE_CHECKING:
     from typing_extensions import Self
-    from tempor.methods.core._params import Params
+    from tempor.methods.core import Params
     from tempor.data.dataset import BaseDataset, PredictiveDataset
     from tempor.data.samples import TimeSeriesSamples
 
diff --git a/tests/methods/treatments/temporal/test_treatments_temporal_base.py b/tests/methods/treatments/temporal/test_treatments_temporal_base.py
index 6296052b..a84621bf 100644
--- a/tests/methods/treatments/temporal/test_treatments_temporal_base.py
+++ b/tests/methods/treatments/temporal/test_treatments_temporal_base.py
@@ -6,7 +6,7 @@
 
 if TYPE_CHECKING:
     from typing_extensions import Self
-    from tempor.methods.core._params import Params
+    from tempor.methods.core import Params
     from tempor.data.dataset import BaseDataset, PredictiveDataset
     from tempor.data.samples import TimeSeriesSamples