Skip to content

Commit

Permalink
Merge branch 'release/0.1.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
crimson206 committed Jun 26, 2024
2 parents d75f617 + 9566322 commit 709a093
Show file tree
Hide file tree
Showing 19 changed files with 441 additions and 7 deletions.
17 changes: 17 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# .flake8
[flake8]

# E203: Whitespace before ':'
# E501: Line too long (79 characters)
# W503: Line break before binary operator
# W605: Invalid escape sequence
# E305: expected 2 blank lines after class or function definition
# # this comment will cause the warning.
# variable = 1

# W605: To use the crimson-templator module
ignore = E501, W605

# E305: To add
per-file-ignores =
generate_toml.py: E305, E302
38 changes: 38 additions & 0 deletions .github/workflows/python-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Test

on:
pull_request:
branches: [ main, develop, 'release/*', 'feature/*' ]
workflow_dispatch:

jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11"]

steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install flake8
pip install setuptools wheel
pip install -r requirements.txt
pip install .
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Run tests with coverage
run: |
pytest --cov=src/crimson/auto_pydantic --cov-report=html
6 changes: 1 addition & 5 deletions .github/release.yaml → .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
name: Release Workflow

on:
push:
branches:
- main
tags:
- 'v*'
workflow_dispatch:

jobs:
build-and-release:
Expand Down
22 changes: 22 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
// Folderable .py

"editor.foldingStrategy": "indentation",
"editor.foldRegions": {
"start": "^\\s*#\\s*region\\b",
"end": "^\\s*#\\s*endregion\\b"
},
"editor.defaultFoldingImportLevel": 2,


// Additional VS Code Settings
"workbench.settings.openDefaultSettings": true,

// Unittest Setup
"python.testing.pytestEnabled": true,
"python.testing.pytestPath": "test",
"python.testing.pytestArgs": [
"test"
],
"python.testing.unittestEnabled": false,
}
1 change: 1 addition & 0 deletions auto-pydantic-dev
Submodule auto-pydantic-dev added at 60c08a
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ classifiers = [
"Operating System :: OS Independent",
]
dependencies = [
"pydantic", # Assuming core also uses pydantic, adjust as necessary
"pydantic",
"crimson-code-extractor",
"crimson-ast-dev-tool"
]
requires-python = ">=3.9"

Expand Down
7 changes: 7 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
inflection
jinja2
crimson-code-extractor
crimson-ast-dev-tool
crimson-file-loader
pytest
pytest-cov
40 changes: 40 additions & 0 deletions scripts/generate_dev_repo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/bin/bash

# Parent repository 이름을 묻기
echo "Enter the parent repository name:"
read parent_repo

# 새로운 repository 이름 생성
dev_repo="${parent_repo}-dev"

# GitHub CLI를 사용하여 새로운 private repository 생성
gh repo create "$dev_repo" --private --description "Development repository for $parent_repo" --gitignore Python

# 결과 확인
if [ $? -eq 0 ]; then
echo "Successfully created repository: $dev_repo"

# 새로운 repository를 현재 디렉토리에 clone
git clone "https://github.com/crimson206/$dev_repo.git"

# clone 결과 확인
if [ $? -eq 0 ]; then
echo "Successfully cloned repository: $dev_repo"

# README 파일 생성 및 추가
cd "$dev_repo"
echo "# $dev_repo" > README.md
git add README.md
git commit -m "Add README file"
git push origin main
echo "Successfully added README file to repository: $dev_repo"

# parent repository의 디렉토리로 이동 (상위 디렉토리로 이동)
cd ..

else
echo "Failed to clone repository: $dev_repo"
fi
else
echo "Failed to create repository: $dev_repo"
fi
1 change: 1 addition & 0 deletions scripts/setup_base.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pip install -r ./scripts/setup_requirements.txt
10 changes: 10 additions & 0 deletions scripts/setup_env.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# !bin/bash

read -p "Please enter the Python version you want to use (e.g., 3.9): " PYTHON_VERSION

conda create --name auto-pydantic python=$PYTHON_VERSION -y

conda activate auto-pydantic

pip install -r requirements.txt

2 changes: 2 additions & 0 deletions scripts/setup_requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pydantic
crimson-templator
3 changes: 3 additions & 0 deletions scripts/unittest.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!bin/bash

coverage run --source=. -m unittest discover -s ./test -p "test_*.py"
1 change: 0 additions & 1 deletion src/crimson/auto_pydantic.py

