WiP: Trying to add postgresql to porject

This commit is contained in:
t0xa 2025-09-07 22:57:52 +03:00
parent 6974cca514
commit 4ccdc39529
8 changed files with 82 additions and 20 deletions

View file

@ -1,9 +1,9 @@
# PostgreSQL Database Configuration
POSTGRES_DB=your_database_name
POSTGRES_USER=your_username
POSTGRES_PASSWORD=your_secure_password
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
POSTGRES_DATABASE_DB=your_database_name
POSTGRES_DATABASE_USER=your_username
POSTGRES_DATABASE_PASSWORD=your_secure_password
POSTGRES_DATABASE_HOST=postgres
POSTGRES_DATABASE_PORT=5432
# FastAPI Configuration
PYTHONPATH=/app

View file

@ -1,9 +1,10 @@
from pydantic_settings import BaseSettings
from pydantic_settings import BaseSettings, SettingsConfigDict
from typing import Optional
class Settings(BaseSettings):
# Base app settings
model_config = SettingsConfigDict(env_file=".env")
app_name: str = "F1tness Parser API"
version: str = "0.0.1"
debug: bool = False
@ -12,13 +13,10 @@ class Settings(BaseSettings):
POSTGRES_DATABASE_URL: Optional[str] = None
POSTGRES_DATABASE_HOST: str = "localhost"
POSTGRES_DATABASE_PORT: int = 5432
POSTGRES_DATABASE_NAME: str = "f1tness_db"
POSTGRES_DATABASE_DB: str = "f1tness_db"
POSTGRES_DATABASE_USER: str = "postgres"
POSTGRES_DATABASE_PASSWORD: str = "password"
class Config:
env_file = ".env"
def get_postgres_database_url(self, async_driver: bool = True) -> str:
"""Method for receiving relational database URL"""
@ -30,9 +28,8 @@ class Settings(BaseSettings):
return (
f"{driver}://{self.POSTGRES_DATABASE_USER}:{self.POSTGRES_DATABASE_PASSWORD}"
f"@{self.POSTGRES_DATABASE_HOST}"
f":{self.POSTGRES_DATABASE_PORT}/{self.POSTGRES_DATABASE_NAME}"
f":{self.POSTGRES_DATABASE_PORT}/{self.POSTGRES_DATABASE_DB}"
)
settings = Settings()

View file

@ -1,13 +1,30 @@
import logging
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from app.api.v1.api import api_router
from app.api.v1.web import web_router
from app.config import settings
from migrations.runner import MigrationRunner
@asynccontextmanager
async def lifespan(app: FastAPI):
logging.info(f"\n\n{settings.get_postgres_database_url()}")
runner = MigrationRunner(settings.get_postgres_database_url())
await runner.run_migrations()
yield
# TODO: (#ToLearn) Почитать про lifespan в FastAPI приложениях
app = FastAPI(
title="Fitness Parser API",
description="API for parsing fitness training data from various sources",
version="0.1.0",
lifespan=lifespan
)
app.mount("/static", StaticFiles(directory="app/static"), name="static")

View file

@ -11,21 +11,29 @@ services:
- ./app:/app/app:ro
- ./data:/app/data:ro
- ./tests:/app/tests:ro
- ./migrations:/app/migrations:ro
env_file:
- .env
environment:
- PYTHONPATH=/app
- PYTHONPATH=${PYTHONPATH}
command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload --reload-dir /app/app
depends_on:
postgres:
condition: service_healthy
postgres:
image: postgres:15
image: postgres:15-alpine
env_file:
- .env
environment:
- POSTGRES_DB=fitness_db
- POSTGRES_USER=fitness_user
- POSTGRES_PASSWORD=fitness_password
- POSTGRES_DB=${POSTGRES_DATABASE_DB}
- POSTGRES_USER=${POSTGRES_DATABASE_USER}
- POSTGRES_PASSWORD=${POSTGRES_DATABASE_PASSWORD}
ports:
- "5432:5432"
- "${POSTGRES_DATABASE_PORT}:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U fitness_user -d fitness_db"]
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_DATABASE_USER} -d ${POSTGRES_DATABASE_DB}"]
interval: 10s
timeout: 5s
retries: 5

View file

@ -10,6 +10,7 @@ class MigrationRunner:
def __init__(self, database_url: str) -> None:
self.engine = create_async_engine(database_url)
self.migrations_dir = Path("migrations/sql")
logging.error(f"{database_url}")
async def get_applied_migrations(self) -> set:
"""Get list of applied migrations"""
@ -33,6 +34,7 @@ class MigrationRunner:
async def run_migrations(self):
"""Run all unapplied migrations"""
applied = await self.get_applied_migrations()
logging.info(f"Applied migrations: {applied}")
# Getting all sql files from migrations_dir
# TODO: (#ToLearn) Read about Path.glob function
@ -47,3 +49,22 @@ class MigrationRunner:
await self._apply_migration(migration_file, migration_name)
else:
logging.info(f"Skipping migration: {migration_name} (already applied)")
async def _apply_migration(self, migration_file: Path, migration_name: str):
"""Apply migrations from migrations/sql folder"""
with open(migration_file, 'r', encoding='utf-8') as f:
sql_content = f.read()
async with self.engine.begin() as conn:
try:
statements = [s.strip() for s in sql_content.split(';') if s.strip()]
for statement in statements:
if statement:
await conn.execute(text(statement))
logging.info(f"Migration {migration_name} applied successfully")
except Exception as e:
logging.error(f"Error applying migration {migration_name}: {e}")
raise

