init
This commit is contained in:
commit
c88e74b029
1
.env.example
Normal file
1
.env.example
Normal file
@ -0,0 +1 @@
|
||||
DATABASE_URL=sqlite:///./data/app.db
|
||||
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.db
|
||||
.env
|
||||
.DS_Store
|
||||
alembic/env.py
|
||||
10
Dockerfile
Normal file
10
Dockerfile
Normal 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
40
alembic.ini
Normal 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
0
app/__init__.py
Normal file
5
app/core/config.py
Normal file
5
app/core/config.py
Normal 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
14
app/database/session.py
Normal 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
12
app/main.py
Normal 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
0
app/models/__init__.py
Normal file
9
app/models/user.py
Normal file
9
app/models/user.py
Normal 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
1
data/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
!.gitignore
|
||||
15
docker-compose.yml
Normal file
15
docker-compose.yml
Normal 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
8
requirements.txt
Normal 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
1
routes/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from . import web
|
||||
31
routes/web.py
Normal file
31
routes/web.py
Normal 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
47
static/style.css
Normal 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
27
templates/index.html
Normal 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
9
tests/test_web.py
Normal 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
|
||||
Loading…
x
Reference in New Issue
Block a user