classicist 1.0.1__py3-none-any.whl → 1.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- classicist/decorators/aliased/__init__.py +111 -14
- classicist/metaclasses/aliased/__init__.py +2 -1
- classicist/version.txt +1 -1
- {classicist-1.0.1.dist-info → classicist-1.0.3.dist-info}/METADATA +64 -20
- {classicist-1.0.1.dist-info → classicist-1.0.3.dist-info}/RECORD +9 -9
- {classicist-1.0.1.dist-info → classicist-1.0.3.dist-info}/WHEEL +1 -1
- {classicist-1.0.1.dist-info → classicist-1.0.3.dist-info}/licenses/LICENSE.md +0 -0
- {classicist-1.0.1.dist-info → classicist-1.0.3.dist-info}/top_level.txt +0 -0
- {classicist-1.0.1.dist-info → classicist-1.0.3.dist-info}/zip-safe +0 -0
|
@@ -6,20 +6,29 @@ from typing import Callable
|
|
|
6
6
|
from functools import wraps
|
|
7
7
|
|
|
8
8
|
import keyword
|
|
9
|
+
import inspect
|
|
10
|
+
import sys
|
|
9
11
|
|
|
10
12
|
logger = logger.getChild(__name__)
|
|
11
13
|
|
|
12
14
|
|
|
13
|
-
def alias(*names: tuple[str]) -> Callable:
|
|
14
|
-
"""Decorator that
|
|
15
|
-
|
|
15
|
+
def alias(*names: tuple[str], scope: object = None) -> Callable:
|
|
16
|
+
"""Decorator that applies one or more alias names to a class, function or method.
|
|
17
|
+
The decorator records the assigned aliases on the class, method or function object,
|
|
18
|
+
and where possible creates aliases in the same scope as the original class or module
|
|
19
|
+
level function directly as the decorator call runs. Methods within classes cannot be
|
|
20
|
+
aliased directly by the `@alias` decorator, but instead require the assistance of the
|
|
21
|
+
corresponding `aliased` metaclass that must be specified on the class definition. If
|
|
22
|
+
control over the scope is required, the optional `scope` keyword argument can be used
|
|
23
|
+
to specify the scope into which to apply the alias, this should be a reference to the
|
|
24
|
+
globals() or locals() at the site in code where the `@alias()` decorator is used."""
|
|
16
25
|
|
|
17
26
|
for name in names:
|
|
18
27
|
if not isinstance(name, str):
|
|
19
28
|
raise AliasError(
|
|
20
29
|
"All @alias decorator name arguments must have a string value; non-string values cannot be used!"
|
|
21
30
|
)
|
|
22
|
-
elif len(name) == 0:
|
|
31
|
+
elif len(name := name.strip()) == 0:
|
|
23
32
|
raise AliasError(
|
|
24
33
|
"All @alias decorator name arguments must be valid Python identifier values; empty strings cannot be used!"
|
|
25
34
|
)
|
|
@@ -32,19 +41,107 @@ def alias(*names: tuple[str]) -> Callable:
|
|
|
32
41
|
f"All @alias decorator name arguments must be valid Python identifier values; reserved keywords, such as '{name}' cannot be used!"
|
|
33
42
|
)
|
|
34
43
|
|
|
35
|
-
def decorator(
|
|
36
|
-
|
|
44
|
+
def decorator(thing: object, *args, **kwargs) -> object:
|
|
45
|
+
nonlocal scope
|
|
37
46
|
|
|
38
|
-
|
|
39
|
-
setattr(function, "_classicist_aliases", tuple([*aliases, *names]))
|
|
40
|
-
else:
|
|
41
|
-
setattr(function, "_classicist_aliases", names)
|
|
47
|
+
thing = unwrap(thing)
|
|
42
48
|
|
|
43
|
-
@
|
|
44
|
-
def wrapper(*args, **kwargs):
|
|
45
|
-
return function(*args, **kwargs)
|
|
49
|
+
logger.debug(f"@alias({names}) called on {thing}")
|
|
46
50
|
|
|
47
|
-
|
|
51
|
+
if isinstance(aliases := getattr(thing, "_classicist_aliases", None), tuple):
|
|
52
|
+
setattr(thing, "_classicist_aliases", tuple([*aliases, *names]))
|
|
53
|
+
else:
|
|
54
|
+
setattr(thing, "_classicist_aliases", names)
|
|
55
|
+
|
|
56
|
+
@wraps(thing)
|
|
57
|
+
def wrapper_class(*args, **kwargs):
|
|
58
|
+
return thing
|
|
59
|
+
|
|
60
|
+
@wraps(thing)
|
|
61
|
+
def wrapper_method(*args, **kwargs):
|
|
62
|
+
return thing(*args, **kwargs)
|
|
63
|
+
|
|
64
|
+
@wraps(thing)
|
|
65
|
+
def wrapper_function(*args, **kwargs):
|
|
66
|
+
return thing
|
|
67
|
+
|
|
68
|
+
if inspect.isclass(thing):
|
|
69
|
+
if not scope:
|
|
70
|
+
scope = sys.modules.get(thing.__module__ or "__main__")
|
|
71
|
+
|
|
72
|
+
if isinstance(scope, object):
|
|
73
|
+
for name in names:
|
|
74
|
+
if hasattr(scope, name):
|
|
75
|
+
raise AliasError(
|
|
76
|
+
"Cannot create alias '%s' for %s class in the %s module as an object with that name already exists!"
|
|
77
|
+
% (
|
|
78
|
+
name,
|
|
79
|
+
thing,
|
|
80
|
+
scope,
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Create a module-level alias for the class
|
|
85
|
+
if isinstance(scope, dict):
|
|
86
|
+
scope[name] = thing
|
|
87
|
+
else:
|
|
88
|
+
setattr(scope, name, thing)
|
|
89
|
+
|
|
90
|
+
return wrapper_class(*args, **kwargs)
|
|
91
|
+
elif inspect.ismethod(thing) or isinstance(thing, classmethod):
|
|
92
|
+
return wrapper_method
|
|
93
|
+
elif inspect.isfunction(thing):
|
|
94
|
+
if not scope:
|
|
95
|
+
scope = sys.modules.get(thing.__module__ or "__main__")
|
|
96
|
+
|
|
97
|
+
# The qualified name for module-level functions only contain the name of the
|
|
98
|
+
# function, whereas functions nested within other functions or classes have
|
|
99
|
+
# names comprised of multiple parts separated by the "." character; because
|
|
100
|
+
# it is only currently possible to alias module-level functions, any nested
|
|
101
|
+
# or class methods are ignored during this stage of the aliasing process.
|
|
102
|
+
if len(thing.__qualname__.split(".")) > 1:
|
|
103
|
+
logger.debug(
|
|
104
|
+
"Unable to apply alias to functions defined beyond the top-level of a module: %s!"
|
|
105
|
+
% (thing.__qualname__)
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
return wrapper_function(*args, **kwargs)
|
|
109
|
+
|
|
110
|
+
# if signature := inspect.signature(thing):
|
|
111
|
+
# if len(parameters := signature.parameters) > 0 and "self" in parameters:
|
|
112
|
+
# return wrapper_function(*args, **kwargs)
|
|
113
|
+
|
|
114
|
+
if isinstance(scope, object):
|
|
115
|
+
# At this point we should only be left with module-level functions to alias
|
|
116
|
+
for name in names:
|
|
117
|
+
# Ensure the scope doesn't already contain an object of the same name
|
|
118
|
+
if hasattr(scope, name):
|
|
119
|
+
raise AliasError(
|
|
120
|
+
"Cannot create alias '%s' for %s function in the %s module as an object with that name already exists!"
|
|
121
|
+
% (
|
|
122
|
+
name,
|
|
123
|
+
thing,
|
|
124
|
+
scope,
|
|
125
|
+
)
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
logger.debug(f"Added alias '{name}' to {scope}.{thing}")
|
|
129
|
+
|
|
130
|
+
if isinstance(scope, dict):
|
|
131
|
+
scope[name] = thing
|
|
132
|
+
elif isinstance(scope, object):
|
|
133
|
+
setattr(scope, name, thing)
|
|
134
|
+
else:
|
|
135
|
+
logger.warning(
|
|
136
|
+
f"No scope was found or specified for {thing} into which to assign aliases!"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
return wrapper_function(*args, **kwargs)
|
|
140
|
+
else:
|
|
141
|
+
raise AliasError(
|
|
142
|
+
"The @alias decorator can only be applied to classes, methods and functions, not %s!"
|
|
143
|
+
% (type(thing))
|
|
144
|
+
)
|
|
48
145
|
|
|
49
146
|
return decorator
|
|
50
147
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from classicist.logging import logger
|
|
2
|
+
from classicist.exceptions.decorators.aliased import AliasError
|
|
2
3
|
|
|
3
4
|
logger = logger.getChild(__name__)
|
|
4
5
|
|
|
@@ -30,7 +31,7 @@ class aliased(type):
|
|
|
30
31
|
if aliases := getattr(value, "_classicist_aliases", None):
|
|
31
32
|
for alias in aliases:
|
|
32
33
|
if hasattr(cls, alias):
|
|
33
|
-
raise
|
|
34
|
+
raise AliasError(
|
|
34
35
|
f"Cannot create alias '{alias}' for method '{name}' as '{cls.__name__}.{alias}' already exists!"
|
|
35
36
|
)
|
|
36
37
|
|
classicist/version.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0.
|
|
1
|
+
1.0.3
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: classicist
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.3
|
|
4
4
|
Summary: Classy class decorators for Python.
|
|
5
5
|
Author: Daniel Sissman
|
|
6
6
|
License-Expression: MIT
|
|
@@ -21,7 +21,7 @@ Requires-Python: >=3.9
|
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE.md
|
|
23
23
|
Provides-Extra: development
|
|
24
|
-
Requires-Dist: black==
|
|
24
|
+
Requires-Dist: black==26.1.*; extra == "development"
|
|
25
25
|
Requires-Dist: pytest==8.3.*; extra == "development"
|
|
26
26
|
Requires-Dist: pytest-codeblocks==0.17.0; extra == "development"
|
|
27
27
|
Requires-Dist: pyflakes; extra == "development"
|
|
@@ -39,7 +39,7 @@ The Classicist library provides several useful decorators and helper methods inc
|
|
|
39
39
|
* `@classproperty` – a decorator that allow class methods to be accessed as class properties;
|
|
40
40
|
* `@annotation` – a decorator that can be used to apply arbitrary annotations to code objects;
|
|
41
41
|
* `@deprecated` – a decorator that can be used to mark functions, classes and methods as being deprecated;
|
|
42
|
-
* `@alias` – a decorator that can be used to add aliases to
|
|
42
|
+
* `@alias` – a decorator that can be used to add aliases to classes, methods defined within classes, module-level functions, and nested functions when overriding the aliasing scope;
|
|
43
43
|
* `@nocache` – a decorator that can be used to mark functions and methods as not being suitable for caching;
|
|
44
44
|
* `shadowproof` – a metaclass that can be used to protect subclasses from class-level attributes
|
|
45
45
|
being overwritten (or shadowed) which can otherwise negatively affect class behaviour in some cases.
|
|
@@ -263,30 +263,72 @@ exampleclass.greeting = "goodbye"
|
|
|
263
263
|
assert exampleclass.greeting == "goodbye"
|
|
264
264
|
```
|
|
265
265
|
|
|
266
|
-
####
|
|
266
|
+
#### Alias Decorator & Metaclass: Add Aliases to Classes, Methods & Functions
|
|
267
267
|
|
|
268
|
-
The `@alias` decorator can be used to add
|
|
269
|
-
classes,
|
|
270
|
-
the
|
|
271
|
-
|
|
268
|
+
The `@alias` decorator can be used to add aliases to classes, methods defined within
|
|
269
|
+
classes, module-level functions, and nested functions when overriding the aliasing scope,
|
|
270
|
+
such that both the original name and any defined aliases can be used to access the same
|
|
271
|
+
code object at runtime.
|
|
272
272
|
|
|
273
|
-
To
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
273
|
+
To alias a class or a module-level function, that is a function defined at the top-level
|
|
274
|
+
of a module file (rather than nested within a function or class), simply decorate the
|
|
275
|
+
class or module-level function with the `@alias(...)` decorator and specify the one or
|
|
276
|
+
more name aliases for the class or function as one or more string arguments passed into
|
|
277
|
+
the decorator method.
|
|
278
278
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
279
|
+
To use the `@alias` decorator on methods defined within a class, it is also necessary to
|
|
280
|
+
set the containing class' metaclass to the `aliased` metaclass provided by the `classicist`
|
|
281
|
+
library; the metaclass iterates through the class' namespace during parse time and sets up
|
|
282
|
+
the aliases as additional attributes on the class so that the aliased methods are available
|
|
283
|
+
at runtime via both their original name and any aliases.
|
|
284
|
+
|
|
285
|
+
The examples below demonstrate adding an alias to a module-level function, a class and a
|
|
286
|
+
method defined within a class, and using the `aliased` metaclass when defining a class
|
|
287
|
+
that contains aliased methods to ensure that any aliases are parsed and translated to
|
|
288
|
+
additional class attributes so that the method is accessible via its original name and
|
|
289
|
+
any alias at runtime.
|
|
290
|
+
|
|
291
|
+
If control over the scope is required, usually for nested functions, the optional `scope`
|
|
292
|
+
keyword-only argument can be used to specify the scope into which to apply the alias; this
|
|
293
|
+
must be a reference to `globals()` or `locals()` at the point in code where the `@alias(...)`
|
|
294
|
+
decorator is applied to the nested function.
|
|
283
295
|
|
|
284
296
|
```python
|
|
285
297
|
from classicist import aliased, alias, is_aliased, aliases
|
|
286
298
|
|
|
287
|
-
|
|
299
|
+
# Define an alias on a module-level method; as this demonstration occurs
|
|
300
|
+
# within the README file which is parsed by and run within an external
|
|
301
|
+
# scope by pytest and pytest-codeblocks, we override the scope within
|
|
302
|
+
# which to apply the alias otherwise the alias would be assigned within
|
|
303
|
+
# an external scope which would prevent the alias from working; however
|
|
304
|
+
# it is rare to need to override the inferred scope, and aliasing of
|
|
305
|
+
# module-level functions defined within actual modules will work normally;
|
|
306
|
+
# for rare cases where overriding scope is necessary the optional `scope`
|
|
307
|
+
# keyword-only argument can be used as shown below.
|
|
308
|
+
@alias("sums", scope=globals())
|
|
309
|
+
def adds(a: int, b: int) -> int:
|
|
310
|
+
return a + b
|
|
311
|
+
|
|
312
|
+
assert globals().get("adds") is adds
|
|
313
|
+
assert globals().get("sums") is sums
|
|
314
|
+
assert adds is sums
|
|
315
|
+
assert adds(1, 2) == 3
|
|
316
|
+
assert sums(1, 2) == 3
|
|
317
|
+
|
|
318
|
+
# Define an alias on a class
|
|
319
|
+
@alias("Color")
|
|
320
|
+
class Colour(object):
|
|
321
|
+
pass
|
|
322
|
+
|
|
323
|
+
assert Colour is Color
|
|
324
|
+
|
|
325
|
+
# Define an alias on a method defined within a class;
|
|
326
|
+
# this also requires the use of the aliased metaclass
|
|
327
|
+
# which is responsible for adding the aliases within
|
|
328
|
+
# the scope of the class once the class has been parsed
|
|
329
|
+
class Welcome(metaclass=aliased):
|
|
288
330
|
@alias("greet")
|
|
289
|
-
def hello(self, name: str):
|
|
331
|
+
def hello(self, name: str) -> str:
|
|
290
332
|
return f"Hello {name}!"
|
|
291
333
|
|
|
292
334
|
assert is_aliased(Welcome.hello) is True
|
|
@@ -305,7 +347,9 @@ assert welcome.greet("you") == "Hello you!"
|
|
|
305
347
|
|
|
306
348
|
⚠️ Note: Aliases must be valid Python identifiers, following the same rules as for all
|
|
307
349
|
other function and method names and aliases cannot be reserved keywords. If an invalid
|
|
308
|
-
alias is specified an `AliasError` exception will be raised at runtime.
|
|
350
|
+
alias is specified an `AliasError` exception will be raised at runtime. Furthermore, if
|
|
351
|
+
a name has already been used in the current scope, an `AliasError` exception will be
|
|
352
|
+
raised at runtime.
|
|
309
353
|
|
|
310
354
|
#### Annotation Decorator: Add Arbitrary Annotations to Code Objects
|
|
311
355
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
classicist/__init__.py,sha256=Rkm1Vx0Z-BHPH1FSzFLrAxwkFEt1xvMxZz-mC8FJSDc,834
|
|
2
|
-
classicist/version.txt,sha256=
|
|
2
|
+
classicist/version.txt,sha256=INLLCW0atBpBQCRtEvB79rjLdD_UgSK3JTLAPUTFwUo,5
|
|
3
3
|
classicist/decorators/__init__.py,sha256=wplcs2JnyGMOh72626-x-WyVotVoT7nvk-dpjeTXQ88,600
|
|
4
|
-
classicist/decorators/aliased/__init__.py,sha256=
|
|
4
|
+
classicist/decorators/aliased/__init__.py,sha256=vW1P9jbGkOD_m9GvgEXF0Uni-Dt6rfVXDVF5D4wC1GY,6926
|
|
5
5
|
classicist/decorators/annotation/__init__.py,sha256=20WwmrXDxT85sItpDiCdC-hZjbyDR6E2mLPMSKQgm8g,1796
|
|
6
6
|
classicist/decorators/classproperty/__init__.py,sha256=ED37_20UeAGKX1ahsv16wTg0JAJT4UBLqiNRbKAp2KE,1646
|
|
7
7
|
classicist/decorators/deprecated/__init__.py,sha256=aAPFQoT-pJf1nauQGiPADkPBREMvEQJaUQc3kjA48Rg,2764
|
|
@@ -16,11 +16,11 @@ classicist/exceptions/metaclasses/shadowproof/__init__.py,sha256=QT-QFRnjlnVexl9
|
|
|
16
16
|
classicist/inspector/__init__.py,sha256=Z8Se9CLkZ4WNRDX9Ui31Kt3BklZMYBpDRo7WWTsCQ_g,1309
|
|
17
17
|
classicist/logging/__init__.py,sha256=LJ-Nih1LacPrO2TvTT6l84So-pgw84AHJ8IhzYKl5rw,57
|
|
18
18
|
classicist/metaclasses/__init__.py,sha256=mYhR5rM7cnJlaNUlgoQWOo44RQSORteRjYd0y0BajxQ,159
|
|
19
|
-
classicist/metaclasses/aliased/__init__.py,sha256=
|
|
19
|
+
classicist/metaclasses/aliased/__init__.py,sha256=grs4Z8dMY6R2fCFn4UCPdCzEJtVaAv-tsWFwY1DCUpI,2033
|
|
20
20
|
classicist/metaclasses/shadowproof/__init__.py,sha256=d55uNjcaCorD3MdDUv7aRVpk2MlE8JzMjint6Vvf_Yc,1492
|
|
21
|
-
classicist-1.0.
|
|
22
|
-
classicist-1.0.
|
|
23
|
-
classicist-1.0.
|
|
24
|
-
classicist-1.0.
|
|
25
|
-
classicist-1.0.
|
|
26
|
-
classicist-1.0.
|
|
21
|
+
classicist-1.0.3.dist-info/licenses/LICENSE.md,sha256=qBmrjPmSCp0YFyaIl2G3FU3rniFD31YC0Yd3MrO1wEg,1070
|
|
22
|
+
classicist-1.0.3.dist-info/METADATA,sha256=iZaQqjD9wd84Bb_Dx_Se-RMYgZTvvf2Hkf2idrnswgk,25128
|
|
23
|
+
classicist-1.0.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
24
|
+
classicist-1.0.3.dist-info/top_level.txt,sha256=beG3ZuwObnmnY_mgNSN5CaVIWpI2VKszjVdKHPgZBhc,11
|
|
25
|
+
classicist-1.0.3.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
26
|
+
classicist-1.0.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|