Refactor code structure for improved readability and maintainability

This commit is contained in:
2026-04-12 22:00:21 +08:00
parent 375ca1bb2b
commit 9dc1795f9f
7 changed files with 1361 additions and 1790 deletions
+5 -5
View File
@@ -4,13 +4,13 @@ ENV PYTHONUNBUFFERED 1
WORKDIR /build
# Create requirements.txt file
FROM base as poetry
RUN pip install poetry==1.8.2
COPY poetry.lock pyproject.toml ./
RUN poetry export -o /requirements.txt --without-hashes
FROM base as deps
RUN pip install uv==0.4.26
COPY pyproject.toml ./
RUN uv pip compile pyproject.toml -o /requirements.txt
FROM base as common
COPY --from=poetry /requirements.txt .
COPY --from=deps /requirements.txt .
# Create venv, add it to path and install requirements
RUN python -m venv /venv
ENV PATH="/venv/bin:$PATH"
+4 -4
View File
@@ -38,7 +38,7 @@ _Check out online example: https://minimal-fastapi-postgres-template.rafsaf.pl,
- [x] Full [Alembic](https://alembic.sqlalchemy.org/en/latest/) migrations setup
- [x] Refresh token endpoint (not only access like in official template)
- [x] Ready to go Dockerfile with [uvicorn](https://www.uvicorn.org/) webserver as an example
- [x] [Poetry](https://python-poetry.org/docs/), `mypy`, `pre-commit` hooks with [ruff](https://github.com/astral-sh/ruff)
- [uv](https://docs.astral.sh/uv/), `mypy`, `pre-commit` hooks with [ruff](https://github.com/astral-sh/ruff)
- [x] Perfect pytest asynchronous test setup with +40 tests and full coverage
<br>
@@ -55,13 +55,13 @@ _Check out online example: https://minimal-fastapi-postgres-template.rafsaf.pl,
See [docs](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-repository-from-a-template).
### 2. Install dependecies with [Poetry](https://python-poetry.org/docs/)
### 2. Install dependecies with [uv](https://docs.astral.sh/uv/)
```bash
cd your_project_name
### Poetry install (python3.13)
poetry install
### uv install (python3.13)
uv sync
```
Note, be sure to use `python3.13` with this template with either poetry or standard venv & pip, if you need to stick to some earlier python version, you should adapt it yourself (remove new versions specific syntax for example `str | int` for python < 3.10)
+64 -42
View File
@@ -1,4 +1,6 @@
import pytest
from datetime import datetime, timedelta
from fastapi import status
from httpx import AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession
@@ -35,7 +37,7 @@ async def test_create_new_temperature(client: AsyncClient) -> None:
@pytest.mark.asyncio(loop_scope="session")
async def test_get_temperature(
async def test_search_temperature(
client: AsyncClient,
session: AsyncSession,
) -> None:
@@ -47,22 +49,40 @@ async def test_get_temperature(
await session.commit()
response = await client.get(
app.url_path_for("get_temperature"),
app.url_path_for("search_temperature"),
)
assert response.status_code == status.HTTP_200_OK
assert len(response.json()) >= 2
assert response.json() == [
{
"update_time": temperature1.update_time.isoformat().replace("+00:00", "Z"),
"temperature": temperature1.temperature,
"id": temperature1.id,
},
{
"update_time": temperature2.update_time.isoformat().replace("+00:00", "Z"),
"temperature": temperature2.temperature,
"id": temperature2.id,
},
]
@pytest.mark.asyncio(loop_scope="session")
async def test_search_temperature_with_limit(
client: AsyncClient,
session: AsyncSession,
) -> None:
response = await client.get(
app.url_path_for("search_temperature"),
params={"limit": 1}
)
assert response.status_code == status.HTTP_200_OK
assert len(response.json()) <= 1
@pytest.mark.asyncio(loop_scope="session")
async def test_search_temperature_with_date_range(
client: AsyncClient,
session: AsyncSession,
) -> None:
now = datetime.utcnow()
start_date = (now - timedelta(days=1)).isoformat() + "Z"
end_date = (now + timedelta(days=1)).isoformat() + "Z"
response = await client.get(
app.url_path_for("search_temperature"),
params={"start_date": start_date, "end_date": end_date}
)
assert response.status_code == status.HTTP_200_OK
assert isinstance(response.json(), list)
@pytest.mark.asyncio(loop_scope="session")
@@ -93,7 +113,7 @@ async def test_create_new_pressure(client: AsyncClient) -> None:
@pytest.mark.asyncio(loop_scope="session")
async def test_get_pressure(
async def test_search_pressure(
client: AsyncClient,
session: AsyncSession,
) -> None:
@@ -105,22 +125,23 @@ async def test_get_pressure(
await session.commit()
response = await client.get(
app.url_path_for("get_pressure"),
app.url_path_for("search_pressure"),
)
assert response.status_code == status.HTTP_200_OK
assert len(response.json()) >= 2
assert response.json() == [
{
"update_time": pressure1.update_time.isoformat().replace("+00:00", "Z"),
"pressure": pressure1.pressure,
"id": pressure1.id,
},
{
"update_time": pressure2.update_time.isoformat().replace("+00:00", "Z"),
"pressure": pressure2.pressure,
"id": pressure2.id,
},
]
@pytest.mark.asyncio(loop_scope="session")
async def test_search_pressure_with_limit(
client: AsyncClient,
session: AsyncSession,
) -> None:
response = await client.get(
app.url_path_for("search_pressure"),
params={"limit": 1}
)
assert response.status_code == status.HTTP_200_OK
assert len(response.json()) <= 1
@pytest.mark.asyncio(loop_scope="session")
@@ -151,7 +172,7 @@ async def test_create_new_humidity(client: AsyncClient) -> None:
@pytest.mark.asyncio(loop_scope="session")
async def test_get_humidity(
async def test_search_humidity(
client: AsyncClient,
session: AsyncSession,
) -> None:
@@ -163,19 +184,20 @@ async def test_get_humidity(
await session.commit()
response = await client.get(
app.url_path_for("get_humidity"),
app.url_path_for("search_humidity"),
)
assert response.status_code == status.HTTP_200_OK
assert len(response.json()) >= 2
assert response.json() == [
{
"update_time": humidity1.update_time.isoformat().replace("+00:00", "Z"),
"humidity": humidity1.humidity,
"id": humidity1.id,
},
{
"update_time": humidity2.update_time.isoformat().replace("+00:00", "Z"),
"humidity": humidity2.humidity,
"id": humidity2.id,
},
]
@pytest.mark.asyncio(loop_scope="session")
async def test_search_humidity_with_limit(
client: AsyncClient,
session: AsyncSession,
) -> None:
response = await client.get(
app.url_path_for("search_humidity"),
params={"limit": 1}
)
assert response.status_code == status.HTTP_200_OK
assert len(response.json()) <= 1
Generated
-1709
View File
File diff suppressed because it is too large Load Diff
+31 -30
View File
@@ -1,39 +1,40 @@
[tool.poetry]
authors = ["admin <admin@example.com>"]
[project]
authors = [{email = "admin@example.com", name = "admin"}]
description = "FastAPI project generated using minimal-fastapi-postgres-template."
name = "app"
version = "0.1.0-alpha"
requires-python = ">=3.13"
dependencies = [
"alembic>=1.14.0,<2",
"asyncpg>=0.30.0,<1",
"bcrypt>=4.3.0,<5",
"fastapi>=0.115.10,<1",
"pydantic>=2.10.6,<3",
"pydantic-settings>=2.8.1,<3",
"pyjwt>=2.10.1,<3",
"python-multipart>=0.0.20,<1",
"sqlalchemy[asyncio]>=2.0.38,<3",
]
[tool.poetry.dependencies]
python = "^3.13"
alembic = "^1.14.0"
asyncpg = "^0.30.0"
bcrypt = "^4.3.0"
fastapi = "^0.115.10"
pydantic = { extras = ["dotenv", "email"], version = "^2.10.6" }
pydantic-settings = "^2.8.1"
pyjwt = "^2.10.1"
python-multipart = "^0.0.20"
sqlalchemy = {extras = ["asyncio"], version = "^2.0.38"}
[tool.poetry.group.dev.dependencies]
coverage = "^7.6.12"
freezegun = "^1.5.1"
greenlet = "^3.1.1"
httpx = "^0.28.1"
mypy = "^1.15.0"
pre-commit = "^4.0.1"
pytest = "^8.3.4"
pytest-asyncio = "^0.26.0"
pytest-cov = "^6.0.0"
pytest-xdist = "^3.6.1"
ruff = "^0.9.9"
uvicorn = { extras = ["standard"], version = "^0.34.0" }
[project.optional-dependencies]
dev = [
"coverage>=7.6.12,<8",
"freezegun>=1.5.1,<2",
"greenlet>=3.1.1,<4",
"httpx>=0.28.1,<1",
"mypy>=1.15.0,<2",
"pre-commit>=4.0.1,<5",
"pytest>=8.3.4,<9",
"pytest-asyncio>=0.26.0,<1",
"pytest-cov>=6.0.0,<7",
"pytest-xdist>=3.6.1,<4",
"ruff>=0.9.9,<1",
"uvicorn[standard]>=0.34.0,<1",
]
[build-system]
build-backend = "poetry.core.masonry.api"
requires = ["poetry-core>=1.0.0"]
build-backend = "hatchling.build"
requires = ["hatchling"]
[tool.pytest.ini_options]
addopts = "-vv -n auto --cov --cov-report xml --cov-report term-missing --cov-fail-under=92"
+64
View File
@@ -0,0 +1,64 @@
# This file was autogenerated by uv via the following command:
# uv pip compile pyproject.toml -o requirements.txt
alembic==1.18.4
# via app (pyproject.toml)
annotated-doc==0.0.4
# via fastapi
annotated-types==0.7.0
# via pydantic
anyio==4.13.0
# via starlette
asyncpg==0.31.0
# via app (pyproject.toml)
bcrypt==4.3.0
# via app (pyproject.toml)
dnspython==2.8.0
# via email-validator
email-validator==2.3.0
# via pydantic
fastapi==0.135.3
# via app (pyproject.toml)
greenlet==3.4.0
# via sqlalchemy
idna==3.11
# via
# anyio
# email-validator
mako==1.3.10
# via alembic
markupsafe==3.0.3
# via mako
pydantic==2.12.5
# via
# app (pyproject.toml)
# fastapi
# pydantic-settings
pydantic-core==2.41.5
# via pydantic
pydantic-settings==2.13.1
# via app (pyproject.toml)
pyjwt==2.12.1
# via app (pyproject.toml)
python-dotenv==1.2.2
# via pydantic-settings
python-multipart==0.0.26
# via app (pyproject.toml)
sqlalchemy==2.0.49
# via
# app (pyproject.toml)
# alembic
starlette==1.0.0
# via fastapi
typing-extensions==4.15.0
# via
# alembic
# fastapi
# pydantic
# pydantic-core
# sqlalchemy
# typing-inspection
typing-inspection==0.4.2
# via
# fastapi
# pydantic
# pydantic-settings
Generated
+1193
View File
File diff suppressed because it is too large Load Diff