From ade49661e64d23ac8b3e3199811214907a0fcbe5 Mon Sep 17 00:00:00 2001 From: ken-morel Date: Wed, 5 Jun 2024 22:53:39 +0100 Subject: [PATCH 1/9] Update __init__.py --- src/pyoload/__init__.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/pyoload/__init__.py b/src/pyoload/__init__.py index 78c8208..5e38190 100644 --- a/src/pyoload/__init__.py +++ b/src/pyoload/__init__.py @@ -9,15 +9,16 @@ from inspect import getmodule from inspect import isclass from inspect import signature -from types import UnionType +from typing import get_origin +from typing import get_args from typing import Any from typing import Callable from typing import GenericAlias from typing import Type try: - from typing import NoneType + from types import NoneType except ImportError: - NoneType = None + NoneType = type(None) class AnnotationError(ValueError): @@ -354,7 +355,7 @@ def cast(val: Any, totype: Any) -> Any: else: sub = totype.__args__[0] return totype.__origin__([Cast.cast(v, sub) for v in val]) - if isinstance(totype, UnionType): + if get_origin(totype) is Union: errors = [] for subtype in totype.__args__: try: @@ -485,14 +486,16 @@ def typeMatch(val: Any, spec: Any) -> bool: else: return True elif isinstance(spec, GenericAlias): - if not isinstance(val, spec.__origin__): + orig = get_origin(spec) + if not isinstance(val, orig): return False - if spec.__origin__ == dict: - if len(spec.__args__) == 2: - kt, vt = spec.__args__ - elif len(spec.__args__) == 1: - kt, vt = Any, spec.__args__[1] + if orig == dict: + args = get_args(spec) + if len(args) == 2: + kt, vt = args + elif len(args) == 1: + kt, vt = Any, args[1] else: return True @@ -502,7 +505,7 @@ def typeMatch(val: Any, spec: Any) -> bool: else: return True else: - sub = spec.__args__[0] + sub = get_args(spec)[0] for val in val: if not typeMatch(val, sub): return False From 6e87d4566813fa871d55a69440c8dcdabced69c2 Mon Sep 17 00:00:00 2001 From: ken-morel Date: Wed, 5 Jun 2024 23:32:38 +0100 Subject: [PATCH 2/9] Update __init__.py --- src/pyoload/__init__.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/pyoload/__init__.py b/src/pyoload/__init__.py index 5e38190..94eabc0 100644 --- a/src/pyoload/__init__.py +++ b/src/pyoload/__init__.py @@ -344,20 +344,21 @@ def cast(val: Any, totype: Any) -> Any: :returns: An instance of the casting type """ if isinstance(totype, GenericAlias): - if totype.__origin__ == dict: - if len(totype.__args__) == 2: - kt, vt = totype.__args__ - elif len(totype.__args__) == 1: - kt, vt = Any, totype.__args__[1] + if get_origin(totype) == dict: + args = get_args(totype) + if len(args) == 2: + kt, vt = args + elif len(args) == 1: + kt, vt = args[0], Any return { Cast.cast(k, kt): Cast.cast(v, vt) for k, v in val.items() } else: - sub = totype.__args__[0] - return totype.__origin__([Cast.cast(v, sub) for v in val]) + sub = args[0] + return get_origin(totype)([Cast.cast(v, sub) for v in val]) if get_origin(totype) is Union: errors = [] - for subtype in totype.__args__: + for subtype in get_args(totype): try: return Cast.cast(val, subtype) except Exception as e: @@ -513,7 +514,7 @@ def typeMatch(val: Any, spec: Any) -> bool: return True -def resolveAnnotations(obj: Type | Callable) -> None: +def resolveAnnotations(obj: Callable) -> None: """ Evaluates all the stringized annotations of the argument @@ -708,7 +709,7 @@ def wrapper(*args, **kw): return wrapper -def annotateClass(cls: Any): +def annotateClass(cls: Any, recur: bool = True): """ Annotates a class object, wrapping and replacing over it's __setattr__ and typechecking over each attribute assignment. @@ -717,11 +718,12 @@ def annotateClass(cls: Any): it recursively annotates the classes methods except `__pyod_norecur__` attribute is defines """ + + if isinstance(cls, bool): + return partial(annotateClass, recur=cls) if not hasattr(cls, "__annotations__"): cls.__annotations__ = {} - if isinstance(cls, bool): - return partial(annotate, recur=cls) - recur = not hasattr(cls, "__pyod_norecur__") + recur = not hasattr(cls, "__pyod_norecur__") and recur setter = cls.__setattr__ if recur: for x in dir(cls): @@ -739,7 +741,7 @@ def annotateClass(cls: Any): @wraps(cls.__setattr__) def new_setter(self: Any, name: str, value: Any) -> Any: - if any(isinstance(x, str) for x in self.__annotations__.values()): + if str in map(type, self.__annotations__.values()): resolveAnnotations(self) if name not in self.__annotations__: From 4ac911166df20ec060a6b4fab7bfd5f40176f7c5 Mon Sep 17 00:00:00 2001 From: ken-morel Date: Wed, 5 Jun 2024 23:35:59 +0100 Subject: [PATCH 3/9] Update __init__.py --- src/pyoload/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyoload/__init__.py b/src/pyoload/__init__.py index 94eabc0..230a26b 100644 --- a/src/pyoload/__init__.py +++ b/src/pyoload/__init__.py @@ -658,7 +658,7 @@ def is_annoted(func): __overloads__: dict[str, list[Callable]] = {} -def overload(func: Callable, name: str | None = None) -> Callable: +def overload(func: Callable, name: str = None) -> Callable: """ returns a wrapper over the passed function which typechecks arguments on each call From be243079891705f7c4abc75a1690902277407d85 Mon Sep 17 00:00:00 2001 From: ken-morel Date: Wed, 5 Jun 2024 23:42:05 +0100 Subject: [PATCH 4/9] Update setup.py --- src/setup.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/setup.py b/src/setup.py index f37b6b3..691fc38 100644 --- a/src/setup.py +++ b/src/setup.py @@ -32,12 +32,12 @@ ) extra_dev = ( - *extra_flake8, + # *extra_flake8, *extra_test, ) extra_ci = ( - *extra_flake8, + # *extra_flake8, *extra_test, 'coveralls', ) @@ -88,7 +88,6 @@ 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', From cbda60731b104815fc8e02ba6b0f8730d5908140 Mon Sep 17 00:00:00 2001 From: ken-morel Date: Wed, 5 Jun 2024 23:50:45 +0100 Subject: [PATCH 5/9] Update __init__.py --- src/pyoload/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pyoload/__init__.py b/src/pyoload/__init__.py index 230a26b..0af6602 100644 --- a/src/pyoload/__init__.py +++ b/src/pyoload/__init__.py @@ -15,6 +15,7 @@ from typing import Callable from typing import GenericAlias from typing import Type +from typing import Union try: from types import NoneType except ImportError: From b93e15caf03b5aed4dbc5f3bf7f867264114e1bc Mon Sep 17 00:00:00 2001 From: ken-morel Date: Wed, 5 Jun 2024 23:53:34 +0100 Subject: [PATCH 6/9] Update __init__.py --- src/pyoload/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyoload/__init__.py b/src/pyoload/__init__.py index 0af6602..d50a3c9 100644 --- a/src/pyoload/__init__.py +++ b/src/pyoload/__init__.py @@ -345,8 +345,8 @@ def cast(val: Any, totype: Any) -> Any: :returns: An instance of the casting type """ if isinstance(totype, GenericAlias): + args = get_args(totype) if get_origin(totype) == dict: - args = get_args(totype) if len(args) == 2: kt, vt = args elif len(args) == 1: From 38ff8099e28b814200cb546bbbc66e158e6a46b5 Mon Sep 17 00:00:00 2001 From: ken-morel Date: Wed, 5 Jun 2024 23:56:46 +0100 Subject: [PATCH 7/9] Update test_cast.py --- src/tests/test_cast.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/tests/test_cast.py b/src/tests/test_cast.py index 1302c40..4bf007c 100644 --- a/src/tests/test_cast.py +++ b/src/tests/test_cast.py @@ -6,13 +6,14 @@ from pyoload import annotate from pyoload import typeMatch from pyoload import AnnotationError +from pyoload import Union assert pyoload.__version__ == "2.0.0" @annotate class foo: - foo = CastedAttr(dict[str, tuple[int | str]]) + foo = CastedAttr(dict[str, tuple[Union[int, str]]]) bar: Cast(list[tuple[float]]) a: "str" @@ -30,7 +31,7 @@ def __init__(self: "Any", bar: "list") -> Any: def test_cast(): q = foo([(1, "67")]) q.foo = {1234: {"5", 16j}} - assert typeMatch(q.foo, dict[str, tuple[int | str]]) + assert typeMatch(q.foo, dict[str, tuple[Union[int, str]]]) assert typeMatch(q.bar, list[tuple[float]]) From 81162a13c244477096b50a508b04e7d9abd8fddb Mon Sep 17 00:00:00 2001 From: ken-morel Date: Thu, 6 Jun 2024 00:02:43 +0100 Subject: [PATCH 8/9] Update test_speed.py --- src/tests/test_speed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/test_speed.py b/src/tests/test_speed.py index 63b5e61..328ad91 100644 --- a/src/tests/test_speed.py +++ b/src/tests/test_speed.py @@ -71,7 +71,7 @@ def speedCast(f): dt = (end - begin) / N / 1000 print(f'Cast str->int: {dt}ms', file=f) - ct = Cast(int | str) + ct = Cast(Union[int, str]) begin = nanos() for x in range(N // NS): ct(3j) From 883e5dbdc3ac183406c039cd590f228ebcc63364 Mon Sep 17 00:00:00 2001 From: ken-morel Date: Thu, 6 Jun 2024 00:57:13 +0100 Subject: [PATCH 9/9] ??? --- docs/conf.py | 32 +++++++------- src/pyoload/__init__.py | 13 +++--- src/setup.py | 85 +++++++++++++++++--------------------- src/tests/logs.yaml | 7 ++++ src/tests/test_annotate.py | 7 ++-- src/tests/test_overload.py | 1 + src/tests/test_speed.py | 30 +++++++------- 7 files changed, 88 insertions(+), 87 deletions(-) create mode 100644 src/tests/logs.yaml diff --git a/docs/conf.py b/docs/conf.py index 1aed149..2418495 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -34,38 +34,38 @@ autoclass_content = "both" sys.setrecursionlimit(500) -project = 'pyoload' -copyright = '2024, ken-morel' -author = 'ken-morel' +project = "pyoload" +copyright = "2024, ken-morel" +author = "ken-morel" -release = '2.0.0' -version = '2.0.0' +release = "2.0.0" +version = "2.0.0" # -- General configuration extensions = [ - 'sphinx.ext.duration', - 'sphinx.ext.doctest', - 'sphinx.ext.autodoc', - 'sphinx.ext.autosummary', - 'sphinx.ext.intersphinx', + "sphinx.ext.duration", + "sphinx.ext.doctest", + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.intersphinx", ] intersphinx_mapping = { - 'python': ('https://docs.python.org/3/', None), - 'sphinx': ('https://www.sphinx-doc.org/en/master/', None), - 'github': ('https://github.com/', None), + "python": ("https://docs.python.org/3/", None), + "sphinx": ("https://www.sphinx-doc.org/en/master/", None), + "github": ("https://github.com/", None), } -intersphinx_disabled_domains = ['std'] +intersphinx_disabled_domains = ["std"] -templates_path = ['_templates'] +templates_path = ["_templates"] # -- Options for HTML output html_theme = "sphinxawesome_theme" # -- Options for EPUB output -epub_show_urls = 'footnote' +epub_show_urls = "footnote" rst_prolog = """\ .. role:: python(code) diff --git a/src/pyoload/__init__.py b/src/pyoload/__init__.py index d50a3c9..d8b5858 100644 --- a/src/pyoload/__init__.py +++ b/src/pyoload/__init__.py @@ -16,8 +16,9 @@ from typing import GenericAlias from typing import Type from typing import Union + try: - from types import NoneType + from types import NoneType except ImportError: NoneType = type(None) @@ -351,9 +352,7 @@ def cast(val: Any, totype: Any) -> Any: kt, vt = args elif len(args) == 1: kt, vt = args[0], Any - return { - Cast.cast(k, kt): Cast.cast(v, vt) for k, v in val.items() - } + return {Cast.cast(k, kt): Cast.cast(v, vt) for k, v in val.items()} else: sub = args[0] return get_origin(totype)([Cast.cast(v, sub) for v in val]) @@ -633,7 +632,7 @@ def wrapper(*pargs, **kw): def unannotate(func: Callable) -> Callable: - if hasattr(func, '__pyod_annotate__'): + if hasattr(func, "__pyod_annotate__"): return func.__pyod_annotate__ else: return func @@ -649,11 +648,11 @@ def annotable(func: Callable) -> Callable: def is_annotable(func): - return not hasattr(func, '__pyod_annotable__') or func.__pyod_annotable__ + return not hasattr(func, "__pyod_annotable__") or func.__pyod_annotable__ def is_annoted(func): - return hasattr(func, '__pyod_annotate__') + return hasattr(func, "__pyod_annotate__") __overloads__: dict[str, list[Callable]] = {} diff --git a/src/setup.py b/src/setup.py index 691fc38..c22c7fd 100644 --- a/src/setup.py +++ b/src/setup.py @@ -7,28 +7,28 @@ project_dir = Path(__file__).parent try: - long_description = (project_dir / 'README.md').read_text() + long_description = (project_dir / "README.md").read_text() except FileNotFoundError: try: - long_description = Path('README.md').read_text() + long_description = Path("README.md").read_text() except FileNotFoundError: try: - long_description = Path('/src/README.md').read_text() + long_description = Path("/src/README.md").read_text() except FileNotFoundError: - long_description = (project_dir.parent / 'README.md').read_text() + long_description = (project_dir.parent / "README.md").read_text() deps = () extra_flake8 = ( - 'flake8', - 'flake8-commas', - 'flake8-quotes', - 'flake8-multiline-containers', + "flake8", + "flake8-commas", + "flake8-quotes", + "flake8-multiline-containers", ) extra_test = ( - 'pytest', - 'pytest-cov', + "pytest", + "pytest-cov", ) extra_dev = ( @@ -39,62 +39,53 @@ extra_ci = ( # *extra_flake8, *extra_test, - 'coveralls', + "coveralls", ) setup( - name='pyoload', + name="pyoload", version=__version__, - packages=find_packages(exclude=['tests', 'tests.*']), + packages=find_packages(exclude=["tests", "tests.*"]), project_urls={ - 'Documentation': 'https://pyoload.readthedocs.io/', - 'Funding': 'https://ko-fi.com/kenmorel', - 'Source': 'https://github.com/ken-morel/pyoload/', - 'Tracker': 'https://github.com/ken-morel/pyoload/issues', + "Documentation": "https://pyoload.readthedocs.io/", + "Funding": "https://ko-fi.com/kenmorel", + "Source": "https://github.com/ken-morel/pyoload/", + "Tracker": "https://github.com/ken-morel/pyoload/issues", }, - url='https://github.com/ken-morel/pyoload', - license='MIT', - author='ken-morel', - author_email='engonken8@gmail.com', - maintainer='ken-morel', - maintainer_email='engonken8@gmail.com', + url="https://github.com/ken-morel/pyoload", + license="MIT", + author="ken-morel", + author_email="engonken8@gmail.com", + maintainer="ken-morel", + maintainer_email="engonken8@gmail.com", description=( - 'Python package for function argument overload,' - ' typechecking and casting' + "Python package for function argument overload," " typechecking and casting" ), long_description=long_description, - long_description_content_type='text/markdown', - + long_description_content_type="text/markdown", install_requires=deps, extras_require={ - 'dev': extra_dev, - 'ci': extra_ci, + "dev": extra_dev, + "ci": extra_ci, }, - classifiers=[ # See https://pypi.org/classifiers/ - - 'Intended Audience :: Developers', - + "Intended Audience :: Developers", # 'Development Status :: 1 - Planning', # 'Development Status :: 2 - Pre-Alpha', - 'Development Status :: 3 - Alpha', + "Development Status :: 3 - Alpha", # 'Development Status :: 4 - Beta', # 'Development Status :: 5 - Production/Stable', # 'Development Status :: 6 - Mature', # 'Development Status :: 7 - Inactive', - - 'License :: OSI Approved :: MIT License', - - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', - - 'Natural Language :: English', - - 'Topic :: Software Development :: Libraries :: Python Modules', + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Natural Language :: English", + "Topic :: Software Development :: Libraries :: Python Modules", ], ) diff --git a/src/tests/logs.yaml b/src/tests/logs.yaml new file mode 100644 index 0000000..36fdefe --- /dev/null +++ b/src/tests/logs.yaml @@ -0,0 +1,7 @@ +typeMatch vs isinstance on int:True 0.26837916ms +typeMatch vs isinstance on int:False 0.24146669ms +typeMatch on dict[str, int]*50:True 29.45430688ms +typeMatch on dict[str, int]*50:False 5.970363880000001ms +Cast str->int: 2.92456138ms +Cast complex->int | str: 0.12061774399999999ms +Cast dict[int,list[str]*10]*10->dict[str,tuple[float]]: 2.7908898460000002ms diff --git a/src/tests/test_annotate.py b/src/tests/test_annotate.py index c10455a..75882cb 100644 --- a/src/tests/test_annotate.py +++ b/src/tests/test_annotate.py @@ -2,11 +2,11 @@ from pyoload import annotate -assert pyoload.__version__ == '2.0.0' +assert pyoload.__version__ == "2.0.0" @annotate -def foo(a, b=3, c: str = 'R') -> int: +def foo(a, b=3, c: str = "R") -> int: assert b == 3 assert isinstance(c, str) return 3 @@ -15,5 +15,6 @@ def foo(a, b=3, c: str = 'R') -> int: def test_annotate(): foo(2) -if __name__ == '__main__': + +if __name__ == "__main__": test_annotate() diff --git a/src/tests/test_overload.py b/src/tests/test_overload.py index dfaf730..96bccf6 100644 --- a/src/tests/test_overload.py +++ b/src/tests/test_overload.py @@ -61,5 +61,6 @@ def test_overload(): assert foo(1, 2) == 2 assert foo(1, 2, 3) == 3 + if __name__ == "__main__": test_overload() diff --git a/src/tests/test_speed.py b/src/tests/test_speed.py index 328ad91..df95d50 100644 --- a/src/tests/test_speed.py +++ b/src/tests/test_speed.py @@ -1,15 +1,17 @@ -from pyoload import typeMatch, Cast -from time import perf_counter_ns as nanos from pathlib import Path +from pyoload import Cast +from pyoload import Union +from pyoload import typeMatch +from time import perf_counter_ns as nanos N = 100000 NS = 10 -src = Path('.').resolve().parent +src = Path(__file__).resolve().parent def test_speed(): - f = open(src / "logs.yaml", 'w') + f = open(src / "logs.yaml", "w") speedTypeMatch(f) speedCast(f) f.close() @@ -26,7 +28,7 @@ def speedTypeMatch(f): end2 = nanos() dt = (end - begin) - (end2 - begin2) dt = dt / 1000 / N - print(f'typeMatch vs isinstance on int:True {dt}ms', file=f) + print(f"typeMatch vs isinstance on int:True {dt}ms", file=f) begin = nanos() for _ in range(N): @@ -38,7 +40,7 @@ def speedTypeMatch(f): end2 = nanos() dt = (end - begin) - (end2 - begin2) dt = dt / 1000 / N - print(f'typeMatch vs isinstance on int:False {dt}ms', file=f) + print(f"typeMatch vs isinstance on int:False {dt}ms", file=f) obj = {str(x): x for x in range(50)} t = dict[str, int] @@ -46,9 +48,9 @@ def speedTypeMatch(f): for _ in range(N): typeMatch(obj, t) end = nanos() - dt = (end - begin) + dt = end - begin dt = dt / 1000 / N - print(f'typeMatch on dict[str, int]*50:True {dt}ms', file=f) + print(f"typeMatch on dict[str, int]*50:True {dt}ms", file=f) obj = {complex(x): float(x) for x in range(50)} t = dict[str, int] @@ -56,9 +58,9 @@ def speedTypeMatch(f): for _ in range(N): typeMatch(obj, t) end = nanos() - dt = (end - begin) + dt = end - begin dt = dt / 1000 / N - print(f'typeMatch on dict[str, int]*50:False {dt}ms', file=f) + print(f"typeMatch on dict[str, int]*50:False {dt}ms", file=f) def speedCast(f): @@ -66,10 +68,10 @@ def speedCast(f): begin = nanos() for x in range(N): - ct('3') + ct("3") end = nanos() dt = (end - begin) / N / 1000 - print(f'Cast str->int: {dt}ms', file=f) + print(f"Cast str->int: {dt}ms", file=f) ct = Cast(Union[int, str]) begin = nanos() @@ -77,7 +79,7 @@ def speedCast(f): ct(3j) end = nanos() dt = (end - begin) / N / NS / 1000 - print(f'Cast complex->int | str: {dt}ms', file=f) + print(f"Cast complex->int | str: {dt}ms", file=f) v = {x: [str(x)] * NS for x in range(NS)} ct = Cast(dict[str, tuple[float]]) @@ -87,6 +89,6 @@ def speedCast(f): end = nanos() dt = (end - begin) / N / NS / 1000 print( - f'Cast dict[int,list[str]*{NS}]*{NS}->dict[str,tuple[float]]: {dt}ms', + f"Cast dict[int,list[str]*{NS}]*{NS}->dict[str,tuple[float]]: {dt}ms", file=f, )