Skip to content

Commit 1556af3

Browse files
authored
Merge pull request #97 from mts-ai/new-dev-3.0
Bump version to 3.0.0
2 parents 64b0b17 + 8629b11 commit 1556af3

File tree

212 files changed

+9659
-11273
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

212 files changed

+9659
-11273
lines changed

.github/workflows/python-publish.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
- name: Set up Python 🐍
2020
uses: actions/setup-python@v3
2121
with:
22-
python-version: '3.11'
22+
python-version: '3.12'
2323

2424
- name: Install Hatch 🐣
2525
run: pip install --upgrade pip setuptools wheel twine "hatch==1.7.0"

.github/workflows/testing.yml

+4-2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ jobs:
3030
- "3.9"
3131
- "3.10"
3232
- "3.11"
33+
- "3.12"
3334

3435
steps:
3536
- uses: actions/checkout@v3
@@ -39,7 +40,7 @@ jobs:
3940
python-version: ${{ matrix.python-version }}
4041
- name: Install poetry
4142
run: |
42-
python -m pip install --upgrade pip poetry==1.8.2 pre-commit
43+
python -m pip install --upgrade pip poetry==1.8.4 pre-commit
4344
poetry config virtualenvs.create false --local
4445
- name: Install dependencies
4546
run: poetry install --all-extras
@@ -55,6 +56,7 @@ jobs:
5556
- "3.9"
5657
- "3.10"
5758
- "3.11"
59+
- "3.12"
5860
db-url:
5961
- "sqlite+aiosqlite:///./db.sqlite3"
6062
- "postgresql+asyncpg://user:passwd@localhost:5432/app"
@@ -87,7 +89,7 @@ jobs:
8789
python-version: ${{ matrix.python-version }}
8890
- name: Install poetry
8991
run: |
90-
python -m pip install --upgrade pip poetry==1.8.2 pre-commit
92+
python -m pip install --upgrade pip poetry==1.8.4 pre-commit
9193
poetry config virtualenvs.create false --local
9294
- name: Install dependencies
9395
run: poetry install --all-extras

.gitignore

-3
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,6 @@ cython_debug/
160160
.idea/
161161

162162
/.python-version
163-
/examples/api_for_tortoise_orm/db.sqlite3
164-
/examples/api_for_tortoise_orm/db.sqlite3-shm
165-
/examples/api_for_tortoise_orm/db.sqlite3-wal
166163
/db.sqlite3
167164
/db.sqlite3-shm
168165
/db.sqlite3-wal

.pre-commit-config.yaml

+19-3
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,37 @@
11
repos:
2+
- repo: https://github.com/pre-commit/mirrors-mypy
3+
rev: 'bbc3dc1'
4+
hooks:
5+
- id: mypy
6+
args:
7+
- --check-untyped-defs
8+
- --ignore-missing-imports
9+
- --install-types
10+
- --non-interactive
11+
- --scripts-are-modules
12+
- --warn-unused-ignores
13+
stages:
14+
- manual
15+
216
- repo: https://github.com/pre-commit/pre-commit-hooks
3-
rev: "v3.2.0"
17+
rev: "v4.1.0"
418
hooks:
519
- id: trailing-whitespace
620
- id: end-of-file-fixer
721
- id: check-yaml
822
- id: check-added-large-files
923
- id: mixed-line-ending
1024
- id: requirements-txt-fixer
25+
- id: pretty-format-json
26+
exclude: "docs/"
1127

1228
- repo: https://github.com/psf/black
13-
rev: "23.3.0"
29+
rev: "25.1.0"
1430
hooks:
1531
- id: black
1632

1733
- repo: https://github.com/charliermarsh/ruff-pre-commit
18-
rev: "v0.1.8"
34+
rev: "v0.9.4"
1935
hooks:
2036
- id: ruff
2137
args: [--fix, --exit-non-zero-on-fix, --unsafe-fixes]

.readthedocs.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ version: 2
88
build:
99
os: ubuntu-22.04
1010
tools:
11-
python: "3.11"
11+
python: "3.12"
1212
# You can also specify other tool versions:
1313
# nodejs: "20"
1414
# rust: "1.70"

