Skip to content

Commit

Permalink
add code gen
Browse files Browse the repository at this point in the history
  • Loading branch information
LuisLuii committed Oct 17, 2022
1 parent 26b4620 commit 9ffc161
Show file tree
Hide file tree
Showing 37 changed files with 1,373 additions and 1,118 deletions.
5 changes: 5 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
include src/fastapi_quickcrud_codegen/model/template/common/*.jinja2
include src/fastapi_quickcrud_codegen/model/template/pydantic/*.jinja2
include src/fastapi_quickcrud_codegen/model/template/route/*.jinja2
include src/fastapi_quickcrud_codegen/model/template/sqlalchemy/*.jinja2
include src/fastapi_quickcrud_codegen/model/template/*.jinja2
Binary file removed dist/fastapi_quickcrud-0.2.1.tar.gz
Binary file not shown.
Binary file removed dist/fastapi_quickcrud-0.2.2.tar.gz
Binary file not shown.
87 changes: 87 additions & 0 deletions sample_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import os

from fastapi import FastAPI
from sqlalchemy import ARRAY, BigInteger, Boolean, CHAR, Column, Date, DateTime, Float, Integer, \
JSON, Numeric, SmallInteger, String, Text, Time, UniqueConstraint, text
from sqlalchemy.dialects.postgresql import INTERVAL, JSONB, UUID
from sqlalchemy.orm import declarative_base, sessionmaker

from fastapi_quickcrud_codegen import crud_router_builder, CrudMethods

TEST_DATABASE_URL = os.environ.get('TEST_DATABASE_URL', 'postgresql://postgres:1234@127.0.0.1:5432/postgres')

app = FastAPI()

Base = declarative_base()
metadata = Base.metadata

from sqlalchemy import create_engine

engine = create_engine(TEST_DATABASE_URL, future=True, echo=True,
pool_use_lifo=True, pool_pre_ping=True, pool_recycle=7200)
async_session = sessionmaker(autocommit=False, autoflush=False, bind=engine)


def get_transaction_session():
try:
db = async_session()
yield db
finally:
db.close()


class UntitledTable256(Base):
primary_key_of_table = "primary_key"
unique_fields = ['primary_key', 'int4_value', 'float4_value']
__tablename__ = 'test_build_myself'
__table_args__ = (
UniqueConstraint('primary_key', 'int4_value', 'float4_value'),
)
primary_key = Column(Integer, primary_key=True, info={'alias_name': 'primary_key'}, autoincrement=True,
server_default="nextval('test_build_myself_id_seq'::regclass)")
bool_value = Column(Boolean, nullable=False, server_default=text("false"))
# bytea_value = Column(LargeBinary)
char_value = Column(CHAR(10))
date_value = Column(Date, server_default=text("now()"))
float4_value = Column(Float, nullable=False)
float8_value = Column(Float(53), nullable=False, server_default=text("10.10"))
int2_value = Column(SmallInteger, nullable=False)
int4_value = Column(Integer, nullable=True)
int8_value = Column(BigInteger, server_default=text("99"))
interval_value = Column(INTERVAL)
json_value = Column(JSON)
jsonb_value = Column(JSONB(astext_type=Text()))
numeric_value = Column(Numeric)
text_value = Column(Text)
time_value = Column(Time)
timestamp_value = Column(DateTime)
timestamptz_value = Column(DateTime(True))
timetz_value = Column(Time(True))
uuid_value = Column(UUID(as_uuid=True))
varchar_value = Column(String)
# xml_value = Column(NullType)
array_value = Column(ARRAY(Integer()))
array_str__value = Column(ARRAY(String()))
# box_valaue = Column(NullType)


crud_route_child2 = crud_router_builder(
db_model=UntitledTable256,
prefix="/blog_comment",
tags=["blog_comment"],
db_session=get_transaction_session,
crud_methods=[CrudMethods.FIND_ONE]
)

app = FastAPI()
[app.include_router(i) for i in [crud_route_child2]]


@app.get("/", tags=["child"])
async def root():
return {"message": "Hello World"}


import uvicorn

uvicorn.run(app, host="0.0.0.0", port=8002, debug=False)
11 changes: 8 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from setuptools import setup, find_packages

VERSION = '0.2.1'
VERSION = '0.0.7'

print("""
Expand All @@ -16,7 +16,7 @@

if __name__ == '__main__':
setup(
name='fastapi_quickcrud',
name='fastapi_quickcrud_code_generator_beta',
version=VERSION,
install_requires=["fastapi<=0.68.2","pydantic<=1.8.2","SQLAlchemy<=1.4.30","StrEnum==0.4.7","starlette==0.14.2",
"aiosqlite==0.17.0","uvicorn==0.17.0","greenlet==1.1.2","anyio==3.5.0"],
Expand All @@ -29,7 +29,11 @@
url='https://github.com/LuisLuii/FastAPIQuickCRUD',
license="MIT License",
keywords=["fastapi", "crud", "restful", "routing","SQLAlchemy", "generator", "crudrouter","postgresql","builder"],
packages=find_packages('src'),
packages=find_packages("src", include="*.jinja2"),
package_data={
'': ['*.jinja2'],
'src.fastapi_quickcrud_codegen.model.template.common': ['*.jinja2'],
},
package_dir={'': 'src'},
setup_requires=["setuptools>=31.6.0"],
classifiers=[
Expand Down Expand Up @@ -60,3 +64,4 @@
],
include_package_data=True,
)
print(find_packages("src"))
2 changes: 1 addition & 1 deletion src/fastapi_quickcrud_codegen/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .misc.utils import sqlalchemy_to_pydantic
from .crud_router import crud_router_builder
from .crud_generator import crud_router_builder
from .misc.type import CrudMethods


162 changes: 162 additions & 0 deletions src/fastapi_quickcrud_codegen/crud_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
from typing import \
List, \
TypeVar, Union, Optional

from fastapi import \
APIRouter
from pydantic import \
BaseModel
from sqlalchemy.sql.schema import Table

from . import sqlalchemy_to_pydantic
from .generator.common_module_template_generator import CommonModuleTemplateGenerator
from .generator.crud_template_generator import CrudTemplateGenerator
from .misc.crud_model import CRUDModel
from .misc.get_table_name import get_table_name
from .misc.type import CrudMethods, SqlType
from .misc.utils import convert_table_to_model
from .model.common_builder import CommonCodeGen
from .model.crud_builder import CrudCodeGen

CRUDModelType = TypeVar("CRUDModelType", bound=BaseModel)
CompulsoryQueryModelType = TypeVar("CompulsoryQueryModelType", bound=BaseModel)
OnConflictModelType = TypeVar("OnConflictModelType", bound=BaseModel)


def crud_router_builder(
*,
db_model_list: Union[Table, 'DeclarativeBaseModel'],
async_mode: Optional[bool],
sql_type: Optional[SqlType],
crud_methods: Optional[List[CrudMethods]] = None,
exclude_columns: Optional[List[str]] = None,
# foreign_include: Optional[Base] = None
) -> APIRouter:
"""
@param db_model:
The Sqlalchemy Base model/Table you want to use it to build api.
@param async_mode:
As your database connection
@param sql_type:
You sql database type
@param db_session:
The callable variable and return a session generator that will be used to get database connection session for fastapi.
@param crud_methods:
Fastapi Quick CRUD supports a few of crud methods, and they save into the Enum class,
get it by : from fastapi_quickcrud_codegen_codegen import CrudMethods
example:
[CrudMethods.GET_MANY,CrudMethods.ONE]
note:
if there is no primary key in your SQLAlchemy model, it dose not support request with
specific resource, such as GET_ONE, UPDATE_ONE, DELETE_ONE, PATCH_ONE AND POST_REDIRECT_GET
this is because POST_REDIRECT_GET need to redirect to GET_ONE api
@param exclude_columns:
Fastapi Quick CRUD will get all the columns in you table to generate a CRUD router,
it is allow you exclude some columns you dont want it expose to operated by API
note:
if the column in exclude list but is it not nullable or no default_value, it may throw error
when you do insert
@param dependencies:
A variable that will be added to the path operation decorators.
@param crud_models:
You can use the sqlalchemy_to_pydantic() to build your own Pydantic model CRUD set
@param foreign_include: BaseModel
Used to build foreign tree api
@return:
APIRouter for fastapi
"""
model_list = []
for db_model_info in db_model_list:

db_model = db_model_info["db_model"]
prefix = db_model_info["prefix"]
tags = db_model_info["tags"]

table_name = db_model.__name__
model_name = get_table_name(db_model)

model_list.append({"model_name": model_name, "file_name": table_name})


db_model, NO_PRIMARY_KEY = convert_table_to_model(db_model)

# code gen
crud_code_generator = CrudCodeGen(model_name, model_name=table_name, tags=tags, prefix=prefix)
# create a file
crud_template_generator = CrudTemplateGenerator()

constraints = db_model.__table__.constraints

common_module_template_generator = CommonModuleTemplateGenerator()

# type
common_code_builder = CommonCodeGen()
common_code_builder.build_type()
common_code_builder.gen(common_module_template_generator.add_type)

# module
common_utils_code_builder = CommonCodeGen()
common_utils_code_builder.build_utils()
common_utils_code_builder.gen(common_module_template_generator.add_utils)

# http_exception
common_http_exception_code_builder = CommonCodeGen()
common_http_exception_code_builder.build_http_exception()
common_http_exception_code_builder.gen(common_module_template_generator.add_http_exception)

# db
common_db_code_builder = CommonCodeGen()
common_db_code_builder.build_db()
common_db_code_builder.gen(common_module_template_generator.add_db)

if not crud_methods and NO_PRIMARY_KEY == False:
crud_methods = CrudMethods.get_declarative_model_full_crud_method()
if not crud_methods and NO_PRIMARY_KEY == True:
crud_methods = CrudMethods.get_table_full_crud_method()

crud_models_builder: CRUDModel = sqlalchemy_to_pydantic
crud_models: CRUDModel = crud_models_builder(db_model=db_model,
constraints=constraints,
crud_methods=crud_methods,
exclude_columns=exclude_columns,
sql_type=sql_type,
exclude_primary_key=NO_PRIMARY_KEY)

methods_dependencies = crud_models.get_available_request_method()
primary_name = crud_models.PRIMARY_KEY_NAME
if primary_name:
path = '/{' + primary_name + '}'
else:
path = ""

def find_one_api():
crud_code_generator.build_find_one_route(async_mode=async_mode, path=path)

api_register = {
CrudMethods.FIND_ONE.value: find_one_api,
}
for request_method in methods_dependencies:
value_of_dict_crud_model = crud_models.get_model_by_request_method(request_method)
crud_model_of_this_request_methods = value_of_dict_crud_model.keys()
for crud_model_of_this_request_method in crud_model_of_this_request_methods:
api_register[crud_model_of_this_request_method.value]()
crud_code_generator.gen(crud_template_generator)

# sql session
common_db_session_code_builder = CommonCodeGen()
common_db_session_code_builder.build_db_session(model_list=model_list)
common_db_session_code_builder.gen(common_module_template_generator.add_memory_sql_session)

# app py
common_app_code_builder = CommonCodeGen()
common_app_code_builder.build_app(model_list=model_list)
common_app_code_builder.gen(common_module_template_generator.add_app)
Loading

0 comments on commit 9ffc161

Please sign in to comment.