Skip to content

Commit f86fa07

Browse files
Merge pull request #55 from connor-makowski/2.0.0
2.0.0
2 parents bf12bdf + ed43a55 commit f86fa07

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+4771
-7204
lines changed

Dockerfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# syntax = docker/dockerfile:1
22

33
## Uncomment the version of python you want to test against
4-
# FROM python:3.10-slim
54
# FROM python:3.11-slim
65
# FROM python:3.12-slim
76
FROM python:3.13-slim

README.md

Lines changed: 84 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,13 @@ A pure python (no special compiler required) type enforcer for type annotations.
77

88
# Setup
99

10-
Make sure you have Python 3.10.x (or higher) installed on your system. You can download it [here](https://www.python.org/downloads/).
10+
Make sure you have Python 3.11.x (or higher) installed on your system. You can download it [here](https://www.python.org/downloads/).
1111

1212
- Unsupported python versions can be used, however newer features will not be available.
1313
- For 3.7: use type_enforced==0.0.16 (only very basic type checking is supported)
1414
- For 3.8: use type_enforced==0.0.16 (only very basic type checking is supported)
1515
- For 3.9: use type_enforced<=1.9.0 (`staticmethod`, union with `|` and `from __future__ import annotations` typechecking are not supported)
16-
- Other notes:
17-
- For python 3.10: `from __future__ import annotations` may cause errors (EG: when using staticmethods and classmethods)
16+
- For 3.10: use type_enforced<=10.2.0 (`from __future__ import annotations` may cause errors (EG: when using staticmethods and classmethods))
1817

1918
### Installation
2019

@@ -27,23 +26,45 @@ pip install type_enforced
2726
import type_enforced
2827

2928
@type_enforced.Enforcer(enabled=True)
30-
def my_fn(a: int , b: [int, str] =2, c: int =3) -> None:
29+
def my_fn(a: int , b: int | str =2, c: int =3) -> None:
3130
pass
3231
```
3332
- Note: `enabled=True` by default if not specified. You can set `enabled=False` to disable type checking for a specific function, method, or class. This is useful for a production vs debugging environment or for undecorating a single method in a larger wrapped class.
3433

35-
# Getting Started
34+
## Getting Started
3635

3736
`type_enforcer` contains a basic `Enforcer` wrapper that can be used to enforce many basic python typing hints. [Technical Docs Here](https://connor-makowski.github.io/type_enforced/type_enforced/enforcer.html).
3837

39-
`type_enforcer` currently supports many single and multi level python types. This includes class instances and classes themselves. For example, you can force an input to be an `int`, a number `[int, float]`, an instance of the self defined `MyClass`, or a even a vector with `list[int]`. Items like `typing.List`, `typing.Dict`, `typing.Union` and `typing.Optional` are supported.
38+
`type_enforcer` currently supports many single and multi level python types. This includes class instances and classes themselves. For example, you can force an input to be an `int`, a number `int | float`, an instance of the self defined `MyClass`, or a even a vector with `list[int]`. Items like `typing.List`, `typing.Dict`, `typing.Union` and `typing.Optional` are supported.
4039

41-
You can pass union types to validate one of multiple types. For example, you could validate an input was an int or a float with `[int, float]`, `[int | float]` or even `typing.Union[int, float]`.
40+
You can pass union types to validate one of multiple types. For example, you could validate an input was an int or a float with `int | float` or `typing.Union[int, float]`.
4241

4342
Nesting is allowed as long as the nested items are iterables (e.g. `typing.List`, `dict`, ...). For example, you could validate that a list is a vector with `list[int]` or possibly `typing.List[int]`.
4443

4544
Variables without an annotation for type are not enforced.
4645

46+
## What changed in 2.0.0?
47+
The main changes in version 2.0.0 revolve around migrating towards the standard python typing hint process and away from the original type_enfoced type hints (as type enforced was originally created before the `|` operator was added to python).
48+
- Support for python3.10 has been dropped.
49+
- List based union types are no longer supported.
50+
- For example `[int, float]` is no longer a supported type hint.
51+
- Use `int|float` or `typing.Union[int, float]` instead.
52+
- Dict types now require two types to be specified.
53+
- The first type is the key type and the second type is the value type.
54+
- For example, `dict[str, int|float]` or `dict[int, float]` are valid types.
55+
- Tuple types now allow for `N` types to be specified.
56+
- Each item refers to the positional type of each item in the tuple.
57+
- Support for elipsis (`...`) is supported if you only specify two types and the second is the elipsis type.
58+
- For example, `tuple[int, ...]` or `tuple[int|str, ...]` are valid types.
59+
- Note: Unions between two tuples are not supported.
60+
- For example, `tuple[int, str] | tuple[str, int]` will not work.
61+
- Constraints and Literals can now be stacked with unions.
62+
- For example, `int | Constraint(ge=0) | Constraint(le=5)` will require any passed values to be integers that are greater than or equal to `0` and less than or equal to `5`.
63+
- For example, `Literal['a', 'b'] | Literal[1, 2]` will require any passed values that are equal (`==`) to `'a'`, `'b'`, `1` or `2`.
64+
- Literals now evaluate during the same time as type checking and operate as OR checks.
65+
- For example, `int | Literal['a', 'b']` will validate that the type is an int or the value is equal to `'a'` or `'b'`.
66+
- Constraints are still are evaluated after type checking and operate independently of the type checking.
67+
4768
## Supported Type Checking Features:
4869

4970
- Function/Method Input Typing
@@ -52,19 +73,30 @@ Variables without an annotation for type are not enforced.
5273
- All standard python types (`str`, `list`, `int`, `dict`, ...)
5374
- Union types
5475
- typing.Union
55-
- `,` separated list (e.g. `[int, float]`)
56-
- `|` separated list (e.g. `[int | float]`)
5776
- `|` separated items (e.g. `int | float`)
58-
- Nested types (e.g. `dict[str]` or `list[int, float]`)
77+
- Nested types (e.g. `dict[str, int]` or `list[int|float]`)
5978
- Note: Each parent level must be an iterable
6079
- Specifically a variant of `list`, `set`, `tuple` or `dict`
61-
- Note: `dict` keys are not validated, only values
80+
- Note: `dict` requires two types to be specified (unions count as a single type)
81+
- The first type is the key type and the second type is the value type
82+
- e.g. `dict[str, int|float]` or `dict[int, float]`
83+
- Note: `list` and `set` require a single type to be specified (unions count as a single type)
84+
- e.g. `list[int]`, `set[str]`, `list[float|str]`
85+
- Note: `tuple` Allows for `N` types to be specified
86+
- Each item refers to the positional type of each item in the tuple
87+
- Support for elipsis (`...`) is supported if you only specify two types and the second is the elipsis type
88+
- e.g. `tuple[int, ...]` or `tuple[int|str, ...]`
89+
- Note: Unions between two tuples are not supported
90+
- e.g. `tuple[int, str] | tuple[str, int]` will not work
6291
- Deeply nested types are supported too:
6392
- `dict[dict[int]]`
6493
- `list[set[str]]`
6594
- Many of the `typing` (package) functions and methods including:
6695
- Standard typing functions:
67-
- `List`, `Set`, `Dict`, `Tuple`
96+
- `List`
97+
- `Set`
98+
- `Dict`
99+
- `Tuple`
68100
- `Union`
69101
- `Optional`
70102
- `Sized`
@@ -76,8 +108,16 @@ Variables without an annotation for type are not enforced.
76108
- Only allow certain values to be passed. Operates slightly differently than other checks.
77109
- e.g. `Literal['a', 'b']` will require any passed values that are equal (`==`) to `'a'` or `'b'`.
78110
- This compares the value of the passed input and not the type of the passed input.
79-
- Note: Multiple types can be passed in the same `Literal`.
80-
- Note: Literals are evaluated after type checking occurs.
111+
- Note: Multiple types can be passed in the same `Literal` as acceptable values.
112+
- e.g. Literal['a', 'b', 1, 2] will require any passed values that are equal (`==`) to `'a'`, `'b'`, `1` or `2`.
113+
- Note: If type is a `str | Literal['a', 'b']`
114+
- The check will validate that the type is a string or the value is equal to `'a'` or `'b'`.
115+
- This means that an input of `'c'` will pass the check since it matches the string type, but an input of `1` will fail.
116+
- Note: If type is a `int | Literal['a', 'b']`
117+
- The check will validate that the type is an int or the value is equal to `'a'` or `'b'`.
118+
- This means that an input of `'c'` will fail the check, but an input of `1` will pass.
119+
- Note: Literals stack when used with unions.
120+
- e.g. `Literal['a', 'b'] | Literal[1, 2]` will require any passed values that are equal (`==`) to `'a'`, `'b'`, `1` or `2`.
81121
- `Callable`
82122
- Essentially creates a union of:
83123
- `staticmethod`, `classmethod`, `types.FunctionType`, `types.BuiltinFunctionType`, `types.MethodType`, `types.BuiltinMethodType`, `types.GeneratorType`
@@ -87,7 +127,11 @@ Variables without an annotation for type are not enforced.
87127
- This is a special type of validation that allows passed input to be validated.
88128
- Standard and custom constraints are supported.
89129
- This is useful for validating that a passed input is within a certain range or meets a certain criteria.
90-
- Note: The constraint is checked after type checking occurs.
130+
- Note: Constraints stack when used with unions.
131+
- e.g. `int | Constraint(ge=0) | Constraint(le=5)` will require any passed values to be integers that are greater than or equal to `0` and less than or equal to `5`.
132+
- Note: The constraint is checked after type checking occurs and operates independently of the type checking.
133+
- This operates differently than other checks (like `Literal`) and is evaluated post type checking.
134+
- For example, if you have an annotation of `str | Constraint(ge=0)`, this will always raise an exception since if you pass a string, it will raise on the constraint check and if you pass an integer, it will raise on the type check.
91135
- Note: See the example below or technical [constraint](https://connor-makowski.github.io/type_enforced/type_enforced/utils.html#Constraint) and [generic constraint](https://connor-makowski.github.io/type_enforced/type_enforced/utils.html#GenericConstraint) docs for more information.
92136
```
93137
@@ -96,21 +140,29 @@ Variables without an annotation for type are not enforced.
96140
```py
97141
>>> import type_enforced
98142
>>> @type_enforced.Enforcer
99-
... def my_fn(a: int , b: [int, str] =2, c: int =3) -> None:
143+
... def my_fn(a: int , b: int|str =2, c: int =3) -> None:
100144
... pass
101145
...
102146
>>> my_fn(a=1, b=2, c=3)
103147
>>> my_fn(a=1, b='2', c=3)
104148
>>> my_fn(a='a', b=2, c=3)
105149
Traceback (most recent call last):
106-
File "<stdin>", line 1, in <module>
107-
File "/home/conmak/development/personal/type_enforced/type_enforced/enforcer.py", line 85, in __call__
150+
File "<python-input-2>", line 1, in <module>
151+
my_fn(a='a', b=2, c=3)
152+
~~~~~^^^^^^^^^^^^^^^^^
153+
File "/app/type_enforced/enforcer.py", line 233, in __call__
108154
self.__check_type__(assigned_vars.get(key), value, key)
109-
File "/home/conmak/development/personal/type_enforced/type_enforced/enforcer.py", line 107, in __check_type__
155+
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
156+
File "/app/type_enforced/enforcer.py", line 266, in __check_type__
110157
self.__exception__(
111-
File "/home/conmak/development/personal/type_enforced/type_enforced/enforcer.py", line 34, in __exception__
112-
raise TypeError(f"({self.__fn__.__qualname__}): {message}")
113-
TypeError: (my_fn): Type mismatch for typed variable `a`. Expected one of the following `[<class 'int'>]` but got `<class 'str'>` instead.
158+
~~~~~~~~~~~~~~~~~~^
159+
f"Type mismatch for typed variable `{key}`. Expected one of the following `{list(expected.keys())}` but got `{obj_type}` with value `{obj}` instead."
160+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
161+
)
162+
^
163+
File "/app/type_enforced/enforcer.py", line 188, in __exception__
164+
raise TypeError(f"TypeEnforced Exception ({self.__fn__.__qualname__}): {message}")
165+
TypeError: TypeEnforced Exception (my_fn): Type mismatch for typed variable `a`. Expected one of the following `[<class 'int'>]` but got `<class 'str'>` with value `a` instead.
114166
```
115167

116168
## Nested Examples
@@ -120,15 +172,15 @@ import typing
120172

121173
@type_enforced.Enforcer
122174
def my_fn(
123-
a: dict[dict[int, float]], # Note: dict keys are not validated, only values
175+
a: dict[str,dict[str, int|float]], # Note: dict keys are not validated, only values
124176
b: list[typing.Set[str]] # Could also just use set
125177
) -> None:
126178
return None
127179

128180
my_fn(a={'i':{'j':1}}, b=[{'x'}]) # Success
129181

130-
my_fn(a={'i':{'j':'k'}}, b=[{'x'}]) # Error:
131-
# TypeError: (my_fn): Type mismatch for typed variable `a[i][j]`. Expected one of the following `[<class 'int'>]` but got `<class 'str'>` instead.
182+
my_fn(a={'i':{'j':'k'}}, b=[{'x'}]) # Error =>
183+
# TypeError: TypeEnforced Exception (my_fn): Type mismatch for typed variable `a['i']['j']`. Expected one of the following `[<class 'int'>, <class 'float'>]` but got `<class 'str'>` with value `k` instead.
132184
```
133185

134186
## Class and Method Use
@@ -213,7 +265,7 @@ import type_enforced
213265
from type_enforced.utils import Constraint
214266

215267
@type_enforced.Enforcer()
216-
def positive_int_test(value: [int, Constraint(ge=0)]) -> bool:
268+
def positive_int_test(value: int |Constraint(ge=0)) -> bool:
217269
return True
218270

219271
positive_int_test(1) # Passes
@@ -233,7 +285,7 @@ CustomConstraint = GenericConstraint(
233285
)
234286

235287
@type_enforced.Enforcer()
236-
def rgb_test(value: [str, CustomConstraint]) -> bool:
288+
def rgb_test(value: str | CustomConstraint) -> bool:
237289
return True
238290

239291
rgb_test('red') # Passes
@@ -285,6 +337,10 @@ x=my_class(Foo()) # Fails
285337

286338
## Validate classes with inheritance
287339

340+
A special helper utility is provided to get all the subclasses of a class (with delayed evaluation - so you can validate subclasses even if they were defined later or in other files).
341+
342+
See: [WithSubclasses](https://connor-makowski.github.io/type_enforced/type_enforced/utils.html#WithSubclasses) for more information.
343+
288344
```py
289345
import type_enforced
290346
from type_enforced.utils import WithSubclasses
@@ -302,7 +358,6 @@ class Baz:
302358
def my_fn(custom_class: WithSubclasses(Foo)):
303359
pass
304360

305-
print(WithSubclasses.get_subclasses(Foo)) # Prints: [<class '__main__.Foo'>, <class '__main__.Bar'>]
306361
my_fn(Foo()) # Passes as expected
307362
my_fn(Bar()) # Passes as expected
308363
my_fn(Baz()) # Raises TypeError as expected
@@ -322,4 +377,4 @@ Make sure Docker is installed and running.
322377
- Update the docs (see ./utils/docs.sh)
323378
- `./run.sh docs`
324379

325-
- Note: You can and should modify the `Dockerfile` to test different python versions.
380+
- Note: You can and should modify the `Dockerfile` to test different python versions.
18.7 KB
Binary file not shown.

dist/type_enforced-2.0.0.tar.gz

26.8 KB
Binary file not shown.

0 commit comments

Comments
 (0)