View file

@ -14,6 +14,7 @@ dependencies = [
"jinja2>=3.1.0",
"python-multipart>=0.0.6",
"sqlalchemy>=2.0.43",
"asyncpg>=0.30.0",
]
[dependency-groups]

20
uv.lock
View file

@ -1,5 +1,5 @@
version = 1
revision = 3
revision = 2
requires-python = ">=3.13"
[[package]]
@ -24,6 +24,22 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" },
]
[[package]]
name = "asyncpg"
version = "0.30.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/2f/4c/7c991e080e106d854809030d8584e15b2e996e26f16aee6d757e387bc17d/asyncpg-0.30.0.tar.gz", hash = "sha256:c551e9928ab6707602f44811817f82ba3c446e018bfe1d3abecc8ba5f3eac851", size = 957746, upload-time = "2024-10-20T00:30:41.127Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3a/22/e20602e1218dc07692acf70d5b902be820168d6282e69ef0d3cb920dc36f/asyncpg-0.30.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05b185ebb8083c8568ea8a40e896d5f7af4b8554b64d7719c0eaa1eb5a5c3a70", size = 670373, upload-time = "2024-10-20T00:29:55.165Z" },
{ url = "https://files.pythonhosted.org/packages/3d/b3/0cf269a9d647852a95c06eb00b815d0b95a4eb4b55aa2d6ba680971733b9/asyncpg-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c47806b1a8cbb0a0db896f4cd34d89942effe353a5035c62734ab13b9f938da3", size = 634745, upload-time = "2024-10-20T00:29:57.14Z" },
{ url = "https://files.pythonhosted.org/packages/8e/6d/a4f31bf358ce8491d2a31bfe0d7bcf25269e80481e49de4d8616c4295a34/asyncpg-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b6fde867a74e8c76c71e2f64f80c64c0f3163e687f1763cfaf21633ec24ec33", size = 3512103, upload-time = "2024-10-20T00:29:58.499Z" },
{ url = "https://files.pythonhosted.org/packages/96/19/139227a6e67f407b9c386cb594d9628c6c78c9024f26df87c912fabd4368/asyncpg-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46973045b567972128a27d40001124fbc821c87a6cade040cfcd4fa8a30bcdc4", size = 3592471, upload-time = "2024-10-20T00:30:00.354Z" },
{ url = "https://files.pythonhosted.org/packages/67/e4/ab3ca38f628f53f0fd28d3ff20edff1c975dd1cb22482e0061916b4b9a74/asyncpg-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9110df111cabc2ed81aad2f35394a00cadf4f2e0635603db6ebbd0fc896f46a4", size = 3496253, upload-time = "2024-10-20T00:30:02.794Z" },
{ url = "https://files.pythonhosted.org/packages/ef/5f/0bf65511d4eeac3a1f41c54034a492515a707c6edbc642174ae79034d3ba/asyncpg-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04ff0785ae7eed6cc138e73fc67b8e51d54ee7a3ce9b63666ce55a0bf095f7ba", size = 3662720, upload-time = "2024-10-20T00:30:04.501Z" },
{ url = "https://files.pythonhosted.org/packages/e7/31/1513d5a6412b98052c3ed9158d783b1e09d0910f51fbe0e05f56cc370bc4/asyncpg-0.30.0-cp313-cp313-win32.whl", hash = "sha256:ae374585f51c2b444510cdf3595b97ece4f233fde739aa14b50e0d64e8a7a590", size = 560404, upload-time = "2024-10-20T00:30:06.537Z" },
{ url = "https://files.pythonhosted.org/packages/c8/a4/cec76b3389c4c5ff66301cd100fe88c318563ec8a520e0b2e792b5b84972/asyncpg-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:f59b430b8e27557c3fb9869222559f7417ced18688375825f8f12302c34e915e", size = 621623, upload-time = "2024-10-20T00:30:09.024Z" },
]
[[package]]
name = "click"
version = "8.2.1"
@ -50,6 +66,7 @@ name = "f1tness-parser"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "asyncpg" },
{ name = "fastapi" },
{ name = "jinja2" },
{ name = "pydantic" },
@ -70,6 +87,7 @@ dev = [
[package.metadata]
requires-dist = [
{ name = "asyncpg", specifier = ">=0.30.0" },
{ name = "fastapi", specifier = ">=0.115.0" },
{ name = "jinja2", specifier = ">=3.1.0" },
{ name = "pydantic", specifier = ">=2.11.7" },