diff --git a/.flake8 b/.flake8 index aa88f0f..eb56e97 100644 --- a/.flake8 +++ b/.flake8 @@ -14,4 +14,5 @@ ignore = E501, W605 # E305: To add per-file-ignores = - generate_toml.py: E305, E302 \ No newline at end of file + generate_toml.py: E305, E302 + __init__.py: F401 \ No newline at end of file diff --git a/auto-pydantic-dev b/auto-pydantic-dev index 34dedc4..adc5e38 160000 --- a/auto-pydantic-dev +++ b/auto-pydantic-dev @@ -1 +1 @@ -Subproject commit 34dedc42b39b0b0b1981ae10d9bcf8251c443e2a +Subproject commit adc5e387dedc8edbc9ba1be7bc0facada6802836 diff --git a/pyproject.toml b/pyproject.toml index 19ded27..b9132ec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,21 +4,29 @@ build-backend = "setuptools.build_meta" [project] name = "crimson-auto-pydantic" -version = "0.1.5" -description = "Template Tools" +version = "0.1.7" +description = "Automatic Pydantic model generation and validation for function parameters and return types." readme = "README.md" authors = [ { name="Sisung Kim", email="sisung.kim1@gmail.com" }, ] classifiers = [ + "Development Status :: 4 - Beta", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Intended Audience :: Developers", - "Topic :: Software Development :: Build Tools", + + "Topic :: Software Development :: Libraries", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", + + "Typing :: Typed", ] dependencies = [ "pydantic", diff --git a/scripts/setup_env.sh b/scripts/setup_env.sh index b5aa7bc..cdfe1d2 100644 --- a/scripts/setup_env.sh +++ b/scripts/setup_env.sh @@ -7,4 +7,5 @@ conda create --name auto-pydantic python=$PYTHON_VERSION -y conda activate auto-pydantic pip install -r requirements.txt - +pip install -r requirements_test.txt +pip install -r requirements_dev.txt diff --git a/src/crimson/auto_pydantic/generator.py b/src/crimson/auto_pydantic/generator.py index 10782fd..6421f2e 100644 --- a/src/crimson/auto_pydantic/generator.py +++ b/src/crimson/auto_pydantic/generator.py @@ -1,40 +1,39 @@ from inflection import camelize -from typing import List, Generic, TypeVar, Union, Callable, Tuple +from typing import List, Generic, TypeVar, Union, Callable from crimson.code_extractor import extract from crimson.ast_dev_tool import safe_unparse, collect_nodes from crimson.intelli_type import IntelliType -from crimson.intelli_type._replace_union import as_union from inspect import getsource import ast T = TypeVar("T") -class Function_(IntelliType, Tuple[as_union, Callable, str, ast.FunctionDef], Generic[T]): +class Function_(IntelliType[Union[Callable, str, ast.FunctionDef]], Generic[T]): """ A versatile representation of a function that can handle various input types. Example: - def func(arg1: int): - return arg1 + def func(arg1: int): + return arg1 - This class can handle any of the following representations: - - func (the function object itself) - - inspect.getsource(func) (the function's source code) - - ast.parse(inspect.getsource(func)).body[0] (the AST node of the function) + This class can handle any of the followings: + - func + - inspect.getsource(func) + - ast.parse(inspect.getsource(func)).body[0] Key features: - - Robust handling of functions, even when they're not the first item in a module - - Capable of processing modules with multiple function definitions - - All auto-pydantic functions are designed to work flexibly with this class - - When multiple functions are present, the first function is used by default + - Robust handling of functions, even when they're not the first item in a module + - Capable of processing modules with multiple function definitions + - All auto-pydantic functions are designed to work flexibly with this class + - When multiple functions are present, the first function is used by default Note: This flexibility allows for easier integration and usage across different scenarios in the auto-pydantic module. """ -class Constructor_(IntelliType, str, Generic[T]): +class Constructor_(IntelliType[str], Generic[T]): """ Represents the constructor code for a Pydantic model. @@ -42,20 +41,20 @@ class Constructor_(IntelliType, str, Generic[T]): It allows the model to mirror the structure of the original function it's based on. Example: - Original function: - def func2(arg1: int, *args: Tuple[int, int], arg2: str = "hi", arg3: int = 1, **kwargs) -> str: - return "bye" + Original function: + def func2(arg1: int, *args: Tuple[int, int], arg2: str = "hi", arg3: int = 1, **kwargs) -> str: + return "bye" - Corresponding constructor: - def __init__(self, arg1: int, *args: Tuple[int, int], arg2: str='hi', arg3: int=1, **kwargs): - super().__init__(arg1=arg1, args=args, arg2=arg2, arg3=arg3, kwargs=kwargs) + Corresponding constructor: + def __init__(self, arg1: int, *args: Tuple[int, int], arg2: str='hi', arg3: int=1, **kwargs): + super().__init__(arg1=arg1, args=args, arg2=arg2, arg3=arg3, kwargs=kwargs) This constructor ensures that the Pydantic model can be instantiated with the same signature as the original function, maintaining consistency in parameter handling. """ -class InputProps_(IntelliType, str, Generic[T]): +class InputProps_(IntelliType[str], Generic[T]): """ Represents the input properties of a Pydantic model based on a function's parameters. @@ -64,25 +63,25 @@ class InputProps_(IntelliType, str, Generic[T]): Example: For the function: - def func2(arg1: int, *args: Tuple[int, int], arg2: str = "hi", arg3: int = 1, **kwargs) -> str: - return "bye" + def func2(arg1: int, *args: Tuple[int, int], arg2: str = "hi", arg3: int = 1, **kwargs) -> str: + return "bye" The corresponding InputProps model would be: - class Func2InputProps(BaseModel): - arg1: int = Field(...) - args: Tuple[int, int] = Field(default=()) - arg2: str = Field(default="'hi'") - arg3: int = Field(default='1') - kwargs: Any = Field(default={}) + class Func2InputProps(BaseModel): + arg1: int = Field(...) + args: Tuple[int, int] = Field(default=()) + arg2: str = Field(default="'hi'") + arg3: int = Field(default='1') + kwargs: Any = Field(default={}) - {optional_constructor} + {optional_constructor} Note: The {optional_constructor} placeholder can be replaced with an actual constructor if needed, allowing for flexible model creation. """ -class OutputProps_(IntelliType, str, Generic[T]): +class OutputProps_(IntelliType[str], Generic[T]): """ Represents the output properties of a Pydantic model based on a function's return type. @@ -90,13 +89,13 @@ class OutputProps_(IntelliType, str, Generic[T]): encapsulates the return type of a target function. Example: - For the function: - def func2(arg1: int, *args: Tuple[int, int], arg2: str = "hi", arg3: int = 1, **kwargs) -> str: - return "bye" + For the function: + def func2(arg1: int, *args: Tuple[int, int], arg2: str = "hi", arg3: int = 1, **kwargs) -> str: + return "bye" The corresponding OutputProps model would be: - class Func2OutputProps(BaseModel): - return: str + class Func2OutputProps(BaseModel): + return: str This model standardizes the function's output, making it easier to validate and work with the return value in a type-safe manner. @@ -104,7 +103,7 @@ class Func2OutputProps(BaseModel): def generate_constructor( - function: Function_[Union[Callable, str, ast.FunctionDef]], indent: int = 4 + function: Union[Callable, str, ast.FunctionDef], indent: int = 4 ) -> Constructor_[str]: """ Generate a constructor string for a Pydantic model based on the given function. diff --git a/src/crimson/auto_pydantic/generator_model.py b/src/crimson/auto_pydantic/generator_model.py index e660f2a..5dc8294 100644 --- a/src/crimson/auto_pydantic/generator_model.py +++ b/src/crimson/auto_pydantic/generator_model.py @@ -11,7 +11,7 @@ T = TypeVar("T") -class Func_(IntelliType, Callable, Generic[T]): +class Func_(IntelliType[Callable], Generic[T]): """ Any function you defined. @@ -22,11 +22,7 @@ def my_func(arg1: int, arg2: str) -> str: """ -class FrameType_: - """Dummy of FrameType""" - - -class CurrentFrame_(IntelliType, FrameType_, Generic[T]): +class CurrentFrame_(IntelliType[FrameType], Generic[T]): """ from inspect import currentframe() @@ -34,22 +30,20 @@ class CurrentFrame_(IntelliType, FrameType_, Generic[T]): """ - _annotation = FrameType - -class InputPropsModel_(IntelliType, BaseModel, Generic[T]): +class InputPropsModel_(IntelliType[BaseModel], Generic[T]): """ The generated model using the inputprops string from 'generator'. """ -class _NameSpace_(IntelliType, Dict[str, Any], Generic[T]): +class _NameSpace_(IntelliType[Dict[str, Any]], Generic[T]): """ It contains variables to be used to generate inputprops, returnprops, and constructor """ -class _FuncName_(IntelliType, str, Generic[T]): +class _FuncName_(IntelliType[str], Generic[T]): """ It might be the literal name of a function. However, it must be Camel case in the real usage. @@ -61,13 +55,15 @@ def snake_case(): """ -class _FunctionNode_(IntelliType, ast.FunctionDef, Generic[T]): +class _FunctionNode_(IntelliType[ast.FunctionDef], Generic[T]): """ The ast module uses it to extract the props of Func_[str]. """ -def generate_inputprops_model(func: Func_[Callable], currentframe: CurrentFrame_[FrameType], *args: Any, **kwargs: Any) -> InputPropsModel_[BaseModel]: +def generate_inputprops_model( + func: Func_[Callable], currentframe: CurrentFrame_[FrameType], *args: Any, **kwargs: Any +) -> InputPropsModel_[BaseModel]: namespace = _prepare_namespace(currentframe, args, kwargs) function_node = _get_function_node(func) func_name = _generate_input_props_name(func.__name__) diff --git a/src/crimson/auto_pydantic/validator.py b/src/crimson/auto_pydantic/validator.py index fdbff56..1f6acf4 100644 --- a/src/crimson/auto_pydantic/validator.py +++ b/src/crimson/auto_pydantic/validator.py @@ -38,7 +38,7 @@ class Bool_: """ -class Validated_(IntelliType, Bool_, Generic[T]): +class Validated_(IntelliType[bool], Generic[T]): """ It doesn't mean whether the validation was successful or not. It rather shows the validate function was conducted or just passed. diff --git a/test/test_validator.py b/test/test_validator.py index bcaa335..27d43fa 100644 --- a/test/test_validator.py +++ b/test/test_validator.py @@ -1,6 +1,6 @@ import pytest from crimson.auto_pydantic.validator import validate, config -from inspect import currentframe +from inspect import currentframe, getsource @pytest.fixture @@ -34,6 +34,7 @@ def init_validate_function(arg1: int, arg2: str = "default") -> str: def test_validate_simple_valid(): # This should not raise any exception + source = getsource(simple_function) validate(simple_function, currentframe(), arg1=1, arg2="test")