README.md

+99-116
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,177 +31,159 @@ 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
36+
from contextlib import asynccontextmanager
3337
from pathlib import Path
34-
from typing import Any, ClassVar, Dict
38+
from typing import Any, ClassVar, Optional
39+
from typing import Union
3540

3641
import uvicorn
37-
from fastapi import APIRouter, Depends, FastAPI
38-
from sqlalchemy import Column, Integer, Text
42+
from fastapi import Depends, FastAPI
43+
from fastapi.responses import ORJSONResponse as JSONResponse
44+
from pydantic import ConfigDict
45+
from sqlalchemy.engine import URL
3946
from sqlalchemy.engine import make_url
40-
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
41-
from sqlalchemy.ext.declarative import declarative_base
42-
from sqlalchemy.orm import sessionmaker
47+
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker, create_async_engine
48+
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
49-
50-
CURRENT_FILE = Path(__file__).resolve()
51-
CURRENT_DIR = CURRENT_FILE.parent
52-
DB_URL = f"sqlite+aiosqlite:///{CURRENT_DIR}/db.sqlite3"
53-
54-
Base = declarative_base()
55-
56-
57-
class User(Base):
58-
__tablename__ = "users"
59-
id = Column(Integer, primary_key=True, autoincrement=True)
60-
name = Column(Text, nullable=True)
61-
62-
63-
class UserAttributesBaseSchema(BaseModel):
64-
name: str
65-
66-
class Config:
67-
"""Pydantic schema config."""
53+
from fastapi_jsonapi.views import ViewBase, Operation, OperationConfig
54+
55+
CURRENT_DIR = Path(__file__).resolve().parent
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+
)
6871

69-
orm_mode = True
72+
self.session_maker: async_sessionmaker[AsyncSession] = async_sessionmaker(
73+
autocommit=False,
74+
bind=self.engine,
75+
expire_on_commit=False,
76+
)
7077

78+
async def dispose(self):
79+
await self.engine.dispose()
7180

72-
class UserSchema(UserAttributesBaseSchema):
73-
"""User base schema."""
81+
async def session(self) -> AsyncIterator[AsyncSession]:
82+
async with self.session_maker() as session:
83+
yield session
7484

7585

76-
class UserPatchSchema(UserAttributesBaseSchema):
77-
"""User PATCH schema."""
86+
db = DB(
87+
url=make_url(f"sqlite+aiosqlite:///{CURRENT_DIR}/db.sqlite3"),
88+
)
7889

7990

80-
class UserInSchema(UserAttributesBaseSchema):
81-
"""User input schema."""
91+
class Base(DeclarativeBase):
92+
pass
8293

8394

84-
def async_session() -> sessionmaker:
85-
engine = create_async_engine(url=make_url(DB_URL))
86-
_async_session = sessionmaker(bind=engine, class_=AsyncSession, expire_on_commit=False)
87-
return _async_session
95+
class User(Base):
96+
__tablename__ = "users"
8897

98+
id: Mapped[int] = mapped_column(primary_key=True)
99+
name: Mapped[Optional[str]]
89100

90-
class Connector:
91-
@classmethod
92-
async def get_session(cls):
93-
"""
94-
Get session as dependency
95101

96-
:return:
97-
"""
98-
sess = async_session()
99-
async with sess() as db_session: # type: AsyncSession
100-
yield db_session
101-
await db_session.rollback()
102+
class UserSchema(BaseModel):
103+
"""User base schema."""
102104

105+
model_config = ConfigDict(
106+
from_attributes=True,
107+
)
103108

104-
async def sqlalchemy_init() -> None:
105-
engine = create_async_engine(url=make_url(DB_URL))
106-
async with engine.begin() as conn:
107-
await conn.run_sync(Base.metadata.create_all)
109+
name: str
108110

109111

110112
class SessionDependency(BaseModel):
111-
session: AsyncSession = Depends(Connector.get_session)
113+
model_config = ConfigDict(
114+
arbitrary_types_allowed=True,
115+
)
112116

