Advaned Data Listing Library for FastAPI
Project description
fastapi-listing
Advanced 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 filters
- pre defined paginator
- pre defined sorter
Advantage
- simplify the intricate process of designing and developing complex listing APIs
- Design components(USP) and plug them from anywhere
- Components can be reusable
- Best for fast changing needs
Usage
➡️ 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)
from fastapi_listing.dao import GenericDao
from app.model import Employee
# your dao (data access object) placed here for the sake of example
class EmployeeDao(GenericDao):
"""write your data layer access logic here. keep it raw!"""
name = "employee"
model = Employee # your sqlalchemy model, support for more orm is coming soon
2️⃣ Just call FastapiListing
from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
# import fastapi-listing dependencies
from fastapi_listing.paginator import ListingPage # default pydantic page model, you can create/extend your own
from fastapi_listing import FastapiListing, MetaInfo
app = FastAPI() # create FastAPI app
def get_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 v2
class EmployeeListDetails(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")
class Config:
orm_mode = True
allow_population_by_field_name = True
@app.get("/employees", response_model=ListingPage[EmployeeListDetails])
def get_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 table
return FastapiListing(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])
def get_employees(db=Depends(get_db)):
dao = EmployeeDao(read_db=db)
return FastapiListing(dao=dao,
pydantic_serializer=EmployeeListDetails, # optional
custom_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❓️
class EmployeeDao(GenericDao):
"""write your data layer access logic here. keep it raw!"""
name = "employee"
model = Employee
def get_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 queries
for complex APIs which we will look at later.
"""
query = self._read_db.query(Employee)
return query
@app.get("/employees", response_model=ListingPage[EmployeeListDetails])
def get_employees(db=Depends(get_db)):
dao = EmployeeDao(read_db=db)
return FastapiListing(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 only Employees belonging to 'X' gender where X could be anything.
- DOB - return Employees belonging to a specific range of DOB.
- First Name - return Employees only starting with specific first names.
from fastapi_listing.filters import generic_filters # collection of inbuilt filters
from fastapi_listing.factory import filter_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])
def get_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)
return FastapiListing(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 implementation
from fastapi_listing.service.adapters import CoreListingParamsAdapter
class YourAdapterClass(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 client
even 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
- pagination
depending upon this return the appropriate transformed client param back to fastapi listing
supported formats for
filter:
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 interceptor
pagination:
{"pageSize": <integer page size>, "page": <integer page number 1 based>}
"""
def get(self, key: Literal["sort", "filter", "pagination"]):
"""
@param key: Literal["sort", "filter", "pagination"]
@return: List[Optional[dict]] for filter/sort and dict for paginator
"""
return utils.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])
def get_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 MetaInfo
dao = EmployeeDao(read_db=db)
return FastapiListing(request=request, dao=dao).get_response(
MetaInfo(default_srt_on="emp_no", filter_mapper=emp_filter_mapper, feature_params_adapter=YourAdapterClass))
Check out docs for supported list of filters. Additionally, you can create custom filters as well.
Thinking about adding Sorting???
I won't leave you hanging there as well😎
@app.get("/employees", response_model=ListingPage[EmployeeListDetails])
def get_employees(request: Request, db=Depends(get_db)):
# define it here or anywhere
emp_sort_mapper = {
"cd": "Employee.emp_no",
"bdt": "Employee.birth_date"
}
dao = EmployeeDao(read_db=db)
return FastapiListing(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:
- Query
- Filter
- Sorter
- Paginator
You can check out customisation section in docs after going through basics and tutorials.
Check out my other repo to see some examples
Features and Readability hand in hand 🤝
- Easy-to-use API for listing and formatting data
- Built-in support for pagination, sorting and filtering
- Well defined interface for filter, sorter, paginator
- Support Dependency Injection for easy testing
- Room to adapt the existing remote client query param semantics
- Write standardise listing APIs that will be understood by generations of upcoming developers
- Write listing features which is easy on human mind to extend or understand
- Break down the most complex listing data APIs into digestible piece of code
With FastAPI Listing you won't end up like
Documentation
View full documentation at: https://fastapi-listing.readthedocs.io ███▓▓░️░️░35%️░️░️░️
Feedback, Questions?
Any form of feedback and questions are welcome! Please create an issue 💭 here.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file fastapi-listing-0.3.2.tar.gz
.
File metadata
- Download URL: fastapi-listing-0.3.2.tar.gz
- Upload date:
- Size: 40.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.2 CPython/3.11.7
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 5535a9aff110e38c1ca830ceac08d159330bb3a1bdb14bcd16bd3c9a9c4704ae |
|
MD5 | f03c905c43ac94392587e2d58119facd |
|
BLAKE2b-256 | 49de8c96dcb1a60547e96867366f340b22015780c982ace074a569fd4b2cc725 |
File details
Details for the file fastapi_listing-0.3.2-py3-none-any.whl
.
File metadata
- Download URL: fastapi_listing-0.3.2-py3-none-any.whl
- Upload date:
- Size: 52.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.2 CPython/3.11.7
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 6b2ddcbfc5b4a60877e9d8e3eda112d6bdd341fd4f35890d7d36caeecdb18780 |
|
MD5 | aa3af02c7e4390306e4b8eb433b2d178 |
|
BLAKE2b-256 | 14c18a65c2f4ac6588bb4a761438ce5e69d19d1c4e94881f38767c3084dae140 |