Package: fastapi-quickcrud
FastAPI Quick CRUDIf you need apply business logic or add on some code, you can use my another open source project which supports CRUD router code generatorIntroductionAdvantagesLimitationsGetting startedInstallationUsageDesignPath ParameterQuery ParameterRequest BodyForeign TreeUpsertAdd description into docsRelationshipFastAPI_quickcrud Response Status Code standardIntroductionI believe that many people who work with FastApi to build RESTful CRUD services end up wasting time writing repitive boilerplate code.FastAPI Quick CRUDcan generate CRUD methods in FastApi from an SQLAlchemy schema:Get oneGet one with foreign keyGet manyGet many with foreign keyUpdate oneUpdate manyPatch onePatch manyCreate/Upsert oneCreate/Upsert manyDelete OneDelete ManyPost Redirect GetFastAPI Quick CRUDis developed based on SQLAlchemy1.4.23version and supports sync and async.AdvantagesSupport SQLAlchemy 1.4- Allows you build a fully asynchronous or synchronous python serviceFull SQLAlchemy DBAPI Support- Support different SQL for SQLAlchemySupport Pagination-Get manyAPI supportorder byoffsetlimitfield in APIRich FastAPI CRUD router generation- Many operations of CRUD are implemented to complete the development and coverage of all aspects of basic CRUD.CRUD route automatically generated- Support Declarative class definitions and Imperative tableFlexible API request-UPDATE ONE/MANYFIND ONE/MANYPATCH ONE/MANYDELETE ONE/MANYsupports Path Parameters (primary key) and Query Parameters as a command to the resource to filter and limit the scope of the scope of data in request.SQL Relationship-FIND ONE/MANYsupports Path get data with relationshipLimitations❌ If there are multipleunique constraints, please usecomposite unique constraintsinstead❌Composite primary keyis not supported❌ Unsupported API requests with on resourcesxxx/{primary key}for tables without a primary key;UPDATE ONEFIND ONEPATCH ONEDELETE ONEGetting startedI try to update the version dependencies as soon as possible to ensure that the core dependencies of this project have the highest version possible.fastapi<=0.68.2
pydantic<=1.8.2
SQLAlchemy<=1.4.30starlette==0.14.2Installationpipinstallfastapi-quickcrudI suggest the following library if you try to connect to PostgreSQLpipinstallpsycopg2
pipinstallasyncpgUsagerun and go tohttp://127.0.0.1:port/docsand see the auto-generated APISimple Code (or see the longer (example)fromfastapiimportFastAPIfromsqlalchemyimportColumn,Integer,\String,Table,ForeignKey,ormfromfastapi_quickcrudimportcrud_router_builderBase=orm.declarative_base()classUser(Base):__tablename__='test_users'id=Column(Integer,primary_key=True,autoincrement=True,unique=True)name=Column(String,nullable=False)email=Column(String,nullable=False)friend=Table('test_friend',Base.metadata,Column('id',ForeignKey('test_users.id',ondelete='CASCADE',onupdate='CASCADE'),nullable=False),Column('friend_name',String,nullable=False))crud_route_1=crud_router_builder(db_model=User,prefix="/user",tags=["User"],async_mode=True)crud_route_2=crud_router_builder(db_model=friend,prefix="/friend",tags=["friend"],async_mode=True)app=FastAPI()app.include_router(crud_route_1)app.include_router(crud_route_2)Foreign Tree With RelationshipfromfastapiimportFastAPIfromfastapi_quickcrudimportcrud_router_builderfromsqlalchemyimport*fromsqlalchemy.ormimport*fromfastapi_quickcrud.crud_routerimportgeneric_sql_crud_router_builderBase=declarative_base()classAccount(Base):__tablename__="account"id=Column(Integer,primary_key=True,autoincrement=True)blog_post=relationship("BlogPost",back_populates="account")classBlogPost(Base):__tablename__="blog_post"id=Column(Integer,primary_key=True,autoincrement=True)account_id=Column(Integer,ForeignKey("account.id"),nullable=False)account=relationship("Account",back_populates="blog_post")blog_comment=relationship("BlogComment",back_populates="blog_post")classBlogComment(Base):__tablename__="blog_comment"id=Column(Integer,primary_key=True,autoincrement=True)blog_id=Column(Integer,ForeignKey("blog_post.id"),nullable=False)blog_post=relationship("BlogPost",back_populates="blog_comment")crud_route_parent=crud_router_builder(db_model=Account,prefix="/account",tags=["account"],foreign_include=[BlogComment,BlogPost])crud_route_child1=generic_sql_crud_router_builder(db_model=BlogPost,prefix="/blog_post",tags=["blog_post"],foreign_include=[BlogComment])crud_route_child2=generic_sql_crud_router_builder(db_model=BlogComment,prefix="/blog_comment",tags=["blog_comment"])app=FastAPI()[app.include_router(i)foriin[crud_route_parent,crud_route_child1,crud_route_child2]]SQLAlchemy to Pydantic Model Converter And Build your own API(example)importuvicornfromfastapiimportFastAPI,Dependsfromfastapi_quickcrudimportCrudMethodsfromfastapi_quickcrudimportsqlalchemy_to_pydanticfromfastapi_quickcrud.misc.memory_sqlimportsync_memory_dbfromsqlalchemyimportCHAR,Column,Integerfromsqlalchemy.ext.declarativeimportdeclarative_baseapp=FastAPI()Base=declarative_base()metadata=Base.metadataclassChild(Base):__tablename__='right'id=Column(Integer,primary_key=True)name=Column(CHAR,nullable=True)friend_model_set=sqlalchemy_to_pydantic(db_model=Child,crud_methods=[CrudMethods.FIND_MANY,CrudMethods.UPSERT_MANY,CrudMethods.UPDATE_MANY,CrudMethods.DELETE_MANY,CrudMethods.CREATE_ONE,CrudMethods.PATCH_MANY,],exclude_columns=[])post_model=friend_model_set.POST[CrudMethods.CREATE_ONE]sync_memory_db.create_memory_table(Child)@app.post("/hello",status_code=201,tags=["Child"],response_model=post_model.responseModel,dependencies=[])asyncdefmy_api(query:post_model.requestBodyModel=Depends(post_model.requestBodyModel),session=Depends(sync_memory_db.get_memory_db_session)):db_item=Child(**query.__dict__)session.add(db_item)session.commit()session.refresh(db_item)returndb_item.__dict__uvicorn.run(app,host="0.0.0.0",port=8000,debug=False)Note:
you can usesqlacodegento generate SQLAlchemy models for your table. This project is based on the model development and testing generated by sqlacodegenMain moduleGenerate CRUD routercrud_router_builder argsdb_session [Optional]execute session generatordefault using in-memory db with create table automaticallyexample:sync SQLALchemy:fromsqlalchemy.ormimportsessionmakerdefget_transaction_session():try:db=sessionmaker(...)yielddbexceptExceptionase:db.rollback()raiseefinally:db.close()Async SQLALchemyfromsqlalchemy.ormimportsessionmakerfromsqlalchemy.ext.asyncioimportAsyncSessionasync_session=sessionmaker(autocommit=False,autoflush=False,bind=engine,class_=AsyncSession)asyncdefget_transaction_session()->AsyncSession:asyncwithasync_session()assession:asyncwithsession.begin():yieldsessiondb_model [Require]SQLALchemy Declarative Base Class or TableNote: There are some constraint in the SQLALchemy Schemaasync_mode  [Optional (auto set by db_session)]bool: if your db session is asyncNote: require async session generator if Trueautocommit [Optional (default True)]bool: if you don't need to commit by your selfNote: require handle the commit in your async session generator if Falsedependencies [Optional]: API dependency injection of fastapiNote: Get the example usage in./examplecrud_methods:CrudMethodsCrudMethods.FIND_ONECrudMethods.FIND_MANYCrudMethods.UPDATE_ONECrudMethods.UPDATE_MANYCrudMethods.PATCH_ONECrudMethods.PATCH_MANYCrudMethods.UPSERT_ONE (only support postgresql yet)CrudMethods.UPSERT_MANY (only support postgresql yet)CrudMethods.CREATE_ONECrudMethods.CREATE_MANYCrudMethods.DELETE_ONECrudMethods.DELETE_MANYCrudMethods.POST_REDIRECT_GETexclude_columns:listset the columns that not to be operated but the columns should nullable or set the default value)foreign_include:list[declarative_base()]add the SqlAlchemy models here, and build the foreign tree get one/many api (don't support SqlAlchemy table)dynamic argument (prefix, tags): extra argument for APIRouter() of fastapiDesignInPUTDELETEPATCH, user can use Path Parameters and Query Parameters to limit the scope of the data affected by the operation, and the Query Parameters is same withFINDAPIPath ParameterIn the design of this tool,Path Parametersshould be a primary key of table, that why limited primary key can only be one.Query ParameterQuery Operation will look like that when python type of column isstringsupport Approximate String Matching that require this(<column_name>____str, <column_name>____str_____matching_pattern)support In-place Operation, get the value of column in the list of input(<column_name>____list, <column_name>____list____comparison_operator)previewnumeric or datetimesupport Range Searching from and to(<column_name>____from, <column_name>____from_____comparison_operator)(<column_name>____to, <column_name>____to_____comparison_operator)support In-place Operation, get the value of column in the list of input(<column_name>____list, <column_name>____list____comparison_operator)previewuuiduuid supports In-place Operation onlysupport In-place Operation, get the value of column in the list of input(<column_name>____list, <column_name>____list____comparison_operator)EXTRA query parameter forGET_MANY:Paginationlimitoffsetorder bypreviewQuery to SQL statement exampleApproximate String Matchingexamplerequest url/test_CRUD?
char_value____str_____matching_pattern=match_regex_with_case_sensitive&
char_value____str_____matching_pattern=does_not_match_regex_with_case_insensitive&
char_value____str_____matching_pattern=case_sensitive&
char_value____str_____matching_pattern=not_case_insensitive&
char_value____str=a&
char_value____str=bgenerated sqlSELECT*FROMuntitled_table_256WHERE(untitled_table_256.char_value~'a')OR(untitled_table_256.char_value~'b'OR(untitled_table_256.char_value!~*'a')OR(untitled_table_256.char_value!~*'b'ORuntitled_table_256.char_valueLIKE'a'ORuntitled_table_256.char_valueLIKE'b'ORuntitled_table_256.char_valueNOTILIKE'a'ORuntitled_table_256.char_valueNOTILIKE'b'In-place OperationexampleIn-place support the following operationgenerated sql if user select Equal operation and input True and Falsepreviewgenerated sqlselect*FROMuntitled_table_256WHEREuntitled_table_256.bool_value=trueORuntitled_table_256.bool_value=falseRange SearchingexampleRange Searching support the following operationgenerated sqlselect*fromuntitled_table_256WHEREuntitled_table_256.date_value>%(date_value_1)sselect*fromuntitled_table_256WHEREuntitled_table_256.date_value<%(date_value_1)sAlso support your custom dependency for each api(there is a example in./example)Request BodyIn the design of this tool, the columns of the table will be used as the fields of request body.In the basic request body in the api generated by this tool, some fields are optional if :it is primary key withautoincrementis True or theserver_defaultordefaultis Trueit is not a primary key, but theserver_defaultordefaultis TrueThe field is nullableForeign TreeTBCUpsert** Upsert supports PosgreSQL only yetPOST API will perform the data insertion action with using the basicRequest Body,
In addition, it also supports upsert(insert on conflict do)The operation will use upsert instead if the unique column in the inserted row that is being inserted already exists in the tableThe tool usesunique columnsin the table as a parameter of on conflict , and you can define which column will be updatedAdd description into docsYou can declarecommentargument forsqlalchemy.Columnto configure the description of columnexample:classParent(Base):__tablename__='parent_o2o'id=Column(Integer,primary_key=True,comment='parent_test')# one-to-many collectionchildren=relationship("Child",back_populates="parent")classChild(Base):__tablename__='child_o2o'id=Column(Integer,primary_key=True,comment='child_pk_test')parent_id=Column(Integer,ForeignKey('parent_o2o.id'),info=({'description':'child_parent_id_test'}))# many-to-one scalarparent=relationship("Parent",back_populates="children")RelationshipNow,FIND_ONEandFIND_MANYare supporting select data with join operationclassParent(Base):__tablename__='parent_o2o'id=Column(Integer,primary_key=True)children=relationship("Child",back_populates="parent")classChild(Base):__tablename__='child_o2o'id=Column(Integer,primary_key=True)parent_id=Column(Integer,ForeignKey('parent_o2o.id'))parent=relationship("Parent",back_populates="children")there is a relationship with using back_populates between Parent table and Child table, theparent_idinChildwill refer toidcolumn inParent.FastApi Quick CRUDwill generate an api with ajoin_foreign_tablefield, and get api will respond to your selection of the reference data row of the corresponding table injoin_foreign_tablefield,Try Request
now there are some data in these two tablewhen i requestCase Onecurl -X 'GET' \
'http://0.0.0.0:8000/parent?join_foreign_table=child_o2o' \
-H 'accept: application/json'Response data[{"id_foreign":[{"id":1,"parent_id":1},{"id":2,"parent_id":1}],"id":1},{"id_foreign":[{"id":3,"parent_id":2},{"id":4,"parent_id":2}],"id":2}]Response headersx-total-count: 4There are response 4 data, response data will be grouped by the parent row, if the child refer to the same parent rowCase Twocurl -X 'GET' \
'http://0.0.0.0:8000/child?join_foreign_table=parent_o2o' \
-H 'accept: application/json'Response data[{"parent_id_foreign":[{"id":1}],"id":1,"parent_id":1},{"parent_id_foreign":[{"id":1}],"id":2,"parent_id":1},{"parent_id_foreign":[{"id":2}],"id":3,"parent_id":2},{"parent_id_foreign":[{"id":2}],"id":4,"parent_id":2}]Response Headerx-total-count: 4FastAPI_quickcrud Response Status Code standardWhen you ask for a specific resource, say a user or with query param, and the user doesn't existGET: get one : https://0.0.0.0:8080/api/:userid?xx=xxUPDATE: update one : https://0.0.0.0:8080/api/:userid?xx=xxPATCH: patch one : https://0.0.0.0:8080/api/:userid?xx=xxDELETE: delete one : https://0.0.0.0:8080/api/:userid?xx=xxthen fastapi-qucikcrud should return 404. In this case, the client requested a resource that doesn't exist.In the other case, you have an api that operate data on batch in the system using the following url:GET: get many : https://0.0.0.0:8080/api/user?xx=xxUPDATE: update many : https://0.0.0.0:8080/api/user?xx=xxDELETE: delete many : https://0.0.0.0:8080/api/user?xx=xxPATCH: patch many : https://0.0.0.0:8080/api/user?xx=xxIf there are no users in the system, then, in this case, you should return 204.TODOmilestones