This commit is contained in:
Toy Rik 2025-12-18 09:07:06 +03:00
commit c88e74b029
18 changed files with 236 additions and 0 deletions

1
.env.example Normal file
View File

@ -0,0 +1 @@
DATABASE_URL=sqlite:///./data/app.db

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
__pycache__/
*.pyc
*.db
.env
.DS_Store
alembic/env.py

10
Dockerfile Normal file
View File

@ -0,0 +1,10 @@
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

40
alembic.ini Normal file
View File

@ -0,0 +1,40 @@
[alembic]
script_location = alembic
prepend_sys_path = .
sqlalchemy.url = sqlite:///./data/app.db
[post_write_hooks]
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
class = logging.Formatter

0
app/__init__.py Normal file
View File

5
app/core/config.py Normal file
View File

@ -0,0 +1,5 @@
import os
from dotenv import load_dotenv
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./data/app.db")

14
app/database/session.py Normal file
View File

@ -0,0 +1,14 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from app.core.config import DATABASE_URL
connect_args = {"check_same_thread": False} if "sqlite" in DATABASE_URL else {}
engine = create_engine(DATABASE_URL, connect_args=connect_args)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()

12
app/main.py Normal file
View File

@ -0,0 +1,12 @@
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from app.database.session import engine
from app.models.user import Base
from routes import web
# Создаём таблицы (для демо; в продакшене — Alembic)
Base.metadata.create_all(bind=engine)
app = FastAPI(title="FastAPI + SQLite Boilerplate")
app.mount("/static", StaticFiles(directory="static"), name="static")
app.include_router(web.router)

0
app/models/__init__.py Normal file
View File

9
app/models/user.py Normal file
View File

@ -0,0 +1,9 @@
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)

1
data/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
!.gitignore

15
docker-compose.yml Normal file
View File

@ -0,0 +1,15 @@
services:
web:
build: .
ports:
- "8000:8000"
env_file:
- .env
volumes:
- ./app:/app/app
- ./templates:/app/templates
- ./static:/app/static
- ./data:/app/data
environment:
- DATABASE_URL=sqlite:///./data/app.db
restart: unless-stopped

8
requirements.txt Normal file
View File

@ -0,0 +1,8 @@
fastapi==0.115.0
uvicorn[standard]==0.32.0
sqlalchemy==2.0.36
alembic==1.13.0
jinja2==3.1.4
python-dotenv==1.0.1
pytest==8.3.0
httpx==0.27.0

1
routes/__init__.py Normal file
View File

@ -0,0 +1 @@
from . import web

31
routes/web.py Normal file
View File

@ -0,0 +1,31 @@
# routes/web.py
from fastapi import APIRouter, Request
from fastapi.templating import Jinja2Templates
from sqlalchemy import text
from app.database.session import engine
router = APIRouter()
templates = Jinja2Templates(directory="templates")
@router.get("/")
def home(request: Request):
db_info = {"status": "Не удалось подключиться", "url": ""}
try:
# Пробуем выполнить простой запрос
with engine.connect() as conn:
result = conn.execute(text("SELECT 1"))
if result.fetchone():
db_info["status"] = "✅ Подключено"
db_info["url"] = str(engine.url)
except Exception as e:
db_info["status"] = f"❌ Ошибка: {str(e)[:60]}..." # Обрезаем длинные ошибки
return templates.TemplateResponse(
"index.html",
{
"request": request,
"title": "FastAPI + SQLite",
"db_info": db_info
}
)

47
static/style.css Normal file
View File

@ -0,0 +1,47 @@
/* static/style.css */
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: #f9fafb;
margin: 0;
padding: 2rem;
display: flex;
justify-content: center;
}
.container {
background: white;
padding: 2.5rem;
border-radius: 16px;
box-shadow: 0 10px 25px rgba(0,0,0,0.08);
max-width: 700px;
width: 100%;
}
h1 {
color: #1e40af;
text-align: center;
margin-top: 0;
}
h2 {
color: #374151;
border-bottom: 1px solid #e5e7eb;
padding-bottom: 0.5rem;
margin-top: 2rem;
}
.db-status {
background: #f0f9ff;
padding: 1.2rem;
border-radius: 10px;
margin: 1.5rem 0;
border-left: 4px solid #3b82f6;
}
.info {
margin-top: 2rem;
text-align: center;
color: #6b7280;
}
code {
background: #f3f4f6;
padding: 0.2rem 0.4rem;
border-radius: 4px;
font-family: monospace;
font-size: 0.9em;
}

27
templates/index.html Normal file
View File

@ -0,0 +1,27 @@
<!-- templates/index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{ title }}</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<div class="container">
<h1>🧱 FastAPI + SQLite</h1>
<div class="db-status">
<h2>🗄 Состояние базы данных</h2>
<p><strong>Статус:</strong> {{ db_info.status | safe }}</p>
{% if db_info.url %}
<p><strong>База:</strong> <code>{{ db_info.url }}</code></p>
{% endif %}
</div>
<div class="info">
<p>📁 Архитектура: всё по папкам (как в Laravel)</p>
<p>🐳 Запущено в Docker без зависимостей</p>
</div>
</div>
</body>
</html>

9
tests/test_web.py Normal file
View File

@ -0,0 +1,9 @@
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_home_page():
response = client.get("/")
assert response.status_code == 200
assert "FastAPI + SQLite" in response.text