classicist 1.0.1__tar.gz → 1.0.3__tar.gz
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-1.0.1/source/classicist.egg-info → classicist-1.0.3}/PKG-INFO +64 -20
- {classicist-1.0.1 → classicist-1.0.3}/README.md +62 -18
- {classicist-1.0.1 → classicist-1.0.3}/requirements.development.txt +1 -1
- classicist-1.0.3/source/classicist/decorators/aliased/__init__.py +171 -0
- {classicist-1.0.1 → classicist-1.0.3}/source/classicist/metaclasses/aliased/__init__.py +2 -1
- classicist-1.0.3/source/classicist/version.txt +1 -0
- {classicist-1.0.1 → classicist-1.0.3/source/classicist.egg-info}/PKG-INFO +64 -20
- {classicist-1.0.1 → classicist-1.0.3}/source/classicist.egg-info/requires.txt +1 -1
- {classicist-1.0.1 → classicist-1.0.3}/tests/test_aliased.py +112 -5
- classicist-1.0.1/source/classicist/decorators/aliased/__init__.py +0 -74
- classicist-1.0.1/source/classicist/version.txt +0 -1
- {classicist-1.0.1 → classicist-1.0.3}/LICENSE.md +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/pyproject.toml +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/requirements.distribution.txt +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/requirements.txt +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/setup.cfg +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/source/classicist/__init__.py +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/source/classicist/decorators/__init__.py +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/source/classicist/decorators/annotation/__init__.py +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/source/classicist/decorators/classproperty/__init__.py +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/source/classicist/decorators/deprecated/__init__.py +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/source/classicist/decorators/hybridmethod/__init__.py +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/source/classicist/decorators/nocache/__init__.py +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/source/classicist/exceptions/__init__.py +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/source/classicist/exceptions/decorators/__init__.py +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/source/classicist/exceptions/decorators/aliased/__init__.py +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/source/classicist/exceptions/decorators/annotation/__init__.py +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/source/classicist/exceptions/metaclasses/__init__.py +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/source/classicist/exceptions/metaclasses/shadowproof/__init__.py +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/source/classicist/inspector/__init__.py +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/source/classicist/logging/__init__.py +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/source/classicist/metaclasses/__init__.py +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/source/classicist/metaclasses/shadowproof/__init__.py +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/source/classicist.egg-info/SOURCES.txt +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/source/classicist.egg-info/dependency_links.txt +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/source/classicist.egg-info/top_level.txt +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/source/classicist.egg-info/zip-safe +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/tests/test_annotation.py +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/tests/test_classproperty.py +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/tests/test_deprecated.py +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/tests/test_hybridmethod.py +0 -0
- {classicist-1.0.1 → classicist-1.0.3}/tests/test_shadowproof.py +0 -0
|
@@ -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
|
|
|
@@ -6,7 +6,7 @@ The Classicist library provides several useful decorators and helper methods inc
|
|
|
6
6
|
* `@classproperty` – a decorator that allow class methods to be accessed as class properties;
|
|
7
7
|
* `@annotation` – a decorator that can be used to apply arbitrary annotations to code objects;
|
|
8
8
|
* `@deprecated` – a decorator that can be used to mark functions, classes and methods as being deprecated;
|
|
9
|
-
* `@alias` – a decorator that can be used to add aliases to
|
|
9
|
+
* `@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;
|
|
10
10
|
* `@nocache` – a decorator that can be used to mark functions and methods as not being suitable for caching;
|
|
11
11
|
* `shadowproof` – a metaclass that can be used to protect subclasses from class-level attributes
|
|
12
12
|
being overwritten (or shadowed) which can otherwise negatively affect class behaviour in some cases.
|
|
@@ -230,30 +230,72 @@ exampleclass.greeting = "goodbye"
|
|
|
230
230
|
assert exampleclass.greeting == "goodbye"
|
|
231
231
|
```
|
|
232
232
|
|
|
233
|
-
####
|
|
233
|
+
#### Alias Decorator & Metaclass: Add Aliases to Classes, Methods & Functions
|
|
234
234
|
|
|
235
|
-
The `@alias` decorator can be used to add
|
|
236
|
-
classes,
|
|
237
|
-
the
|
|
238
|
-
|
|
235
|
+
The `@alias` decorator can be used to add aliases to classes, methods defined within
|
|
236
|
+
classes, module-level functions, and nested functions when overriding the aliasing scope,
|
|
237
|
+
such that both the original name and any defined aliases can be used to access the same
|
|
238
|
+
code object at runtime.
|
|
239
239
|
|
|
240
|
-
To
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
240
|
+
To alias a class or a module-level function, that is a function defined at the top-level
|
|
241
|
+
of a module file (rather than nested within a function or class), simply decorate the
|
|
242
|
+
class or module-level function with the `@alias(...)` decorator and specify the one or
|
|
243
|
+
more name aliases for the class or function as one or more string arguments passed into
|
|
244
|
+
the decorator method.
|
|
245
245
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
246
|
+
To use the `@alias` decorator on methods defined within a class, it is also necessary to
|
|
247
|
+
set the containing class' metaclass to the `aliased` metaclass provided by the `classicist`
|
|
248
|
+
library; the metaclass iterates through the class' namespace during parse time and sets up
|
|
249
|
+
the aliases as additional attributes on the class so that the aliased methods are available
|
|
250
|
+
at runtime via both their original name and any aliases.
|
|
251
|
+
|
|
252
|
+
The examples below demonstrate adding an alias to a module-level function, a class and a
|
|
253
|
+
method defined within a class, and using the `aliased` metaclass when defining a class
|
|
254
|
+
that contains aliased methods to ensure that any aliases are parsed and translated to
|
|
255
|
+
additional class attributes so that the method is accessible via its original name and
|
|
256
|
+
any alias at runtime.
|
|
257
|
+
|
|
258
|
+
If control over the scope is required, usually for nested functions, the optional `scope`
|
|
259
|
+
keyword-only argument can be used to specify the scope into which to apply the alias; this
|
|
260
|
+
must be a reference to `globals()` or `locals()` at the point in code where the `@alias(...)`
|
|
261
|
+
decorator is applied to the nested function.
|
|
250
262
|
|
|
251
263
|
```python
|
|
252
264
|
from classicist import aliased, alias, is_aliased, aliases
|
|
253
265
|
|
|
254
|
-
|
|
266
|
+
# Define an alias on a module-level method; as this demonstration occurs
|
|
267
|
+
# within the README file which is parsed by and run within an external
|
|
268
|
+
# scope by pytest and pytest-codeblocks, we override the scope within
|
|
269
|
+
# which to apply the alias otherwise the alias would be assigned within
|
|
270
|
+
# an external scope which would prevent the alias from working; however
|
|
271
|
+
# it is rare to need to override the inferred scope, and aliasing of
|
|
272
|
+
# module-level functions defined within actual modules will work normally;
|
|
273
|
+
# for rare cases where overriding scope is necessary the optional `scope`
|
|
274
|
+
# keyword-only argument can be used as shown below.
|
|
275
|
+
@alias("sums", scope=globals())
|
|
276
|
+
def adds(a: int, b: int) -> int:
|
|
277
|
+
return a + b
|
|
278
|
+
|
|
279
|
+
assert globals().get("adds") is adds
|
|
280
|
+
assert globals().get("sums") is sums
|
|
281
|
+
assert adds is sums
|
|
282
|
+
assert adds(1, 2) == 3
|
|
283
|
+
assert sums(1, 2) == 3
|
|
284
|
+
|
|
285
|
+
# Define an alias on a class
|
|
286
|
+
@alias("Color")
|
|
287
|
+
class Colour(object):
|
|
288
|
+
pass
|
|
289
|
+
|
|
290
|
+
assert Colour is Color
|
|
291
|
+
|
|
292
|
+
# Define an alias on a method defined within a class;
|
|
293
|
+
# this also requires the use of the aliased metaclass
|
|
294
|
+
# which is responsible for adding the aliases within
|
|
295
|
+
# the scope of the class once the class has been parsed
|
|
296
|
+
class Welcome(metaclass=aliased):
|
|
255
297
|
@alias("greet")
|
|
256
|
-
def hello(self, name: str):
|
|
298
|
+
def hello(self, name: str) -> str:
|
|
257
299
|
return f"Hello {name}!"
|
|
258
300
|
|
|
259
301
|
assert is_aliased(Welcome.hello) is True
|
|
@@ -272,7 +314,9 @@ assert welcome.greet("you") == "Hello you!"
|
|
|
272
314
|
|
|
273
315
|
⚠️ Note: Aliases must be valid Python identifiers, following the same rules as for all
|
|
274
316
|
other function and method names and aliases cannot be reserved keywords. If an invalid
|
|
275
|
-
alias is specified an `AliasError` exception will be raised at runtime.
|
|
317
|
+
alias is specified an `AliasError` exception will be raised at runtime. Furthermore, if
|
|
318
|
+
a name has already been used in the current scope, an `AliasError` exception will be
|
|
319
|
+
raised at runtime.
|
|
276
320
|
|
|
277
321
|
#### Annotation Decorator: Add Arbitrary Annotations to Code Objects
|
|
278
322
|
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
from classicist.logging import logger
|
|
2
|
+
from classicist.exceptions.decorators.aliased import AliasError
|
|
3
|
+
from classicist.inspector import unwrap
|
|
4
|
+
|
|
5
|
+
from typing import Callable
|
|
6
|
+
from functools import wraps
|
|
7
|
+
|
|
8
|
+
import keyword
|
|
9
|
+
import inspect
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
logger = logger.getChild(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
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."""
|
|
25
|
+
|
|
26
|
+
for name in names:
|
|
27
|
+
if not isinstance(name, str):
|
|
28
|
+
raise AliasError(
|
|
29
|
+
"All @alias decorator name arguments must have a string value; non-string values cannot be used!"
|
|
30
|
+
)
|
|
31
|
+
elif len(name := name.strip()) == 0:
|
|
32
|
+
raise AliasError(
|
|
33
|
+
"All @alias decorator name arguments must be valid Python identifier values; empty strings cannot be used!"
|
|
34
|
+
)
|
|
35
|
+
elif not name.isidentifier():
|
|
36
|
+
raise AliasError(
|
|
37
|
+
f"All @alias decorator name arguments must be valid Python identifier values; strings such as '{name}' are not considered valid identifiers by Python!"
|
|
38
|
+
)
|
|
39
|
+
elif keyword.iskeyword(name):
|
|
40
|
+
raise AliasError(
|
|
41
|
+
f"All @alias decorator name arguments must be valid Python identifier values; reserved keywords, such as '{name}' cannot be used!"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def decorator(thing: object, *args, **kwargs) -> object:
|
|
45
|
+
nonlocal scope
|
|
46
|
+
|
|
47
|
+
thing = unwrap(thing)
|
|
48
|
+
|
|
49
|
+
logger.debug(f"@alias({names}) called on {thing}")
|
|
50
|
+
|
|
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
|
+
)
|
|
145
|
+
|
|
146
|
+
return decorator
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def is_aliased(function: callable) -> bool:
|
|
150
|
+
"""The is_aliased() helper method can be used to determine if a class method has
|
|
151
|
+
been aliased."""
|
|
152
|
+
|
|
153
|
+
function = unwrap(function)
|
|
154
|
+
|
|
155
|
+
return isinstance(getattr(function, "_classicist_aliases", None), tuple)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def aliases(function: callable) -> list[str]:
|
|
159
|
+
"""The aliases() helper method can be used to obtain any class method aliases."""
|
|
160
|
+
|
|
161
|
+
function = unwrap(function)
|
|
162
|
+
|
|
163
|
+
if isinstance(aliases := getattr(function, "_classicist_aliases", None), tuple):
|
|
164
|
+
return list(aliases)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
__all__ = [
|
|
168
|
+
"alias",
|
|
169
|
+
"is_aliased",
|
|
170
|
+
"aliases",
|
|
171
|
+
]
|
|
@@ -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
|
|
|
@@ -0,0 +1 @@
|
|
|
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
|
|
|
@@ -2,29 +2,123 @@ from classicist import aliased, alias, aliases, is_aliased
|
|
|
2
2
|
from classicist.exceptions.decorators.aliased import AliasError
|
|
3
3
|
|
|
4
4
|
import pytest
|
|
5
|
+
import sys
|
|
6
|
+
import types
|
|
7
|
+
import conftest
|
|
8
|
+
|
|
9
|
+
# Obtain a reference to the current module
|
|
10
|
+
module = sys.modules[__name__]
|
|
11
|
+
assert isinstance(module, types.ModuleType)
|
|
12
|
+
assert module.__name__ == __name__
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Define and alias a module-level function for testing below
|
|
16
|
+
@alias("doubled")
|
|
17
|
+
def doubler(value: int) -> int:
|
|
18
|
+
"""Sample method that doubles and returns the provided number."""
|
|
19
|
+
|
|
20
|
+
return value * 2
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_alias_function():
|
|
24
|
+
"""Test using the @alias decorator on a module-level function."""
|
|
25
|
+
|
|
26
|
+
# Ensure that both the original function "doubler" and its alias exist in the module
|
|
27
|
+
assert hasattr(module, "doubler")
|
|
28
|
+
assert hasattr(module, "doubled")
|
|
29
|
+
|
|
30
|
+
# Ensure that the module attributes directly reference the function objects
|
|
31
|
+
# and ensure that the function names both exist in scope; by ensuring that
|
|
32
|
+
# the aliased name also exists equally in scope and has the same visibility
|
|
33
|
+
# we ensure that the module-level alias is working correctly
|
|
34
|
+
assert getattr(module, "doubler") is doubler
|
|
35
|
+
assert getattr(module, "doubled") is doubled
|
|
36
|
+
|
|
37
|
+
assert doubler is doubled
|
|
38
|
+
|
|
39
|
+
assert doubler(1) == 2
|
|
40
|
+
assert doubled(2) == 4
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_alias_function_defined_in_an_imported_module():
|
|
44
|
+
"""Test using the @alias decorator on an imported module-level function."""
|
|
45
|
+
|
|
46
|
+
# Ensure that both the original function "halves" and its alias exist in the module
|
|
47
|
+
assert hasattr(conftest, "halves")
|
|
48
|
+
assert hasattr(conftest, "divide")
|
|
49
|
+
|
|
50
|
+
# Ensure that the module attributes directly reference the function object and
|
|
51
|
+
# that the original and aliased function names both exist in scope; by ensuring
|
|
52
|
+
# that the aliased name exists equally in scope and has the same visibility as
|
|
53
|
+
# the original function object, we confirm that the alias is working correctly
|
|
54
|
+
assert getattr(conftest, "halves") is conftest.halves
|
|
55
|
+
assert getattr(conftest, "divide") is conftest.divide
|
|
56
|
+
|
|
57
|
+
# Ensure that the original and the alias refer to the same object in memory
|
|
58
|
+
assert conftest.halves is conftest.divide
|
|
59
|
+
|
|
60
|
+
# Test the original and aliased function names
|
|
61
|
+
assert conftest.halves(2) == 1
|
|
62
|
+
assert conftest.divide(4) == 2
|
|
63
|
+
|
|
64
|
+
# Ensure that both the original and aliased function can be imported
|
|
65
|
+
from conftest import halves, divide
|
|
66
|
+
|
|
67
|
+
# Ensure that the imported names refer to the same object in memory
|
|
68
|
+
assert conftest.halves is halves
|
|
69
|
+
assert conftest.divide is divide
|
|
70
|
+
|
|
71
|
+
# Test the original and aliased function names
|
|
72
|
+
assert halves(2) == 1
|
|
73
|
+
assert divide(4) == 2
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_alias_class():
|
|
77
|
+
"""Test using the @alias decorator on a class."""
|
|
78
|
+
|
|
79
|
+
@alias("Greeter")
|
|
80
|
+
class Welcome(object):
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
# Ensure that both the original and the aliased names exists
|
|
84
|
+
assert isinstance(Welcome, type)
|
|
85
|
+
assert isinstance(Greeter, type)
|
|
86
|
+
|
|
87
|
+
# Ensure both the original and the aliased names refer to the same object in memory
|
|
88
|
+
assert Welcome is Greeter
|
|
89
|
+
assert Greeter is Welcome
|
|
5
90
|
|
|
6
91
|
|
|
7
92
|
def test_alias_class_method():
|
|
8
93
|
"""Test using the @alias decorator on a method."""
|
|
9
94
|
|
|
95
|
+
# Create a sample class that aliases a method; due to the way that Python classes
|
|
96
|
+
# are parsed by the interpreter, it is necessary to use a metaclass or custom base
|
|
97
|
+
# class to apply the method aliases to the class scope, thus the use of the aliased
|
|
98
|
+
# metaclass in the class definition below; this special metaclass scans the class'
|
|
99
|
+
# methods, looking for any with assigned aliases, and then adds the aliases to the
|
|
100
|
+
# class' scope so that the methods can be accessed both via their original name and
|
|
101
|
+
# any aliases that have been defined; without the metaclass the aliases won't exist.
|
|
10
102
|
class Welcome(metaclass=aliased):
|
|
11
103
|
@alias("sweet", "greet")
|
|
12
104
|
def hello(self, name: str) -> str:
|
|
13
105
|
return f"hello: {name}"
|
|
14
106
|
|
|
107
|
+
# Ensure both the original and the aliased names refer to the same object in memory
|
|
15
108
|
assert Welcome.hello is Welcome.sweet
|
|
16
109
|
assert Welcome.hello is Welcome.greet
|
|
17
110
|
|
|
111
|
+
# Ensure both the original and the aliased names report as having been aliased
|
|
18
112
|
assert is_aliased(Welcome.hello) is True
|
|
19
113
|
assert is_aliased(Welcome.sweet) is True
|
|
20
114
|
assert is_aliased(Welcome.greet) is True
|
|
21
115
|
|
|
22
|
-
|
|
116
|
+
# Check that the class method object reports its aliases correctly
|
|
23
117
|
assert aliases(Welcome.hello) == ["sweet", "greet"]
|
|
24
118
|
|
|
25
119
|
|
|
26
120
|
def test_alias_class_method_property():
|
|
27
|
-
"""Test using the @alias decorator with
|
|
121
|
+
"""Test using the @alias decorator with @property and @classmethod decorators."""
|
|
28
122
|
|
|
29
123
|
class Welcome(metaclass=aliased):
|
|
30
124
|
@property
|
|
@@ -33,14 +127,16 @@ def test_alias_class_method_property():
|
|
|
33
127
|
def hello(self, name: str) -> str:
|
|
34
128
|
return f"hello: {name}"
|
|
35
129
|
|
|
130
|
+
# Ensure both the original and the aliased names refer to the same object in memory
|
|
36
131
|
assert Welcome.hello is Welcome.sweet
|
|
37
132
|
assert Welcome.hello is Welcome.greet
|
|
38
133
|
|
|
134
|
+
# Ensure both the original and the aliased names report as having been aliased
|
|
39
135
|
assert is_aliased(Welcome.hello) is True
|
|
40
136
|
assert is_aliased(Welcome.sweet) is True
|
|
41
137
|
assert is_aliased(Welcome.greet) is True
|
|
42
138
|
|
|
43
|
-
|
|
139
|
+
# Check that the class method object reports its aliases correctly
|
|
44
140
|
assert aliases(Welcome.hello) == ["sweet", "greet"]
|
|
45
141
|
|
|
46
142
|
|
|
@@ -70,23 +166,34 @@ def test_alias_class_method_alias_with_valid_identifier():
|
|
|
70
166
|
assert welcome.hello("me") == "hello: me"
|
|
71
167
|
assert welcome.greet("me") == "hello: me"
|
|
72
168
|
|
|
169
|
+
# Ensure that aliases are inherited by subclasses
|
|
73
170
|
class SubWelcome(Welcome):
|
|
74
171
|
pass
|
|
75
172
|
|
|
173
|
+
# Ensure that aliases are inherited by subclasses
|
|
76
174
|
assert hasattr(SubWelcome, "hello") is True
|
|
77
175
|
assert hasattr(SubWelcome, "greet") is True
|
|
78
176
|
|
|
177
|
+
# Ensure that aliases are inherited by subclass instances
|
|
79
178
|
subwelcome = SubWelcome()
|
|
80
179
|
|
|
180
|
+
# Ensure that aliases are inherited by subclass instances
|
|
81
181
|
assert hasattr(subwelcome, "hello") is True
|
|
82
182
|
assert hasattr(subwelcome, "greet") is True
|
|
83
183
|
|
|
84
184
|
# Ensure that the aliased method functionality operates as expected
|
|
85
185
|
assert subwelcome.hello("me") == "hello: me"
|
|
86
|
-
|
|
87
|
-
# Ensure when the alias hasn't been registered that access raises an AttributeError
|
|
88
186
|
assert subwelcome.greet("me") == "hello: me"
|
|
89
187
|
|
|
188
|
+
# Ensure when an alias hasn't been registered that access raises an AttributeError
|
|
189
|
+
with pytest.raises(AttributeError) as exception:
|
|
190
|
+
assert subwelcome.sweet("me") == "hello: me"
|
|
191
|
+
|
|
192
|
+
assert (
|
|
193
|
+
str(exception)
|
|
194
|
+
== "AttributeError: 'SubWelcome' object has no attribute 'sweet'. Did you mean: 'greet'?"
|
|
195
|
+
)
|
|
196
|
+
|
|
90
197
|
|
|
91
198
|
def test_alias_class_method_with_invalid_identifier():
|
|
92
199
|
"""Test using the @alias decorator with an invalid identifier."""
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
from classicist.logging import logger
|
|
2
|
-
from classicist.exceptions.decorators.aliased import AliasError
|
|
3
|
-
from classicist.inspector import unwrap
|
|
4
|
-
|
|
5
|
-
from typing import Callable
|
|
6
|
-
from functools import wraps
|
|
7
|
-
|
|
8
|
-
import keyword
|
|
9
|
-
|
|
10
|
-
logger = logger.getChild(__name__)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def alias(*names: tuple[str]) -> Callable:
|
|
14
|
-
"""Decorator that marks a method with one or more alias names. The decorator does
|
|
15
|
-
not modify the function – it simply records the aliases on the function object."""
|
|
16
|
-
|
|
17
|
-
for name in names:
|
|
18
|
-
if not isinstance(name, str):
|
|
19
|
-
raise AliasError(
|
|
20
|
-
"All @alias decorator name arguments must have a string value; non-string values cannot be used!"
|
|
21
|
-
)
|
|
22
|
-
elif len(name) == 0:
|
|
23
|
-
raise AliasError(
|
|
24
|
-
"All @alias decorator name arguments must be valid Python identifier values; empty strings cannot be used!"
|
|
25
|
-
)
|
|
26
|
-
elif not name.isidentifier():
|
|
27
|
-
raise AliasError(
|
|
28
|
-
f"All @alias decorator name arguments must be valid Python identifier values; strings such as '{name}' are not considered valid identifiers by Python!"
|
|
29
|
-
)
|
|
30
|
-
elif keyword.iskeyword(name):
|
|
31
|
-
raise AliasError(
|
|
32
|
-
f"All @alias decorator name arguments must be valid Python identifier values; reserved keywords, such as '{name}' cannot be used!"
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
def decorator(function: Callable) -> Callable:
|
|
36
|
-
function = unwrap(function)
|
|
37
|
-
|
|
38
|
-
if isinstance(aliases := getattr(function, "_classicist_aliases", None), tuple):
|
|
39
|
-
setattr(function, "_classicist_aliases", tuple([*aliases, *names]))
|
|
40
|
-
else:
|
|
41
|
-
setattr(function, "_classicist_aliases", names)
|
|
42
|
-
|
|
43
|
-
@wraps(function)
|
|
44
|
-
def wrapper(*args, **kwargs):
|
|
45
|
-
return function(*args, **kwargs)
|
|
46
|
-
|
|
47
|
-
return wrapper
|
|
48
|
-
|
|
49
|
-
return decorator
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def is_aliased(function: callable) -> bool:
|
|
53
|
-
"""The is_aliased() helper method can be used to determine if a class method has
|
|
54
|
-
been aliased."""
|
|
55
|
-
|
|
56
|
-
function = unwrap(function)
|
|
57
|
-
|
|
58
|
-
return isinstance(getattr(function, "_classicist_aliases", None), tuple)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def aliases(function: callable) -> list[str]:
|
|
62
|
-
"""The aliases() helper method can be used to obtain any class method aliases."""
|
|
63
|
-
|
|
64
|
-
function = unwrap(function)
|
|
65
|
-
|
|
66
|
-
if isinstance(aliases := getattr(function, "_classicist_aliases", None), tuple):
|
|
67
|
-
return list(aliases)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
__all__ = [
|
|
71
|
-
"alias",
|
|
72
|
-
"is_aliased",
|
|
73
|
-
"aliases",
|
|
74
|
-
]
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
1.0.1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{classicist-1.0.1 → classicist-1.0.3}/source/classicist/decorators/classproperty/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{classicist-1.0.1 → classicist-1.0.3}/source/classicist/exceptions/decorators/aliased/__init__.py
RENAMED
|
File without changes
|
{classicist-1.0.1 → classicist-1.0.3}/source/classicist/exceptions/decorators/annotation/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|