Skip to content

Commit 801c7db

Browse files
CosmoVNatalia Grigoreva
authored and
Natalia Grigoreva
committed
docs
1 parent 2510a7e commit 801c7db

24 files changed

+419
-362
lines changed

README.md

+72-99
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
[![📖 Docs (gh-pages)](https://github.com/mts-ai/FastAPI-JSONAPI/actions/workflows/documentation.yaml/badge.svg)](https://mts-ai.github.io/FastAPI-JSONAPI/)
1010

11+
1112
# FastAPI-JSONAPI
1213

1314
FastAPI-JSONAPI is a FastAPI extension for building REST APIs.
@@ -30,25 +31,61 @@ pip install FastAPI-JSONAPI
3031
Create a test.py file and copy the following code into it
3132

3233
```python
34+
import sys
35+
from collections.abc import AsyncIterator
3336
from contextlib import asynccontextmanager
3437
from pathlib import Path
3538
from typing import Any, ClassVar, Optional
39+
from typing import Union
3640

3741
import uvicorn
38-
from fastapi import APIRouter, Depends, FastAPI
42+
from fastapi import Depends, FastAPI
43+
from fastapi.responses import ORJSONResponse as JSONResponse
3944
from pydantic import ConfigDict
45+
from sqlalchemy.engine import URL
4046
from sqlalchemy.engine import make_url
4147
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker, create_async_engine
4248
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
4349

44-
from fastapi_jsonapi import RoutersJSONAPI, init
45-
from fastapi_jsonapi.misc.sqla.generics.base import DetailViewBaseGeneric, ListViewBaseGeneric
50+
from fastapi_jsonapi import ApplicationBuilder
51+
from fastapi_jsonapi.misc.sqla.generics.base import ViewBaseGeneric
4652
from fastapi_jsonapi.schema_base import BaseModel
47-
from fastapi_jsonapi.views.utils import HTTPMethod, HTTPMethodConfig
48-
from fastapi_jsonapi.views.view_base import ViewBase
53+
from fastapi_jsonapi.views import ViewBase, Operation, OperationConfig
4954

5055
CURRENT_DIR = Path(__file__).resolve().parent
51-
DB_URL = f"sqlite+aiosqlite:///{CURRENT_DIR}/db.sqlite3"
56+
sys.path.append(f"{CURRENT_DIR.parent.parent}")
57+
58+
59+
class DB:
60+
def __init__(
61+
self,
62+
url: Union[str, URL],
63+
echo: bool = False,
64+
echo_pool: bool = False,
65+
):
66+
self.engine: AsyncEngine = create_async_engine(
67+
url=url,
68+
echo=echo,
69+
echo_pool=echo_pool,
70+
)
71+
72+
self.session_maker: async_sessionmaker[AsyncSession] = async_sessionmaker(
73+
autocommit=False,
74+
bind=self.engine,
75+
expire_on_commit=False,
76+
)
77+
78+
async def dispose(self):
79+
await self.engine.dispose()
80+
81+
async def session(self) -> AsyncIterator[AsyncSession]:
82+
async with self.session_maker() as session:
83+
yield session
84+
85+
86+
db = DB(
87+
url=make_url(f"sqlite+aiosqlite:///{CURRENT_DIR}/db.sqlite3"),
88+
)
5289

5390

5491
class Base(DeclarativeBase):
@@ -62,64 +99,22 @@ class User(Base):
6299
name: Mapped[Optional[str]]
63100

64101

65-
class UserAttributesBaseSchema(BaseModel):
102+
class UserSchema(BaseModel):
103+
"""User base schema."""
104+
66105
model_config = ConfigDict(
67106
from_attributes=True,
68107
)
69108

70109
name: str
71110

72111

73-
class UserSchema(UserAttributesBaseSchema):
74-
"""User base schema."""
75-
76-
77-
class UserPatchSchema(UserAttributesBaseSchema):
78-
"""User PATCH schema."""
79-
80-
81-
class UserInSchema(UserAttributesBaseSchema):
82-
"""User input schema."""
83-
84-
85-
def async_session() -> tuple[AsyncEngine, async_sessionmaker]:
86-
engine_: AsyncEngine = create_async_engine(
87-
url=f"{make_url(DB_URL)}",
88-
echo=True,
89-
)
90-
session_maker_: async_sessionmaker[AsyncSession] = async_sessionmaker(
91-
autocommit=False,
92-
bind=engine,
93-
expire_on_commit=False,
94-
)
95-
return engine_, session_maker_
96-
97-
98-
engine, session_maker = async_session()
99-
100-
101-
class Connector:
102-
@classmethod
103-
async def dispose(cls):
104-
await engine.dispose()
105-
106-
@classmethod
107-
async def init(cls) -> None:
108-
async with engine.begin() as conn:
109-
await conn.run_sync(Base.metadata.create_all)
110-
111-
@classmethod
112-
async def session(cls):
113-
async with session_maker() as db_session:
114-
yield db_session
115-
116-
117112
class SessionDependency(BaseModel):
118113
model_config = ConfigDict(
119114
arbitrary_types_allowed=True,
120115
)
121116

122-
session: AsyncSession = Depends(Connector.session)
117+
session: AsyncSession = Depends(db.session)
123118

124119

125120
def session_dependency_handler(view: ViewBase, dto: SessionDependency) -> dict[str, Any]:
@@ -128,89 +123,67 @@ def session_dependency_handler(view: ViewBase, dto: SessionDependency) -> dict[s
128123
}
129124

130125

131-
class UserDetailView(DetailViewBaseGeneric):
132-
method_dependencies: ClassVar[dict[HTTPMethod, HTTPMethodConfig]] = {
133-
HTTPMethod.ALL: HTTPMethodConfig(
126+
class UserView(ViewBaseGeneric):
127+
operation_dependencies: ClassVar = {
128+
Operation.ALL: OperationConfig(
134129
dependencies=SessionDependency,
135130
prepare_data_layer_kwargs=session_dependency_handler,
136-
)
137-
}
138-
139-
140-
class UserListView(ListViewBaseGeneric):
141-
method_dependencies: ClassVar[dict[HTTPMethod, HTTPMethodConfig]] = {
142-
HTTPMethod.ALL: HTTPMethodConfig(
143-
dependencies=SessionDependency,
144-
prepare_data_layer_kwargs=session_dependency_handler,
145-
)
131+
),
146132
}
147133

148134

149135
def add_routes(app: FastAPI):
150-
tags = [
151-
{
152-
"name": "User",
153-
"description": "",
154-
},
155-
]
156-
157-
router: APIRouter = APIRouter()
158-
RoutersJSONAPI(
159-
router=router,
136+
builder = ApplicationBuilder(app)
137+
builder.add_resource(
160138
path="/users",
161139
tags=["User"],
162-
class_detail=UserDetailView,
163-
class_list=UserListView,
140+
view=UserView,
164141
schema=UserSchema,
165-
resource_type="user",
166-
schema_in_patch=UserPatchSchema,
167-
schema_in_post=UserInSchema,
168142
model=User,
143+
resource_type="user",
169144
)
170-
171-
app.include_router(router, prefix="")
172-
return tags
145+
builder.initialize()
173146

174147

175148
# noinspection PyUnusedLocal
176149
@asynccontextmanager
177150
async def lifespan(app: FastAPI):
178151
add_routes(app)
179-
init(app)
180152

181-
await Connector.init()
153+
async with db.engine.begin() as conn:
154+
await conn.run_sync(Base.metadata.create_all)
182155

183156
yield
184157

185-
await Connector.dispose()
158+
await db.dispose()
186159

187160

188161
app = FastAPI(
189-
lifespan=lifespan,
190162
title="FastAPI and SQLAlchemy",
163+
lifespan=lifespan,
191164
debug=True,
192-
openapi_url="/openapi.json",
165+
default_response_class=JSONResponse,
193166
docs_url="/docs",
167+
openapi_url="/openapi.json",
194168
)
195169

196170

197171
if __name__ == "__main__":
198172
uvicorn.run(
199-
"main:app",
173+
app,
200174
host="0.0.0.0",
201175
port=8080,
202-
reload=True,
203-
app_dir=f"{CURRENT_DIR}",
204176
)
205177
```
206178

207179
This example provides the following API structure:
208180

209-
| URL | method | endpoint | Usage |
210-
|-------------------|--------|-------------|---------------------------|
211-
| `/users` | GET | user_list | Get a collection of users |
212-
| `/users` | POST | user_list | Create a user |
213-
| `/users` | DELETE | user_list | Delete users |
214-
| `/users/{obj_id}` | GET | user_detail | Get user details |
215-
| `/users/{obj_id}` | PATCH | user_detail | Update a user |
216-
| `/users/{obj_id}` | DELETE | user_detail | Delete a user |
181+
| URL | method | endpoint | Usage |
182+
|--------------------|--------|-------------|-------------------------------|
183+
| `/users/` | GET | user_list | Get a collection of users |
184+
| `/users/` | POST | user_list | Create a user |
185+
| `/users/` | DELETE | user_list | Delete users |
186+
| `/users/{obj_id}/` | GET | user_detail | Get user details |
187+
| `/users/{obj_id}/` | PATCH | user_detail | Update a user |
188+
| `/users/{obj_id}/` | DELETE | user_detail | Delete a user |
189+
| `/operations/` | POST | atomic | Create, update, delete users |

docs/api_filtering_example.rst

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,16 @@ Filtering API example
66

77

88

9-
Filter by jsonb contains
9+
Filter by jsonb contains. Before using the filter, you must define it and apply it
10+
to the schema as shown here :ref:`custom_sql_filtering`. Some useful filters are
11+
defined in module **fastapi_jsonapi.types_metadata.custom_filter_sql.py**
1012

1113
.. code-block:: json
1214
1315
[
1416
{
1517
"name": "words",
16-
"op": "jsonb_contains",
18+
"op": "sqlite_json_contains",
1719
"val": {"location": "Moscow", "spam": "eggs"}
1820
}
1921
]

docs/api_limited_methods_example.rst

+8-8
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,23 @@ For example, you want to create only GET, POST and GET LIST methods,
88
so user can't update or delete any items.
99

1010

11-
Set ``methods`` on Routers registration:
11+
Set ``operations`` on Routers registration:
1212

1313
.. code-block:: python
1414
15-
RoutersJSONAPI(
15+
builder = ApplicationBuilder(app)
16+
builder.add_resource(
1617
router=router,
1718
path="/users",
1819
tags=["User"],
19-
class_detail=UserDetailView,
20-
class_list=UserListView,
20+
view=UserView,
2121
schema=UserSchema,
2222
model=User,
2323
resource_type="user",
24-
methods=[
25-
RoutersJSONAPI.Methods.GET_LIST,
26-
RoutersJSONAPI.Methods.POST,
27-
RoutersJSONAPI.Methods.GET,
24+
operations=[
25+
Operation.GET_LIST,
26+
Operation.POST,
27+
Operation.GET,
2828
],
2929
)
3030

docs/changelog.rst

+35-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,39 @@
11
Changelog
22
#########
33

4+
**3.0.0**
5+
*********
6+
7+
Backwards compatibility changes
8+
===============================
9+
* Removed support pydantic v1 by `@NatalyaGrigoreva`_
10+
* Minimal fastapi version up to fastapi>=0.112.3 by `@NatalyaGrigoreva`_
11+
* Updated minimal pydantic version pydantic>=2.6.0 by `@NatalyaGrigoreva`_
12+
* Added required dependency orjson>=3.10.0 by `@NatalyaGrigoreva`_
13+
* Updated framework api by `@NatalyaGrigoreva`_, `@CosmoV`_
14+
15+
Features
16+
========
17+
* Added support of pydantic v2 by `@NatalyaGrigoreva`_
18+
* Improved sqla orm query building by `@NatalyaGrigoreva`_
19+
* Updated logic of creation custom sql filters by `@mahenzon`_
20+
* Several bugfixes by `@NatalyaGrigoreva`_
21+
22+
Performance improvements
23+
========================
24+
25+
* Updated ViewBase logic of response building by `@CosmoV`_
26+
* Added storages for application lifetime entities by `@CosmoV`_
27+
* Updated "fields" feature logic by `@CosmoV`_
28+
29+
Authors
30+
"""""""
31+
32+
* `@CosmoV`_
33+
* `@NatalyaGrigoreva`_
34+
* `@mahenzon`_
35+
36+
437
**2.8.0**
538
*********
639

@@ -69,7 +102,7 @@ Fix relationships filtering, refactor alchemy helpers
69102

70103
* Fix filter by relationships by `@CosmoV`_ in `#52 <https://github.com/mts-ai/FastAPI-JSONAPI/pull/52>`_
71104
* Add Codecov by `@mahenzon`_ in `#72 <https://github.com/mts-ai/FastAPI-JSONAPI/pull/72>`_
72-
* Set RoutersJSONAPI class on AtomicViewHandler by `@mahenzon`_ in `#7b2557f <https://github.com/mts-ai/FastAPI-JSONAPI/commit/7b2557f9e341c1210200efce0f7b47c15d4cac4e>`_
105+
* Set ApplicationBuilder class on AtomicViewHandler by `@mahenzon`_ in `#7b2557f <https://github.com/mts-ai/FastAPI-JSONAPI/commit/7b2557f9e341c1210200efce0f7b47c15d4cac4e>`_
73106

74107
Authors
75108
"""""""
@@ -316,3 +349,4 @@ Enhancements and bug fixes
316349
.. _`@mahenzon`: https://github.com/mahenzon
317350
.. _`@CosmoV`: https://github.com/CosmoV
318351
.. _`@tpynio`: https://github.com/tpynio
352+
.. _`@NatalyaGrigoreva`: https://github.com/NatalyaGrigoreva

docs/configuration.rst

-1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,3 @@ You have access to 5 configuration keys:
99
* MAX_PAGE_SIZE: the maximum page size. If you specify a page size greater than this value you will receive a 400 Bad Request response.
1010
* MAX_INCLUDE_DEPTH: the maximum length of an include through schema relationships
1111
* ALLOW_DISABLE_PAGINATION: if you want to disallow to disable pagination you can set this configuration key to False
12-
* CATCH_EXCEPTIONS: if you want fastapi_jsonapi to catch all exceptions and return them as JsonApiException (default is True)

0 commit comments

Comments
 (0)