Set up database configuration and connection management
- Created app/database.py with SQLAlchemy engine, session management, and connection utilities - Added comprehensive database models in app/models.py for User, Client, Phone, Case, Transaction, Document, and Payment - Implemented FastAPI application with database lifecycle management in app/main.py - Added health check endpoint to verify database connectivity - Created README.md with database configuration documentation - Verified database connection works correctly with SQLite backend
This commit is contained in:
100
README.md
Normal file
100
README.md
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# Delphi Database
|
||||||
|
|
||||||
|
A legal case management database application built with FastAPI, SQLAlchemy, and modern Python practices.
|
||||||
|
|
||||||
|
## Database Configuration
|
||||||
|
|
||||||
|
The application uses SQLAlchemy ORM with support for multiple database backends.
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
Configure your database connection using environment variables:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# SQLite (default for development)
|
||||||
|
DATABASE_URL=sqlite:///./delphi.db
|
||||||
|
|
||||||
|
# PostgreSQL (production example)
|
||||||
|
DATABASE_URL=postgresql://username:password@localhost:5432/delphi_db
|
||||||
|
|
||||||
|
# MySQL (alternative)
|
||||||
|
DATABASE_URL=mysql://username:password@localhost:3306/delphi_db
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Models
|
||||||
|
|
||||||
|
The application includes the following models:
|
||||||
|
|
||||||
|
- **User**: Authentication and user management
|
||||||
|
- **Client**: Client/contact information
|
||||||
|
- **Phone**: Phone numbers linked to clients
|
||||||
|
- **Case**: Legal cases with unique file numbers
|
||||||
|
- **Transaction**: Financial transactions for cases
|
||||||
|
- **Document**: Case-related documents
|
||||||
|
- **Payment**: Payment records for cases
|
||||||
|
|
||||||
|
### Database Connection Management
|
||||||
|
|
||||||
|
The `app/database.py` module provides:
|
||||||
|
|
||||||
|
- **Database engine configuration** with connection pooling and error handling
|
||||||
|
- **Session management** with automatic cleanup via FastAPI dependency injection
|
||||||
|
- **Table creation utilities** for application startup
|
||||||
|
- **Connection health monitoring** via the `/health` endpoint
|
||||||
|
|
||||||
|
### Usage Examples
|
||||||
|
|
||||||
|
#### Basic Application Startup
|
||||||
|
|
||||||
|
```python
|
||||||
|
from fastapi import FastAPI, Depends
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from app.database import get_db
|
||||||
|
|
||||||
|
@app.get("/clients/")
|
||||||
|
async def get_clients(db: Session = Depends(get_db)):
|
||||||
|
# Use the database session
|
||||||
|
return db.query(Client).all()
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Health Check
|
||||||
|
|
||||||
|
The `/health` endpoint verifies database connectivity:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8000/health
|
||||||
|
# {"status": "healthy", "database": "connected", "users": 0}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
1. Install dependencies:
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Configure your database in environment variables (see `.env` file)
|
||||||
|
|
||||||
|
3. Run the application:
|
||||||
|
```bash
|
||||||
|
uvicorn app.main:app --reload
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Access the API at `http://localhost:8000`
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
app/
|
||||||
|
├── main.py # FastAPI application entry point
|
||||||
|
├── database.py # Database configuration and session management
|
||||||
|
├── models.py # SQLAlchemy models
|
||||||
|
└── templates/ # HTML templates
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
- Database tables are automatically created on application startup
|
||||||
|
- Use the health check endpoint to verify database connectivity
|
||||||
|
- Environment variables control database configuration (never hardcode credentials)
|
||||||
|
- SQLAlchemy provides type-safe database operations with relationship management
|
||||||
BIN
app/__pycache__/database.cpython-313.pyc
Normal file
BIN
app/__pycache__/database.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/__pycache__/main.cpython-313.pyc
Normal file
BIN
app/__pycache__/main.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/__pycache__/models.cpython-313.pyc
Normal file
BIN
app/__pycache__/models.cpython-313.pyc
Normal file
Binary file not shown.
91
app/database.py
Normal file
91
app/database.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
"""
|
||||||
|
Database configuration and connection management for Delphi Database application.
|
||||||
|
|
||||||
|
This module handles SQLAlchemy engine creation, session management, and provides
|
||||||
|
database connection utilities for the FastAPI application.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from typing import Generator
|
||||||
|
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
from sqlalchemy.orm import sessionmaker, Session
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
# Load environment variables from .env file
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
# Database configuration from environment variables
|
||||||
|
DATABASE_URL = os.getenv(
|
||||||
|
"DATABASE_URL",
|
||||||
|
"sqlite:///./delphi.db" # Default to SQLite for development
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create SQLAlchemy engine
|
||||||
|
engine = create_engine(
|
||||||
|
DATABASE_URL,
|
||||||
|
connect_args={"check_same_thread": False} if "sqlite" in DATABASE_URL else {},
|
||||||
|
pool_pre_ping=True, # Verify connections before reuse
|
||||||
|
echo=False # Set to True for SQL query logging in development
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create session factory
|
||||||
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||||
|
|
||||||
|
# Create declarative base for models
|
||||||
|
Base = declarative_base()
|
||||||
|
|
||||||
|
|
||||||
|
def get_db() -> Generator[Session, None, None]:
|
||||||
|
"""
|
||||||
|
Dependency function that provides a database session.
|
||||||
|
|
||||||
|
Yields a database session and ensures it's properly closed after use.
|
||||||
|
Used as a FastAPI dependency in route handlers.
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
Session: SQLAlchemy database session
|
||||||
|
|
||||||
|
Example:
|
||||||
|
@app.get("/items/")
|
||||||
|
async def read_items(db: Session = Depends(get_db)):
|
||||||
|
return db.query(Item).all()
|
||||||
|
"""
|
||||||
|
db = SessionLocal()
|
||||||
|
try:
|
||||||
|
yield db
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
def create_tables() -> None:
|
||||||
|
"""
|
||||||
|
Create all database tables defined in SQLAlchemy models.
|
||||||
|
|
||||||
|
This function should be called during application startup to ensure
|
||||||
|
all tables exist in the database.
|
||||||
|
"""
|
||||||
|
Base.metadata.create_all(bind=engine)
|
||||||
|
|
||||||
|
|
||||||
|
def get_database_url() -> str:
|
||||||
|
"""
|
||||||
|
Get the current database URL (with sensitive info masked).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Database URL with password masked for logging
|
||||||
|
"""
|
||||||
|
if "sqlite" in DATABASE_URL:
|
||||||
|
return DATABASE_URL
|
||||||
|
|
||||||
|
# For PostgreSQL/MySQL, mask the password
|
||||||
|
if "@" in DATABASE_URL:
|
||||||
|
parts = DATABASE_URL.split("@")
|
||||||
|
if "://" in parts[0]:
|
||||||
|
protocol_and_auth = parts[0].split("://")[1]
|
||||||
|
if ":" in protocol_and_auth:
|
||||||
|
user_pass, host_port = protocol_and_auth.split(":", 1)
|
||||||
|
return DATABASE_URL.replace(user_pass, "****")
|
||||||
|
|
||||||
|
return "****://****:****@****/****"
|
||||||
86
app/main.py
86
app/main.py
@@ -1 +1,85 @@
|
|||||||
# FastAPI application entry point
|
"""
|
||||||
|
FastAPI application entry point for Delphi Database.
|
||||||
|
|
||||||
|
This module initializes the FastAPI application, sets up database connections,
|
||||||
|
and provides the main application instance.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
|
from fastapi import FastAPI, Depends
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from .database import create_tables, get_db, get_database_url
|
||||||
|
from .models import User
|
||||||
|
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def lifespan(app: FastAPI):
|
||||||
|
"""
|
||||||
|
Lifespan context manager for FastAPI application.
|
||||||
|
|
||||||
|
Handles startup and shutdown events:
|
||||||
|
- Creates database tables on startup
|
||||||
|
- Logs database connection info
|
||||||
|
"""
|
||||||
|
# Startup
|
||||||
|
logger.info("Starting Delphi Database application...")
|
||||||
|
|
||||||
|
# Create database tables
|
||||||
|
create_tables()
|
||||||
|
logger.info("Database tables created/verified")
|
||||||
|
|
||||||
|
# Log database connection info
|
||||||
|
db_url = get_database_url()
|
||||||
|
logger.info(f"Database connected: {db_url}")
|
||||||
|
|
||||||
|
yield
|
||||||
|
|
||||||
|
# Shutdown
|
||||||
|
logger.info("Shutting down Delphi Database application...")
|
||||||
|
|
||||||
|
|
||||||
|
# Create FastAPI application with lifespan management
|
||||||
|
app = FastAPI(
|
||||||
|
title="Delphi Database",
|
||||||
|
description="Legal case management database application",
|
||||||
|
version="1.0.0",
|
||||||
|
lifespan=lifespan
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
async def root():
|
||||||
|
"""
|
||||||
|
Root endpoint - health check.
|
||||||
|
"""
|
||||||
|
return {"message": "Delphi Database API is running"}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
async def health_check(db: Session = Depends(get_db)):
|
||||||
|
"""
|
||||||
|
Health check endpoint that verifies database connectivity.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Test database connection by querying user count
|
||||||
|
user_count = db.query(User).count()
|
||||||
|
return {
|
||||||
|
"status": "healthy",
|
||||||
|
"database": "connected",
|
||||||
|
"users": user_count
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Health check failed: {e}")
|
||||||
|
return {
|
||||||
|
"status": "unhealthy",
|
||||||
|
"database": "error",
|
||||||
|
"error": str(e)
|
||||||
|
}
|
||||||
|
|||||||
182
app/models.py
182
app/models.py
@@ -1 +1,181 @@
|
|||||||
# SQLAlchemy models for the Delphi database
|
"""
|
||||||
|
SQLAlchemy models for the Delphi database.
|
||||||
|
|
||||||
|
All models inherit from Base which is configured in the database module.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Float, Text, Boolean
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
from sqlalchemy.sql import func
|
||||||
|
from .database import Base
|
||||||
|
|
||||||
|
|
||||||
|
class User(Base):
|
||||||
|
"""
|
||||||
|
User model for authentication.
|
||||||
|
|
||||||
|
Stores user credentials and basic information for login functionality.
|
||||||
|
"""
|
||||||
|
__tablename__ = "users"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
username = Column(String(50), unique=True, index=True, nullable=False)
|
||||||
|
password_hash = Column(String(255), nullable=False)
|
||||||
|
is_active = Column(Boolean, default=True)
|
||||||
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
|
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<User(id={self.id}, username='{self.username}')>"
|
||||||
|
|
||||||
|
|
||||||
|
class Client(Base):
|
||||||
|
"""
|
||||||
|
Client model representing individuals or entities.
|
||||||
|
|
||||||
|
Core client information imported from ROLODEX data.
|
||||||
|
"""
|
||||||
|
__tablename__ = "clients"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
rolodex_id = Column(String(20), unique=True, index=True)
|
||||||
|
last_name = Column(String(50))
|
||||||
|
first_name = Column(String(50))
|
||||||
|
middle_initial = Column(String(10))
|
||||||
|
company = Column(String(100))
|
||||||
|
address = Column(String(255))
|
||||||
|
city = Column(String(50))
|
||||||
|
state = Column(String(2))
|
||||||
|
zip_code = Column(String(10))
|
||||||
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
|
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||||
|
|
||||||
|
# Relationships
|
||||||
|
phones = relationship("Phone", back_populates="client")
|
||||||
|
cases = relationship("Case", back_populates="client")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<Client(id={self.id}, name='{self.first_name} {self.last_name}')>"
|
||||||
|
|
||||||
|
|
||||||
|
class Phone(Base):
|
||||||
|
"""
|
||||||
|
Phone number model linked to clients.
|
||||||
|
|
||||||
|
Stores phone number information for clients.
|
||||||
|
"""
|
||||||
|
__tablename__ = "phones"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
client_id = Column(Integer, ForeignKey("clients.id"), nullable=False)
|
||||||
|
phone_type = Column(String(20)) # home, work, mobile, fax, etc.
|
||||||
|
phone_number = Column(String(20))
|
||||||
|
extension = Column(String(10))
|
||||||
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
|
|
||||||
|
# Relationships
|
||||||
|
client = relationship("Client", back_populates="phones")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<Phone(id={self.id}, number='{self.phone_number}')>"
|
||||||
|
|
||||||
|
|
||||||
|
class Case(Base):
|
||||||
|
"""
|
||||||
|
Case model representing legal cases or files.
|
||||||
|
|
||||||
|
Main case information imported from FILES data.
|
||||||
|
"""
|
||||||
|
__tablename__ = "cases"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
file_no = Column(String(20), unique=True, index=True, nullable=False)
|
||||||
|
client_id = Column(Integer, ForeignKey("clients.id"), nullable=False)
|
||||||
|
status = Column(String(20), default="active")
|
||||||
|
case_type = Column(String(50))
|
||||||
|
description = Column(Text)
|
||||||
|
open_date = Column(DateTime(timezone=True))
|
||||||
|
close_date = Column(DateTime(timezone=True))
|
||||||
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
|
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||||
|
|
||||||
|
# Relationships
|
||||||
|
client = relationship("Client", back_populates="cases")
|
||||||
|
transactions = relationship("Transaction", back_populates="case")
|
||||||
|
documents = relationship("Document", back_populates="case")
|
||||||
|
payments = relationship("Payment", back_populates="case")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<Case(id={self.id}, file_no='{self.file_no}')>"
|
||||||
|
|
||||||
|
|
||||||
|
class Transaction(Base):
|
||||||
|
"""
|
||||||
|
Transaction model for financial transactions.
|
||||||
|
|
||||||
|
Records financial activities related to cases.
|
||||||
|
"""
|
||||||
|
__tablename__ = "transactions"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
case_id = Column(Integer, ForeignKey("cases.id"), nullable=False)
|
||||||
|
transaction_date = Column(DateTime(timezone=True))
|
||||||
|
transaction_type = Column(String(20))
|
||||||
|
amount = Column(Float)
|
||||||
|
description = Column(Text)
|
||||||
|
reference = Column(String(50))
|
||||||
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
|
|
||||||
|
# Relationships
|
||||||
|
case = relationship("Case", back_populates="transactions")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<Transaction(id={self.id}, amount={self.amount})>"
|
||||||
|
|
||||||
|
|
||||||
|
class Document(Base):
|
||||||
|
"""
|
||||||
|
Document model for case-related documents.
|
||||||
|
|
||||||
|
Stores information about documents associated with cases.
|
||||||
|
"""
|
||||||
|
__tablename__ = "documents"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
case_id = Column(Integer, ForeignKey("cases.id"), nullable=False)
|
||||||
|
document_type = Column(String(50))
|
||||||
|
file_name = Column(String(255))
|
||||||
|
file_path = Column(String(500))
|
||||||
|
description = Column(Text)
|
||||||
|
uploaded_date = Column(DateTime(timezone=True))
|
||||||
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
|
|
||||||
|
# Relationships
|
||||||
|
case = relationship("Case", back_populates="documents")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<Document(id={self.id}, file_name='{self.file_name}')>"
|
||||||
|
|
||||||
|
|
||||||
|
class Payment(Base):
|
||||||
|
"""
|
||||||
|
Payment model for payment records.
|
||||||
|
|
||||||
|
Records payments made or received for cases.
|
||||||
|
"""
|
||||||
|
__tablename__ = "payments"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
case_id = Column(Integer, ForeignKey("cases.id"), nullable=False)
|
||||||
|
payment_date = Column(DateTime(timezone=True))
|
||||||
|
payment_type = Column(String(20))
|
||||||
|
amount = Column(Float)
|
||||||
|
description = Column(Text)
|
||||||
|
check_number = Column(String(20))
|
||||||
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
|
|
||||||
|
# Relationships
|
||||||
|
case = relationship("Case", back_populates="payments")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<Payment(id={self.id}, amount={self.amount})>"
|
||||||
|
|||||||
Reference in New Issue
Block a user