Fist CONDFIGURATIONS

This commit is contained in:
Hossein 2025-08-07 00:16:18 +03:30
commit 991073ce1b
12 changed files with 961 additions and 0 deletions

59
.dockerignore Normal file
View File

@ -0,0 +1,59 @@
# Git
.git
.gitignore
# Python
__pycache__
*.pyc
*.pyo
*.pyd
.Python
env
pip-log.txt
pip-delete-this-directory.txt
.tox
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.log
.git
.mypy_cache
.pytest_cache
.hypothesis
# Virtual environments
venv/
env/
ENV/
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Docker
Dockerfile
docker-compose.yml
.dockerignore
# Environment files
.env
.env.local
.env.production
.env.staging
# Documentation
README.md
*.md
# Logs
logs/
*.log

155
.gitignore vendored Normal file
View File

@ -0,0 +1,155 @@
# Environment variables
.env
.env.local
.env.production
.env.staging
.env.development
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
Pipfile.lock
# PEP 582
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Database
*.db
*.sqlite
*.sqlite3
# Logs
logs/
*.log
# Alembic
alembic/versions/*.py
!alembic/versions/__init__.py

36
Dockerfile Normal file
View File

@ -0,0 +1,36 @@
# Use Python 3.11 slim image as base
FROM python:3.11-slim
# Set working directory
WORKDIR /app
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
# Install system dependencies
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
gcc \
g++ \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements first for better caching
COPY requirements.txt .
# Install Python dependencies
RUN pip install --no-cache-dir --upgrade pip \
&& pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
# Expose port
EXPOSE 8000
# Health check
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
# Run the application
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]

258
README.md Normal file
View File

@ -0,0 +1,258 @@
# FastAPI ISS Project
A FastAPI application with Docker and docker-compose support.
## Features
- FastAPI REST API with CRUD operations
- PostgreSQL database integration
- SQLAlchemy ORM with Alembic migrations
- Docker containerization
- Docker Compose for easy deployment
- Health check endpoints
- CORS middleware enabled
- Auto-generated API documentation
## API Endpoints
- `GET /` - Welcome message
- `GET /health` - Health check
- `GET /items` - Get all items
- `GET /items/{item_id}` - Get specific item
- `POST /items` - Create new item
- `PUT /items/{item_id}` - Update item
- `DELETE /items/{item_id}` - Delete item
## Quick Start with Docker Compose
1. **Set up environment variables:**
```bash
# Copy the example environment file
cp env.example .env
# Edit the .env file with your secure passwords
# nano .env # or use your preferred editor
```
2. **Build and start the application:**
```bash
docker-compose up --build
```
3. **Access the application:**
- API: http://localhost:8000
- Interactive API docs: http://localhost:8000/docs
- ReDoc documentation: http://localhost:8000/redoc
4. **Stop the application:**
```bash
docker-compose down
```
## Development
### Running locally without Docker
1. **Install dependencies:**
```bash
pip install -r requirements.txt
```
2. **Run the application:**
```bash
uvicorn main:app --reload --host 0.0.0.0 --port 8000
```
### Docker Commands
**Build the image:**
```bash
docker build -t fastapi-iss .
```
**Run the container:**
```bash
docker run -p 8000:8000 fastapi-iss
```
**Run in detached mode:**
```bash
docker run -d -p 8000:8000 --name fastapi-app fastapi-iss
```
## API Examples
### Create an item
```bash
curl -X POST "http://localhost:8000/items" \
-H "Content-Type: application/json" \
-d '{"name": "Sample Item", "description": "A sample item", "price": 29.99}'
```
### Get all items
```bash
curl "http://localhost:8000/items"
```
### Get specific item
```bash
curl "http://localhost:8000/items/1"
```
### Update item
```bash
curl -X PUT "http://localhost:8000/items/1" \
-H "Content-Type: application/json" \
-d '{"name": "Updated Item", "description": "Updated description", "price": 39.99}'
```
### Delete item
```bash
curl -X DELETE "http://localhost:8000/items/1"
```
### Database Connection
You can connect to the PostgreSQL database directly:
```bash
# Using psql (if installed)
psql -h localhost -p ${POSTGRES_PORT} -U ${POSTGRES_USER} -d ${POSTGRES_DB}
# Using Docker
docker exec -it fastapi-iss-postgres-1 psql -U ${POSTGRES_USER} -d ${POSTGRES_DB}
```
## Project Structure
```
FastApi-ISS/
├── main.py # FastAPI application
├── database.py # Database configuration and models
├── requirements.txt # Python dependencies
├── Dockerfile # Docker configuration
├── docker-compose.yml # Docker Compose configuration
├── env.example # Example environment variables
├── .env # Environment variables (create from env.example)
├── alembic.ini # Alembic configuration
├── alembic/ # Database migrations
│ ├── env.py # Alembic environment
│ └── script.py.mako # Migration template
├── .dockerignore # Docker ignore file
├── .gitignore # Git ignore file
└── README.md # This file
```
## Environment Variables
The application uses environment variables for configuration. Copy `env.example` to `.env` and customize the values:
### Database Configuration
- `POSTGRES_DB`: Database name (default: `fastapi_db`)
- `POSTGRES_USER`: Database user (default: `fastapi_user`)
- `POSTGRES_PASSWORD`: Database password (**CHANGE THIS**)
- `POSTGRES_HOST`: Database host (default: `postgres`)
- `POSTGRES_PORT`: Database port (default: `5432`)
- `DATABASE_URL`: Full database connection string (auto-generated from above)
### Application Configuration
- `PYTHONPATH`: Python path (default: `/app`)
- `PYTHONUNBUFFERED`: Python output buffering (default: `1`)
- `FASTAPI_TITLE`: Application title
- `FASTAPI_DESCRIPTION`: Application description
- `FASTAPI_VERSION`: Application version
### Security Configuration
- `SECRET_KEY`: Secret key for JWT tokens (**CHANGE THIS**)
- `ALGORITHM`: JWT algorithm (default: `HS256`)
- `ACCESS_TOKEN_EXPIRE_MINUTES`: Token expiration time
### CORS Configuration
- `ALLOWED_ORIGINS`: Allowed origins for CORS
- `ALLOWED_CREDENTIALS`: Allow credentials in CORS
- `ALLOWED_METHODS`: Allowed HTTP methods
- `ALLOWED_HEADERS`: Allowed HTTP headers
### Logging
- `LOG_LEVEL`: Logging level (default: `INFO`)
## Database
The application uses PostgreSQL with the following configuration (configurable via environment variables):
- **Database**: `fastapi_db` (configurable via `POSTGRES_DB`)
- **User**: `fastapi_user` (configurable via `POSTGRES_USER`)
- **Password**: Configurable via `POSTGRES_PASSWORD` (change this!)
- **Port**: `5432` (configurable via `POSTGRES_PORT`)
### Database Migrations
To run database migrations:
```bash
# Generate a new migration
alembic revision --autogenerate -m "Description of changes"
# Apply migrations
alembic upgrade head
# Rollback migrations
alembic downgrade -1
```
## Health Checks
The application includes health checks that can be accessed at:
- Docker health check: `http://localhost:8000/health`
- Docker Compose health check: Configured in docker-compose.yml
## Security
### Environment Variables
- **Never commit `.env` files** to version control
- Use `env.example` as a template for your `.env` file
- Change all default passwords and secret keys
- Use strong, unique passwords for production
### Production Deployment
- Change all default credentials
- Use strong secret keys
- Configure proper CORS settings
- Enable HTTPS
- Use environment-specific `.env` files
### Database Security
- Use strong database passwords
- Limit database access to necessary users only
- Regularly update dependencies
- Monitor database logs
## Troubleshooting
1. **Port already in use:**
- Change the port in docker-compose.yml or use a different port
- Kill existing processes using the port
2. **Docker build fails:**
- Ensure Docker is running
- Clear Docker cache: `docker system prune -a`
3. **Application not starting:**
- Check logs: `docker-compose logs`
- Verify all files are present in the project directory
4. **Database connection issues:**
- Verify `.env` file exists and has correct values
- Check if PostgreSQL container is running: `docker-compose ps`
- Check database logs: `docker-compose logs postgres`
## Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Test with Docker Compose
5. Submit a pull request
## License
This project is open source and available under the MIT License.

108
alembic.ini Normal file
View File

@ -0,0 +1,108 @@
# A generic, single database configuration.
[alembic]
# path to migration scripts
script_location = alembic
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
# Uncomment the line below if you want the files to be prepended with date and time
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
# sys.path path, will be prepended to sys.path if present.
# defaults to the current working directory.
prepend_sys_path = .
# timezone to use when rendering the date within the migration file
# as well as the filename.
# If specified, requires the python-dateutil library that can be
# installed by adding `alembic[tz]` to the pip requirements
# string value is passed to dateutil.tz.gettz()
# leave blank for localtime
# timezone =
# max length of characters to apply to the
# "slug" field
# truncate_slug_length = 40
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false
# version number format
version_num_format = %04d
# version path separator; As mentioned above, this is the character used to split
# version_locations. The default within new alembic.ini files is "os", which uses
# os.pathsep. If this key is omitted entirely, it falls back to the legacy
# behavior of splitting on spaces and/or commas.
# Valid values for version_path_separator are:
#
# version_path_separator = :
# version_path_separator = ;
# version_path_separator = space
version_path_separator = os
# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8
# sqlalchemy.url will be set from environment variable in env.py
sqlalchemy.url =
[post_write_hooks]
# post_write_hooks defines scripts or Python functions that are run
# on newly generated revision scripts. See the documentation for further
# detail and examples
# format using "black" - use the console_scripts runner, against the "black" entrypoint
# hooks = black
# black.type = console_scripts
# black.entrypoint = black
# black.options = -l 79 REVISION_SCRIPT_FILENAME
# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
# hooks = ruff
# ruff.type = exec
# ruff.executable = %(here)s/.venv/bin/ruff
# ruff.options = --fix REVISION_SCRIPT_FILENAME
# Logging configuration
[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
datefmt = %H:%M:%S

85
alembic/env.py Normal file
View File

@ -0,0 +1,85 @@
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
import os
import sys
# Add the parent directory to the Python path
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from database import Base, DATABASE_URL
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
if config.config_file_name is not None:
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
target_metadata = Base.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def get_url():
return DATABASE_URL
def run_migrations_offline() -> None:
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = get_url()
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online() -> None:
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
configuration = config.get_section(config.config_ini_section)
configuration["sqlalchemy.url"] = get_url()
connectable = engine_from_config(
configuration,
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

24
alembic/script.py.mako Normal file
View File

@ -0,0 +1,24 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade() -> None:
${upgrades if upgrades else "pass"}
def downgrade() -> None:
${downgrades if downgrades else "pass"}

48
database.py Normal file
View File

@ -0,0 +1,48 @@
from sqlalchemy import create_engine, Column, Integer, String, Float, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy.sql import func
import os
# Database URL from environment variable
DATABASE_URL = os.getenv("DATABASE_URL")
if not DATABASE_URL:
# Fallback to constructing from individual variables
POSTGRES_USER = os.getenv("POSTGRES_USER", "fastapi_user")
POSTGRES_PASSWORD = os.getenv("POSTGRES_PASSWORD", "fastapi_password")
POSTGRES_HOST = os.getenv("POSTGRES_HOST", "postgres")
POSTGRES_PORT = os.getenv("POSTGRES_PORT", "5432")
POSTGRES_DB = os.getenv("POSTGRES_DB", "fastapi_db")
DATABASE_URL = f"postgresql://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_HOST}:{POSTGRES_PORT}/{POSTGRES_DB}"
# Create SQLAlchemy engine
engine = create_engine(DATABASE_URL)
# Create SessionLocal class
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# Create Base class
Base = declarative_base()
# Database model
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, nullable=False)
description = Column(String, nullable=True)
price = Column(Float, nullable=False)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
# Dependency to get database session
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# Create tables
def create_tables():
Base.metadata.create_all(bind=engine)

53
docker-compose.yml Normal file
View File

@ -0,0 +1,53 @@
version: '3.8'
services:
fastapi-app:
build: .
ports:
- "8000:8000"
volumes:
- .:/app
env_file:
- .env
environment:
- PYTHONPATH=/app
- DATABASE_URL=${DATABASE_URL}
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
- fastapi-network
postgres:
image: postgres:15
env_file:
- .env
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
ports:
- "${POSTGRES_PORT}:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
networks:
- fastapi-network
networks:
fastapi-network:
driver: bridge
volumes:
postgres_data:

32
env.example Normal file
View File

@ -0,0 +1,32 @@
# Database Configuration
POSTGRES_DB=fastapi_db
POSTGRES_USER=fastapi_user
POSTGRES_PASSWORD=your_secure_password_here
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
# Database URL (constructed from above variables)
DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
# FastAPI Configuration
FASTAPI_TITLE=FastAPI ISS Project
FASTAPI_DESCRIPTION=A FastAPI project with Docker and PostgreSQL support
FASTAPI_VERSION=1.0.0
# Application Configuration
PYTHONPATH=/app
PYTHONUNBUFFERED=1
# Security (for production, change these values)
SECRET_KEY=your_super_secret_key_here_change_in_production
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=30
# CORS Configuration
ALLOWED_ORIGINS=["*"]
ALLOWED_CREDENTIALS=true
ALLOWED_METHODS=["*"]
ALLOWED_HEADERS=["*"]
# Logging
LOG_LEVEL=INFO

96
main.py Normal file
View File

@ -0,0 +1,96 @@
from fastapi import FastAPI, HTTPException, Depends
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import List, Optional
from sqlalchemy.orm import Session
import uvicorn
import os
from database import get_db, create_tables, Item as ItemModel
app = FastAPI(
title=os.getenv("FASTAPI_TITLE", "FastAPI ISS Project"),
description=os.getenv("FASTAPI_DESCRIPTION", "A FastAPI project with Docker and PostgreSQL support"),
version=os.getenv("FASTAPI_VERSION", "1.0.0")
)
# Add CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=eval(os.getenv("ALLOWED_ORIGINS", '["*"]')),
allow_credentials=eval(os.getenv("ALLOWED_CREDENTIALS", "True")),
allow_methods=eval(os.getenv("ALLOWED_METHODS", '["*"]')),
allow_headers=eval(os.getenv("ALLOWED_HEADERS", '["*"]')),
)
# Pydantic models
class ItemBase(BaseModel):
name: str
description: Optional[str] = None
price: float
class ItemCreate(ItemBase):
pass
class Item(ItemBase):
id: int
class Config:
from_attributes = True
@app.on_event("startup")
async def startup_event():
create_tables()
@app.get("/")
async def root():
return {"message": "Welcome to FastAPI ISS Project!", "status": "running"}
@app.get("/health")
async def health_check():
return {"status": "healthy", "service": "FastAPI ISS"}
@app.get("/items", response_model=List[Item])
async def get_items(db: Session = Depends(get_db)):
items = db.query(ItemModel).all()
return items
@app.get("/items/{item_id}", response_model=Item)
async def get_item(item_id: int, db: Session = Depends(get_db)):
item = db.query(ItemModel).filter(ItemModel.id == item_id).first()
if item is None:
raise HTTPException(status_code=404, detail="Item not found")
return item
@app.post("/items", response_model=Item)
async def create_item(item: ItemCreate, db: Session = Depends(get_db)):
db_item = ItemModel(**item.dict())
db.add(db_item)
db.commit()
db.refresh(db_item)
return db_item
@app.put("/items/{item_id}", response_model=Item)
async def update_item(item_id: int, item: ItemCreate, db: Session = Depends(get_db)):
db_item = db.query(ItemModel).filter(ItemModel.id == item_id).first()
if db_item is None:
raise HTTPException(status_code=404, detail="Item not found")
for key, value in item.dict().items():
setattr(db_item, key, value)
db.commit()
db.refresh(db_item)
return db_item
@app.delete("/items/{item_id}")
async def delete_item(item_id: int, db: Session = Depends(get_db)):
db_item = db.query(ItemModel).filter(ItemModel.id == item_id).first()
if db_item is None:
raise HTTPException(status_code=404, detail="Item not found")
db.delete(db_item)
db.commit()
return {"message": "Item deleted successfully"}
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)

7
requirements.txt Normal file
View File

@ -0,0 +1,7 @@
fastapi==0.104.1
uvicorn[standard]==0.24.0
pydantic==2.5.0
python-multipart==0.0.6
sqlalchemy==2.0.23
psycopg2-binary==2.9.9
alembic==1.13.0