Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bump version to 3.0.0 #97

Merged
merged 21 commits into from
Mar 13, 2025
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
2 changes: 1 addition & 1 deletion .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Set up Python 🐍
uses: actions/setup-python@v3
with:
python-version: '3.11'
python-version: '3.12'

- name: Install Hatch 🐣
run: pip install --upgrade pip setuptools wheel twine "hatch==1.7.0"
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ jobs:
- "3.9"
- "3.10"
- "3.11"
- "3.12"

steps:
- uses: actions/checkout@v3
Expand All @@ -39,7 +40,7 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install poetry
run: |
python -m pip install --upgrade pip poetry==1.8.2 pre-commit
python -m pip install --upgrade pip poetry==1.8.4 pre-commit
poetry config virtualenvs.create false --local
- name: Install dependencies
run: poetry install --all-extras
Expand All @@ -55,6 +56,7 @@ jobs:
- "3.9"
- "3.10"
- "3.11"
- "3.12"
db-url:
- "sqlite+aiosqlite:///./db.sqlite3"
- "postgresql+asyncpg://user:passwd@localhost:5432/app"
Expand Down Expand Up @@ -87,7 +89,7 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install poetry
run: |
python -m pip install --upgrade pip poetry==1.8.2 pre-commit
python -m pip install --upgrade pip poetry==1.8.4 pre-commit
poetry config virtualenvs.create false --local
- name: Install dependencies
run: poetry install --all-extras
Expand Down
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,6 @@ cython_debug/
.idea/

/.python-version
/examples/api_for_tortoise_orm/db.sqlite3
/examples/api_for_tortoise_orm/db.sqlite3-shm
/examples/api_for_tortoise_orm/db.sqlite3-wal
/db.sqlite3
/db.sqlite3-shm
/db.sqlite3-wal
Expand Down
22 changes: 19 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,21 +1,37 @@
repos:
- repo: https://github.com/pre-commit/mirrors-mypy
rev: 'bbc3dc1'
hooks:
- id: mypy
args:
- --check-untyped-defs
- --ignore-missing-imports
- --install-types
- --non-interactive
- --scripts-are-modules
- --warn-unused-ignores
stages:
- manual

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: "v3.2.0"
rev: "v4.1.0"
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: mixed-line-ending
- id: requirements-txt-fixer
- id: pretty-format-json
exclude: "docs/"

- repo: https://github.com/psf/black
rev: "23.3.0"
rev: "25.1.0"
hooks:
- id: black

- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: "v0.1.8"
rev: "v0.9.4"
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix, --unsafe-fixes]
2 changes: 1 addition & 1 deletion .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ version: 2
build:
os: ubuntu-22.04
tools:
python: "3.11"
python: "3.12"
# You can also specify other tool versions:
# nodejs: "20"
# rust: "1.70"
Expand Down
215 changes: 99 additions & 116 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

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


# FastAPI-JSONAPI

FastAPI-JSONAPI is a FastAPI extension for building REST APIs.
Expand All @@ -30,177 +31,159 @@ pip install FastAPI-JSONAPI
Create a test.py file and copy the following code into it

```python
import sys
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from pathlib import Path
from typing import Any, ClassVar, Dict
from typing import Any, ClassVar, Optional
from typing import Union

import uvicorn
from fastapi import APIRouter, Depends, FastAPI
from sqlalchemy import Column, Integer, Text
from fastapi import Depends, FastAPI
from fastapi.responses import ORJSONResponse as JSONResponse
from pydantic import ConfigDict
from sqlalchemy.engine import URL
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 sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

from fastapi_jsonapi import RoutersJSONAPI, init
from fastapi_jsonapi.misc.sqla.generics.base import DetailViewBaseGeneric, ListViewBaseGeneric
from fastapi_jsonapi import ApplicationBuilder
from fastapi_jsonapi.misc.sqla.generics.base import ViewBaseGeneric
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
DB_URL = f"sqlite+aiosqlite:///{CURRENT_DIR}/db.sqlite3"

Base = declarative_base()


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


class UserAttributesBaseSchema(BaseModel):
name: str

class Config:
"""Pydantic schema config."""
from fastapi_jsonapi.views import ViewBase, Operation, OperationConfig

CURRENT_DIR = Path(__file__).resolve().parent
sys.path.append(f"{CURRENT_DIR.parent.parent}")


class DB:
def __init__(
self,
url: Union[str, URL],
echo: bool = False,
echo_pool: bool = False,
):
self.engine: AsyncEngine = create_async_engine(
url=url,
echo=echo,
echo_pool=echo_pool,
)

orm_mode = True
self.session_maker: async_sessionmaker[AsyncSession] = async_sessionmaker(
autocommit=False,
bind=self.engine,
expire_on_commit=False,
)

async def dispose(self):
await self.engine.dispose()

class UserSchema(UserAttributesBaseSchema):
"""User base schema."""
async def session(self) -> AsyncIterator[AsyncSession]:
async with self.session_maker() as session:
yield session


class UserPatchSchema(UserAttributesBaseSchema):
"""User PATCH schema."""
db = DB(
url=make_url(f"sqlite+aiosqlite:///{CURRENT_DIR}/db.sqlite3"),
)


class UserInSchema(UserAttributesBaseSchema):
"""User input schema."""
class Base(DeclarativeBase):
pass


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 User(Base):
__tablename__ = "users"

id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[Optional[str]]

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()
class UserSchema(BaseModel):
"""User base schema."""

model_config = ConfigDict(
from_attributes=True,
)

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)
name: str


class SessionDependency(BaseModel):
session: AsyncSession = Depends(Connector.get_session)
model_config = ConfigDict(
arbitrary_types_allowed=True,
)

class Config:
arbitrary_types_allowed = True
session: AsyncSession = Depends(db.session)


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


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


class UserListView(ListViewBaseGeneric):
method_dependencies: ClassVar[Dict[HTTPMethod, HTTPMethodConfig]] = {
HTTPMethod.ALL: HTTPMethodConfig(
class UserView(ViewBaseGeneric):
operation_dependencies: ClassVar = {
Operation.ALL: OperationConfig(
dependencies=SessionDependency,
prepare_data_layer_kwargs=session_dependency_handler,
)
),
}


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

router: APIRouter = APIRouter()
RoutersJSONAPI(
router=router,
builder = ApplicationBuilder(app)
builder.add_resource(
path="/users",
tags=["User"],
class_detail=UserDetailView,
class_list=UserListView,
view=UserView,
schema=UserSchema,
resource_type="user",
schema_in_patch=UserPatchSchema,
schema_in_post=UserInSchema,
model=User,
resource_type="user",
)
builder.initialize()

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

# noinspection PyUnusedLocal
@asynccontextmanager
async def lifespan(app: FastAPI):
add_routes(app)

def create_app() -> FastAPI:
"""
Create app factory.
async with db.engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)

yield

await db.dispose()

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

app = FastAPI(
title="FastAPI and SQLAlchemy",
lifespan=lifespan,
debug=True,
default_response_class=JSONResponse,
docs_url="/docs",
openapi_url="/openapi.json",
)

app = create_app()

if __name__ == "__main__":
uvicorn.run(
"main:app",
app,
host="0.0.0.0",
port=8080,
reload=True,
app_dir=str(CURRENT_DIR),
)
```

This example provides the following API structure:

| 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 |
| 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 |
| `/operations/` | POST | atomic | Create, update, delete users |
Loading