113-
class Config:
114-
arbitrary_types_allowed = True
117+
session: AsyncSession = Depends(db.session)
115118

116119

117-
def session_dependency_handler(view: ViewBase, dto: SessionDependency) -> Dict[str, Any]:
120+
def session_dependency_handler(view: ViewBase, dto: SessionDependency) -> dict[str, Any]:
118121
return {
119122
"session": dto.session,
120123
}
121124

122125

123-
class UserDetailView(DetailViewBaseGeneric):
124-
method_dependencies: ClassVar[Dict[HTTPMethod, HTTPMethodConfig]] = {
125-
HTTPMethod.ALL: HTTPMethodConfig(
126-
dependencies=SessionDependency,
127-
prepare_data_layer_kwargs=session_dependency_handler,
128-
)
129-
}
130-
131-
132-
class UserListView(ListViewBaseGeneric):
133-
method_dependencies: ClassVar[Dict[HTTPMethod, HTTPMethodConfig]] = {
134-
HTTPMethod.ALL: HTTPMethodConfig(
126+
class UserView(ViewBaseGeneric):
127+
operation_dependencies: ClassVar = {
128+
Operation.ALL: OperationConfig(
135129
dependencies=SessionDependency,
136130
prepare_data_layer_kwargs=session_dependency_handler,
137-
)
131+
),
138132
}
139133

140134

141135
def add_routes(app: FastAPI):
142-
tags = [
143-
{
144-
"name": "User",
145-
"description": "",
146-
},
147-
]
148-
149-
router: APIRouter = APIRouter()
150-
RoutersJSONAPI(
151-
router=router,
136+
builder = ApplicationBuilder(app)
137+
builder.add_resource(
152138
path="/users",
153139
tags=["User"],
154-
class_detail=UserDetailView,
155-
class_list=UserListView,
140+
view=UserView,
156141
schema=UserSchema,
157-
resource_type="user",
158-
schema_in_patch=UserPatchSchema,
159-
schema_in_post=UserInSchema,
160142
model=User,
143+
resource_type="user",
161144
)
145+
builder.initialize()
162146

163-
app.include_router(router, prefix="")
164-
return tags
165147

148+
# noinspection PyUnusedLocal
149+
@asynccontextmanager
150+
async def lifespan(app: FastAPI):
151+
add_routes(app)
166152

167-
def create_app() -> FastAPI:
168-
"""
169-
Create app factory.
153+
async with db.engine.begin() as conn:
154+
await conn.run_sync(Base.metadata.create_all)
155+
156+
yield
157+
158+
await db.dispose()
170159

171-
:return: app
172-
"""
173-
app = FastAPI(
174-
title="FastAPI and SQLAlchemy",
175-
debug=True,
176-
openapi_url="/openapi.json",
177-
docs_url="/docs",
178-
)
179-
add_routes(app)
180-
app.on_event("startup")(sqlalchemy_init)
181-
init(app)
182-
return app
183160

161+
app = FastAPI(
162+
title="FastAPI and SQLAlchemy",
163+
lifespan=lifespan,
164+
debug=True,
165+
default_response_class=JSONResponse,
166+
docs_url="/docs",
167+
openapi_url="/openapi.json",
168+
)
184169

185-
app = create_app()
186170

187171
if __name__ == "__main__":
188172
uvicorn.run(
189-
"main:app",
173+
app,
190174
host="0.0.0.0",
191175
port=8080,
192-
reload=True,
193-
app_dir=str(CURRENT_DIR),
194176
)
195177
```
196178

197179
This example provides the following API structure:
198180

199-
| URL | method | endpoint | Usage |
200-
|-------------------|--------|-------------|---------------------------|
201-
| `/users` | GET | user_list | Get a collection of users |
202-
| `/users` | POST | user_list | Create a user |
203-
| `/users` | DELETE | user_list | Delete users |
204-
| `/users/{obj_id}` | GET | user_detail | Get user details |
205-
| `/users/{obj_id}` | PATCH | user_detail | Update a user |
206-
| `/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 |

0 commit comments

Comments
 (0)