Skip to content

Commit

Permalink
1.2.0: Add support for validating Literals.
Browse files Browse the repository at this point in the history
  • Loading branch information
connor-makowski committed Nov 18, 2023
1 parent b58d79f commit 4e7c129
Show file tree
Hide file tree
Showing 8 changed files with 436 additions and 344 deletions.
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,21 @@ Variables without an annotation for type are not enforced.
- Deeply nested types are supported too:
- `dict[dict[int]]`
- `list[set[]]`
- Many of the `typing` (package) functions including:
- Standard generics:
- Many of the `typing` (package) functions and methods including:
- Standard typing functions:
- `List`, `Set`, `Dict`, `Tuple`
- `Union`
- `Optional`
- `Optional`
- `Sized`
- Essentially creates a union of:
- `list`, `tuple`, `dict`, `set`, `str`, `bytes`, `bytearray`, `memoryview`, `range`
- Note: Can not have a nested type
- Because this does not always meet the criteria for `Nested types` above
- `Literal`
- Only allow certain values to be passed. Operates slightly differently than other checks.
- e.g. `Literal['a', 'b']` will require any passed values that are equal (`==`) to `'a'` or `'b'`.
- This compares the value of the passed input and not the type of the passed input.
- Note: Multiple types can be passed in the same `Literal`.
- Note: Other functions might have support, but there are not currently tests to validate them
- Feel free to create an issue (or better yet a PR) if you want to add tests/support

Expand Down
Binary file added dist/type_enforced-1.2.0-py3-none-any.whl
Binary file not shown.
Binary file added dist/type_enforced-1.2.0.tar.gz
Binary file not shown.
714 changes: 377 additions & 337 deletions docs/type_enforced/enforcer.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "type_enforced"
version = "1.1.1"
version = "1.2.0"
description = "A pure python type enforcer for python type annotations"
authors = [
{name="Connor Makowski", email="[email protected]"}
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = type_enfoced
version = 1.1.1
version = 1.2.0
description_file = README.md

[options]
Expand Down
27 changes: 27 additions & 0 deletions test/test_fn_13.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type_enforced
from typing import Literal


@type_enforced.Enforcer
def my_fn(a: Literal["a", "b"]):
pass


success_1 = True
try:
my_fn(a="a")
my_fn(a="b")
except:
success_1 = False

success_2 = False
try:
my_fn(a="c")
except Exception as e:
if "Type mismatch" in str(e):
success_2 = True

if success_1 and success_2:
print("test_fn_13.py passed")
else:
print("test_fn_13.py failed")
24 changes: 22 additions & 2 deletions type_enforced/enforcer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from types import FunctionType, MethodType, GenericAlias
from typing import Type, Union, Sized
from typing import Type, Union, Sized, Literal
from functools import update_wrapper, wraps

# Python 3.10+ has a UnionType object that is used to represent Union types
Expand Down Expand Up @@ -87,6 +87,14 @@ def __get_checkable_type__(self, item_annotation):
valid_types.update(
self.__get_checkable_type__(valid_type.__args__)
)
# Handle Literals
# Note: These will be handled separately in the self.__check_type__
# as the object is validated and not its type.
elif valid_type.__origin__ == Literal:
valid_types = {
Literal: {i: None for i in valid_type.__args__}
}

# Handle Sized objects
elif valid_type == Sized:
valid_types = {
Expand Down Expand Up @@ -185,8 +193,20 @@ def __check_type__(self, obj, acceptable_types, key):
else:
passed_type = type(obj)
if passed_type not in acceptable_types:
# Add special string to store any string to add before acceptable types
# in any exception message
pre_acceptable_types_str = ""
# Special check for Literals
if Literal in acceptable_types:
if obj in acceptable_types[Literal]:
return
else:
pre_acceptable_types_str = "Literal"
acceptable_types = acceptable_types[Literal]
passed_type = obj
# Raise the exception
self.__exception__(
f"Type mismatch for typed variable `{key}`. Expected one of the following `{str(list(acceptable_types.keys()))}` but got `{passed_type}` instead."
f"Type mismatch for typed variable `{key}`. Expected one of the following `{pre_acceptable_types_str}{str(list(acceptable_types.keys()))}` but got `{passed_type}` instead."
)
sub_type = acceptable_types.get(passed_type)
if sub_type is not None:
Expand Down

0 comments on commit 4e7c129

Please sign in to comment.