From f75e810f7d0e669db59a11ecfa959fce622b6856 Mon Sep 17 00:00:00 2001 From: BJ Dierkes Date: Sun, 23 Jun 2024 22:44:18 -0500 Subject: [PATCH] Type Annotations: core.extension, core.handler - Resolves Issue #698 - Resolves Issue #700 - Related to PR #628 --- CHANGELOG.md | 2 ++ cement/core/extension.py | 32 ++++++++++-------- cement/core/handler.py | 71 ++++++++++++++++++++++++---------------- 3 files changed, 64 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24e5e575..c7339397 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ Refactoring: - `[core.controller]` [Issue #695](https://github.com/datafolklabs/cement/issues/695) - `[core.deprecations]` [Issue #696](https://github.com/datafolklabs/cement/issues/696) - `[core.exc]` [Issue #697](https://github.com/datafolklabs/cement/issues/697) + - `[core.extension]` [Issue #698](https://github.com/datafolklabs/cement/issues/698) + - `[core.handler]` [Issue #700](https://github.com/datafolklabs/cement/issues/700) - `[core.interface]` [Issue #702](https://github.com/datafolklabs/cement/issues/702) - `[core.meta]` [Issue #705](https://github.com/datafolklabs/cement/issues/705) - `[utils.fs]` [Issue #688](https://github.com/datafolklabs/cement/issues/688) diff --git a/cement/core/extension.py b/cement/core/extension.py index f587bdcd..475c6c03 100644 --- a/cement/core/extension.py +++ b/cement/core/extension.py @@ -1,7 +1,9 @@ """Cement core extensions module.""" +from __future__ import annotations import sys from abc import abstractmethod +from typing import Any, List, TYPE_CHECKING from ..core import exc from ..core.interface import Interface from ..core.handler import Handler @@ -10,6 +12,10 @@ from ..utils.misc import minimal_logger LOG = minimal_logger(__name__) +if TYPE_CHECKING: + from ..core.foundation import App # pragma: nocover + + class ExtensionInterface(Interface): """ @@ -19,15 +25,15 @@ class ExtensionInterface(Interface): :class:`ExtensionHandler` base class as a starting point. """ - class Meta: + class Meta(Interface.Meta): """Handler meta-data.""" #: The string identifier of the interface. - interface = 'extension' + interface: str = 'extension' @abstractmethod - def load_extension(self, ext_module): + def load_extension(self, ext_module: str) -> None: """ Load an extension whose module is ``ext_module``. For example, ``cement.ext.ext_json``. @@ -39,7 +45,7 @@ class ExtensionInterface(Interface): pass # pragma: no cover @abstractmethod - def load_extensions(self, ext_list): + def load_extensions(self, ext_list: List[str]) -> None: """ Load all extensions from ``ext_list``. @@ -61,7 +67,7 @@ class ExtensionHandler(ExtensionInterface, Handler): """ - class Meta: + class Meta(Handler.Meta): """ Handler meta-data (can be passed as keyword arguments to the parent @@ -69,14 +75,14 @@ class ExtensionHandler(ExtensionInterface, Handler): """ #: The string identifier of the handler. - label = 'cement' + label: str = 'cement' - def __init__(self, **kw): + def __init__(self, **kw: Any) -> None: super().__init__(**kw) - self.app = None - self._loaded_extensions = [] + self.app: App = None # type: ignore + self._loaded_extensions: List[str] = [] - def get_loaded_extensions(self): + def get_loaded_extensions(self) -> List[str]: """ Get all loaded extensions. @@ -86,7 +92,7 @@ class ExtensionHandler(ExtensionInterface, Handler): """ return self._loaded_extensions - def list(self): + def list(self) -> List[str]: """ Synonymous with ``get_loaded_extensions()``. @@ -96,7 +102,7 @@ class ExtensionHandler(ExtensionInterface, Handler): """ return self._loaded_extensions - def load_extension(self, ext_module): + def load_extension(self, ext_module: str) -> None: """ Given an extension module name, load or in other-words ``import`` the extension. @@ -132,7 +138,7 @@ class ExtensionHandler(ExtensionInterface, Handler): except ImportError as e: raise exc.FrameworkError(e.args[0]) - def load_extensions(self, ext_list): + def load_extensions(self, ext_list: List[str]) -> None: """ Given a list of extension modules, iterate over the list and pass individually to ``self.load_extension()``. diff --git a/cement/core/handler.py b/cement/core/handler.py index 1eff624d..109c16f9 100644 --- a/cement/core/handler.py +++ b/cement/core/handler.py @@ -3,8 +3,10 @@ Cement core handler module. """ +from __future__ import annotations import re from abc import ABC +from typing import Any, List, Dict, Optional, Type, Union, TYPE_CHECKING from ..core import exc from ..core.meta import MetaMixin from ..utils.misc import minimal_logger @@ -12,6 +14,10 @@ from ..utils.misc import minimal_logger LOG = minimal_logger(__name__) +if TYPE_CHECKING: + from ..core.foundation import App # pragma: nocover + + class Handler(ABC, MetaMixin): """Base handler class that all Cement Handlers should subclass from.""" @@ -24,13 +30,13 @@ class Handler(ABC, MetaMixin): """ - label = NotImplemented + label: str = NotImplemented """The string identifier of this handler.""" - interface = NotImplemented + interface: str = NotImplemented """The interface that this class implements.""" - config_section = None + config_section: str = None # type: ignore """ A config section to merge config_defaults with. @@ -39,14 +45,14 @@ class Handler(ABC, MetaMixin): no section is set by the user/developer. """ - config_defaults = None + config_defaults: Optional[Dict[str, Any]] = None """ A config dictionary that is merged into the applications config in the ``[]`` block. These are defaults and do not override any existing defaults under that section. """ - overridable = False + overridable: bool = False """ Whether or not handler can be overridden by ``App.Meta.handler_override_options``. Will be listed as an @@ -54,7 +60,7 @@ class Handler(ABC, MetaMixin): ``App.Meta.output_handler``, etc). """ - def __init__(self, **kw): + def __init__(self, **kw: Any) -> None: super(Handler, self).__init__(**kw) try: assert self._meta.label, \ @@ -64,9 +70,9 @@ class Handler(ABC, MetaMixin): except AssertionError as e: raise exc.FrameworkError(e.args[0]) - self.app = None + self.app: App = None # type: ignore - def _setup(self, app): + def _setup(self, app: App) -> None: """ Called during application initialization and must ``setup`` the handler object making it ready for the framework or the application to make @@ -88,11 +94,11 @@ class Handler(ABC, MetaMixin): "into section '%s'" % self._meta.config_section) dict_obj = dict() dict_obj[self._meta.config_section] = self._meta.config_defaults - self.app.config.merge(dict_obj, override=False) + self.app.config.merge(dict_obj, override=False) # type: ignore self._validate() - def _validate(self): + def _validate(self) -> None: """ Perform any validation to ensure proper data, meta-data, etc. """ @@ -106,11 +112,15 @@ class HandlerManager(object): """ - def __init__(self, app): + def __init__(self, app: App): self.app = app - self.__handlers__ = {} + self.__handlers__: Dict[str, dict[str, Type[Handler]]] = {} - def get(self, interface, handler_label, fallback=None, **kwargs): + def get(self, + interface: str, + handler_label: str, + fallback: Optional[Type[Handler]] = None, + **kwargs: Any) -> Union[Handler, Type[Handler]]: """ Get a handler object. @@ -144,7 +154,7 @@ class HandlerManager(object): """ setup = kwargs.get('setup', False) - if interface not in self.app.interface.list(): + if interface not in self.app.interface.list(): # type: ignore raise exc.InterfaceError("Interface '%s' does not exist!" % interface) @@ -160,7 +170,7 @@ class HandlerManager(object): raise exc.InterfaceError("handlers['%s']['%s'] does not exist!" % (interface, handler_label)) - def list(self, interface): + def list(self, interface: str) -> List[Type[Handler]]: """ Return a list of handlers for a given ``interface``. @@ -181,7 +191,7 @@ class HandlerManager(object): app.handler.list('log') """ - if not self.app.interface.defined(interface): + if not self.app.interface.defined(interface): # type: ignore raise exc.InterfaceError("Interface '%s' does not exist!" % interface) @@ -190,7 +200,9 @@ class HandlerManager(object): res.append(self.__handlers__[interface][label]) return res - def register(self, handler_class, force=False): + def register(self, + handler_class: Type[Handler], + force: bool = False) -> None: """ Register a handler class to an interface. If the same object is already registered then no exception is raised, however if a different @@ -243,7 +255,7 @@ class HandlerManager(object): LOG.debug("registering handler '%s' into handlers['%s']['%s']" % (handler_class, interface, obj._meta.label)) - if interface not in self.app.interface.list(): + if interface not in self.app.interface.list(): # type: ignore raise exc.InterfaceError("Handler interface '%s' doesn't exist." % interface) elif interface not in self.__handlers__.keys(): @@ -264,7 +276,7 @@ class HandlerManager(object): (interface, obj._meta.label) ) - interface_class = self.app.interface.get(interface) + interface_class = self.app.interface.get(interface) # type: ignore if not issubclass(handler_class, interface_class): raise exc.InterfaceError("Handler %s " % handler_class.__name__ + @@ -273,7 +285,7 @@ class HandlerManager(object): self.__handlers__[interface][obj._meta.label] = handler_class - def registered(self, interface, handler_label): + def registered(self, interface: str, handler_label: str) -> bool: """ Check if a handler is registered. @@ -291,14 +303,14 @@ class HandlerManager(object): app.handler.registered('log', 'colorlog') """ - if interface in self.app.interface.list(): + if interface in self.app.interface.list(): # type: ignore if interface in self.__handlers__.keys() and \ handler_label in self.__handlers__[interface]: return True return False - def setup(self, handler_class): + def setup(self, handler_class: Type[Handler]) -> Handler: """ Setup a handler class so that it can be used. @@ -319,7 +331,10 @@ class HandlerManager(object): h._setup(self.app) return h - def resolve(self, interface, handler_def, **kwargs): + def resolve(self, + interface: str, + handler_def: Union[str, Handler, Type[Handler]], + **kwargs: Any) -> Union[Handler, Optional[Handler]]: """ Resolves the actual handler, as it can be either a string identifying the handler to load from ``self.__handlers__``, or it can be an @@ -374,15 +389,15 @@ class HandlerManager(object): han = None if type(handler_def) is str: - han = self.get(interface, handler_def)(**meta_defaults) + han = self.get(interface, handler_def)(**meta_defaults) # type: ignore elif hasattr(handler_def, '_meta'): - if not self.registered(interface, handler_def._meta.label): - self.register(handler_def.__class__) + if not self.registered(interface, handler_def._meta.label): # type: ignore + self.register(handler_def.__class__) # type: ignore han = handler_def elif hasattr(handler_def, 'Meta'): - han = handler_def(**meta_defaults) + han = handler_def(**meta_defaults) # type: ignore if not self.registered(interface, han._meta.label): - self.register(handler_def) + self.register(handler_def) # type: ignore msg = "Unable to resolve handler '%s' of interface '%s'" % \ (handler_def, interface)