commit 991073ce1b837134491fa7ad886dc56cc4aa89f5 Author: Hossein Date: Thu Aug 7 00:16:18 2025 +0330 Fist CONDFIGURATIONS diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a3a4477 --- /dev/null +++ b/.dockerignore @@ -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 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4738816 --- /dev/null +++ b/.gitignore @@ -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 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a076288 --- /dev/null +++ b/Dockerfile @@ -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"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..bcd02d8 --- /dev/null +++ b/README.md @@ -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. \ No newline at end of file diff --git a/alembic.ini b/alembic.ini new file mode 100644 index 0000000..ee3fb9f --- /dev/null +++ b/alembic.ini @@ -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 \ No newline at end of file diff --git a/alembic/env.py b/alembic/env.py new file mode 100644 index 0000000..8da473e --- /dev/null +++ b/alembic/env.py @@ -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() \ No newline at end of file diff --git a/alembic/script.py.mako b/alembic/script.py.mako new file mode 100644 index 0000000..b5bd516 --- /dev/null +++ b/alembic/script.py.mako @@ -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"} \ No newline at end of file diff --git a/database.py b/database.py new file mode 100644 index 0000000..b5e2e8c --- /dev/null +++ b/database.py @@ -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) \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2cd609a --- /dev/null +++ b/docker-compose.yml @@ -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: \ No newline at end of file diff --git a/env.example b/env.example new file mode 100644 index 0000000..494106d --- /dev/null +++ b/env.example @@ -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 \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..0cceb22 --- /dev/null +++ b/main.py @@ -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) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1295531 --- /dev/null +++ b/requirements.txt @@ -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 \ No newline at end of file