Skip to content

Commit 3375951

Browse files
committed
Add MANIFEST.in and documentation in README.md
1 parent 786f759 commit 3375951

File tree

10 files changed

+239
-18
lines changed

10 files changed

+239
-18
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@
77
/db.sqlite3-wal
88
/.idea
99
/examples/api_for_sqlalchemy/db.sqlite3
10+
/dist/

MANIFEST.in

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
include README.md
2+
include LICENSE
3+
include requirements.txt

README.md

+216-14
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,227 @@
11
# FastAPI-REST-JSONAPI
22

3-
## Examples:
3+
FastAPI-REST-JSONAPI is a FastAPI extension for building REST APIs.
4+
Implementation of a strong specification [JSONAPI 1.0](http://jsonapi.org/).
5+
This framework is designed to quickly build REST APIs and fit the complexity
6+
of real life projects with legacy data and multiple data storages.
47

5-
### 1. App API-FOR-TORTOISE-ORM
68

7-
8-
#### Install dependencies
9-
```shell
10-
pip install -r requirements.txt
11-
pip install -r requirements-dev.txt
9+
## Install
10+
```bash
11+
pip install FastAPI-REST-JSONAPI
1212
```
1313

14+
## A minimal API
15+
16+
Create a test.py file and copy the following code into it
17+
18+
```python
19+
import sys
20+
from pathlib import Path
21+
from typing import Any, Dict, List, Union, Optional
22+
23+
import uvicorn
24+
from fastapi import APIRouter, Depends, FastAPI
25+
from pydantic import BaseModel
26+
from sqlalchemy import Column, Text, Integer, select
27+
from sqlalchemy.engine import make_url
28+
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
29+
from sqlalchemy.ext.declarative import declarative_base
30+
from sqlalchemy.orm import sessionmaker
31+
from sqlalchemy.sql import Select
32+
33+
from fastapi_rest_jsonapi import RoutersJSONAPI
34+
from fastapi_rest_jsonapi import SqlalchemyEngine
35+
from fastapi_rest_jsonapi.data_layers.orm import DBORMType
36+
from fastapi_rest_jsonapi.openapi import custom_openapi
37+
from fastapi_rest_jsonapi.querystring import QueryStringManager
38+
from fastapi_rest_jsonapi.schema import JSONAPIResultListSchema
39+
from fastapi_rest_jsonapi.schema import collect_app_orm_schemas
40+
41+
CURRENT_FILE = Path(__file__).resolve()
42+
CURRENT_DIR = CURRENT_FILE.parent
43+
PROJECT_DIR = CURRENT_DIR.parent.parent
44+
45+
sys.path.append(str(PROJECT_DIR))
46+
47+
Base = declarative_base()
48+
49+
50+
def async_session() -> sessionmaker:
51+
uri = "sqlite+aiosqlite:///db.sqlite3"
52+
engine = create_async_engine(url=make_url(uri))
53+
_async_session = sessionmaker(bind=engine, class_=AsyncSession, expire_on_commit=False)
54+
return _async_session
55+
56+
57+
class Connector:
58+
59+
@classmethod
60+
async def get_session(cls):
61+
"""
62+
Getting a session to the database.
63+
64+
:return:
65+
"""
66+
async_session_ = async_session()
67+
async with async_session_() as db_session:
68+
async with db_session.begin():
69+
yield db_session
70+
71+
72+
class User(Base):
73+
__tablename__ = "users"
74+
id = Column(Integer, primary_key=True, autoincrement=True)
75+
first_name: str = Column(Text, nullable=True)
76+
77+
78+
class UserBaseSchema(BaseModel):
79+
"""User base schema."""
80+
81+
class Config:
82+
"""Pydantic schema config."""
83+
orm_mode = True
84+
85+
first_name: Optional[str] = None
86+
87+
88+
class UserPatchSchema(UserBaseSchema):
89+
"""User PATCH schema."""
90+
91+
92+
class UserInSchema(UserBaseSchema):
93+
"""User input schema."""
94+
95+
96+
class UserSchema(UserInSchema):
97+
"""User item schema."""
98+
99+
class Config:
100+
"""Pydantic model config."""
101+
orm_mode = True
102+
model = "users"
103+
104+
id: int
105+
106+
107+
class UserDetail:
108+
109+
@classmethod
110+
async def get(cls, obj_id: int, session: AsyncSession = Depends(Connector.get_session)) -> UserSchema:
111+
user: User = (await session.execute(select(User).where(User.id == obj_id))).scalar_one()
112+
return UserSchema.from_orm(user)
113+
114+
@classmethod
115+
async def patch(cls, obj_id: int, data: UserPatchSchema, session: AsyncSession = Depends(Connector.get_session)) -> UserSchema:
116+
user: User = (await session.execute(select(User).where(User.id == obj_id))).scalar_one()
117+
user.first_name = data.first_name
118+
await session.commit()
119+
return UserSchema.from_orm(user)
120+
121+
@classmethod
122+
async def delete(cls, obj_id: int, session: AsyncSession = Depends(Connector.get_session)) -> None:
123+
user: User = (await session.execute(select(User).where(User.id == obj_id))).scalar_one()
124+
await session.delete(user)
125+
await session.commit()
126+
127+
128+
class UserList:
129+
@classmethod
130+
async def get(
131+
cls, query_params: QueryStringManager, session: AsyncSession = Depends(Connector.get_session)
132+
) -> Union[Select, JSONAPIResultListSchema]:
133+
user_query = select(User)
134+
dl = SqlalchemyEngine(query=user_query, schema=UserSchema, model=User, session=session)
135+
count, users_db = await dl.get_collection(qs=query_params)
136+
total_pages = count // query_params.pagination.size + (count % query_params.pagination.size and 1)
137+
users: List[UserSchema] = [UserSchema.from_orm(i_user) for i_user in users_db]
138+
return JSONAPIResultListSchema(
139+
meta={"count": count, "totalPages": total_pages},
140+
data=[{"id": i_obj.id, "attributes": i_obj.dict(), "type": "user"} for i_obj in users],
141+
)
142+
143+
@classmethod
144+
async def post(cls, data: UserInSchema, session: AsyncSession = Depends(Connector.get_session)) -> UserSchema:
145+
user = User(first_name=data.first_name)
146+
session.add(user)
147+
await session.commit()
148+
return UserSchema.from_orm(user)
149+
150+
151+
def add_routes(app: FastAPI) -> List[Dict[str, Any]]:
152+
tags = [
153+
{
154+
"name": "User",
155+
"description": "",
156+
},
157+
]
158+
159+
routers: APIRouter = APIRouter()
160+
RoutersJSONAPI(
161+
routers=routers,
162+
path="/user",
163+
tags=["User"],
164+
class_detail=UserDetail,
165+
class_list=UserList,
166+
schema=UserSchema,
167+
type_resource="user",
168+
schema_in_patch=UserPatchSchema,
169+
schema_in_post=UserInSchema,
170+
model=User,
171+
engine=DBORMType.sqlalchemy,
172+
)
173+
174+
app.include_router(routers, prefix="")
175+
return tags
176+
177+
178+
async def sqlalchemy_init() -> None:
179+
uri = "sqlite+aiosqlite:///db.sqlite3"
180+
engine = create_async_engine(url=make_url(uri))
181+
async with engine.begin() as conn:
182+
await conn.run_sync(Base.metadata.drop_all)
183+
await conn.run_sync(Base.metadata.create_all)
184+
185+
186+
def create_app() -> FastAPI:
187+
"""
188+
Create app factory.
189+
190+
:return: app
191+
"""
192+
app = FastAPI(
193+
title="FastAPI and SQLAlchemy",
194+
debug=True,
195+
openapi_url="/openapi.json",
196+
docs_url="/docs",
197+
)
198+
add_routes(app)
199+
app.on_event("startup")(sqlalchemy_init)
200+
custom_openapi(app, title="API for SQLAlchemy")
201+
collect_app_orm_schemas(app)
202+
return app
203+
204+
205+
app = create_app()
206+
14207

15-
#### Start app
16-
```shell
17-
# in dir fastapi-rest-jsonapi
208+
if __name__ == "__main__":
209+
uvicorn.run(
210+
"test:app",
211+
host="0.0.0.0",
212+
port=8084,
213+
reload=True,
214+
app_dir=str(CURRENT_DIR),
215+
)
18216

19-
export PYTHOPATH="${PYTHONPATH}:./"
20-
# reload may not work :(
21-
python examples/api_for_tortoise_orm/main.py
22217
```
23218

24-
Visit http://0.0.0.0:8080/docs
219+
This example provides the following API structure:
25220

221+
| URL | method | endpoint | Usage |
222+
|-------------------|--------|---------------|---------------------------|
223+
| /user | GET | user_list | Get a collection of users |
224+
| /user | POST | user_list | Create a user |
225+
| /user/< int:int > | GET | user_detail | Get user details |
226+
| /user/< int:int > | PATCH | person_detail | Update a user |
227+
| /user/< int:int > | DELETE | person_detail | Delete a user |

examples/api_for_sqlalchemy/extensions/sqlalchemy.py

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from typing import Optional
2-
31
from sqlalchemy.engine import make_url
42
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
53
from sqlalchemy.ext.declarative import declarative_base

examples/api_for_sqlalchemy/helpers/factories/user.py

+8
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ async def before_create(
4444
cls._set_first_name(data_for_create_user, model_kwargs)
4545
cls._set_last_name(data_for_create_user, model_kwargs)
4646
cls._set_status(data_for_create_user, model_kwargs)
47+
cls._set_age(data_for_create_user, model_kwargs)
4748
return data_for_create_user
4849

4950
@classmethod
@@ -60,6 +61,13 @@ def _set_last_name(cls, data_for_create_user: Dict, kwargs: Dict):
6061
"""
6162
data_for_create_user["last_name"] = kwargs.get("last_name", "Last name")
6263

64+
@classmethod
65+
def _set_age(cls, data_for_create_user: Dict, kwargs: Dict):
66+
"""
67+
Set first name.
68+
"""
69+
data_for_create_user["age"] = kwargs.get("age", 0)
70+
6371
@classmethod
6472
def _set_status(cls, data_for_create_user: Dict, kwargs: Dict):
6573
"""Status setter."""

examples/api_for_sqlalchemy/models/pydantic/user.py

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class Enum:
2626

2727
first_name: Optional[str] = None
2828
last_name: Optional[str] = None
29+
age: Optional[int] = None
2930
status: UserStatusEnum = Field(default=UserStatusEnum.active)
3031

3132

examples/api_for_sqlalchemy/models/sqlalchemy/user.py

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class User(Base, BaseModelMixin):
1313
id = Column(Integer, primary_key=True, autoincrement=True)
1414
first_name: str = Column(Text, nullable=True)
1515
last_name: str = Column(Text, nullable=True)
16+
age: int = Column(Integer, nullable=True)
1617
status = Column(EnumColumn(UserStatusEnum), nullable=False, default=UserStatusEnum.active)
1718

1819
def __repr__(self):

requirements-all.txt

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
SQLAlchemy>=1.4.39,<2.0.0
2+
pydantic>=1.9.1
3+
fastapi>=0.79.0
4+
uvicorn>=0.18.2
5+
simplejson>=3.17.6
6+
tortoise-orm>=0.19.2

requirements.txt

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,4 @@ SQLAlchemy>=1.4.39,<2.0.0
22
pydantic>=1.9.1
33
fastapi>=0.79.0
44
uvicorn>=0.18.2
5-
simplejson>=3.17.6
6-
tortoise-orm>=0.19.2
5+
simplejson>=3.17.6

setup.py

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
extra_packages = {
1212
"tests": ["pytest"],
1313
"docs": ["sphinx"],
14+
"tortoise-orm": ["tortoise-orm>=0.19.2"],
15+
"sqlalchemy": ["SQLAlchemy>=1.4.39,<2.0.0"],
1416
}
1517
all_packages = []
1618
for value in extra_packages.values():

0 commit comments

Comments
 (0)