diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a15980e..fdb2703 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -88,6 +88,9 @@ repos: files: '(newtype)/.*\.py$' # Single quote critical due to escape character '\' used in RegEx search string (see YAML - 7.3 Flow Scalar Styles) args: [--config-file=./pyproject.toml, --ignore-missing-imports, --scripts-are-modules] exclude: '(docs|tests)/.*\.py$' + additional_dependencies: + - "mypy>=1.0.0" + - "." # Install the current package - repo: local hooks: diff --git a/docs/development/building.md b/docs/development/building.md index 22ae7ba..d672976 100644 --- a/docs/development/building.md +++ b/docs/development/building.md @@ -197,17 +197,17 @@ poetry self add poetry-dynamic-versioning poetry version patch ``` -2. Commit your changes and push to the repository. +2. Set the tag to the new version: +``` +git tag v0.1.3 +``` + +3. Commit your changes and push to the repository. ```bash git commit -m "Release version 0.1.3" ``` -3. Set the tag to the new version: -``` -git tag v0.1.3 -``` - 4. Push the changes to the repository: ``` git push --tags diff --git a/examples/email_str.py b/examples/email_str.py index efb2046..7bdd381 100644 --- a/examples/email_str.py +++ b/examples/email_str.py @@ -7,7 +7,7 @@ from newtype import NewType, newtype_exclude -class EmailStr(NewType(str)): +class EmailStr(NewType(str)): # type: ignore[misc] # you can define `__slots__` to save space __slots__ = ( '_local_part', diff --git a/examples/newtype_enums.py b/examples/newtype_enums.py index 04bd069..7b5990d 100644 --- a/examples/newtype_enums.py +++ b/examples/newtype_enums.py @@ -1,11 +1,11 @@ from enum import Enum +from typing import Optional, Type import pytest +from newtype import NewType -from newtype import NewType, newtype_exclude - -class ENV(NewType(str), Enum): +class ENV(NewType(str), Enum): # type: ignore[misc] LOCAL = "LOCAL" DEV = "DEV" @@ -23,26 +23,33 @@ class RegularENV(str, Enum): PREPROD = "PREPROD" PROD = "PROD" +RollYourOwnNewTypeEnum: "Optional[Type[RollYourOwnNewTypeEnum]]" = None + class ENVVariant(str): __VALID_MEMBERS__ = ["LOCAL", "DEV", "SIT", "UAT", "PREPROD", "PROD"] - def __new__(cls, value: str): + def __new__(cls, value: str) -> "ENVVariant": members = ENVVariant.__VALID_MEMBERS__ - # if isinstance(value, RollYourOwnNewTypeEnum): - # value_as_str = str(value.value) - # else: - value_as_str = str(value) + value_as_str = str(value.value if hasattr(value, "value") else value) if value_as_str not in members: raise ValueError(f"`value` = {value} must be one of `{members}`; `value_as_str` = {value_as_str}") return super().__new__(cls, value_as_str) - # why not i write my own `.replace(..)` - # yes, you can but how? - def my_replace(self, old: "ENVVariant", new: "ENVVariant", count: int=-1): - return ENVVariant(str(self).replace(str(old), str(new), count)) + def my_replace(self, old: "ENVVariant", new: "ENVVariant", count: int=-1) -> "ENVVariant": + # Convert both old and new to their string values + old_str = str(old.value if hasattr(old, "value") else old) + new_str = str(new.value if hasattr(new, "value") else new) + # Do the replacement on string values + result = str(self.value if hasattr(self, "value") else self).replace(old_str, new_str, count) + # For enums, we need to look up the enum member by value + if issubclass(type(self), Enum): + return type(self)(result) # This will find the enum member + + # For non-enum types, create new instance directly + return type(self)(result) -class RollYourOwnNewTypeEnum(ENVVariant, Enum): +class RollYourOwnNewTypeEnum(ENVVariant, Enum): # type: ignore[no-redef] LOCAL = "LOCAL" DEV = "DEV" @@ -51,8 +58,8 @@ class RollYourOwnNewTypeEnum(ENVVariant, Enum): PREPROD = "PREPROD" PROD = "PROD" - -def test_nt_env_replace(): +# mypy doesn't raise errors here +def test_nt_env_replace() -> None: env = ENV.LOCAL @@ -63,6 +70,7 @@ def test_nt_env_replace(): # let's say now we want to replace the environment # nevermind about the reason why we want to do so env = env.replace(ENV.LOCAL, ENV.DEV) + # reveal_type(env) # Revealed type is "newtype_enums.ENV" # replacement is successful assert env is ENV.DEV @@ -76,11 +84,13 @@ def test_nt_env_replace(): # cannot replace with something that is not a `ENV` env = env.replace(ENV.DEV, "NotAnEnv") + # reveal_type(env) # Revealed type is "newtype_enums.ENV" + with pytest.raises(ValueError): # cannot even make 'DEV' -> 'dev' env = env.lower() -def test_reg_env_replace(): +def test_reg_env_replace() -> None: env = RegularENV.LOCAL @@ -90,7 +100,7 @@ def test_reg_env_replace(): assert isinstance(env, RegularENV) # pass # now we try to replace - env = env.replace(RegularENV.LOCAL, RegularENV.DEV) + env = env.replace("LOCAL", "DEV") # we are hoping that it will continue to be a `RegularENV.DEV` but it is not assert env is not RegularENV.DEV # pass, no longer a `RegularENV` @@ -98,7 +108,9 @@ def test_reg_env_replace(): assert not isinstance(env, RegularENV) assert isinstance(env, str) # 'downcast' (?) to `str` -def test_ryont_env_replace(): +def test_ryont_env_replace() -> None: + + assert RollYourOwnNewTypeEnum is not None env = RollYourOwnNewTypeEnum.LOCAL @@ -130,8 +142,8 @@ def test_ryont_env_replace(): env = RollYourOwnNewTypeEnum.LOCAL - # env = env.my_replace(RollYourOwnNewTypeEnum.LOCAL, RollYourOwnNewTypeEnum.PREPROD) + env = env.my_replace(RollYourOwnNewTypeEnum.LOCAL, RollYourOwnNewTypeEnum.PREPROD) assert isinstance(env, str) - assert env is not RollYourOwnNewTypeEnum.PREPROD + assert env is RollYourOwnNewTypeEnum.PREPROD assert isinstance(env, RollYourOwnNewTypeEnum) diff --git a/newtype/__init__.py b/newtype/__init__.py index 9fa86b7..6ecbc55 100644 --- a/newtype/__init__.py +++ b/newtype/__init__.py @@ -42,4 +42,11 @@ def __init__(self, val: int) -> None: __version__ = "0.0.0" # Don't manually change, let poetry-dynamic-versioning handle it -__all__ = ["NewType", "newtype_exclude", "func_is_excluded", "NewTypeInit", "NewTypeMethod"] +__all__ = [ + "NewType", + "newtype_exclude", + "func_is_excluded", + "NewTypeInit", + "NewTypeMethod", + "mypy_plugin", +] diff --git a/newtype/mypy_plugin.py b/newtype/mypy_plugin.py new file mode 100644 index 0000000..c2f299b --- /dev/null +++ b/newtype/mypy_plugin.py @@ -0,0 +1,169 @@ +import logging +import os +from typing import Any, Callable, List, Optional, Type, Union + +from mypy.nodes import Argument, FuncDef, RefExpr, SymbolTableNode, TypeInfo, Var +from mypy.plugin import ClassDefContext, Plugin +from mypy.plugins.common import add_method +from mypy.types import AnyType, CallableType, Instance, TypeOfAny, UnionType +from mypy.types import Type as MypyType + + +# Set up logging +logger = logging.getLogger("newtype.mypy_plugin") +# Remove any existing handlers to prevent duplicates +for handler in logger.handlers[:]: + logger.removeHandler(handler) + +# Only enable logging if __PYNT_DEBUG__ is set to "true" +if os.environ.get("__PYNT_DEBUG__", "").lower() == "true": + # Create a file handler + file_handler = logging.FileHandler("mypy_plugin.log") + file_handler.setFormatter( + logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") + ) + logger.addHandler(file_handler) + logger.setLevel(logging.DEBUG) +else: + logger.setLevel(logging.WARNING) + + +def convert_union_type(typ: MypyType) -> MypyType: + """Convert a type to use UnionType instead of | operator.""" + if isinstance(typ, UnionType): + # If it's already a UnionType, convert its items + return UnionType([convert_union_type(t) for t in typ.items]) + elif isinstance(typ, Instance) and typ.args: + return typ.copy_modified(args=[convert_union_type(arg) for arg in typ.args]) + return typ + + +class NewTypePlugin(Plugin): + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + logger.info("Initializing NewTypePlugin") + + def get_base_class_hook(self, fullname: str) -> Optional[Callable[[ClassDefContext], None]]: + logger.debug(f"get_base_class_hook called with fullname: {fullname}") + if "newtype.NewType" in fullname: + logger.info(f"Found NewType class: {fullname}") + return handle_newtype_class + logger.debug(f"No hook for {fullname}") + return None + + +def handle_newtype_class(ctx: ClassDefContext) -> None: # noqa: C901 + logger.info(f"Processing NewType class: {ctx.cls.fullname}") + + if not hasattr(ctx.reason, "args") or not ctx.reason.args: + logger.warning("No arguments provided to NewType") + return + + # Get base type from NewType argument + base_type_expr = ctx.reason.args[0] + logger.debug(f"Base type expression: {base_type_expr}") + + if not isinstance(base_type_expr, RefExpr): + logger.warning(f"Base type expression is not a RefExpr: {type(base_type_expr)}") + return + + base_type: Optional[SymbolTableNode] + + # Handle built-in types specially + if base_type_expr.fullname and base_type_expr.fullname.startswith("builtins."): + logger.debug(f"Looking up built-in type: {base_type_expr.fullname}") + base_type = ctx.api.lookup_fully_qualified(base_type_expr.fullname) + else: + logger.debug(f"Looking up qualified type: {base_type_expr.fullname}") + base_type = ctx.api.lookup_qualified(base_type_expr.fullname, ctx.cls) + + if not base_type: + logger.warning(f"Could not find base type: {base_type_expr.fullname}") + return + if not isinstance(base_type.node, TypeInfo): + logger.warning(f"Base type node is not a TypeInfo: {type(base_type.node)}") + return + + # Set up inheritance + logger.info(f"Setting up inheritance for {ctx.cls.fullname} from {base_type.node.fullname}") + base_instance = Instance(base_type.node, []) + info = ctx.cls.info + info.bases = [base_instance] + info.mro = [info, base_type.node] + base_type.node.mro[1:] + logger.debug(f"MRO: {[t.fullname for t in info.mro]}") + + # Copy all methods from base type + logger.info(f"Processing methods from base type {base_type.node.fullname}") + for name, node in base_type.node.names.items(): + if isinstance(node.node, FuncDef) and isinstance(node.node.type, CallableType): + logger.debug(f"Processing method: {name}") + method_type = node.node.type + + # Convert return type to subtype if it matches base type + ret_type = convert_union_type(method_type.ret_type) + logger.debug(f"Original return type for {name}: {ret_type}") + + if isinstance(ret_type, Instance) and ret_type.type == base_type.node: + logger.debug(f"Converting return type for {name} to {info.fullname}") + ret_type = Instance(info, []) + elif isinstance(ret_type, UnionType): + logger.debug(f"Processing union return type for {name}: {ret_type}") + items: List[Union[MypyType, Instance]] = [] + for item in ret_type.items: + if isinstance(item, Instance) and item.type == base_type.node: + logger.debug(f"Converting union item from {item} to {info.fullname}") + items.append(Instance(info, [])) + else: + items.append(item) + ret_type = UnionType(items) + logger.debug(f"Final union return type for {name}: {ret_type}") + + # Create arguments list, preserving original argument types + arguments = [] + if method_type.arg_types: + logger.debug(f"Processing arguments for method {name}") + # Skip first argument (self) + for i, (arg_type, arg_kind, arg_name) in enumerate( + zip( + method_type.arg_types[1:], + method_type.arg_kinds[1:], + method_type.arg_names[1:] or [""] * len(method_type.arg_types[1:]), + ), + start=1, + ): + logger.debug( + f"Processing argument {i} for {name}: \ + {arg_name or f'arg{i}'} of type {arg_type}" + ) + + # Special handling for __contains__ method + if name == "__contains__" and i == 1: + logger.debug( + "Using Any type for __contains__ argument to satisfy Container protocol" + ) + arg_type = AnyType(TypeOfAny.special_form) + else: + # Convert any union types in arguments + arg_type = convert_union_type(arg_type) + + # Create a new variable for the argument + var = Var(arg_name or f"arg{i}", arg_type) + var.is_ready = True + + # Create the argument + arg = Argument( + variable=var, + type_annotation=arg_type, + initializer=None, + kind=arg_kind, + ) + arguments.append(arg) + + # Add method to class + logger.info(f"Adding method {name} to {ctx.cls.fullname} with return type {ret_type}") + add_method(ctx, name, arguments, ret_type) + + +def plugin(version: str) -> Type[Plugin]: + logger.info(f"Initializing plugin for mypy version: {version}") + return NewTypePlugin diff --git a/newtype/newtype.py b/newtype/newtype.py index cca92bf..47f1cfb 100644 --- a/newtype/newtype.py +++ b/newtype/newtype.py @@ -69,10 +69,10 @@ T = TypeVar("T", bound=type) -__GLOBAL_INTERNAL_TYPE_CACHE__: "WeakKeyDictionary" = WeakKeyDictionary() +__GLOBAL_INTERNAL_TYPE_CACHE__: "WeakKeyDictionary[type, type]" = WeakKeyDictionary() -def newtype_exclude(func: "Callable"): +def newtype_exclude(func: "Callable[..., Any]") -> "Callable[..., Any]": """Decorator to exclude a method from type wrapping. This decorator marks methods that should not be wrapped by NewTypeMethod, @@ -98,7 +98,7 @@ def dangerous_operation(self) -> str: return func -def func_is_excluded(func): +def func_is_excluded(func: "Callable[..., Any]") -> bool: """Check if a function is excluded from type wrapping. Args: @@ -163,7 +163,7 @@ def __init__(self, val: str) -> None: try: # we try to see if it is cached, if it is not, no problem either if base_type in __GLOBAL_INTERNAL_TYPE_CACHE__: - return __GLOBAL_INTERNAL_TYPE_CACHE__[base_type] + return cast(T, __GLOBAL_INTERNAL_TYPE_CACHE__[base_type]) except KeyError: pass @@ -191,7 +191,7 @@ class BaseNewType(base_type): # type: ignore[valid-type, misc] NEWTYPE_INIT_KWARGS_STR, ) - def __init_subclass__(cls, **init_subclass_context) -> None: + def __init_subclass__(cls, **init_subclass_context: Any) -> None: """Initialize a subclass of BaseNewType. This method is called when creating a new subclass of BaseNewType. @@ -230,7 +230,7 @@ def __init_subclass__(cls, **init_subclass_context) -> None: setattr(cls, k, v) cls.__init__ = NewTypeInit(constructor) # type: ignore[method-assign] - def __new__(cls, value=None, *_args, **_kwargs): + def __new__(cls, value: Any = None, *_args: Any, **_kwargs: Any) -> "BaseNewType": """Create a new instance of BaseNewType. This method handles the creation of new instances, ensuring that @@ -261,7 +261,7 @@ def __new__(cls, value=None, *_args, **_kwargs): inst = cast("BaseNewType", base_type.__new__(cls, value)) return inst - def __init__(self, _value=None, *_args, **_kwargs): + def __init__(self, _value: Any = None, *_args: Any, **_kwargs: Any) -> None: """Initialize an instance of BaseNewType. This method is called when an instance is created. It handles diff --git a/poetry.lock b/poetry.lock index 908edd6..e6bb344 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1822,13 +1822,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "poetry-dynamic-versioning" -version = "1.5.0" +version = "1.6.0" description = "Plugin for Poetry to enable dynamic versioning based on VCS tags" optional = false python-versions = "<4.0,>=3.7" files = [ - {file = "poetry_dynamic_versioning-1.5.0-py3-none-any.whl", hash = "sha256:d111952266a5a6963bf3a44b757ff034810e09ff1580098022964e78a097765b"}, - {file = "poetry_dynamic_versioning-1.5.0.tar.gz", hash = "sha256:68b25c26407cf0d13d51d5dc7fc45667bc041032fda42ce89401c4ce82917837"}, + {file = "poetry_dynamic_versioning-1.6.0-py3-none-any.whl", hash = "sha256:6c54bc6e6426b70f7cf7d0cff33799d1079abe541624104680dd3b288068beaa"}, + {file = "poetry_dynamic_versioning-1.6.0.tar.gz", hash = "sha256:568b728eeb7b2c4a3b818565bbaffc2128bde68ad037152d464b92975f45ce70"}, ] [package.dependencies] @@ -1870,13 +1870,13 @@ files = [ [[package]] name = "pydantic" -version = "2.10.4" +version = "2.10.5" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d"}, - {file = "pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06"}, + {file = "pydantic-2.10.5-py3-none-any.whl", hash = "sha256:4dd4e322dbe55472cb7ca7e73f4b63574eecccf2835ffa2af9021ce113c83c53"}, + {file = "pydantic-2.10.5.tar.gz", hash = "sha256:278b38dbbaec562011d659ee05f63346951b3a248a6f3642e1bc68894ea2b4ff"}, ] [package.dependencies] @@ -2662,29 +2662,29 @@ files = [ [[package]] name = "ruff" -version = "0.8.6" +version = "0.9.1" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.8.6-py3-none-linux_armv6l.whl", hash = "sha256:defed167955d42c68b407e8f2e6f56ba52520e790aba4ca707a9c88619e580e3"}, - {file = "ruff-0.8.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:54799ca3d67ae5e0b7a7ac234baa657a9c1784b48ec954a094da7c206e0365b1"}, - {file = "ruff-0.8.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e88b8f6d901477c41559ba540beeb5a671e14cd29ebd5683903572f4b40a9807"}, - {file = "ruff-0.8.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0509e8da430228236a18a677fcdb0c1f102dd26d5520f71f79b094963322ed25"}, - {file = "ruff-0.8.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91a7ddb221779871cf226100e677b5ea38c2d54e9e2c8ed847450ebbdf99b32d"}, - {file = "ruff-0.8.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:248b1fb3f739d01d528cc50b35ee9c4812aa58cc5935998e776bf8ed5b251e75"}, - {file = "ruff-0.8.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:bc3c083c50390cf69e7e1b5a5a7303898966be973664ec0c4a4acea82c1d4315"}, - {file = "ruff-0.8.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52d587092ab8df308635762386f45f4638badb0866355b2b86760f6d3c076188"}, - {file = "ruff-0.8.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:61323159cf21bc3897674e5adb27cd9e7700bab6b84de40d7be28c3d46dc67cf"}, - {file = "ruff-0.8.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ae4478b1471fc0c44ed52a6fb787e641a2ac58b1c1f91763bafbc2faddc5117"}, - {file = "ruff-0.8.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0c000a471d519b3e6cfc9c6680025d923b4ca140ce3e4612d1a2ef58e11f11fe"}, - {file = "ruff-0.8.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9257aa841e9e8d9b727423086f0fa9a86b6b420fbf4bf9e1465d1250ce8e4d8d"}, - {file = "ruff-0.8.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:45a56f61b24682f6f6709636949ae8cc82ae229d8d773b4c76c09ec83964a95a"}, - {file = "ruff-0.8.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:496dd38a53aa173481a7d8866bcd6451bd934d06976a2505028a50583e001b76"}, - {file = "ruff-0.8.6-py3-none-win32.whl", hash = "sha256:e169ea1b9eae61c99b257dc83b9ee6c76f89042752cb2d83486a7d6e48e8f764"}, - {file = "ruff-0.8.6-py3-none-win_amd64.whl", hash = "sha256:f1d70bef3d16fdc897ee290d7d20da3cbe4e26349f62e8a0274e7a3f4ce7a905"}, - {file = "ruff-0.8.6-py3-none-win_arm64.whl", hash = "sha256:7d7fc2377a04b6e04ffe588caad613d0c460eb2ecba4c0ccbbfe2bc973cbc162"}, - {file = "ruff-0.8.6.tar.gz", hash = "sha256:dcad24b81b62650b0eb8814f576fc65cfee8674772a6e24c9b747911801eeaa5"}, + {file = "ruff-0.9.1-py3-none-linux_armv6l.whl", hash = "sha256:84330dda7abcc270e6055551aca93fdde1b0685fc4fd358f26410f9349cf1743"}, + {file = "ruff-0.9.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3cae39ba5d137054b0e5b472aee3b78a7c884e61591b100aeb544bcd1fc38d4f"}, + {file = "ruff-0.9.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:50c647ff96f4ba288db0ad87048257753733763b409b2faf2ea78b45c8bb7fcb"}, + {file = "ruff-0.9.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0c8b149e9c7353cace7d698e1656ffcf1e36e50f8ea3b5d5f7f87ff9986a7ca"}, + {file = "ruff-0.9.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:beb3298604540c884d8b282fe7625651378e1986c25df51dec5b2f60cafc31ce"}, + {file = "ruff-0.9.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39d0174ccc45c439093971cc06ed3ac4dc545f5e8bdacf9f067adf879544d969"}, + {file = "ruff-0.9.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:69572926c0f0c9912288915214ca9b2809525ea263603370b9e00bed2ba56dbd"}, + {file = "ruff-0.9.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:937267afce0c9170d6d29f01fcd1f4378172dec6760a9f4dface48cdabf9610a"}, + {file = "ruff-0.9.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:186c2313de946f2c22bdf5954b8dd083e124bcfb685732cfb0beae0c47233d9b"}, + {file = "ruff-0.9.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f94942a3bb767675d9a051867c036655fe9f6c8a491539156a6f7e6b5f31831"}, + {file = "ruff-0.9.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:728d791b769cc28c05f12c280f99e8896932e9833fef1dd8756a6af2261fd1ab"}, + {file = "ruff-0.9.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2f312c86fb40c5c02b44a29a750ee3b21002bd813b5233facdaf63a51d9a85e1"}, + {file = "ruff-0.9.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ae017c3a29bee341ba584f3823f805abbe5fe9cd97f87ed07ecbf533c4c88366"}, + {file = "ruff-0.9.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5dc40a378a0e21b4cfe2b8a0f1812a6572fc7b230ef12cd9fac9161aa91d807f"}, + {file = "ruff-0.9.1-py3-none-win32.whl", hash = "sha256:46ebf5cc106cf7e7378ca3c28ce4293b61b449cd121b98699be727d40b79ba72"}, + {file = "ruff-0.9.1-py3-none-win_amd64.whl", hash = "sha256:342a824b46ddbcdddd3abfbb332fa7fcaac5488bf18073e841236aadf4ad5c19"}, + {file = "ruff-0.9.1-py3-none-win_arm64.whl", hash = "sha256:1cd76c7f9c679e6e8f2af8f778367dca82b95009bc7b1a85a47f1521ae524fa7"}, + {file = "ruff-0.9.1.tar.gz", hash = "sha256:fd2b25ecaf907d6458fa842675382c8597b3c746a2dde6717fe3415425df0c17"}, ] [[package]] diff --git a/pyproject.toml b/pyproject.toml index d6edbb8..90fd02b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,10 +4,10 @@ build-backend = "poetry_dynamic_versioning.backend" [tool.poetry] name = "python-newtype" -version = "0.1.3" +version = "0.1.4" homepage = "https://github.com/jymchng/python-newtype-dev" repository = "https://github.com/jymchng/python-newtype-dev" -license = "Apache-2.0" +license = "MIT" documentation = "https://py-nt.asyncmove.com" description = "A Python library for creating and managing new types with enhanced type safety and flexibility." authors = ["Jim Chng "] @@ -349,7 +349,16 @@ exclude = [ '/*.sh', 'newtype/*.pyi', ] -# plugins = [""] +plugins = ["newtype.mypy_plugin"] +disallow_untyped_defs = true +disallow_incomplete_defs = true +check_untyped_defs = true +disallow_untyped_decorators = true +no_implicit_optional = true +warn_redundant_casts = true +warn_return_any = true +warn_unreachable = true +strict_optional = true [[tool.mypy.overrides]] module = "tests.*"