-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
369 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,7 @@ dependencies: | |
- rich | ||
- sqlalchemy >=1.4.36 | ||
- tomli >=1.0.0 | ||
- typing_extensions | ||
|
||
- pip: | ||
- ../ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,7 @@ dependencies: | |
- rich | ||
- sqlalchemy >=1.4.36 | ||
- tomli >=1.0.0 | ||
- typing_extensions | ||
|
||
# Misc | ||
- black | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
from __future__ import annotations | ||
|
||
import functools | ||
import sys | ||
import types | ||
from typing import Any | ||
from typing import Callable | ||
from typing import Mapping | ||
|
||
|
||
__all__ = ["get_annotations"] | ||
|
||
|
||
if sys.version_info >= (3, 10): | ||
from inspect import get_annotations | ||
else: | ||
|
||
def get_annotations( # noqa: C901, PLR0912, PLR0915 | ||
obj: Callable[..., object] | type[Any] | types.ModuleType, | ||
*, | ||
globals: Mapping[str, Any] | None = None, # noqa: A002 | ||
locals: Mapping[str, Any] | None = None, # noqa: A002 | ||
eval_str: bool = False, | ||
) -> dict[str, Any]: | ||
"""Compute the annotations dict for an object. | ||
obj may be a callable, class, or module. | ||
Passing in an object of any other type raises TypeError. | ||
Returns a dict. get_annotations() returns a new dict every time | ||
it's called; calling it twice on the same object will return two | ||
different but equivalent dicts. | ||
This function handles several details for you: | ||
* If eval_str is true, values of type str will | ||
be un-stringized using eval(). This is intended | ||
for use with stringized annotations | ||
("from __future__ import annotations"). | ||
* If obj doesn't have an annotations dict, returns an | ||
empty dict. (Functions and methods always have an | ||
annotations dict; classes, modules, and other types of | ||
callables may not.) | ||
* Ignores inherited annotations on classes. If a class | ||
doesn't have its own annotations dict, returns an empty dict. | ||
* All accesses to object members and dict values are done | ||
using getattr() and dict.get() for safety. | ||
* Always, always, always returns a freshly-created dict. | ||
eval_str controls whether or not values of type str are replaced | ||
with the result of calling eval() on those values: | ||
* If eval_str is true, eval() is called on values of type str. | ||
* If eval_str is false (the default), values of type str are unchanged. | ||
globals and locals are passed in to eval(); see the documentation | ||
for eval() for more information. If either globals or locals is | ||
None, this function may replace that value with a context-specific | ||
default, contingent on type(obj): | ||
* If obj is a module, globals defaults to obj.__dict__. | ||
* If obj is a class, globals defaults to | ||
sys.modules[obj.__module__].__dict__ and locals | ||
defaults to the obj class namespace. | ||
* If obj is a callable, globals defaults to obj.__globals__, | ||
although if obj is a wrapped function (using | ||
functools.update_wrapper()) it is first unwrapped. | ||
""" | ||
if isinstance(obj, type): | ||
# class | ||
obj_dict = getattr(obj, "__dict__", None) | ||
if obj_dict and hasattr(obj_dict, "get"): | ||
ann = obj_dict.get("__annotations__", None) | ||
if isinstance(ann, types.GetSetDescriptorType): | ||
ann = None | ||
else: | ||
ann = None | ||
|
||
obj_globals = None | ||
module_name = getattr(obj, "__module__", None) | ||
if module_name: | ||
module = sys.modules.get(module_name, None) | ||
if module: | ||
obj_globals = getattr(module, "__dict__", None) | ||
obj_locals = dict(vars(obj)) | ||
unwrap = obj | ||
elif isinstance(obj, types.ModuleType): | ||
# module | ||
ann = getattr(obj, "__annotations__", None) | ||
obj_globals = obj.__dict__ | ||
obj_locals = None | ||
unwrap = None | ||
elif callable(obj): | ||
# this includes types.Function, types.BuiltinFunctionType, | ||
# types.BuiltinMethodType, functools.partial, functools.singledispatch, | ||
# "class funclike" from Lib/test/test_inspect... on and on it goes. | ||
ann = getattr(obj, "__annotations__", None) | ||
obj_globals = getattr(obj, "__globals__", None) | ||
obj_locals = None | ||
unwrap = obj | ||
else: | ||
raise TypeError(f"{obj!r} is not a module, class, or callable.") | ||
|
||
if ann is None: | ||
return {} | ||
|
||
if not isinstance(ann, dict): | ||
raise ValueError(f"{obj!r}.__annotations__ is neither a dict nor None") | ||
|
||
if not ann: | ||
return {} | ||
|
||
if not eval_str: | ||
return dict(ann) | ||
|
||
if unwrap is not None: | ||
while True: | ||
if hasattr(unwrap, "__wrapped__"): | ||
unwrap = unwrap.__wrapped__ | ||
continue | ||
if isinstance(unwrap, functools.partial): | ||
unwrap = unwrap.func | ||
continue | ||
break | ||
if hasattr(unwrap, "__globals__"): | ||
obj_globals = unwrap.__globals__ | ||
|
||
if globals is None: | ||
globals = obj_globals # noqa: A001 | ||
if locals is None: | ||
locals = obj_locals # noqa: A001 | ||
|
||
eval_func = eval | ||
return_value = { | ||
key: value | ||
if not isinstance(value, str) | ||
else eval_func(value, globals, locals) | ||
for key, value in ann.items() | ||
} | ||
return return_value |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.