Changed dependency management to poetry

This commit is contained in:
pptx704
2025-06-24 06:55:47 +03:00
parent 52aa93d67c
commit 90ae2ea8e4
17 changed files with 2034 additions and 54 deletions

101
.dockerignore Normal file
View File

@ -0,0 +1,101 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# Virtual environments
venv/
env/
ENV/
.venv/
.env/
# Poetry
poetry.lock
# Testing
.pytest_cache/
.coverage
htmlcov/
.tox/
.nox/
coverage.xml
*.cover
.hypothesis/
# IDEs
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Git
.git/
.gitignore
# Docker
Dockerfile
docker-compose.yml
.dockerignore
# Database
*.db
*.sqlite
*.sqlite3
# Logs
*.log
logs/
# Environment files
.env
.env.local
.env.development
.env.test
.env.production
# Documentation
docs/
# Temporary files
*.tmp
*.temp
.cache/
# Node.js (if any frontend assets)
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Local development
docker-data/
test.db

View File

@ -4,4 +4,6 @@ POSTGRES_DB=
JWT_SECRET=
JWT_ALGORITHM=
JWT_EXPIRE_MINUTES=
JWT_EXPIRE_MINUTES=
SQLALCHEMY_DATABASE_URL=

1
.gitignore vendored
View File

@ -174,3 +174,4 @@ cython_debug/
# PyPI configuration file
.pypirc
docker-data/

27
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,27 @@
repos:
- repo: local
hooks:
- id: isort
name: isort
entry: poetry run isort app main.py
language: system
types: [python]
args: ["--profile", "black"]
pass_filenames: false
- id: black
name: black
entry: poetry run black app main.py
language: system
types: [python]
args: ["--line-length", "80"]
pass_filenames: false
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: check-merge-conflict

View File

@ -1,17 +1,27 @@
FROM python:3.14.0a3-slim
FROM python:3.12-slim
# Turns off buffering for easier container logging
ENV PYTHONUNBUFFERED=1
COPY --from=ghcr.io/ufoscout/docker-compose-wait:latest /wait /wait
# Install pip requirements
COPY requirements.txt .
RUN pip install --no-cache-dir --upgrade -r requirements.txt
WORKDIR /app
WORKDIR /
COPY . /
# Install poetry
RUN pip install poetry
# Configure poetry to not create virtual environment in container
RUN poetry config virtualenvs.create false
# Copy poetry files
COPY pyproject.toml poetry.lock* /app/
# Install dependencies
RUN poetry install --only main --no-root --no-directory
COPY . /app
RUN poetry install --only main
EXPOSE 8000
CMD /wait; alembic upgrade head; uvicorn main:app --host 0.0.0.0
CMD /wait; poetry run alembic upgrade head; poetry run fastapi run

View File

