Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def add_routes(app: FastAPI):
router: APIRouter = APIRouter()
RoutersJSONAPI(
router=router,
path="/user",
path="/users",
tags=["User"],
class_detail=UserDetailView,
class_list=UserListView,
Expand Down Expand Up @@ -195,11 +195,11 @@ if __name__ == "__main__":

This example provides the following API structure:

| URL | method | endpoint | Usage |
|------------------|--------|-------------|---------------------------|
| `/user` | GET | user_list | Get a collection of users |
| `/user` | POST | user_list | Create a user |
| `/user` | DELETE | user_list | Delete users |
| `/user/{obj_id}` | GET | user_detail | Get user details |
| `/user/{obj_id}` | PATCH | user_detail | Update a user |
| `/user/{obj_id}` | DELETE | user_detail | Delete a user |
| URL | method | endpoint | Usage |
|-------------------|--------|-------------|---------------------------|
| `/users` | GET | user_list | Get a collection of users |
| `/users` | POST | user_list | Create a user |
| `/users` | DELETE | user_list | Delete users |
| `/users/{obj_id}` | GET | user_detail | Get user details |
| `/users/{obj_id}` | PATCH | user_detail | Update a user |
| `/users/{obj_id}` | DELETE | user_detail | Delete a user |
46 changes: 46 additions & 0 deletions docs/api_limited_methods_example.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
.. _api_limited_methods_example:

Limit API methods
#################

Sometimes you won't need all the CRUD methods.
For example, you want to create only GET, POST and GET LIST methods,
so user can't update or delete any items.


Set ``methods`` on Routers registration:

.. code-block:: python

RoutersJSONAPI(
router=router,
path="/users",
tags=["User"],
class_detail=UserDetailView,
class_list=UserListView,
schema=UserSchema,
model=User,
resource_type="user",
methods=[
RoutersJSONAPI.Methods.GET_LIST,
RoutersJSONAPI.Methods.POST,
RoutersJSONAPI.Methods.GET,
],
)


This will limit generated views to:

======================== ====== ============= ===========================
URL method endpoint Usage
======================== ====== ============= ===========================
/users GET user_list Get a collection of users
/users POST user_list Create a user
/users/{user_id} GET user_detail Get user details
======================== ====== ============= ===========================


Full code example (should run "as is"):

.. literalinclude:: ../examples/api_limited_methods.py
:language: python
24 changes: 12 additions & 12 deletions docs/filtering.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ So this is a first example:

.. sourcecode:: http

GET /user?filter=[{"name":"first_name","op":"eq","val":"John"}] HTTP/1.1
GET /users?filter=[{"name":"first_name","op":"eq","val":"John"}] HTTP/1.1
Accept: application/vnd.api+json

In this example we want to retrieve user records for people named John. So we can see that the filtering interface completely fits that of SQLAlchemy: a list a filter information.
Expand All @@ -37,7 +37,7 @@ Example with field:

.. sourcecode:: http

GET /user?filter=[{"name":"first_name","op":"eq","field":"birth_date"}] HTTP/1.1
GET /users?filter=[{"name":"first_name","op":"eq","field":"birth_date"}] HTTP/1.1
Accept: application/vnd.api+json

In this example, we want to retrieve people whose name is equal to their birth_date. This example is absurd, it's just here to explain the syntax of this kind of filter.
Expand Down Expand Up @@ -74,7 +74,7 @@ There is a shortcut to achieve the same filtering:

.. sourcecode:: http

GET /user?filter=[{"name":"group.name","op":"ilike","val":"%admin%"}] HTTP/1.1
GET /users?filter=[{"name":"group.name","op":"ilike","val":"%admin%"}] HTTP/1.1
Accept: application/vnd.api+json

You can also use boolean combination of operations:
Expand Down Expand Up @@ -116,22 +116,22 @@ You can also use boolean combination of operations:

.. sourcecode:: http

GET /user?filter=[{"name":"group.name","op":"ilike","val":"%admin%"},{"or":[{"not":{"name":"first_name","op":"eq","val":"John"}},{"and":[{"name":"first_name","op":"like","val":"%Jim%"},{"name":"date_create","op":"gt","val":"1990-01-01"}]}]}] HTTP/1.1
GET /users?filter=[{"name":"group.name","op":"ilike","val":"%admin%"},{"or":[{"not":{"name":"first_name","op":"eq","val":"John"}},{"and":[{"name":"first_name","op":"like","val":"%Jim%"},{"name":"date_create","op":"gt","val":"1990-01-01"}]}]}] HTTP/1.1
Accept: application/vnd.api+json


Filtering records by a field that is null

.. sourcecode:: http

GET /user?filter=[{"name":"name","op":"is_","val":null}] HTTP/1.1
GET /users?filter=[{"name":"name","op":"is_","val":null}] HTTP/1.1
Accept: application/vnd.api+json

Filtering records by a field that is not null

.. sourcecode:: http

GET /user?filter=[{"name":"name","op":"isnot","val":null}] HTTP/1.1
GET /users?filter=[{"name":"name","op":"isnot","val":null}] HTTP/1.1
Accept: application/vnd.api+json


Expand Down Expand Up @@ -172,22 +172,22 @@ For example

.. sourcecode:: http

GET /user?filter[first_name]=John HTTP/1.1
GET /users?filter[first_name]=John HTTP/1.1
Accept: application/vnd.api+json

equals:

.. sourcecode:: http

GET /user?filter=[{"name":"first_name","op":"eq","val":"John"}] HTTP/1.1
GET /users?filter=[{"name":"first_name","op":"eq","val":"John"}] HTTP/1.1
Accept: application/vnd.api+json


You can also use more than one simple filter in a request:

.. sourcecode:: http

GET /user?filter[first_name]=John&filter[gender]=male HTTP/1.1
GET /users?filter[first_name]=John&filter[gender]=male HTTP/1.1
Accept: application/vnd.api+json

which is equal to:
Expand All @@ -209,17 +209,17 @@ which is equal to:

.. sourcecode:: http

GET /user?filter=[{"name":"first_name","op":"eq","val":"John"},{"name":"gender","op":"eq","val":"male"}] HTTP/1.1
GET /users?filter=[{"name":"first_name","op":"eq","val":"John"},{"name":"gender","op":"eq","val":"male"}] HTTP/1.1

You can also use relationship attribute in a request:

.. sourcecode:: http

GET /user?filter[group_id]=1 HTTP/1.1
GET /users?filter[group_id]=1 HTTP/1.1
Accept: application/vnd.api+json

which is equal to:

.. sourcecode:: http

GET /user?filter=[{"name":"group.id","op":"eq","val":"1"}] HTTP/1.1
GET /users?filter=[{"name":"group.id","op":"eq","val":"1"}] HTTP/1.1
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ FastAPI-JSONAPI with FastAPI.
minimal_api_example
api_filtering_example
quickstart
api_limited_methods_example
routing
atomic_operations
view_dependencies
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def add_routes(app: FastAPI):
router: APIRouter = APIRouter()
RoutersJSONAPI(
router=router,
path="/user",
path="/users",
tags=["User"],
class_detail=UserDetailView,
class_list=UserListView,
Expand Down
16 changes: 8 additions & 8 deletions docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,27 @@ This example provides the following API:
+----------------+--------+----------------+---------------------------------+
| url | method | endpoint | action |
+================+========+================+=================================+
| /user | GET | user_list | Retrieve a collection of users |
| /users | GET | user_list | Retrieve a collection of users |
+----------------+--------+----------------+---------------------------------+
| /user | POST | user_list | Create a user |
| /users | POST | user_list | Create a user |
+----------------+--------+----------------+---------------------------------+
| /user/<int:id> | GET | user_detail | Retrieve details of a user |
| /users/<int:id> | GET | user_detail | Retrieve details of a user |
+----------------+--------+----------------+---------------------------------+
| /user/<int:id> | PATCH | user_detail | Update a user |
| /users/<int:id> | PATCH | user_detail | Update a user |
+----------------+--------+----------------+---------------------------------+
| /user/<int:id> | DELETE | user_detail | Delete a user |
| /users/<int:id> | DELETE | user_detail | Delete a user |
+----------------+--------+----------------+---------------------------------+

in developing

+-------------------------------------------+--------+------------------+------------------------------------------------------+
| url | method | endpoint | action |
+===========================================+========+==================+======================================================+
| /user/<int:id>/group | GET | computer_list | Retrieve a collection computers related to a user |
| /users/<int:id>/group | GET | computer_list | Retrieve a collection computers related to a user |
+-------------------------------------------+--------+------------------+------------------------------------------------------+
| /user/<int:id>/group | POST | computer_list | Create a computer related to a user |
| /users/<int:id>/group | POST | computer_list | Create a computer related to a user |
+-------------------------------------------+--------+------------------+------------------------------------------------------+
| /user/<int:id>/relationships/group | GET | user_computers | Retrieve relationships between a user and computers |
| /users/<int:id>/relationships/group | GET | user_computers | Retrieve relationships between a user and computers |
+-------------------------------------------+--------+------------------+------------------------------------------------------+
| /users/<int:id>/relationships/computers | POST | user_computers | Create relationships between a user and computers |
+-------------------------------------------+--------+------------------+------------------------------------------------------+
Expand Down
2 changes: 1 addition & 1 deletion examples/api_for_tortoise_orm/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def add_routes(app: FastAPI) -> List[Dict[str, Any]]:
# TODO: fix example
RoutersJSONAPI(
router=routers,
path="/user",
path="/users",
tags=["User"],
class_detail=UserDetail,
class_list=UserList,
Expand Down
156 changes: 156 additions & 0 deletions examples/api_limited_methods.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import sys
from pathlib import Path
from typing import Any, Dict

import uvicorn
from fastapi import APIRouter, Depends, FastAPI
from sqlalchemy import Column, Integer, Text
from sqlalchemy.engine import make_url
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

from fastapi_jsonapi import RoutersJSONAPI, init
from fastapi_jsonapi.misc.sqla.generics.base import DetailViewBaseGeneric, ListViewBaseGeneric
from fastapi_jsonapi.schema_base import BaseModel
from fastapi_jsonapi.views.utils import HTTPMethod, HTTPMethodConfig
from fastapi_jsonapi.views.view_base import ViewBase

CURRENT_FILE = Path(__file__).resolve()
CURRENT_DIR = CURRENT_FILE.parent
PROJECT_DIR = CURRENT_DIR.parent.parent
DB_URL = f"sqlite+aiosqlite:///{CURRENT_DIR}/db.sqlite3"
sys.path.append(str(PROJECT_DIR))

Base = declarative_base()


class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(Text, nullable=True)


class UserAttributesBaseSchema(BaseModel):
name: str

class Config:
orm_mode = True


class UserSchema(UserAttributesBaseSchema):
"""User base schema."""


def async_session() -> sessionmaker:
engine = create_async_engine(url=make_url(DB_URL))
_async_session = sessionmaker(bind=engine, class_=AsyncSession, expire_on_commit=False)
return _async_session


class Connector:
@classmethod
async def get_session(cls):
"""
Get session as dependency

:return:
"""
sess = async_session()
async with sess() as db_session: # type: AsyncSession
yield db_session
await db_session.rollback()


async def sqlalchemy_init() -> None:
engine = create_async_engine(url=make_url(DB_URL))
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)


class SessionDependency(BaseModel):
session: AsyncSession = Depends(Connector.get_session)

class Config:
arbitrary_types_allowed = True


def session_dependency_handler(view: ViewBase, dto: SessionDependency) -> Dict[str, Any]:
return {
"session": dto.session,
}


class UserDetailView(DetailViewBaseGeneric):
method_dependencies = {
HTTPMethod.ALL: HTTPMethodConfig(
dependencies=SessionDependency,
prepare_data_layer_kwargs=session_dependency_handler,
),
}


class UserListView(ListViewBaseGeneric):
method_dependencies = {
HTTPMethod.ALL: HTTPMethodConfig(
dependencies=SessionDependency,
prepare_data_layer_kwargs=session_dependency_handler,
),
}


def add_routes(app: FastAPI):
tags = [
{
"name": "User",
"description": "",
},
]

router: APIRouter = APIRouter()
RoutersJSONAPI(
router=router,
path="/users",
tags=["User"],
class_detail=UserDetailView,
class_list=UserListView,
schema=UserSchema,
model=User,
resource_type="user",
methods=[
RoutersJSONAPI.Methods.GET_LIST,
RoutersJSONAPI.Methods.POST,
RoutersJSONAPI.Methods.GET,
],
)

app.include_router(router, prefix="")
return tags


def create_app() -> FastAPI:
"""
Create app factory.

:return: app
"""
app = FastAPI(
title="FastAPI app with limited methods",
debug=True,
openapi_url="/openapi.json",
docs_url="/docs",
)
add_routes(app)
app.on_event("startup")(sqlalchemy_init)
init(app)
return app


app = create_app()

if __name__ == "__main__":
uvicorn.run(
app,
host="0.0.0.0",
port=8080,
)
Loading