This file was deleted.

2 changes: 2 additions & 0 deletions src/crimson/auto_pydantic/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .generator import generate_constructor, generate_output_props, generate_input_props
from .validator import validate
84 changes: 84 additions & 0 deletions src/crimson/auto_pydantic/generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
from inflection import camelize
from typing import List
from crimson.code_extractor import extract
from crimson.ast_dev_tool import safe_unparse
import ast


def generate_constructor(function_node: ast.FunctionDef, indent: int = 4) -> str:
func_spec = extract.extract_func_spec(function_node)
indent = " " * indent
if func_spec.name == "__init__":
start = "def __init__("
else:
start = "def __init__(self, "

def_init = indent + start + safe_unparse(function_node.args) + "):\n"
super_init = indent * 2 + "super().__init__("
for arg_spec in func_spec.arg_specs:
arg_name = arg_spec.name
if arg_spec.name in ["self", "cls"]:
continue
super_init += f"{arg_name}={arg_name}, "
super_init = super_init[:-2] + ")"

constructor = def_init + super_init
return constructor


def generate_input_props(function_node: ast.FunctionDef, constructor: bool = True):
func_spec = extract.extract_func_spec(function_node)

input_props_name = _generate_input_props_name(func_spec.name)
input_props_lines: List[str] = [f"class {input_props_name}(BaseModel):"]
arg_line_template = " {arg_name}: {annotation} = {field}"

for arg_spec in func_spec.arg_specs:
if arg_spec.name in ["self", "cls"]:
continue

annotation = arg_spec.annotation if arg_spec.annotation is not None else "Any"

if arg_spec.default is not None:
field = f"Field(default={repr(arg_spec.default)})"
elif arg_spec.type == "vararg":
field = "Field(default=())"
elif arg_spec.type == "kwarg":
field = "Field(default={})"
else:
field = "Field(...)"

arg_line = arg_line_template.format(arg_name=arg_spec.name, annotation=annotation, field=field)
input_props_lines.append(arg_line)

input_props = "\n".join(input_props_lines)

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

return input_props


def generate_output_props(function_node: ast.FunctionDef):
func_spec = extract.extract_func_spec(function_node)
Func_name = camelize(func_spec.name, uppercase_first_letter=True)

output_props_lines: List[str] = [f"class {Func_name}OutputProps(BaseModel):"]
arg_line_template = " return: {annotation}"

annotation = func_spec.return_annotation if func_spec.return_annotation is not None else "Any"

arg_line = arg_line_template.format(
annotation=annotation,
)

output_props_lines.append(arg_line)
output_props = "\n".join(output_props_lines)
return output_props


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
59 changes: 59 additions & 0 deletions src/crimson/auto_pydantic/validator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from typing import Callable, Dict, Any
import ast
from crimson.ast_dev_tool import get_first_node
from crimson.auto_pydantic.generator import generate_input_props, _generate_input_props_name
from inspect import getsource
import typing
import threading

_data_classes_lock = threading.Lock()
_data_classes = {}


def validate(func: Callable, currentframe, *args, **kwargs):
namespace = _prepare_namespace(currentframe, args, kwargs)
function_node = _get_function_node(func)
func_name = _generate_input_props_name(func.__name__)

InputProps = _get_or_create_input_props(func, function_node, func_name, namespace)
namespace[func_name] = InputProps

_execute_validation(func_name, namespace)


def _prepare_namespace(currentframe, args, kwargs) -> Dict[str, Any]:
namespace = {}
namespace.update(currentframe.f_globals.copy())
namespace.update(_get_types())
namespace.update({"args": args, "kwargs": kwargs})
return namespace


def _get_function_node(func: Callable) -> ast.FunctionDef:
func_source = getsource(func)
return get_first_node(func_source, ast.FunctionDef)


def _get_or_create_input_props(
func: Callable, function_node: ast.FunctionDef, func_name: str, namespace: Dict[str, Any]
):
with _data_classes_lock:
if func not in _data_classes:
_data_classes[func] = _create_input_props(function_node, func_name, namespace)
return _data_classes[func]


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]


def _execute_validation(func_name: str, namespace: Dict[str, Any]):
validation = f"\n{func_name}(*args, **kwargs)"
exec(validation, namespace)


def _get_types() -> Dict[str, Any]:
return {"Any": typing.Any}
1 change: 1 addition & 0 deletions template
Submodule template added at 18db60
Loading

0 comments on commit 709a093

Please sign in to comment.