mirror of
https://github.com/furyhawk/listen.git
synced 2026-07-21 00:45:36 +00:00
Refactor code structure for improved readability and maintainability
This commit is contained in:
+5
-5
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+31
-30
@@ -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"
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user