Package: fastapi-listing
fastapi-listingAdvanced items listing library that gives you freedom to design complex listing APIs that can be read by human.➡️ Craft powerful Listing REST APIs designed to serve websites akin to Stack Overflow:Comes with:pre defined filterspre defined paginatorpre defined sorterAdvantagesimplify the intricate process of designing and developing complex listing APIsDesign components(USP) and plug them from anywhereComponents can bereusableBest for fast changing needsUsage➡️ With the New Compact Version(Older version was provided with a guide style which is supported by this version as well)
create your listing API in 2 easy steps 🥳1️⃣ Create a Dao (Data Access Object) layer (a simple class)fromfastapi_listing.daoimportGenericDaofromapp.modelimportEmployee# your dao (data access object) placed here for the sake of exampleclassEmployeeDao(GenericDao):"""write your data layer access logic here. keep it raw!"""name="employee"model=Employee# your sqlalchemy model, support for more orm is coming soon2️⃣ Just callFastapiListingfromfastapiimportFastAPI,Dependsfromsqlalchemy.ormimportSession# import fastapi-listing dependenciesfromfastapi_listing.paginatorimportListingPage# default pydantic page model, you can create/extend your ownfromfastapi_listingimportFastapiListing,MetaInfoapp=FastAPI()# create FastAPI appdefget_db()->Session:"""replicating sessionmaker for any fastapi app.anyone could be using a different way or opensource packages to produce sessions.it all comes down to a single result that is yielding a session.for the sake of simplicity and testing purpose I'm replicating this behaviour in this way.:return: Session"""# your pydantic response class if you are using one# Supports pydantic v2classEmployeeListDetails(BaseModel):emp_no:int=Field(alias="empid",title="Employee ID")birth_date:date=Field(alias="bdt",title="Birth Date")first_name:str=Field(alias="fnm",title="First Name")last_name:str=Field(alias="lnm",title="Last Name")gender:str=Field(alias="gdr",title="Gender")hire_date:date=Field(alias="hdt",title="Hiring Date")classConfig:orm_mode=Trueallow_population_by_field_name=True@app.get("/employees",response_model=ListingPage[EmployeeListDetails])defget_employees(db=Depends(get_db)):dao=EmployeeDao(read_db=db)# passing pydantic serializer is optional, automatically generates a# select query based on pydantic class fields for easy cases like columns of same tablereturnFastapiListing(dao=dao,pydantic_serializer=EmployeeListDetails).get_response(MetaInfo(default_srt_on="emp_no"))Voila 🎉 your very first listing response(that's even extensible user can manipulate default page structure)Your pydantic class contains some dynamic fields that you populate at runtime❓️@app.get("/employees",response_model=ListingPage[EmployeeListDetails])defget_employees(db=Depends(get_db)):dao=EmployeeDao(read_db=db)returnFastapiListing(dao=dao,pydantic_serializer=EmployeeListDetails,# optionalcustom_fields=True# just tell fastapi-listing that your model contains custom_fields).get_response(MetaInfo(default_srt_on="emp_no"))Auto generated query  doesn't fulfil your use case❓️classEmployeeDao(GenericDao):"""write your data layer access logic here. keep it raw!"""name="employee"model=Employeedefget_default_read(self,fields_to_read:Optional[list]):"""Extend and return your query from here.Use it when use cases are comparatively easier than complex.Alternatively fastapi-listing provides a robust way to write performance packed queriesfor complex APIs which we will look at later."""query=self._read_db.query(Employee)returnquery@app.get("/employees",response_model=ListingPage[EmployeeListDetails])defget_employees(db=Depends(get_db)):dao=EmployeeDao(read_db=db)returnFastapiListing(dao=dao).get_response(MetaInfo(default_srt_on="emp_no"))Thinking about adding filters???Don't worry I've got you covered😎➡️ Say you want to add filter on Employee for:gender - return onlyEmployeesbelonging to 'X' gender where X could be anything.DOB - returnEmployeesbelonging to a specific range of DOB.First Name - returnEmployeesonly starting with specific first names.fromfastapi_listing.filtersimportgeneric_filters# collection of inbuilt filtersfromfastapi_listing.factoryimportfilter_factory# import filter_factory to register filter against a listing# {"alias": ("<model.field>", "<filter_definition>")}emp_filter_mapper={"gdr":("Employee.gender",generic_filters.EqualityFilter),"bdt":("Employee.birth_date",generic_filters.MySqlNativeDateFormateRangeFilter),"fnm":("Employee.first_name",generic_filters.StringStartsWithFilter),}filter_factory.register_filter_mapper(emp_filter_mapper)# You just registered the number of filters allowed to client on this listing@app.get("/employees",response_model=ListingPage[EmployeeListDetails])defget_employees(request:Request,db=Depends(get_db)):"""request is optional to pass.you can pass filter query_param or use request object.make fastapi-listing adapt to your client existing query_param format"""dao=EmployeeDao(read_db=db)returnFastapiListing(request=request,dao=dao).get_response(MetaInfo(default_srt_on="emp_no",filter_mapper=emp_filter_mapper))Thinking about how fastapi-listing reads filter/sorter/paginator params❓️# Extend adapter to make fastapi-listing adapt your existing clients# default implementationfromfastapi_listing.service.adaptersimportCoreListingParamsAdapterclassYourAdapterClass(CoreListingParamsAdapter):# Extend to add your behaviour"""Utilise this adapter class to make your remote client site:- filter,- sorter,- paginator.query params adapt to fastapi listing library.With this you can utilise same listing api to multiple remote clienteven if it's a front end server or other backend server.core service is always going to request one of the following fundamental key- sort- filter- paginationdepending upon this return the appropriate transformed client param back to fastapi listingsupported formats forfilter:simple filter - [{"field":"<key used in filter mapper>", "value":{"search":"<client param>"}}, ...]if you are using a range filter -[{"field":"<key used in filter mapper>", "value":{"start":"<start range>", "end": "<end range>"}}, ...]if you are using a list filter i.e. search on given items[{"field":"<key used in filter mapper>", "value":{"list":["<client params>"]}}, ...]sort:[{"field":<"key used in sort mapper>", "type":"asc or "dsc"}, ...]by default single sort allowed you can change it by extending sort interceptorpagination:{"pageSize": <integer page size>, "page": <integer page number 1 based>}"""defget(self,key:Literal["sort","filter","pagination"]):"""@param key: Literal["sort", "filter", "pagination"]@return: List[Optional[dict]] for filter/sort and dict for paginator"""returnutils.dictify_query_params(self.dependency.get(key))Pass request or extract feature(filter/sorter/paginator) params at router and pass them as kwargs@app.get("/employees",response_model=ListingPage[EmployeeListDetails])defget_employees(request:Request,db=Depends(get_db)):params=request.query_params# filter, sort. pagination = params.get("filter"), params.get("sort"), params.get("paginator")# you can pass above args as kwargs in MetaInfodao=EmployeeDao(read_db=db)returnFastapiListing(request=request,dao=dao).get_response(MetaInfo(default_srt_on="emp_no",filter_mapper=emp_filter_mapper,feature_params_adapter=YourAdapterClass))Check outdocsfor supported list of filters.
Additionally, you can createcustom filtersas well.Thinking about adding Sorting???I won't leave you hanging there as well😎@app.get("/employees",response_model=ListingPage[EmployeeListDetails])defget_employees(request:Request,db=Depends(get_db)):# define it here or anywhereemp_sort_mapper={"cd":"Employee.emp_no","bdt":"Employee.birth_date"}dao=EmployeeDao(read_db=db)returnFastapiListing(request=request,dao=dao).get_response(MetaInfo(default_srt_on="emp_no",filter_mapper=emp_filter_mapper,feature_params_adapter=YourAdapterClass,sort_mapper=emp_sort_mapper))Provided features are not meeting your requirements???It is customizable.😎➡️ You can write custom:QueryFilterSorterPaginatorYou can check out customisation section in docs after going through basics and tutorials.Check out my otherrepoto see some examplesFeatures and Readability hand in hand 🤝Easy-to-use API for listing and formatting dataBuilt-in support for pagination, sorting and filteringWell defined interface for filter, sorter, paginatorSupport Dependency Injection for easy testingRoom to adapt the existing remote client query param semanticsWrite standardise listing APIs that will be understood by generations of upcoming developersWrite listing features which is easy on human mind to extend or understandBreak down the most complex listing data APIs into digestible piece of codeWith FastAPI Listing you won't end up likeDocumentationView full documentation at:https://fastapi-listing.readthedocs.io███▓▓░️░️░35%️░️░️░️Feedback, Questions?Any form of feedback and questions are welcome! Please create an issue  💭here.