Skip to content

Project Structure

John R. D'Orazio edited this page Feb 9, 2026 · 4 revisions

Project Structure

This page describes the organization of the Axigraph API codebase.

Directory Layout

axigraph-api/
├── app/                      # Application source code
│   ├── __init__.py
│   ├── main.py               # FastAPI application entry point
│   ├── api/                  # API routes
│   │   └── v1/               # API version 1
│   │       ├── __init__.py   # Router aggregation
│   │       ├── auth.py       # Authentication endpoints
│   │       ├── projects.py   # Project management
│   │       ├── ontologies.py # Ontology CRUD
│   │       ├── classes.py    # OWL class operations
│   │       ├── properties.py # OWL property operations
│   │       └── search.py     # Search and SPARQL
│   ├── core/                 # Core functionality
│   │   ├── __init__.py
│   │   ├── config.py         # Settings (Pydantic)
│   │   ├── database.py       # Database connection
│   │   └── auth.py           # JWT validation
│   ├── models/               # SQLAlchemy models
│   │   ├── __init__.py       # Model exports
│   │   └── project.py        # Project, ProjectMember
│   ├── schemas/              # Pydantic schemas
│   │   ├── auth.py           # Auth request/response
│   │   ├── project.py        # Project schemas
│   │   ├── ontology.py       # Ontology schemas
│   │   ├── owl_class.py      # OWL class schemas
│   │   ├── owl_property.py   # OWL property schemas
│   │   └── search.py         # Search schemas
│   ├── services/             # Business logic
│   │   ├── ontology.py       # Ontology operations
│   │   ├── project_service.py # Project operations
│   │   └── search.py         # Search service
│   ├── collab/               # Collaboration (WebSocket)
│   │   ├── presence.py       # User presence
│   │   └── protocol.py       # Message protocol
│   └── git/                  # Git integration (future)
├── alembic/                  # Database migrations
│   ├── env.py                # Migration environment
│   ├── script.py.mako        # Migration template
│   └── versions/             # Migration files
├── tests/                    # Test suite
│   ├── conftest.py           # Pytest fixtures
│   └── test_*.py             # Test files
├── scripts/                  # Utility scripts
│   └── setup-zitadel.py      # Zitadel configuration
├── config/                   # Configuration files
├── docs/                     # Documentation
├── .env                      # Environment variables (not in git)
├── .env.example              # Example environment file
├── alembic.ini               # Alembic configuration
├── compose.yaml              # Docker Compose for infrastructure
├── Dockerfile                # Container build instructions
├── pyproject.toml            # Project configuration
└── README.md

Key Components

app/main.py

The FastAPI application entry point:

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI(
    title="Axigraph API",
    description="Collaborative OWL Ontology Curation Platform",
    version="0.1.0",
)

# CORS middleware
app.add_middleware(CORSMiddleware, ...)

# Include API routers
app.include_router(api_v1_router, prefix="/api/v1")

app/core/config.py

Configuration management using Pydantic Settings:

from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    app_env: str = "development"
    database_url: PostgresDsn
    # ... other settings

    model_config = SettingsConfigDict(env_file=".env")

settings = get_settings()  # Cached singleton

app/core/database.py

Async SQLAlchemy database setup:

from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession

class Base(DeclarativeBase):
    pass

engine = create_async_engine(str(settings.database_url))
async_session_maker = async_sessionmaker(engine)

async def get_db() -> AsyncGenerator[AsyncSession, None]:
    async with async_session_maker() as session:
        yield session

app/core/auth.py

JWT token validation and user extraction:

class CurrentUser(BaseModel):
    id: str
    email: str | None
    name: str | None
    roles: list[str] = []

# Dependency injection types
RequiredUser = Annotated[CurrentUser, Depends(get_current_user)]
OptionalUser = Annotated[CurrentUser | None, Depends(get_current_user_optional)]

app/api/v1/__init__.py

Router aggregation:

from fastapi import APIRouter

router = APIRouter()

router.include_router(auth.router, prefix="/auth", tags=["Authentication"])
router.include_router(projects.router, prefix="/projects", tags=["Projects"])
router.include_router(ontologies.router, prefix="/ontologies", tags=["Ontologies"])
# ... more routers

Architectural Patterns

Layered Architecture

┌──────────────────────────────────────┐
│         API Layer (Routers)          │  ← HTTP handling, validation
├──────────────────────────────────────┤
│         Service Layer                │  ← Business logic
├──────────────────────────────────────┤
│         Data Layer (Models)          │  ← Database operations
└──────────────────────────────────────┘

Request Flow

  1. Router receives HTTP request
  2. Pydantic schema validates request body
  3. Dependencies inject services, user context
  4. Service executes business logic
  5. Model interacts with database
  6. Response schema formats the response

Example Flow

# Router (app/api/v1/projects.py)
@router.post("", response_model=ProjectResponse)
async def create_project(
    project: ProjectCreate,              # Validated by Pydantic
    service: ProjectService = Depends(), # Injected service
    user: RequiredUser,                  # Authenticated user
) -> ProjectResponse:
    return await service.create(project, user)

# Service (app/services/project_service.py)
async def create(self, project: ProjectCreate, owner: CurrentUser):
    db_project = Project(
        name=project.name,
        owner_id=owner.id,
        ...
    )
    self.db.add(db_project)
    await self.db.commit()
    return self._to_response(db_project, owner)

Dependency Injection

FastAPI's Depends() is used throughout:

# Database session
def get_db():
    return async_session_maker()

# Service with database
def get_project_service(db: AsyncSession = Depends(get_db)):
    return ProjectService(db)

# In router
@router.get("/")
async def list(service: ProjectService = Depends(get_project_service)):
    ...

Naming Conventions

Type Convention Example
Files snake_case.py project_service.py
Classes PascalCase ProjectService
Functions snake_case create_project
Variables snake_case user_id
Constants UPPER_SNAKE_CASE MAX_LIMIT
API paths kebab-case (usually) or snake_case /api/v1/projects

Schema Conventions

For each entity, we typically have:

Schema Purpose Example
{Entity}Base Shared fields ProjectBase
{Entity}Create Input for creation ProjectCreate
{Entity}Update Input for updates (all optional) ProjectUpdate
{Entity}Response API response ProjectResponse
{Entity}ListResponse Paginated list ProjectListResponse

Adding New Features

1. Add a Model

# app/models/comment.py
class Comment(Base):
    __tablename__ = "comments"
    ...

# app/models/__init__.py
from .comment import Comment

2. Create Migration

alembic revision --autogenerate -m "Add comments"
alembic upgrade head

3. Add Schemas

# app/schemas/comment.py
class CommentCreate(BaseModel): ...
class CommentResponse(BaseModel): ...

4. Add Service

# app/services/comment_service.py
class CommentService:
    def __init__(self, db: AsyncSession):
        self.db = db

    async def create(self, ...): ...

5. Add Router

# app/api/v1/comments.py
router = APIRouter()

@router.post("", response_model=CommentResponse)
async def create_comment(...): ...

6. Register Router

# app/api/v1/__init__.py
router.include_router(comments.router, prefix="/comments", tags=["Comments"])

Next Steps

Clone this wiki locally