Skip to content

Commit

Permalink
Better Generic support (#46)
Browse files Browse the repository at this point in the history
  • Loading branch information
tmke8 committed Dec 14, 2023
1 parent 2bf0c8c commit 8320cec
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 55 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,4 @@ jobs:
poetry install --no-interaction --no-root
- name: Run type check
run: |
poetry run mypy tests --enable-incomplete-feature=TypeVarTuple --enable-incomplete-feature=Unpack
poetry run mypy tests
9 changes: 2 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ match background_color:

## Installation

Requires Python >= 3.11
Requires Python >= 3.11 (because features from PEP 646 are required)

```
pip install type-enum
```

And for the mypy plugin (requires mypy >= 1.5):
And for the mypy plugin (requires mypy >= 1.7):
```
pip install type-enum-plugin
```
Expand All @@ -48,11 +48,6 @@ Then register the plugin with
plugins = "type_enum_plugin"
```

And then run mypy with the following flags (hopefully this soon won't be needed anymore):
```
--enable-incomplete-feature=TypeVarTuple --enable-incomplete-feature=Unpack
```

## Advanced usage
### Exhaustiveness checking
To make exhaustiveness checking work, you first need the mypy plugin, and then you also need to use the special `T` attribute that is synthesized for each `TypeEnum`:
Expand Down
65 changes: 33 additions & 32 deletions type-enum/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion type-enum/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ python = "^3.11"

[tool.poetry.group.dev.dependencies]
ruff = "^0.0.254"
mypy = "^1.5.1"
mypy = "^1.7.1"
type-enum-plugin = { path = "../type-enum-plugin", develop = true }
typing-extensions = "^4.5.0"

Expand Down
5 changes: 5 additions & 0 deletions type-enum/tests/test_instantiation.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ def f(x: Maybe.T[int]) -> int:
return 0

self.assertEqual(f(a), 3)
self.assertEqual(repr(Maybe.Some), "<class 'tests.test_instantiation.Some'>")

with self.assertRaises(TypeError):
Maybe.Some[int, str](3) # type: ignore[misc]

def test_generic_multi(self) -> None:
U = TypeVar("U")
Expand All @@ -92,6 +96,7 @@ def f(x: E.T[str, int]) -> int:
return u

self.assertEqual(f(a), 3)
self.assertEqual(repr(a), "E.A(3)")

def test_invalid_body(self) -> None:
with self.assertRaises(TypeError):
Expand Down
45 changes: 31 additions & 14 deletions type-enum/type_enum/_core.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import functools
from operator import or_
from types import new_class
from typing import (
Any,
Generic,
Expand Down Expand Up @@ -60,12 +61,15 @@ def __new__(cls, name: str, bases: tuple[type, ...], ns: dict[str, Any]):
if len(types) == 1 and types[0] == ():
types = ()
# subtype = _create_tuple_class(name, attr_name, types)
subtype = _create_subclass(name, attr_name, types)
typevars: tuple[type, ...] = tuple(
typ for typ in types if isinstance(typ, TypeVar)
)
subtype = _create_subclass(
name, attr_name, types, typevars, ns["__module__"]
)

ns[attr_name] = subtype
member_map[attr_name] = Entry(
subtype, tuple(typ for typ in types if isinstance(typ, TypeVar))
)
member_map[attr_name] = Entry(subtype, typevars)

for attr_name in ns:
if not is_dunder(attr_name) and attr_name not in typ_anns:
Expand Down Expand Up @@ -112,19 +116,32 @@ def __iter__(cls) -> Iterator[type]:
return (typ for typ, _ in cls._member_map.values())


def _create_subclass(basename: str, typename: str, types: tuple[type, ...]):
subtype = NamedTuple(typename, [(f"field{i}", typ) for i, typ in enumerate(types)])
def _create_subclass(
basename: str,
typename: str,
types: tuple[type, ...],
typevars: tuple[type, ...],
module: str,
) -> type:
def body(namespace: dict[str, Any]) -> None:
namespace["__module__"] = module
namespace["__annotations__"] = {f"field{i}": typ for i, typ in enumerate(types)}
num_values = len(types)
repr_fmt = "(" + ", ".join("%r" for _ in range(num_values)) + ")"

def __repr__(self) -> str:
"Return a nicely formatted representation string"
return basename + "." + self.__class__.__name__ + repr_fmt % tuple(self)

namespace["__repr__"] = __repr__

baseclasses = (NamedTuple,)
if typevars:
baseclasses += (Generic[*typevars],)
subtype = new_class(typename, baseclasses, exec_body=body)
subtype.__doc__ = (
f"{basename}.{typename}(" + ", ".join(type_to_str(typ) for typ in types) + ")"
)
num_values = len(types)
repr_fmt = "(" + ", ".join("%r" for _ in range(num_values)) + ")"

def __repr__(self) -> str:
"Return a nicely formatted representation string"
return basename + "." + self.__class__.__name__ + repr_fmt % tuple(self)

subtype.__repr__ = __repr__
return subtype


Expand Down

0 comments on commit 8320cec

Please sign in to comment.