Skip to content

Commit 6dbc7e1

Browse files
committed
Merge branch 'switch-logger-to-rotoger' of https://github.com/grillazz/fastapi-sqlalchemy-asyncpg into switch-logger-to-rotoger
2 parents 5d34b04 + f87d751 commit 6dbc7e1

File tree

9 files changed

+157
-666
lines changed

9 files changed

+157
-666
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ safety: ## Check for insecure dependencies
6060

6161
.PHONY: py-upgrade
6262
py-upgrade: ## Upgrade Python syntax to a newer version
63-
pyupgrade --py313-plus `find app -name "*.py"`
63+
pyupgrade --py314-plus `find app -name "*.py"`
6464

6565
.PHONY: lint
6666
lint: ## Lint and format project code

README.md

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -48,22 +48,10 @@ This example demonstrates the seamless integration of [FastAPI](https://fastapi.
4848
with [Pydantic 2.0](https://github.com/pydantic/pydantic), a robust and powerful data validation library.
4949
The integration is further enhanced by the use of [SQLAlchemy ORM](https://www.sqlalchemy.org/), a popular and feature-rich Object-Relational Mapping tool,
5050
and [PostgreSQL17](https://www.postgresql.org/docs/17/release.html) relational database.
51-
5251
The entire stack is connected using the [asyncpg](https://github.com/MagicStack/asyncpg) Database Client Library,
5352
which provides a robust and efficient way to interact with PostgreSQL databases in Python,
5453
leveraging the power of asyncio and event loops.
5554

56-
Notably, this example showcases the latest and greatest versions of SQLAlchemy and psycopg,
57-
which are renowned for their robustness, power, and speed. The inclusion of FastAPI adds a modern, fast, and high-performance web framework to the mix
58-
allowing for the rapid development of APIs with Python 3.13.
59-
60-
FastAPI has received significant recognition in the industry, including a review on thoughtworks Technology Radar in April 2021,
61-
where it was classified as a Trial technology, with comments praising its performance, ease of use,
62-
and features such as API documentation using OpenAPI. Additionally, FastAPI was recognized in the Python Developers Survey 2023 Results,
63-
conducted by the Python Software Foundation and JetBrains, where it was reported that 1 in 4 Python developers use FastAPI,
64-
with a 4 percentage point increase from the previous year.
65-
66-
6755
### Built With
6856
[![FastAPI][fastapi.tiangolo.com]][fastapi-url]
6957
[![Pydantic][pydantic.com]][pydantic-url]
@@ -73,6 +61,11 @@ with a 4 percentage point increase from the previous year.
7361
[![asyncpg][asyncpg.github.io]][asyncpg-url]
7462
[![alembic][alembic.sqlalchemy.org]][alembic-url]
7563
[![rich][rich.readthedocs.io]][rich-url]
64+
[![redis][redis.io]][redis-url]
65+
[![structlog][structlog.org]][structlog-url]
66+
[![rotoger][rotoger]][rotoger-url]
67+
[![granian][granian]][granian-url]
68+
7669

7770

7871

@@ -106,7 +99,7 @@ Next models were generated with https://github.com/agronholm/sqlacodegen
10699

107100
To elevate the logging capabilities beyond simple colored output,
108101
this project has transitioned to [Rotoger](https://github.com/tinyplugins/rotoger).
109-
This powerful library provides a comprehensive, production-ready logging setup for modern asynchronous applications,
102+
This tiny library provides a comprehensive, production-ready logging setup for modern asynchronous applications,
110103
addressing challenges like log management, performance, and readability.
111104

112105
Rotoger is built upon the excellent [structlog](http://structlog.org/) library and brings several key advantages:
@@ -218,7 +211,7 @@ I've included a few of my favorites to kick things off!
218211

219212
## Change Log
220213
<details>
221-
<summary>2025 (3 changes)</summary>
214+
<summary>2025 (7 changes)</summary>
222215
<ul>
223216
<li>[SEP 2 2025] add sample high availability with nginx as load balancer</li>
224217
<li>[AUG 23 2025] intro exception handlers</li>
@@ -280,21 +273,27 @@ I've included a few of my favorites to kick things off!
280273
[linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555
281274
[linkedin-url]: https://www.linkedin.com/in/python-has-powers/
282275

283-
[fastapi.tiangolo.com]: https://img.shields.io/badge/FastAPI-0.116.1-009485?style=for-the-badge&logo=fastapi&logoColor=white
276+
[fastapi.tiangolo.com]: https://img.shields.io/badge/FastAPI-0.121.2-009485?style=for-the-badge&logo=fastapi&logoColor=white
284277
[fastapi-url]: https://fastapi.tiangolo.com/
285-
[pydantic.com]: https://img.shields.io/badge/Pydantic-2.12.0a1-e92063?style=for-the-badge&logo=pydantic&logoColor=white
278+
[pydantic.com]: https://img.shields.io/badge/Pydantic-2.12.4-e92063?style=for-the-badge&logo=pydantic&logoColor=white
286279
[pydantic-url]: https://docs.pydantic.dev/latest/
287-
[sqlalchemy.org]: https://img.shields.io/badge/SQLAlchemy-2.0.43-bb0000?color=bb0000&style=for-the-badge
280+
[sqlalchemy.org]: https://img.shields.io/badge/SQLAlchemy-2.0.44-bb0000?color=bb0000&style=for-the-badge&logo=python&logoColor=white
288281
[sqlalchemy-url]: https://docs.sqlalchemy.org/en/20/
289-
[uvicorn.org]: https://img.shields.io/badge/Uvicorn-0.35.0-2094f3?style=for-the-badge&logo=uvicorn&logoColor=white
282+
[uvicorn.org]: https://img.shields.io/badge/Uvicorn-0.38.0-2094f3?style=for-the-badge&logo=python&logoColor=white
290283
[uvicorn-url]: https://www.uvicorn.org/
291284
[asyncpg.github.io]: https://img.shields.io/badge/asyncpg-0.30.0-2e6fce?style=for-the-badge&logo=postgresql&logoColor=white
292285
[asyncpg-url]: https://magicstack.github.io/asyncpg/current/
293-
[pytest.org]: https://img.shields.io/badge/pytest-8.4.1-fff?style=for-the-badge&logo=pytest&logoColor=white
294-
[pytest-url]: https://docs.pytest.org/en/6.2.x/
295-
[alembic.sqlalchemy.org]: https://img.shields.io/badge/alembic-1.16.4-6BA81E?style=for-the-badge&logo=alembic&logoColor=white
286+
[pytest.org]: https://img.shields.io/badge/pytest-9.0.1-fff?style=for-the-badge&logo=pytest&logoColor=white
287+
[pytest-url]: https://docs.pytest.org/en/9.0.x/
288+
[alembic.sqlalchemy.org]: https://img.shields.io/badge/alembic-1.17.2-6BA81E?style=for-the-badge&logo=python&logoColor=white
296289
[alembic-url]: https://alembic.sqlalchemy.org/en/latest/
297-
[rich.readthedocs.io]: https://img.shields.io/badge/rich-14.1.0-009485?style=for-the-badge&logo=rich&logoColor=white
290+
[rich.readthedocs.io]: https://img.shields.io/badge/rich-14.2.0-009485?style=for-the-badge&logo=rich&logoColor=white
298291
[rich-url]: https://rich.readthedocs.io/en/latest/
299-
[redis.io]: https://img.shields.io/badge/redis-6.4.0-dc382d?style=for-the-badge&logo=redis&logoColor=white
292+
[redis.io]: https://img.shields.io/badge/redis-7.0.1-dc382d?style=for-the-badge&logo=redis&logoColor=white
300293
[redis-url]: https://redis.io/
294+
[structlog.org]: https://img.shields.io/badge/structlog-25.5.0-000000?style=for-the-badge&logo=python&logoColor=white
295+
[structlog-url]: https://www.structlog.org/en/stable/
296+
[rotoger]: https://img.shields.io/badge/rotoger-0.2.1-00bfff?style=for-the-badge&logo=python&logoColor=white
297+
[rotoger-url]: https://github.com/tinyplugins/rotoger
298+
[granian]: https://img.shields.io/badge/granian-2.5.7-4f6cb4?style=for-the-badge&logo=rust&logoColor=white
299+
[granian-url]: https://github.com/emmett-framework/granian

app/api/health.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
router = APIRouter()
1313

1414

15-
16-
1715
@router.get("/redis", status_code=status.HTTP_200_OK)
1816
async def redis_check(request: Request):
1917
"""

app/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ async def lifespan(app: FastAPI):
4747
def create_app() -> FastAPI:
4848
app = FastAPI(
4949
title="Stuff And Nonsense API",
50-
version="0.20.0",
50+
version="1.22.0",
5151
lifespan=lifespan,
5252
)
5353
app.include_router(stuff_router)

app/models/shakespeare.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ class Character(Base):
4646
abbrev: Mapped[str | None] = mapped_column(String(32))
4747
description: Mapped[str | None] = mapped_column(String(2056))
4848

49-
work: Mapped[list["Work"]] = relationship(
49+
work: Mapped[list[Work]] = relationship(
5050
"Work", secondary="shakespeare.character_work", back_populates="character"
5151
)
52-
paragraph: Mapped[list["Paragraph"]] = relationship(
52+
paragraph: Mapped[list[Paragraph]] = relationship(
5353
"Paragraph", back_populates="character"
5454
)
5555

@@ -122,11 +122,11 @@ class Work(Base):
122122
total_paragraphs: Mapped[int] = mapped_column(Integer)
123123
notes: Mapped[str | None] = mapped_column(Text)
124124

125-
character: Mapped[list["Character"]] = relationship(
125+
character: Mapped[list[Character]] = relationship(
126126
"Character", secondary="shakespeare.character_work", back_populates="work"
127127
)
128-
chapter: Mapped[list["Chapter"]] = relationship("Chapter", back_populates="work")
129-
paragraph: Mapped[list["Paragraph"]] = relationship(
128+
chapter: Mapped[list[Chapter]] = relationship("Chapter", back_populates="work")
129+
paragraph: Mapped[list[Paragraph]] = relationship(
130130
"Paragraph", back_populates="work"
131131
)
132132

@@ -170,8 +170,8 @@ class Chapter(Base):
170170
chapter_number: Mapped[int] = mapped_column(Integer)
171171
description: Mapped[str] = mapped_column(String(256))
172172

173-
work: Mapped["Work"] = relationship("Work", back_populates="chapter")
174-
paragraph: Mapped[list["Paragraph"]] = relationship(
173+
work: Mapped[Work] = relationship("Work", back_populates="chapter")
174+
paragraph: Mapped[list[Paragraph]] = relationship(
175175
"Paragraph", back_populates="chapter"
176176
)
177177

@@ -259,11 +259,9 @@ class Paragraph(Base):
259259
char_count: Mapped[int] = mapped_column(Integer)
260260
word_count: Mapped[int] = mapped_column(Integer)
261261

262-
character: Mapped["Character"] = relationship(
263-
"Character", back_populates="paragraph"
264-
)
265-
chapter: Mapped["Chapter"] = relationship("Chapter", back_populates="paragraph")
266-
work: Mapped["Work"] = relationship("Work", back_populates="paragraph")
262+
character: Mapped[Character] = relationship("Character", back_populates="paragraph")
263+
chapter: Mapped[Chapter] = relationship("Chapter", back_populates="paragraph")
264+
work: Mapped[Work] = relationship("Work", back_populates="paragraph")
267265

268266
@classmethod
269267
async def find(cls, db_session: AsyncSession, character: str):

app/models/stuff.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class Stuff(Base):
2929
name: Mapped[str] = mapped_column(String, primary_key=True, unique=True)
3030
description: Mapped[str | None]
3131

32-
nonsense: Mapped["Nonsense"] = relationship(
32+
nonsense: Mapped[Nonsense] = relationship(
3333
"Nonsense", secondary="happy_hog.stuff_full_of_nonsense"
3434
)
3535

@@ -47,7 +47,7 @@ class StuffFullOfNonsense(Base):
4747
UUID(as_uuid=True), default=uuid.uuid4, primary_key=True
4848
)
4949
stuff_id: Mapped[Stuff] = mapped_column(UUID, ForeignKey("happy_hog.stuff.id"))
50-
nonsense_id: Mapped["Nonsense"] = mapped_column(
50+
nonsense_id: Mapped[Nonsense] = mapped_column(
5151
UUID, ForeignKey("happy_hog.nonsense.id")
5252
)
5353
but_why: Mapped[str | None]

app/schemas/shakespeare.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from __future__ import annotations
2-
31
from typing import Any
42

53
from pydantic import BaseModel

pyproject.toml

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,44 @@
11
[project]
22
name = "fastapi-sqlalchemy-asyncpg"
3-
version = "0.22.0"
3+
version = "1.22.0"
44
description = "A modern FastAPI application with SQLAlchemy 2.0 and AsyncPG for high-performance async database operations. Features include JWT authentication with Redis token storage, password hashing, connection pooling, data processing with Polars, Rich logging, task scheduling with APScheduler, and Shakespeare datasets integration."
55
readme = "README.md"
6-
requires-python = ">=3.14"
6+
requires-python = "==3.14.0"
77
dependencies = [
8-
"fastapi[all]>=0.116.2",
9-
"pydantic[email]>=2.12.0a1",
10-
"pydantic-settings>=2.10.1",
8+
"fastapi[all]==0.121.2",
9+
"pydantic[email]==2.12.4",
10+
"pydantic-settings==2.12.0",
1111
"sqlalchemy==2.0.44",
12-
"uvicorn==0.38.0",
13-
"asyncpg>=0.30.0",
14-
"alembic>=1.16.5",
15-
"httpx>=0.28.1",
16-
"pytest>=8.4.2",
17-
"pytest-cov>=7.0.0",
18-
"uvloop>=0.21.0",
19-
"httptools>=0.6.4",
20-
"rich>=14.1.0",
21-
"pyjwt>=2.10.1",
22-
"redis>=6.4.0",
23-
"bcrypt>=4.3.0",
24-
"polars==1.35.2",
25-
"python-multipart>=0.0.20",
26-
"fastexcel>=0.15.1",
27-
"inline-snapshot>=0.29.0",
28-
"dirty-equals>=0.10.0",
29-
"polyfactory>=2.22.2",
30-
"granian>=2.5.4",
12+
"uvicorn[standard]==0.38.0",
13+
"asyncpg==0.30.0",
14+
"alembic==1.17.2",
15+
"httpx==0.28.1",
16+
"pytest==9.0.1",
17+
"pytest-cov==7.0.0",
18+
"uvloop==0.22.1",
19+
"httptools==0.7.1",
20+
"rich==14.2.0",
21+
"pyjwt==2.10.1",
22+
"redis==7.0.1",
23+
"bcrypt==5.0.0",
24+
"polars[pyarrow]==1.35.2",
25+
"python-multipart==0.0.20",
26+
"fastexcel==0.16.0",
27+
"inline-snapshot==0.31.1",
28+
"dirty-equals==0.10.0",
29+
"polyfactory==3.0.0",
30+
"granian==2.5.7",
3131
"apscheduler[redis,sqlalchemy]>=4.0.0a6",
3232
"rotoger==0.2.1",
3333
]
3434

3535
[tool.uv]
3636
dev-dependencies = [
37-
"ruff>=0.13.1",
38-
"devtools[pygments]>=0.12.2",
39-
"pyupgrade>=3.20.0",
40-
"ipython>=9.5.0",
41-
"sqlacodegen<=3.1.1",
42-
"tryceratops>=2.4.1",
43-
"locust>=2.40.5",
44-
"sqlalchemy-utils>=0.41.1"
37+
"ruff==0.14.5",
38+
"devtools[pygments]==0.12.2",
39+
"pyupgrade==3.21.1",
40+
"ipython==9.7.0",
41+
"tryceratops==2.4.1",
4542
]
4643

4744

@@ -50,7 +47,7 @@ strict = true
5047
exclude = ["venv", ".venv", "alembic"]
5148

5249
[tool.ruff]
53-
target-version = "py313"
50+
target-version = "py314"
5451
exclude = ["alembic"]
5552

5653
[tool.ruff.lint]

0 commit comments

Comments
 (0)