@ -21,3 +21,36 @@ Template repository for all FastAPI backend projects developed by Omukk.
git clone git@git.omukk.dev/<username>/<repo>.git
cd <repo>
```
## Development and Testing
1. **Install dependencies**:
```
poetry install
poetry run pre-commit install
```
2. **Run database server**:
```bash
docker compose up db -d
```
3. **Run Dev Server**:
```bash
poetry run fastapi dev
```
4. **Stop Database Server**:
```bash
docker compose down db
```
## Development Rules
- Create a separate branch from `dev` and create a PR to `dev` after making changes
- Branch names must be meaningful. Check [docs](https://docs.omukk.dev/doc/repos-bvFEDvytPz) for more details
- Always run `black` and `isort` to maintain code consistency (this is done automatically using pre-commit hooks)-
```bash
poetry run isort app main.py
poetry run black app main.py
# Make sure to run isort first
```
- Use static type checking using `mypy` if you feel like it (It is recommended but not mandatory). Static type checking might help you to identify critical bugs.

View File

@ -5,13 +5,13 @@ from sqlalchemy import pool
from alembic import context
from app.const import SQLALCHEMY_DATABASE_URL
from app.settings import settings
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
config.set_main_option("sqlalchemy.url", SQLALCHEMY_DATABASE_URL)
config.set_main_option("sqlalchemy.url", settings.SQLALCHEMY_DATABASE_URL)
# Interpret the config file for Python logging.
# This line sets up loggers basically.
if config.config_file_name is not None:

View File

@ -4,18 +4,16 @@ from sqlalchemy.orm import sessionmaker
from .const import SQLALCHEMY_DATABASE_URL
engine = create_engine(
SQLALCHEMY_DATABASE_URL
)
engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
db.close()

View File

@ -1,7 +1,17 @@
from app.database import Base
from sqlalchemy.sql import func
import uuid
from sqlalchemy import (
Boolean,
Column,
DateTime,
ForeignKey,
Integer,
PrimaryKeyConstraint,
String,
)
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy import Column, Integer, String, DateTime, Boolean, ForeignKey, PrimaryKeyConstraint
from sqlalchemy.orm import relationship
from sqlalchemy.schema import Index
from sqlalchemy.sql import func
from app.database import Base

View File

@ -3,4 +3,4 @@ from sqlalchemy.orm import Session
from .. import schemas
from ..models import User
from ..security import get_password_hash, verify_password, create_jwt_token
from ..security import create_jwt_token, get_password_hash, verify_password

View File

@ -1,14 +1,11 @@
from app import database, schemas
from fastapi import APIRouter, Depends, status
from sqlalchemy.orm import Session
from ..repositories import auth
from app import database, schemas
from .. import schemas
from ..security import get_user
from ..database import get_db
from ..repositories import auth
from ..security import get_user
router = APIRouter(
prefix = "",
tags = ['auth']
)
router = APIRouter(prefix="", tags=["auth"])

View File

@ -1,12 +1,15 @@
from typing import List, Optional
from pydantic import BaseModel, EmailStr
import uuid
import enum
import datetime
import enum
import uuid
from typing import List, Optional
from pydantic import BaseModel, EmailStr
class BaseResponse(BaseModel):
message: str
class User(BaseModel):
id: uuid.UUID

View File

@ -1,25 +1,26 @@
import os
from passlib.context import CryptContext
from datetime import UTC, datetime, timedelta
import jwt
from datetime import datetime, timedelta, UTC
from bcrypt import checkpw, gensalt, hashpw
from fastapi import HTTPException, Security
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from app import schemas
from app.database import SessionLocal
from app.models import User
from app.settings import settings
from fastapi import Security, HTTPException
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from .database import get_db
from .models import User
from .settings import settings
from . import schemas
pwd_context = CryptContext(schemes=["bcrypt"])
def get_password_hash(password: str) -> str:
return pwd_context.hash(password)
return hashpw(password.encode("utf-8"), gensalt()).decode("utf-8")
def verify_password(plain_password: str, hashed_password: str) -> bool:
return pwd_context.verify(plain_password, hashed_password)
return checkpw(
plain_password.encode("utf-8"), hashed_password.encode("utf-8")
)
def create_jwt_token(data: dict) -> str:
_ed = timedelta(minutes=settings.JWT_EXPIRE_MINUTES)
@ -28,25 +29,38 @@ def create_jwt_token(data: dict) -> str:
token_payload = data
token_payload.update({"iat": iat, "exp": exp})
token = jwt.encode(token_payload, settings.JWT_SECRET, algorithm=settings.JWT_ALGORITHM)
token = jwt.encode(
token_payload, settings.JWT_SECRET, algorithm=settings.JWT_ALGORITHM
)
return token
def get_user_from_token(token: str) -> schemas.User:
try:
payload = jwt.decode(token, settings.JWT_SECRET, algorithms=[settings.JWT_ALGORITHM])
payload = jwt.decode(
token, settings.JWT_SECRET, algorithms=[settings.JWT_ALGORITHM]
)
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=401, detail="Token has expired")
except jwt.JWTError:
raise HTTPException(status_code=401, detail="Invalid authentication credentials")
raise HTTPException(
status_code=401, detail="Invalid authentication credentials"
)
user_id = payload.get("user_id")
# Return user from database
...
def get_user(authorization: HTTPAuthorizationCredentials = Security(HTTPBearer())) -> schemas.User:
def get_user(
authorization: HTTPAuthorizationCredentials = Security(HTTPBearer()),
) -> schemas.User:
if authorization.scheme.lower() != "bearer":
raise HTTPException(status_code=401, detail="Invalid authentication scheme")
raise HTTPException(
status_code=401, detail="Invalid authentication scheme"
)
token = authorization.credentials
return get_user_from_token(token)

View File

@ -1,13 +1,16 @@
import os
from pydantic_settings import BaseSettings
from dotenv import load_dotenv
from pydantic_settings import BaseSettings
load_dotenv()
class Settings(BaseSettings):
JWT_SECRET: str
JWT_ALGORITHM: str
JWT_EXPIRE_MINUTES: int
SQLALCHEMY_DATABASE_URL: str
settings = Settings()

View File

@ -9,7 +9,7 @@ app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"]
allow_headers=["*"],
)
@ -17,4 +17,5 @@ app.add_middleware(
async def index() -> str:
return "Version 0.0.1"
app.include_router(auth.router)

1741
poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

39
pyproject.toml Normal file
View File

@ -0,0 +1,39 @@
[project]
name = "app"
version = "0.1.0"
description = "Fastapi Poetry Template"
authors = [
{name = "pptx704"},
]
readme = "README.md"
requires-python = "^3.12"
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.dependencies]
bcrypt = "^4.3.0"
fastapi = {extras = ["standard"], version = "^0.115.13"}
pydantic-settings = "^2.10.0"
sqlalchemy = "^2.0.41"
pyjwt = "^2.10.1"
alembic = "^1.16.2"
python-dotenv = "^1.1.0"
psycopg2-binary = "^2.9.10"
[tool.poetry.group.dev.dependencies]
pytest = "^8.4.1"
pytest-asyncio = "^1.0.0"
httpx = "^0.28.1"
black = "^25.1.0"
isort = "^6.0.1"
pre-commit = "^4.2.0"
[tool.black]
line-length = 80
[tool.isort]
profile = "black"
line_length = 80