Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release/0.1.3 #4

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "crimson-auto-pydantic"
version = "0.1.2"
version = "0.1.3"
description = "Template Tools"
readme = "README.md"
authors = [
Expand All @@ -24,7 +24,8 @@ dependencies = [
"pydantic",
"crimson-code-extractor",
"crimson-ast-dev-tool",
"inflection"
"inflection",
"crimson-intelli-type"
]
requires-python = ">=3.9"

Expand All @@ -33,4 +34,4 @@ requires-python = ">=3.9"
"Bug Tracker" = "https://github.com/crimson206/auto-pydantic/issues"

[tool.black]
line-length = 120
line-length = 120
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ pydantic
crimson-code-extractor
crimson-ast-dev-tool
inflection
crimson-intelli-type
17 changes: 17 additions & 0 deletions src/crimson/auto_pydantic/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,19 @@
from .generator import generate_constructor, generate_output_props, generate_input_props
from .validator import validate

import zipfile
import os


def extract_tests(destination):
"""
ZIP 파일에서 test 폴더를 추출합니다.

:param destination: test 폴더를 추출할 목적지 경로
"""
package_dir = os.path.dirname(__file__)
zip_path = os.path.join(package_dir, "test.zip")

with zipfile.ZipFile(zip_path, "r") as zip_ref:
zip_ref.extractall(destination)
print(f"Tests have been extracted to {destination}")
140 changes: 103 additions & 37 deletions src/crimson/auto_pydantic/generator.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,92 @@
from inflection import camelize
from typing import List
from typing import List, Generic, TypeVar, Union, Callable, Tuple
from crimson.code_extractor import extract
from crimson.ast_dev_tool import safe_unparse
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")

def generate_constructor(function_node: ast.FunctionDef, indent: int = 4) -> str:

class Function_(IntelliType, Tuple[as_union, Callable, str, ast.FunctionDef], Generic[T]):
"""
def func(arg1:int):
return arg1


It can be any of them among,

func
function_code = inspect.getsource(func)
function_node = ast.parse(function_code)

It is safe even if,
- The function is not places at the first
- There are many functions in it

All the functions from auto-pydantic will deal with them flexibly.
If there are many functions in it, the first function is the object this module uses.
"""


class Constructor_(IntelliType, str, Generic[T]):
"""
# generate_constructor

## Description
The `generate_constructor` function generates a constructor method for a Pydantic model based on the given function node. It creates an `__init__` method that initializes the model's attributes using the function's parameters.

## Parameters
- `function_node` (ast.FunctionDef): An Abstract Syntax Tree (AST) node representing the function for which the constructor is being generated.
- `indent` (int, optional): The number of spaces to use for indentation. Default is 4.

## Returns
- `str`: A string containing the generated constructor method.

## Functionality
1. Extracts the function specification from the given AST node.
2. Determines if the function is already an `__init__` method or a regular function.
3. Generates the method signature with appropriate parameters.
4. Creates a `super().__init__()` call to initialize the Pydantic model.
5. Includes all function parameters in the `super().__init__()` call, except for `self` and `cls`.

## Example Output
```python
def __init__(self, arg1: int, arg2: str = 'default'):
super().__init__(arg1=arg1, arg2=arg2)
```

## Notes
- The function handles both regular functions and existing `__init__` methods.
- It properly handles default values and type annotations from the original function.
- The generated constructor is compatible with Pydantic's model initialization.
It is the code lines of the constructor of the pydantic model.

It allows you to use the model in the same structure where you use the function.

function:
def func2(arg1: int, *args: Tuple[int, int], arg2: str = "hi", arg3: int = 1, **kwargs, ) -> str:
return "bye"

constructor: str
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)

"""


class InputProps_(IntelliType, str, Generic[T]):
"""
It is the code lines of the InputProps model for the target function's input parameters.

function:
def func2(arg1: int, *args: Tuple[int, int], arg2: str = "hi", arg3: int = 1, **kwargs, ) -> str:
return "bye"

input_props: str
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\}

"""


class OutputProps_(IntelliType, str, Generic[T]):
"""
It is the code lines of the OutputProps model for the target function's return annotation.

function:
def func2(arg1: int, *args: Tuple[int, int], arg2: str = "hi", arg3: int = 1, **kwargs, ) -> str:
return "bye"

output_props: str
class Func2OutputProps(BaseModel):
return: str
"""


def generate_constructor(
function: Function_[Union[Callable, str, ast.FunctionDef]], indent: int = 4
) -> Constructor_[str]:
function_node = _convert_to_FunctionDef(function)
func_spec = extract.extract_func_spec(function_node)
indent = " " * indent
if func_spec.name == "__init__":
Expand All @@ -58,7 +107,10 @@ def __init__(self, arg1: int, arg2: str = 'default'):
return constructor


def generate_input_props(function_node: ast.FunctionDef, constructor: bool = True):
def generate_input_props(
function: Function_[Union[Callable, str, ast.FunctionDef]], add_constructor: bool = True
) -> InputProps_[str]:
function_node: ast.FunctionDef = _convert_to_FunctionDef(function)
func_spec = extract.extract_func_spec(function_node)

input_props_name = _generate_input_props_name(func_spec.name)
Expand All @@ -85,14 +137,16 @@ def generate_input_props(function_node: ast.FunctionDef, constructor: bool = Tru

input_props = "\n".join(input_props_lines)

if constructor is True:
constructor = generate_constructor(function_node)
input_props += "\n\n" + constructor
if add_constructor is True:
add_constructor = generate_constructor(function_node)
input_props += "\n\n" + add_constructor

return input_props


def generate_output_props(function_node: ast.FunctionDef):
def generate_output_props(function: Function_[Union[Callable, str, ast.FunctionDef]]) -> OutputProps_[str]:
function_node = _convert_to_FunctionDef(function)

func_spec = extract.extract_func_spec(function_node)
Func_name = camelize(func_spec.name, uppercase_first_letter=True)

Expand All @@ -114,3 +168,15 @@ def _generate_input_props_name(func_name: str) -> str:
Func_name = camelize(func_name, uppercase_first_letter=True)
input_props_name = f"{Func_name}InputProps"
return input_props_name


def _convert_to_FunctionDef(function: Function_[Union[Callable, str, ast.FunctionDef]]) -> ast.FunctionDef:
if type(function) is Callable:
function = getsource(function)

if type(function) is str:
function = ast.parse(function)

function_node: ast.FunctionDef = collect_nodes(function, ast.FunctionDef)[0]

return function_node
Binary file added src/crimson/auto_pydantic/test.zip
Binary file not shown.
2 changes: 2 additions & 0 deletions src/crimson/auto_pydantic/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import typing
import threading


_data_classes_lock = threading.Lock()
_data_classes = {}

Expand Down Expand Up @@ -45,6 +46,7 @@ def _get_or_create_input_props(

def _create_input_props(function_node: ast.FunctionDef, func_name: str, namespace: Dict[str, Any]):
model = generate_input_props(function_node)

local_scope = {}
exec(model, namespace, local_scope)
return local_scope[func_name]
Expand Down
4 changes: 1 addition & 3 deletions test/test_validator.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import pytest
from pydantic import ValidationError
from typing import List, Optional
from crimson.auto_pydantic.validator import validate
from pydantic import BaseModel, Field
from inspect import currentframe
from pydantic import BaseModel, Field # noqa: F401


def simple_function(arg1: int, arg2: str = "default") -> str:
Expand Down
Loading