From de983a73d2735ef0bf05abd87822289af3832812 Mon Sep 17 00:00:00 2001 From: HotSwapp <47397945+HotSwapp@users.noreply.github.com> Date: Mon, 6 Oct 2025 18:22:18 -0500 Subject: [PATCH] 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 --- README.md | 100 +++++++++++++ app/__pycache__/database.cpython-313.pyc | Bin 0 -> 2942 bytes app/__pycache__/main.cpython-313.pyc | Bin 0 -> 2906 bytes app/__pycache__/models.cpython-313.pyc | Bin 0 -> 8835 bytes app/database.py | 91 ++++++++++++ app/main.py | 86 ++++++++++- app/models.py | 182 ++++++++++++++++++++++- delphi.db | Bin 0 -> 73728 bytes 8 files changed, 457 insertions(+), 2 deletions(-) create mode 100644 README.md create mode 100644 app/__pycache__/database.cpython-313.pyc create mode 100644 app/__pycache__/main.cpython-313.pyc create mode 100644 app/__pycache__/models.cpython-313.pyc create mode 100644 app/database.py create mode 100644 delphi.db diff --git a/README.md b/README.md new file mode 100644 index 0000000..5323865 --- /dev/null +++ b/README.md @@ -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 diff --git a/app/__pycache__/database.cpython-313.pyc b/app/__pycache__/database.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cfc457d21ee955df6351fb04325993b2104f43c0 GIT binary patch literal 2942 zcmai0O>7*u73Tcx-)OZO{n$!mMP?)?tMSjS(^`nECW7V4ax6JYtFaD678s0nxIgji z%!ZtiEdd48B0$sh$8OO8KFVl=xVKyi_?TmlQupA+z(x!-J@_U;E>ILb^pU%(RRaMf zFdXuck9>UZB)MN^9!)kHtB)U%kTX^aZ#aWvIl9(vNsNN3aTe;k=f zGc_f*Bs5cqjiW-w)C!p&fXG%6GJDMQMd<_SA~Ji;w3)fA(!N3p^wMU!&}a5tR;XS` zba$ftre07Ankk#|B_#%{T$0WH5%iHt2h1UJK(q#7&Jf54MejseHco6l*XHfOUb7j& zMc*q`%OSOcs_$XjbGXDKMbWLa~?oKKf%q%M=jk4k@}eKl?hd zxH}9vO1JH**|!Oe5@yRvnB3#~wqrSdK)m%RJwJDA?&_R*&01Z#xp{(p3|5hud_JGc zJHksjXDy1i#8E$Zpm^J2c8yp;1svck+%l9X-S)I)(=v;C>%Q+=bxN#y)hkCTDOP-# zq}gHM7yVkT8bm4HF1aDAMCw}AbD}sUK}fwKKW(P~j?e^r2Z3l{K>CMLXRw1&4~?N! zw2nTPF#K1P&Up9VnTb(Zdj0>-DWP|j)2Jl9t&}7~wk_CDnLN6B~kx%x&FZ&^lPfi)-LV`rgyfws`gF{GXR)!5e6Jz0qjHZ%HX z$>&M!UhMVn_x;n-9|xwCzbMmE5#)!3!&ZUsU;!$C z?BT#ln=lUr&{*MI_nq=#ydyBB%M!p=Hk8eW+7CJ35DZT1f(@vTlM_S*7|Fo^ha)@% z3`Sgz2*GEE$Ym8jbRE0~38!N@#UPu7kG%>N z1Sf-Hsb9SRv-h8kPpq5tuSWTs)X~wvYCz5X3~Y}l?^J^dq%V&_CR!&D(~=Ka zjNK-V0k~NaoXlF+m<4pEn=rYyP_EmI-SMf@Rujq0^~;bU%kZ{-IG z-4{tSo9bC8nn zIQ_Km)K2o$UK}NRe(g3ABTx0wC;IrVKED0^hMxU2*6h*mUwiQS@7`7Ddz&Ax%1li5aJwwaxNlAYbn&OT;e&o#0OyZV{?si(sePlnI! z4xf8;s3Pe zF{6>LAV=;7xg)Ov%3&8}j!f`rjdH4?{5cuLTkipy6dmvqJPt$%=xclqr#{Nxf`UB7 z6^#_Ygm51n;{*IYw(fKCo!0AVmh$AwW6r*SYEO|QX>>Olrc+A}oz4|L`kdixu6 z@fjN1k0UAlNiXB{FZ^id@q>pZgk7Hji?Dj2z8U*i^M&i{ibuT_B4c<>Z$ZlVHG_*5v dVOP(+L?C*3J|Ugm8yb`*A58oc1e4+z{{{GG0l)wN literal 0 HcmV?d00001 diff --git a/app/__pycache__/main.cpython-313.pyc b/app/__pycache__/main.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..211c7405fe0b9eb167587005190bf087f3d93d29 GIT binary patch literal 2906 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 JfRx#j{{`;$#@+w` literal 0 HcmV?d00001 diff --git a/app/database.py b/app/database.py new file mode 100644 index 0000000..4b35001 --- /dev/null +++ b/app/database.py @@ -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 "****://****:****@****/****" diff --git a/app/main.py b/app/main.py index 0110a0f..1551825 100644 --- a/app/main.py +++ b/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) + } diff --git a/app/models.py b/app/models.py index 9c65c6d..f4409cd 100644 --- a/app/models.py +++ b/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"" + + +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"" + + +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"" + + +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"" + + +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"" + + +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"" + + +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"" diff --git a/delphi.db b/delphi.db new file mode 100644 index 0000000000000000000000000000000000000000..6b2fd5069f426355f8272277fce9d98d2885de74 GIT binary patch literal 73728 zcmeI&Z*S8^0LSr|(xeTgr981vg|LJwnz28?RPE`~;<9cbg^*5E)38kJs@@Viik%HM zO%sFe(cZ-#?=9@Zx$DYr3_BnPM%7%uJtZ$??&fWPOe|~r8M9zczEiZD7!(cS9 zBjdx$g^H$CJ~oU>rLr3TU5x+KL%cq#Uc}E@Zu@1MtCjbEZk&&6SLgi7N@e-iWozlL zrC{l5{qOp3i|;J_x$sNvTJ?|l!}*JIzsF5@AW&j~_G(pcT-IFAcibo56KiNc9k_lJ zTArgeFK&0tX4f=!+qcb!MzM{t*EUo;W5aVAKi$7j(=T7vUW_CAao<&S>UlNotJ~aa znYlgdi=Go7pljYSJ4So2Yqa-Ut#^(3V$TlU_`S@mOFOk3J6V!);n^0Ut9io@Yo$q z@827_@!=dRUYpb*joao(u6 z&R)mdz0($D8|gVVjE=cu#`|xZ4-8Qj%GUKpXQirNzoL06BPZyMRY=+Ph3S;C8o?LG zl)X-JdbC{AuUyfN&&iy!%4}n$kW^M|Y`j)fYJaN`$z~GCq1SgU-yY<$+T4_KvSw&U zM_CG+H5YsWPiV7@-TeZCN5zUDQP%yn_nIbT^w`aev*k$dQlT)#IN4e2m>3n6Zu ztLj&E?LdVnXJwp-d|sN4P1TCoHkrQ<>s9?dUHiV+M>6IE+3Z}}(SCdP{(f$qP5Mh) z<~mJF8qX_>HC@-WV@nPJ*a7`pz+vDE7aaUcK3R2~mSFNGyrEWxi_meqL|7iL+imgb#>s=j_n`%;BF zJPLeQUOeT-;`9*7>g9z~wn=95^;%88bV+-0Gr4TanxyeUA3f?m zuGkO9lfMx;5klw5%zp9^6Y-rp^7;e3~m2ut~iO`cASwL z<|co7+3tDKq;Yy|u}(~u^jq@s{+&0pdNFdYV_E(h!|Gqo@p@xxuDGO+Lw6)bj(k2l zJ#JL3NK|gi+4{wqs{Vnl{iuda(n|anpJL>zxt9!