From 557c60fc8fb9e65522621b1127603bde2bf3b5f5 Mon Sep 17 00:00:00 2001 From: Andrew Mirsky Date: Thu, 3 Jul 2025 11:13:08 -0400 Subject: [PATCH 1/2] to improve mypy static checking, add instance variable to BasePlugin for the plugin's configuration, cast to the appropriate type --- amqtt/plugins/base.py | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/amqtt/plugins/base.py b/amqtt/plugins/base.py index d9abcfc..f09cd91 100644 --- a/amqtt/plugins/base.py +++ b/amqtt/plugins/base.py @@ -1,5 +1,5 @@ from dataclasses import dataclass, is_dataclass -from typing import Any, Generic, TypeVar +from typing import Any, Generic, TypeVar, cast from amqtt.contexts import Action, BaseContext from amqtt.session import Session @@ -8,28 +8,49 @@ C = TypeVar("C", bound=BaseContext) class BasePlugin(Generic[C]): - """The base from which all plugins should inherit.""" + """The base from which all plugins should inherit. + Type Parameters + --------------- + C: + A BaseContext: either BrokerContext or ClientContext, depending on plugin usage + + Attributes + ---------- + context (C): + Information about the environment in which this plugin is executed. Modifying + the broker or client state should happen through methods available here. + + config (self.Config): + An instance of the Config dataclass defined by the plugin (or an empty dataclass, if not + defined). If using entrypoint- or mixed-style configuration, use `_get_config_option()` + to access the variable. + + """ def __init__(self, context: C) -> None: self.context: C = context + # since the PluginManager will hydrate the config from a plugin's `Config` class, this is a safe cast + self.config = cast("self.Config", context.config) # inner class, so type: ignore[name-defined] + # Deprecated: included to support entrypoint-style configs. Replaced by dataclass Config class. def _get_config_section(self, name: str) -> dict[str, Any] | None: if not self.context.config or not hasattr(self.context.config, "get") or not self.context.config.get(name, None): return None section_config: int | dict[str, Any] | None = self.context.config.get(name, None) - # mypy has difficulty excluding int from `config`'s type, unless isinstance` is its own check + # mypy has difficulty excluding int from `config`'s type, unless there's an explicit check if isinstance(section_config, int): return None return section_config + # Deprecated : supports entrypoint-style configs as well as dataclass configuration. def _get_config_option(self, option_name: str, default: Any=None) -> Any: if not self.context.config: return default if is_dataclass(self.context.config): - return getattr(self.context.config, option_name.replace("-", "_"), default) # type: ignore[unreachable] + return getattr(self.context.config, option_name.replace("-", "_"), default) if option_name in self.context.config: return self.context.config[option_name] return default @@ -57,7 +78,7 @@ class BaseTopicPlugin(BasePlugin[BaseContext]): return default if is_dataclass(self.context.config): - return getattr(self.context.config, option_name.replace("-", "_"), default) # type: ignore[unreachable] + return getattr(self.context.config, option_name.replace("-", "_"), default) if self.topic_config and option_name in self.topic_config: return self.topic_config[option_name] return default @@ -87,7 +108,7 @@ class BaseAuthPlugin(BasePlugin[BaseContext]): return default if is_dataclass(self.context.config): - return getattr(self.context.config, option_name.replace("-", "_"), default) # type: ignore[unreachable] + return getattr(self.context.config, option_name.replace("-", "_"), default) if self.auth_config and option_name in self.auth_config: return self.auth_config[option_name] return default From f5320b9b88478c79c6f76335ec213ecb64d240c2 Mon Sep 17 00:00:00 2001 From: Andrew Mirsky Date: Thu, 3 Jul 2025 11:32:54 -0400 Subject: [PATCH 2/2] linting corrections --- amqtt/plugins/base.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/amqtt/plugins/base.py b/amqtt/plugins/base.py index f09cd91..41b951b 100644 --- a/amqtt/plugins/base.py +++ b/amqtt/plugins/base.py @@ -9,6 +9,7 @@ C = TypeVar("C", bound=BaseContext) class BasePlugin(Generic[C]): """The base from which all plugins should inherit. + Type Parameters --------------- C: @@ -30,7 +31,7 @@ class BasePlugin(Generic[C]): def __init__(self, context: C) -> None: self.context: C = context # since the PluginManager will hydrate the config from a plugin's `Config` class, this is a safe cast - self.config = cast("self.Config", context.config) # inner class, so type: ignore[name-defined] + self.config = cast("self.Config", context.config) # type: ignore[name-defined] # Deprecated: included to support entrypoint-style configs. Replaced by dataclass Config class. def _get_config_section(self, name: str) -> dict[str, Any] | None: @@ -50,7 +51,8 @@ class BasePlugin(Generic[C]): return default if is_dataclass(self.context.config): - return getattr(self.context.config, option_name.replace("-", "_"), default) + # overloaded context.config for BasePlugin `Config` class, so ignoring static type check + return getattr(self.context.config, option_name.replace("-", "_"), default) # type: ignore[unreachable] if option_name in self.context.config: return self.context.config[option_name] return default @@ -78,7 +80,8 @@ class BaseTopicPlugin(BasePlugin[BaseContext]): return default if is_dataclass(self.context.config): - return getattr(self.context.config, option_name.replace("-", "_"), default) + # overloaded context.config for BasePlugin `Config` class, so ignoring static type check + return getattr(self.context.config, option_name.replace("-", "_"), default) # type: ignore[unreachable] if self.topic_config and option_name in self.topic_config: return self.topic_config[option_name] return default @@ -108,7 +111,8 @@ class BaseAuthPlugin(BasePlugin[BaseContext]): return default if is_dataclass(self.context.config): - return getattr(self.context.config, option_name.replace("-", "_"), default) + # overloaded context.config for BasePlugin `Config` class, so ignoring static type check + return getattr(self.context.config, option_name.replace("-", "_"), default) # type: ignore[unreachable] if self.auth_config and option_name in self.auth_config: return self.auth_config[option_name] return default