Skip to content

Commit 85b8759

Browse files
authored
Merge pull request #17 from LuisLuii/develop
Develop
2 parents 9e73d07 + dffcc2d commit 85b8759

49 files changed

Lines changed: 2512 additions & 806 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@ tests/htmlcov
55
/pyproject.toml
66
*.xml
77
*.pyc
8+
workspace.xml
89
*.iml
10+
*.xml
911
*.sh

README.md

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
- [Path Parameter](#path-parameter)
3030
- [Query Parameter](#query-parameter)
3131
- [Request Body](#request-body)
32+
- [Foreign Tree](#foreign-tree)
3233
- [Upsert](#upsert)
3334
- [Add description into docs](#add-description-into-docs)
3435
- [Relationship](#relationship)
@@ -43,9 +44,9 @@ I believe that many people who work with FastApi to build RESTful CRUD services
4344
`FastAPI Quick CRUD` can generate CRUD methods in FastApi from an SQLAlchemy schema:
4445

4546
- Get one
46-
- Get one with foreign key (IN DEVELOPMENT)
47+
- Get one with foreign key
4748
- Get many
48-
- Get many with foreign key (IN DEVELOPMENT)
49+
- Get many with foreign key
4950
- Update one
5051
- Update many
5152
- Patch one
@@ -156,12 +157,67 @@ app.include_router(crud_route_1)
156157
app.include_router(crud_route_2)
157158
```
158159

160+
### Foreign Tree With Relationship
161+
```python
162+
from fastapi import FastAPI
163+
from fastapi_quickcrud import crud_router_builder
164+
from sqlalchemy import *
165+
from sqlalchemy.orm import *
166+
from fastapi_quickcrud.crud_router import generic_sql_crud_router_builder
167+
168+
Base = declarative_base()
169+
170+
class Account(Base):
171+
__tablename__ = "account"
172+
id = Column(Integer, primary_key=True, autoincrement=True)
173+
blog_post = relationship("BlogPost", back_populates="account")
174+
175+
176+
class BlogPost(Base):
177+
__tablename__ = "blog_post"
178+
id = Column(Integer, primary_key=True, autoincrement=True)
179+
account_id = Column(Integer, ForeignKey("account.id"), nullable=False)
180+
account = relationship("Account", back_populates="blog_post")
181+
blog_comment = relationship("BlogComment", back_populates="blog_post")
182+
183+
184+
class BlogComment(Base):
185+
__tablename__ = "blog_comment"
186+
id = Column(Integer, primary_key=True, autoincrement=True)
187+
blog_id = Column(Integer, ForeignKey("blog_post.id"), nullable=False)
188+
blog_post = relationship("BlogPost", back_populates="blog_comment")
189+
190+
191+
crud_route_parent = crud_router_builder(
192+
db_model=Account,
193+
prefix="/account",
194+
tags=["account"],
195+
foreign_include=[BlogComment, BlogPost]
196+
197+
)
198+
crud_route_child1 = generic_sql_crud_router_builder(
199+
db_model=BlogPost,
200+
prefix="/blog_post",
201+
tags=["blog_post"],
202+
foreign_include=[BlogComment]
203+
204+
)
205+
crud_route_child2 = generic_sql_crud_router_builder(
206+
db_model=BlogComment,
207+
prefix="/blog_comment",
208+
tags=["blog_comment"]
209+
210+
)
211+
212+
app = FastAPI()
213+
[app.include_router(i) for i in [crud_route_parent, crud_route_child1, crud_route_child2]]
214+
215+
```
159216

160217
### SQLAlchemy to Pydantic Model Converter And Build your own API([example](https://github.com/LuisLuii/FastAPIQuickCRUD/blob/main/tutorial/basic_usage/quick_usage_with_async_SQLALchemy_Base.py))
161218
```python
162219
import uvicorn
163220
from fastapi import FastAPI, Depends
164-
from sqlalchemy.orm import declarative_base
165221
from fastapi_quickcrud import CrudMethods
166222
from fastapi_quickcrud import sqlalchemy_to_pydantic
167223
from fastapi_quickcrud.misc.memory_sql import sync_memory_db
@@ -437,6 +493,12 @@ In the basic request body in the api generated by this tool, some fields are opt
437493
* [x] it is not a primary key, but the `server_default` or `default` is True
438494
* [x] The field is nullable
439495

496+
497+
### Foreign Tree
498+
TBC
499+
500+
501+
440502
## Upsert
441503

442504
** Upsert supports PosgreSQL only yet

src/fastapi_quickcrud/crud_router.py

Lines changed: 88 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
Depends, APIRouter
1111
from pydantic import \
1212
BaseModel
13-
from sqlalchemy.orm import declarative_base
1413
from sqlalchemy.sql.schema import Table
1514

1615
from . import sqlalchemy_to_pydantic
@@ -23,7 +22,7 @@
2322
from .misc.crud_model import CRUDModel
2423
from .misc.memory_sql import async_memory_db, sync_memory_db
2524
from .misc.type import CrudMethods, SqlType
26-
from .misc.utils import convert_table_to_model
25+
from .misc.utils import convert_table_to_model, Base
2726

2827
CRUDModelType = TypeVar("CRUDModelType", bound=BaseModel)
2928
CompulsoryQueryModelType = TypeVar("CompulsoryQueryModelType", bound=BaseModel)
@@ -40,33 +39,20 @@ def crud_router_builder(
4039
dependencies: Optional[List[callable]] = None,
4140
crud_models: Optional[CRUDModel] = None,
4241
async_mode: Optional[bool] = None,
42+
foreign_include: Optional[Base] = None,
4343
sql_type: Optional[SqlType] = None,
4444
**router_kwargs: Any) -> APIRouter:
4545
"""
46-
:param db_session: Callable function
47-
db_session should be a callable function, and return a session generator.
48-
Also you can handle commit by yourelf or othe business logic
49-
50-
SQLAlchemy based example(SQLAlchemy was supported async since 1.4 version):
51-
async:
52-
async def get_transaction_session() -> AsyncSession:
53-
async with async_session() as session:
54-
async with session.begin():
55-
yield session
56-
sync:
57-
def get_transaction_session():
58-
try:
59-
db = sync_session()
60-
yield db
61-
db.commit()
62-
except Exception as e:
63-
db.rollback()
64-
raise e
65-
finally:
66-
db.close()
67-
68-
69-
:param crud_methods: List[CrudMethods]
46+
@param db_model:
47+
The Sqlalchemy Base model/Table you want to use it to build api.
48+
49+
@param db_session:
50+
The callable variable and return a session generator that will be used to get database connection session for fastapi.
51+
52+
@param autocommit:
53+
set False if you handle commit in your db_session.
54+
55+
@param crud_methods:
7056
Fastapi Quick CRUD supports a few of crud methods, and they save into the Enum class,
7157
get it by : from fastapi_quickcrud import CrudMethods
7258
example:
@@ -76,21 +62,33 @@ def get_transaction_session():
7662
specific resource, such as GET_ONE, UPDATE_ONE, DELETE_ONE, PATCH_ONE AND POST_REDIRECT_GET
7763
this is because POST_REDIRECT_GET need to redirect to GET_ONE api
7864
79-
:param exclude_columns: List[str]
65+
@param exclude_columns:
8066
Fastapi Quick CRUD will get all the columns in you table to generate a CRUD router,
8167
it is allow you exclude some columns you dont want it expose to operated by API
8268
note:
8369
if the column in exclude list but is it not nullable or no default_value, it may throw error
8470
when you do insert
8571
86-
:param crud_models:
87-
:param db_model:
88-
SQLAlchemy model,
89-
:param dependencies:
90-
:param async_mode:
91-
:param autocommit:
92-
:param router_kwargs: Optional arguments that ``APIRouter().include_router`` takes.
93-
:return:
72+
@param dependencies:
73+
A variable that will be added to the path operation decorators.
74+
75+
@param crud_models:
76+
You can use the sqlalchemy_to_pydantic() to build your own Pydantic model CRUD set
77+
78+
@param async_mode:
79+
As your database connection
80+
81+
@param foreign_include: BaseModel
82+
Used to build foreign tree api
83+
84+
@param sql_type:
85+
You sql database type
86+
87+
@param router_kwargs:
88+
other argument for FastApi's views
89+
90+
@return:
91+
APIRouter for fastapi
9492
"""
9593

9694
db_model, NO_PRIMARY_KEY = convert_table_to_model(db_model)
@@ -108,18 +106,19 @@ def get_transaction_session():
108106

109107
if async_mode is None:
110108
async_mode = inspect.isasyncgen(db_session())
111-
109+
112110
if sql_type is None:
113111
async def async_runner(f):
114112
return [i.bind.name async for i in f()]
113+
115114
try:
116115
if async_mode:
117116
sql_type, = asyncio.get_event_loop().run_until_complete(async_runner(db_session))
118117
else:
119118
sql_type, = [i.bind.name for i in db_session()]
120119
except Exception:
121120
raise RuntimeError("Some unknown problem occurred error, maybe you are uvicorn.run with reload=True. "
122-
"Try declaring sql_type for crud_router_builder yourself using from fastapi_quickcrud.misc.type import SqlType")
121+
"Try declaring sql_type for crud_router_builder yourself using from fastapi_quickcrud.misc.type import SqlType")
123122

124123
if not crud_methods and NO_PRIMARY_KEY == False:
125124
crud_methods = CrudMethods.get_declarative_model_full_crud_method()
@@ -144,9 +143,15 @@ async def async_runner(f):
144143
crud_methods=crud_methods,
145144
exclude_columns=exclude_columns,
146145
sql_type=sql_type,
146+
foreign_include=foreign_include,
147147
exclude_primary_key=NO_PRIMARY_KEY)
148148

149-
crud_service = query_service(model=db_model, async_mode=async_mode)
149+
foreign_table_mapping = {db_model.__tablename__: db_model}
150+
if foreign_include:
151+
for i in foreign_include:
152+
model , _= convert_table_to_model(i)
153+
foreign_table_mapping[model.__tablename__] = i
154+
crud_service = query_service(model=db_model, async_mode=async_mode, foreign_table_mapping=foreign_table_mapping)
150155
# else:
151156
# crud_service = SQLAlchemyPostgreQueryService(model=db_model, async_mode=async_mode)
152157

@@ -372,6 +377,48 @@ def put_many_api(request_response_model: dict, dependencies):
372377
async_mode=async_mode,
373378
response_model=_response_model)
374379

380+
def find_one_foreign_tree_api(request_response_model: dict, dependencies):
381+
_foreign_list_model = request_response_model.get('foreignListModel', None)
382+
for i in _foreign_list_model:
383+
_request_query_model = i["request_query_model"]
384+
_response_model = i["response_model"]
385+
_path = i["path"]
386+
_function_name = i["function_name"]
387+
request_url_param_model = i["primary_key_dataclass_model"]
388+
routes_source.find_one_foreign_tree(path=_path,
389+
request_query_model=_request_query_model,
390+
response_model=_response_model,
391+
request_url_param_model=request_url_param_model,
392+
db_session=db_session,
393+
query_service=crud_service,
394+
parsing_service=result_parser,
395+
execute_service=execute_service,
396+
dependencies=dependencies,
397+
api=api,
398+
function_name=_function_name,
399+
async_mode=async_mode)
400+
401+
def find_many_foreign_tree_api(request_response_model: dict, dependencies):
402+
_foreign_list_model = request_response_model.get('foreignListModel', None)
403+
for i in _foreign_list_model:
404+
_request_query_model = i["request_query_model"]
405+
_response_model = i["response_model"]
406+
_path = i["path"]
407+
_function_name = i["function_name"]
408+
request_url_param_model = i["primary_key_dataclass_model"]
409+
routes_source.find_many_foreign_tree(path=_path,
410+
request_query_model=_request_query_model,
411+
response_model=_response_model,
412+
request_url_param_model=request_url_param_model,
413+
db_session=db_session,
414+
query_service=crud_service,
415+
parsing_service=result_parser,
416+
execute_service=execute_service,
417+
dependencies=dependencies,
418+
api=api,
419+
async_mode=async_mode,
420+
function_name=_function_name)
421+
375422
api_register = {
376423
CrudMethods.FIND_ONE.value: find_one_api,
377424
CrudMethods.FIND_MANY.value: find_many_api,
@@ -385,7 +432,9 @@ def put_many_api(request_response_model: dict, dependencies):
385432
CrudMethods.PATCH_ONE.value: patch_one_api,
386433
CrudMethods.PATCH_MANY.value: patch_many_api,
387434
CrudMethods.UPDATE_ONE.value: put_one_api,
388-
CrudMethods.UPDATE_MANY.value: put_many_api
435+
CrudMethods.UPDATE_MANY.value: put_many_api,
436+
CrudMethods.FIND_ONE_WITH_FOREIGN_TREE.value: find_one_foreign_tree_api,
437+
CrudMethods.FIND_MANY_WITH_FOREIGN_TREE.value: find_many_foreign_tree_api
389438
}
390439
api = APIRouter(**router_kwargs)
391440

0 commit comments

Comments
 (0)