1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
|
# ==================================================================================================================== #
# _____ _ _ ____ _ #
# _ __ _ |_ _|__ ___ | (_)_ __ __ _ | _ \ ___ ___ ___ _ __ __ _| |_ ___ _ __ ___ #
# | '_ \| | | || |/ _ \ / _ \| | | '_ \ / _` | | | | |/ _ \/ __/ _ \| '__/ _` | __/ _ \| '__/ __| #
# | |_) | |_| || | (_) | (_) | | | | | | (_| |_| |_| | __/ (_| (_) | | | (_| | || (_) | | \__ \ #
# | .__/ \__, ||_|\___/ \___/|_|_|_| |_|\__, (_)____/ \___|\___\___/|_| \__,_|\__\___/|_| |___/ #
# |_| |___/ |___/ #
# ==================================================================================================================== #
# Authors: #
# Patrick Lehmann #
# #
# License: #
# ==================================================================================================================== #
# Copyright 2017-2026 Patrick Lehmann - Bötzingen, Germany #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); #
# you may not use this file except in compliance with the License. #
# You may obtain a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
# See the License for the specific language governing permissions and #
# limitations under the License. #
# #
# SPDX-License-Identifier: Apache-2.0 #
# ==================================================================================================================== #
#
"""Decorators controlling visibility of entities in a Python module.
.. hint::
See :ref:`high-level help <DECO>` for explanations and usage examples.
"""
import sys
from functools import wraps
from types import FunctionType
from typing import Union, Type, TypeVar, Callable, Any, Optional as Nullable
__all__ = ["export", "Param", "RetType", "Func", "T"]
try:
# See https://stackoverflow.com/questions/47060133/python-3-type-hinting-for-decorator
from typing import ParamSpec # WORKAROUND: exists since Python 3.10
Param = ParamSpec("Param") #: A parameter specification for function or method
RetType = TypeVar("RetType") #: Type variable for a return type
Func = Callable[Param, RetType] #: Type specification for a function
except ImportError: # pragma: no cover
Param = ... #: A parameter specification for function or method
RetType = TypeVar("RetType") #: Type variable for a return type
Func = Callable[..., RetType] #: Type specification for a function
T = TypeVar("T", bound=Union[Type, FunctionType]) #: A type variable for a classes or functions.
C = TypeVar("C", bound=Callable) #: A type variable for functions or methods.
def export(entity: T) -> T:
"""
Register the given function or class as publicly accessible in a module.
Creates or updates the ``__all__`` attribute in the module in which the decorated entity is defined to include the
name of the decorated entity.
+---------------------------------------------+------------------------------------------------+
| ``to_export.py`` | ``another_file.py`` |
+=============================================+================================================+
| .. code-block:: python | .. code-block:: python |
| | |
| from pyTooling.Decorators import export | from .to_export import * |
| | |
| @export | |
| def exported(): | # 'exported' will be listed in __all__ |
| pass | assert "exported" in globals() |
| | |
| def not_exported(): | # 'not_exported' won't be listed in __all__ |
| pass | assert "not_exported" not in globals() |
| | |
+---------------------------------------------+------------------------------------------------+
:param entity: The function or class to include in `__all__`.
:returns: The unmodified function or class.
:raises AttributeError: If parameter ``entity`` has no ``__module__`` member.
:raises TypeError: If parameter ``entity`` is not a top-level entity in a module.
:raises TypeError: If parameter ``entity`` has no ``__name__``.
"""
# * Based on an idea by Duncan Booth:
# http://groups.google.com/group/comp.lang.python/msg/11cbb03e09611b8a
# * Improved via a suggestion by Dave Angel:
# http://groups.google.com/group/comp.lang.python/msg/3d400fb22d8a42e1
if not hasattr(entity, "__module__"):
raise AttributeError(f"{entity} has no __module__ attribute. Please ensure it is a top-level function or class reference defined in a module.")
if hasattr(entity, "__qualname__"):
if any(i in entity.__qualname__ for i in (".", "<locals>", "<lambda>")):
raise TypeError(f"Only named top-level functions and classes may be exported, not {entity}")
if not hasattr(entity, "__name__") or entity.__name__ == "<lambda>":
raise TypeError(f"Entity must be a named top-level function or class, not {entity.__class__}")
try:
module = sys.modules[entity.__module__]
except KeyError:
raise ValueError(f"Module {entity.__module__} is not present in sys.modules. Please ensure it is in the import path before calling export().")
if hasattr(module, "__all__"):
if entity.__name__ not in module.__all__: # type: ignore
module.__all__.append(entity.__name__) # type: ignore
else:
module.__all__ = [entity.__name__] # type: ignore
return entity
@export
def notimplemented(message: str) -> Callable:
"""
Mark a method as *not implemented* and replace the implementation with a new method raising a :exc:`NotImplementedError`.
The original method is stored in ``<method>.__wrapped__`` and it's doc-string is copied to the replacing method. In
additional the field ``<method>.__notImplemented__`` is added.
.. admonition:: ``example.py``
.. code-block:: python
class Data:
@notimplemented
def method(self) -> bool:
'''This method needs to be implemented'''
return True
:param method: Method that is marked as *not implemented*.
:returns: Replacement method, which raises a :exc:`NotImplementedError`.
.. seealso::
* :func:`~pyTooling.Metaclasses.abstractmethod`
* :func:`~pyTooling.Metaclasses.mustoverride`
"""
def decorator(method: C) -> C:
@wraps(method)
def func(*_, **__):
raise NotImplementedError(message)
func.__notImplemented__ = True
return func
return decorator
@export
def readonly(func: Callable) -> property:
"""
Marks a property as *read-only*.
The doc-string will be taken from the getter-function.
It will remove ``<property>.setter`` and ``<property>.deleter`` from the property descriptor.
:param func: Function to convert to a read-only property.
:returns: A property object with just a getter.
.. seealso::
:class:`property`
A decorator to convert getter, setter and deleter methods into a property applying the descriptor protocol.
"""
prop = property(fget=func, fset=None, fdel=None, doc=func.__doc__)
return prop
@export
def InheritDocString(baseClass: type, merge: bool = False) -> Callable[[Union[Func, type]], Union[Func, type]]:
"""
Copy the doc-string from given base-class to the method this decorator is applied to.
.. admonition:: ``example.py``
.. code-block:: python
from pyTooling.Decorators import InheritDocString
class Class1:
def method(self):
'''Method's doc-string.'''
class Class2(Class1):
@InheritDocString(Class1)
def method(self):
super().method()
:param baseClass: Base-class to copy the doc-string from to the new method being decorated.
:returns: Decorator function that copies the doc-string.
"""
def decorator(param: Union[Func, type]) -> Union[Func, type]:
"""
Decorator function, which copies the doc-string from base-class' method to method ``m``.
:param param: Method to which the doc-string from a method in ``baseClass`` (with same className) should be copied.
:returns: Same method, but with overwritten doc-string field (``__doc__``).
"""
if isinstance(param, type):
baseDoc = baseClass.__doc__
elif callable(param):
baseDoc = getattr(baseClass, param.__name__).__doc__
else:
return param
if merge:
if param.__doc__ is None:
param.__doc__ = baseDoc
elif baseDoc is not None:
param.__doc__ = baseDoc + "\n\n" + param.__doc__
else:
param.__doc__ = baseDoc
return param
return decorator
|