From 5019468518f27192d1bec4fe5a9835f13dd61048 Mon Sep 17 00:00:00 2001 From: Sean Stewart Date: Wed, 10 Jul 2024 21:25:36 -0400 Subject: [PATCH] feat: re-organize utility modules --- src/typelib/binding.py | 3 ++- src/typelib/constants.py | 12 +----------- src/typelib/ctx.py | 5 +++-- src/typelib/graph.py | 3 ++- src/typelib/interchange.py | 2 +- src/typelib/marshal/api.py | 3 ++- src/typelib/marshal/routines.py | 3 ++- src/typelib/py/__init__.py | 1 + src/typelib/{ => py}/classes.py | 0 src/typelib/{ => py}/compat.py | 0 src/typelib/{ => py}/contrib.py | 0 src/typelib/{ => py}/frames.py | 2 +- src/typelib/{ => py}/future.py | 23 +++++++++++++++-------- src/typelib/{ => py}/inspection.py | 15 ++++++++------- src/typelib/{ => py}/refs.py | 2 +- src/typelib/serdes.py | 3 ++- src/typelib/unmarshal/api.py | 3 ++- src/typelib/unmarshal/routines.py | 3 ++- tests/unit/marshal/test_api.py | 2 +- tests/unit/py/__init__.py | 0 tests/unit/{ => py}/test_classes.py | 2 +- tests/unit/{ => py}/test_frames.py | 2 +- tests/unit/{ => py}/test_future.py | 2 +- tests/unit/{ => py}/test_inspection.py | 2 +- tests/unit/{ => py}/test_refs.py | 2 +- tests/unit/test_graph.py | 3 ++- tests/unit/test_interchange.py | 3 ++- tests/unit/unmarshal/test_api.py | 2 +- tests/unit/unmarshal/test_routines.py | 4 +++- 29 files changed, 59 insertions(+), 48 deletions(-) create mode 100644 src/typelib/py/__init__.py rename src/typelib/{ => py}/classes.py (100%) rename src/typelib/{ => py}/compat.py (100%) rename src/typelib/{ => py}/contrib.py (100%) rename src/typelib/{ => py}/frames.py (96%) rename src/typelib/{ => py}/future.py (75%) rename src/typelib/{ => py}/inspection.py (99%) rename src/typelib/{ => py}/refs.py (99%) create mode 100644 tests/unit/py/__init__.py rename tests/unit/{ => py}/test_classes.py (98%) rename tests/unit/{ => py}/test_frames.py (99%) rename tests/unit/{ => py}/test_future.py (97%) rename tests/unit/{ => py}/test_inspection.py (99%) rename tests/unit/{ => py}/test_refs.py (98%) diff --git a/src/typelib/binding.py b/src/typelib/binding.py index f2ef192..51d34a9 100644 --- a/src/typelib/binding.py +++ b/src/typelib/binding.py @@ -8,7 +8,8 @@ import inspect import typing as tp -from typelib import classes, compat, inspection, unmarshal +from typelib import unmarshal +from typelib.py import classes, compat, inspection P = compat.ParamSpec("P") R = tp.TypeVar("R") diff --git a/src/typelib/constants.py b/src/typelib/constants.py index e60c26b..98a2f4a 100644 --- a/src/typelib/constants.py +++ b/src/typelib/constants.py @@ -1,4 +1,4 @@ -import inspect +"""Constants used throughout the library.""" class empty: @@ -6,14 +6,4 @@ class empty: DEFAULT_ENCODING = "utf-8" -POSITIONAL_ONLY = inspect.Parameter.POSITIONAL_ONLY -POSITIONAL_OR_KEYWORD = inspect.Parameter.POSITIONAL_OR_KEYWORD -KEYWORD_ONLY = inspect.Parameter.KEYWORD_ONLY -SELF_NAME = "self" -TOO_MANY_POS = "too many positional arguments" -VAR_POSITIONAL = inspect.Parameter.VAR_POSITIONAL -VAR_KEYWORD = inspect.Parameter.VAR_KEYWORD -KWD_KINDS = (VAR_KEYWORD, KEYWORD_ONLY) -POS_KINDS = (VAR_POSITIONAL, POSITIONAL_ONLY) -NULLABLES = (None, Ellipsis, type(None), type(Ellipsis)) PKG_NAME = __name__.split(".", maxsplit=1)[0] diff --git a/src/typelib/ctx.py b/src/typelib/ctx.py index d091f42..f67684a 100644 --- a/src/typelib/ctx.py +++ b/src/typelib/ctx.py @@ -1,10 +1,11 @@ -"""A simple hashmap type for working with types in a contextual manner.""" +"""A simple hashmap for working with types in a contextual manner.""" from __future__ import annotations import dataclasses -from typelib import graph, inspection, refs +from typelib import graph +from typelib.py import inspection, refs class TypeContext(dict): diff --git a/src/typelib/graph.py b/src/typelib/graph.py index 52d71b3..1207b7a 100644 --- a/src/typelib/graph.py +++ b/src/typelib/graph.py @@ -8,7 +8,8 @@ import inspect import typing -from typelib import classes, compat, constants, inspection, refs +from typelib import constants +from typelib.py import classes, compat, inspection, refs __all__ = ("static_order", "itertypes", "get_type_graph") diff --git a/src/typelib/interchange.py b/src/typelib/interchange.py index 6cd10f8..f404633 100644 --- a/src/typelib/interchange.py +++ b/src/typelib/interchange.py @@ -5,10 +5,10 @@ import dataclasses import typing as tp -from typelib import classes, compat, inspection from typelib import codec as mcodec from typelib import marshal as mmarshal from typelib import unmarshal as munmarshal +from typelib.py import classes, compat, inspection __all__ = ("protocol", "InterchangeProtocol") diff --git a/src/typelib/marshal/api.py b/src/typelib/marshal/api.py index 6a337e7..5f6dde1 100644 --- a/src/typelib/marshal/api.py +++ b/src/typelib/marshal/api.py @@ -4,8 +4,9 @@ import typing as tp -from typelib import compat, ctx, graph, inspection, refs, serdes +from typelib import ctx, graph, serdes from typelib.marshal import routines +from typelib.py import compat, inspection, refs T = tp.TypeVar("T") diff --git a/src/typelib/marshal/routines.py b/src/typelib/marshal/routines.py index 8fbee4e..8152b9c 100644 --- a/src/typelib/marshal/routines.py +++ b/src/typelib/marshal/routines.py @@ -12,7 +12,8 @@ import typing as tp import uuid -from typelib import compat, graph, inspection, serdes +from typelib import graph, serdes +from typelib.py import compat, inspection T = tp.TypeVar("T") diff --git a/src/typelib/py/__init__.py b/src/typelib/py/__init__.py new file mode 100644 index 0000000..20b1266 --- /dev/null +++ b/src/typelib/py/__init__.py @@ -0,0 +1 @@ +"""Components for Python compatibility, introspection, and reflection.""" diff --git a/src/typelib/classes.py b/src/typelib/py/classes.py similarity index 100% rename from src/typelib/classes.py rename to src/typelib/py/classes.py diff --git a/src/typelib/compat.py b/src/typelib/py/compat.py similarity index 100% rename from src/typelib/compat.py rename to src/typelib/py/compat.py diff --git a/src/typelib/contrib.py b/src/typelib/py/contrib.py similarity index 100% rename from src/typelib/contrib.py rename to src/typelib/py/contrib.py diff --git a/src/typelib/frames.py b/src/typelib/py/frames.py similarity index 96% rename from src/typelib/frames.py rename to src/typelib/py/frames.py index cd4670f..cd86aa5 100644 --- a/src/typelib/frames.py +++ b/src/typelib/py/frames.py @@ -1,4 +1,4 @@ -"""Utilities for with stack traces and frames.""" +"""Utilities for working with stack traces and frames.""" from __future__ import annotations diff --git a/src/typelib/future.py b/src/typelib/py/future.py similarity index 75% rename from src/typelib/future.py rename to src/typelib/py/future.py index 92d481c..1856dd5 100644 --- a/src/typelib/future.py +++ b/src/typelib/py/future.py @@ -13,30 +13,34 @@ @functools.cache def transform(annotation: str, *, union: str = "typing.Union") -> str: - """Transform a :py:class:`types.UnionType` (``str | int``) into a :py:class:`typing.Union`. + """Transform a modern annotations into their :py:mod:`typing` equivalent: + + - :py:class:`types.UnionType` into a :py:class:`typing.Union` (``str | int`` -> ``typing.Union[str, int]``) + - builtin generics into typing generics (``dict[str, int]`` -> ``typing.Dict[str, int]``) Args: annotation: The annotation to transform, as a string. union: The name of the Union type to subscript (defaults `"typing.Union"`). Notes: - This is a raw string transformation that does not test for the *correctness* - of your annotation. As such, if you attempt to evaluate the transformed string - at runtime and there are errors in your declaration, they will result in an - error in the transformed annotation as well. + While this transformation requires your expression be valid Python syntax, it + doesn't make sure the type annotation is valid. """ parsed = ast.parse(annotation, mode="eval") - transformed = TransformUnion(union=union).generic_visit(parsed) + transformed = TransformAnnotation(union=union).generic_visit(parsed) unparsed = ast.unparse(transformed).strip() return unparsed -class TransformUnion(ast.NodeTransformer): +class TransformAnnotation(ast.NodeTransformer): + """A :py:class:`ast.NodeTransformer` that transforms :py:class:`typing.Union`.""" + def __init__(self, union: str = "typing.Union") -> None: self.union = union def visit_BinOp(self, node: ast.BinOp): - # Ignore anython but a bitwise OR `|` + """Transform a :py:class:`ast.BinOp` to :py:class:`typing.Union`.""" + # Ignore anything but a bitwise OR `|` if not isinstance(node.op, ast.BitOr): return node # Build a stack of args to the bitor @@ -59,6 +63,7 @@ def visit_BinOp(self, node: ast.BinOp): return union def visit_Name(self, node: ast.Name): + """Transform a builtin :py:class:`ast.Name` to the `typing` equivalent.""" # Re-write new-style builtin generics as old-style typing generics if node.id not in _GENERICS: return node @@ -68,6 +73,7 @@ def visit_Name(self, node: ast.Name): return new def visit_Subscript(self, node: ast.Subscript): + """Transform all subscripts within a :py:class:`ast.Subscript`.""" # Scan all subscripts to we transform nested new-style types. transformed = self.visit(node.slice) new = ast.Subscript( @@ -80,6 +86,7 @@ def visit_Subscript(self, node: ast.Subscript): return new def visit_Tuple(self, node: ast.Tuple): + """Transform all values within a :py:class:`ast.Tuple`.""" # Scan all tuples to ensure we transform nested new-style types. transformed = [self.visit(n) for n in node.elts] new = ast.Tuple(elts=transformed, ctx=node.ctx) diff --git a/src/typelib/inspection.py b/src/typelib/py/inspection.py similarity index 99% rename from src/typelib/inspection.py rename to src/typelib/py/inspection.py index f27bb0d..d12de3d 100644 --- a/src/typelib/inspection.py +++ b/src/typelib/py/inspection.py @@ -41,7 +41,8 @@ overload, ) -from typelib import compat, constants, contrib, refs +from typelib import constants +from typelib.py import compat, contrib, refs __all__ = ( "BUILTIN_TYPES", @@ -94,7 +95,7 @@ def origin(annotation: Any) -> Any: For the purposes of this library, if we can resolve to a builtin type, we will. Examples: - >>> from typelib import inspection + >>> from typelib.py import inspection >>> from typing import Dict, Mapping, NewType, Optional >>> origin(Dict) @@ -167,7 +168,7 @@ def get_args(annotation: Any) -> Tuple[Any, ...]: -> return Any Examples: - >>> from typelib import inspection + >>> from typelib.py import inspection >>> from typing import Dict, TypeVar, Any >>> T = TypeVar("T") >>> get_args(Dict) @@ -213,7 +214,7 @@ def get_name(obj: Union[type, refs.ForwardRef, Callable]) -> str: """Safely retrieve the name of either a standard object or a type annotation. Examples: - >>> from typelib import inspection + >>> from typelib.py import inspection >>> from typing import Dict, Any >>> T = TypeVar("T") >>> get_name(Dict) @@ -234,7 +235,7 @@ def get_qualname(obj: Union[type, refs.ForwardRef, Callable]) -> str: """Safely retrieve the qualname of either a standard object or a type annotation. Examples: - >>> from typelib import inspection + >>> from typelib.py import inspection >>> from typing import Dict, Any >>> T = TypeVar("T") >>> get_qualname(Dict) @@ -269,7 +270,7 @@ def resolve_supertype(annotation: type[Any] | types.FunctionType) -> Any: """Get the highest-order supertype for a NewType. Examples: - >>> from typelib import inspection + >>> from typelib.py import inspection >>> from typing import NewType >>> UserID = NewType("UserID", int) >>> AdminID = NewType("AdminID", UserID) @@ -579,7 +580,7 @@ def isfinal(obj: type) -> bool: Examples: >>> from typing import NewType - >>> from typelib.compat import Final + >>> from typelib.py.compat import Final >>> isfinal(Final[str]) True >>> isfinal(NewType("Foo", Final[str])) diff --git a/src/typelib/refs.py b/src/typelib/py/refs.py similarity index 99% rename from src/typelib/refs.py rename to src/typelib/py/refs.py index 8be0d4d..f21dc71 100644 --- a/src/typelib/refs.py +++ b/src/typelib/py/refs.py @@ -7,7 +7,7 @@ import sys import typing -from typelib import frames, future +from typelib.py import frames, future __all__ = ("ForwardRef", "evaluate", "forwardref") diff --git a/src/typelib/serdes.py b/src/typelib/serdes.py index 92e4749..ebd8274 100644 --- a/src/typelib/serdes.py +++ b/src/typelib/serdes.py @@ -13,7 +13,8 @@ import pendulum from more_itertools import peekable -from typelib import compat, constants, inspection +from typelib import constants +from typelib.py import compat, inspection @t.overload diff --git a/src/typelib/unmarshal/api.py b/src/typelib/unmarshal/api.py index 71b5d36..16a9d01 100644 --- a/src/typelib/unmarshal/api.py +++ b/src/typelib/unmarshal/api.py @@ -4,7 +4,8 @@ import typing as tp -from typelib import compat, ctx, graph, inspection, refs +from typelib import ctx, graph +from typelib.py import compat, inspection, refs from typelib.unmarshal import routines T = tp.TypeVar("T") diff --git a/src/typelib/unmarshal/routines.py b/src/typelib/unmarshal/routines.py index 4c6d92f..835d625 100644 --- a/src/typelib/unmarshal/routines.py +++ b/src/typelib/unmarshal/routines.py @@ -14,7 +14,8 @@ import typing as tp import uuid -from typelib import compat, constants, graph, inspection, serdes +from typelib import constants, graph, serdes +from typelib.py import compat, inspection T = tp.TypeVar("T") diff --git a/tests/unit/marshal/test_api.py b/tests/unit/marshal/test_api.py index 1e321ac..d92a4e6 100644 --- a/tests/unit/marshal/test_api.py +++ b/tests/unit/marshal/test_api.py @@ -9,8 +9,8 @@ import uuid import pytest -from typelib import compat, refs from typelib.marshal import api +from typelib.py import compat, refs from tests import models diff --git a/tests/unit/py/__init__.py b/tests/unit/py/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/test_classes.py b/tests/unit/py/test_classes.py similarity index 98% rename from tests/unit/test_classes.py rename to tests/unit/py/test_classes.py index 6dbce57..1114254 100644 --- a/tests/unit/test_classes.py +++ b/tests/unit/py/test_classes.py @@ -2,7 +2,7 @@ import dataclasses -from typelib import classes +from typelib.py import classes def test_slotted(): diff --git a/tests/unit/test_frames.py b/tests/unit/py/test_frames.py similarity index 99% rename from tests/unit/test_frames.py rename to tests/unit/py/test_frames.py index d56aa75..2cbfe03 100644 --- a/tests/unit/test_frames.py +++ b/tests/unit/py/test_frames.py @@ -2,7 +2,7 @@ from unittest import mock import pytest -from typelib import frames +from typelib.py import frames GLOBAL: str = "FOO" _LOCAL: str = "BAR" diff --git a/tests/unit/test_future.py b/tests/unit/py/test_future.py similarity index 97% rename from tests/unit/test_future.py rename to tests/unit/py/test_future.py index 409ab54..b5f20cf 100644 --- a/tests/unit/test_future.py +++ b/tests/unit/py/test_future.py @@ -1,5 +1,5 @@ import pytest -from typelib import future +from typelib.py import future @pytest.mark.suite( diff --git a/tests/unit/test_inspection.py b/tests/unit/py/test_inspection.py similarity index 99% rename from tests/unit/test_inspection.py rename to tests/unit/py/test_inspection.py index d7e0748..a2887e6 100644 --- a/tests/unit/test_inspection.py +++ b/tests/unit/py/test_inspection.py @@ -21,7 +21,7 @@ from unittest import mock import pytest -from typelib import inspection, refs +from typelib.py import inspection, refs class MyClass: ... diff --git a/tests/unit/test_refs.py b/tests/unit/py/test_refs.py similarity index 98% rename from tests/unit/test_refs.py rename to tests/unit/py/test_refs.py index 38c8a33..6955af3 100644 --- a/tests/unit/test_refs.py +++ b/tests/unit/py/test_refs.py @@ -1,7 +1,7 @@ from unittest import mock import pytest -from typelib import refs +from typelib.py import refs def evaluated_ref(t: str) -> refs.ForwardRef: diff --git a/tests/unit/test_graph.py b/tests/unit/test_graph.py index 3554de0..6ad637e 100644 --- a/tests/unit/test_graph.py +++ b/tests/unit/test_graph.py @@ -5,7 +5,8 @@ import typing import pytest -from typelib import graph, refs +from typelib import graph +from typelib.py import refs @dataclasses.dataclass diff --git a/tests/unit/test_interchange.py b/tests/unit/test_interchange.py index 4e94ee9..577f0d3 100644 --- a/tests/unit/test_interchange.py +++ b/tests/unit/test_interchange.py @@ -7,7 +7,8 @@ import uuid import pytest -from typelib import compat, interchange, refs +from typelib import interchange +from typelib.py import compat, refs from tests import models diff --git a/tests/unit/unmarshal/test_api.py b/tests/unit/unmarshal/test_api.py index a791c30..74cf414 100644 --- a/tests/unit/unmarshal/test_api.py +++ b/tests/unit/unmarshal/test_api.py @@ -9,7 +9,7 @@ import uuid import pytest -from typelib import compat, refs +from typelib.py import compat, refs from typelib.unmarshal import api from tests import models diff --git a/tests/unit/unmarshal/test_routines.py b/tests/unit/unmarshal/test_routines.py index 3d43034..dde8bce 100644 --- a/tests/unit/unmarshal/test_routines.py +++ b/tests/unit/unmarshal/test_routines.py @@ -157,7 +157,9 @@ def test_date_unmarshaller(given_input, expected_output): ), time=dict( given_input=datetime.time(tzinfo=datetime.timezone.utc), - expected_output=datetime.datetime.today().replace( + expected_output=datetime.datetime.today() + .astimezone(tz=datetime.timezone.utc) + .replace( hour=0, minute=0, second=0, microsecond=0, tzinfo=datetime.timezone.utc ), ),