Create comprehensive OpenSpec proposal for implementing a minimal GraphQL API for member management with flexible validation requirements. Key features: - Only firstName required for member creation - Conditional validation (email/phone format validated only when provided) - Full CRUD operations via GraphQL - SQLite database with Alembic migrations - FastAPI + Strawberry GraphQL integration Includes 4 capability specs: - project-setup: Python project structure with uv dependency management - database-layer: SQLAlchemy async models with nullable fields - graphql-api: Strawberry schema with minimal required fields - member-crud: Business logic with conditional validation Implementation broken down into 20 discrete tasks across 5 phases. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
14 KiB
Implementation Tasks: Add GraphQL Member API
Change ID: add-graphql-member-api
Status: Draft
Created: 2025-11-20
Task Breakdown
This change is implemented through ~18 discrete, verifiable tasks. Each task delivers user-visible progress and includes validation steps.
Phase 1: Project Foundation
Task 1: Initialize Python project with uv
Objective: Create pyproject.toml and configure uv for dependency management
Steps:
- Run
uv initto create basic project structure - Edit pyproject.toml to set name="clubber", version="0.1.0"
- Set Python requirement to ">=3.11"
- Add project metadata (description, authors, license)
Validation:
uv synccompletes successfully- pyproject.toml contains project metadata
Dependencies: None
Task 2: Add core dependencies
Objective: Install FastAPI, Strawberry, SQLAlchemy, and Alembic
Steps:
- Run
uv add fastapi uvicorn[standard] - Run
uv add strawberry-graphql[fastapi] - Run
uv add sqlalchemy[asyncio] aiosqlite - Run
uv add alembic pydantic-settings
Validation:
- All packages appear in pyproject.toml dependencies
uv syncresolves dependencies without conflicts- uv.lock file is generated
Dependencies: Task 1
Task 3: Add development dependencies
Objective: Install testing and code quality tools
Steps:
- Run
uv add --dev pytest pytest-asyncio - Run
uv add --dev black ruff isort
Validation:
- Development packages in [tool.uv.dev-dependencies] or similar
uv run pytest --versionworksuv run black --versionworks
Dependencies: Task 1
Task 4: Configure development tools in pyproject.toml
Objective: Set up black, ruff, and isort configurations
Steps:
- Add [tool.black] section with line-length=88, target-version=["py311"]
- Add [tool.ruff] section with line-length=88, select=["E", "F", "W", "I"]
- Add [tool.isort] section with profile="black"
- Add [tool.pytest.ini_options] with asyncio_mode="auto"
Validation:
uv run black --check .runs (passes even if no code yet)uv run ruff check .runs- Tool configurations are in pyproject.toml
Dependencies: Task 3
Task 5: Create src directory structure
Objective: Set up layered architecture directories
Steps:
- Create directories:
src/models/,src/schemas/,src/resolvers/ - Create empty
__init__.pyin each directory - Create
src/main.py,src/config.py,src/database.pyas placeholders
Validation:
- Directory structure matches design.md
- All directories have __init__.py files
ls -R src/shows complete structure
Dependencies: Task 1
Can run in parallel with: Task 2, Task 3
Phase 2: Database Layer
Task 6: Create configuration management
Objective: Implement Settings class with environment variable support
Steps:
- In
src/config.py, create Settings class using pydantic-settings - Add fields: database_url (default: "sqlite+aiosqlite:///./clubber.db"), debug (default: False)
- Configure .env file loading
- Create
.env.examplewith documented settings
Validation:
- Settings() instantiates with defaults
- DATABASE_URL environment variable overrides default
- .env.example exists and documents all settings
Dependencies: Task 2, Task 5
Task 7: Set up database connection and session factory
Objective: Create async SQLAlchemy engine and session management
Steps:
- In
src/database.py, import SQLAlchemy async components - Create async_engine using settings.database_url
- Create async_session_maker with AsyncSession
- Implement get_db_session() dependency for FastAPI
- Create declarative Base for models
Validation:
- async_engine is created without errors
- async_session_maker is callable
- get_db_session yields AsyncSession
Dependencies: Task 6
Task 8: Define Member SQLAlchemy model
Objective: Create database model for members table with minimal required fields
Steps:
- In
src/models/member.py, import SQLAlchemy components - Define Member class inheriting from Base
- Add id field (primary key, auto-increment)
- Add first_name field (nullable=False, required)
- Add optional fields with nullable=True: last_name, street, apartment_number, zip, city, country, email, phone
- Add timestamps: created_at, updated_at with defaults
- Set tablename = "members"
Validation:
- Member class has all fields defined
- Only first_name has nullable=False
- All other data fields have nullable=True
- Field types and length constraints match spec
from src.models.member import Memberworks
Dependencies: Task 7
Task 9: Initialize Alembic for migrations
Objective: Set up Alembic configuration for database versioning
Steps:
- Run
uv run alembic init migrations - Edit
migrations/env.pyto import Base and use async operations - Edit
alembic.inito use config.py for database URL - Update env.py to reference all models (import src.models.member)
Validation:
migrations/directory existsalembic.iniis configureduv run alembic currentexecutes without errors
Dependencies: Task 8
Task 10: Create initial database migration
Objective: Generate migration to create members table
Steps:
- Run
uv run alembic revision --autogenerate -m "create members table" - Review generated migration in
migrations/versions/ - Verify upgrade() creates members table with all columns
- Verify downgrade() drops members table
Validation:
- Migration file exists in migrations/versions/
- Migration includes all Member model fields
uv run alembic upgrade headcreates database- clubber.db file exists with members table
Dependencies: Task 9
Phase 3: GraphQL API Layer
Task 11: Define Strawberry GraphQL types
Objective: Create GraphQL schema types with minimal required fields
Steps:
- In
src/schemas/member.py, import strawberry - Define @strawberry.type Member with camelCase fields (only firstName, createdAt, updatedAt as non-null)
- Define @strawberry.input CreateMemberInput (only firstName required)
- Define @strawberry.input UpdateMemberInput (all fields optional except id)
- Map snake_case Python to camelCase GraphQL with field aliases
Validation:
- Member type has all fields from spec with correct nullability
- CreateMemberInput requires only firstName
- All other fields are optional (nullable GraphQL types)
from src.schemas.member import Memberworks
Dependencies: Task 2
Can run in parallel with: Database layer tasks (Task 6-10)
Task 12: Implement validation helpers
Objective: Create conditional validation functions for optional fields
Steps:
- Create
src/validation.py(or add to schemas) - Define EMAIL_PATTERN and PHONE_PATTERN regex
- Implement validate_email(email: Optional[str]) with conditional logic (only validate if provided)
- Implement validate_phone(phone: Optional[str]) with conditional logic (only validate if provided)
- Implement validate_first_name(first_name: str) to ensure non-empty
- Create ValidationError custom exception
Validation:
- validate_email("test@example.com") passes
- validate_email(None) passes (no validation)
- validate_email("invalid") raises ValidationError
- validate_phone("+15551234567") passes
- validate_phone(None) passes (no validation)
- validate_phone("123") raises ValidationError
- validate_first_name("") raises ValidationError
Dependencies: Task 11
Task 13: Implement Query resolvers
Objective: Create GraphQL query resolvers for reading members
Steps:
- In
src/resolvers/member.py, import necessary components - Define Query class with @strawberry.type
- Implement member(id: ID) -> Optional[Member] resolver
- Implement members() -> List[Member] resolver with sorting
- Use get_db_session dependency for database access
Validation:
- Resolvers are async functions
- Database queries use SQLAlchemy 2.0 select() syntax
- Resolvers convert DB models to GraphQL types
Dependencies: Task 11, Task 12
Task 14: Implement Mutation resolvers
Objective: Create GraphQL mutation resolvers for modifying members
Steps:
- In
src/resolvers/member.py, define Mutation class - Implement createMember(input: CreateMemberInput) -> Member
- Implement updateMember(input: UpdateMemberInput) -> Member
- Implement deleteMember(id: ID) -> bool
- Add validation calls in createMember and updateMember
- Implement error handling for not found cases
Validation:
- createMember validates email and phone
- updateMember allows partial updates
- deleteMember raises error for non-existent ID
- All mutations use database session correctly
Dependencies: Task 13
Task 15: Integrate GraphQL with FastAPI
Objective: Mount Strawberry schema on FastAPI app
Steps:
- In
src/main.py, import FastAPI and Strawberry components - Create FastAPI app instance
- Create Strawberry schema from Query and Mutation
- Create GraphQLRouter with schema and graphiql=True
- Mount router at app.add_route("/graphql", ...)
- Add startup event to test database connection
Validation:
uv run uvicorn src.main:app --reloadstarts server- http://localhost:8000/graphql shows GraphiQL
- Schema introspection shows Member type and operations
Dependencies: Task 14
Phase 4: Testing and Sample Data
Task 16: Create test infrastructure
Objective: Set up pytest fixtures for database testing
Steps:
- Create
tests/directory with__init__.py - Create
tests/conftest.pywith fixtures - Implement async_db_session fixture using in-memory SQLite
- Implement test database setup/teardown
- Configure pytest-asyncio in pyproject.toml
Validation:
uv run pytest --collect-onlyfinds tests directory- Fixtures can be imported by test files
- Test database is isolated from development database
Dependencies: Task 3, Task 10
Task 17: Write tests for CRUD operations
Objective: Verify all member operations work correctly including minimal creation
Steps:
- Create
tests/test_member_crud.py - Write test_create_member_success (with all fields)
- Write test_create_member_minimal (only firstName)
- Write test_create_member_invalid_email (conditional validation)
- Write test_create_member_no_validation_when_null (email=None doesn't validate)
- Write test_get_member_by_id
- Write test_list_members_sorted (including members with null lastName)
- Write test_update_member_partial
- Write test_delete_member
- Write test_validation_errors
Validation:
uv run pytestruns all tests- All tests pass including minimal member creation
- Conditional validation tests pass (null fields skip validation)
- Coverage for resolvers is >80%
Dependencies: Task 16, Task 15
Task 18: Create database seed script
Objective: Implement idempotent script to create sample member with partial data
Steps:
- Create
scripts/directory - Create
scripts/seed.pywith async main function - Check if member with email "jane.doe@example.com" exists
- If not, create sample member with firstName, lastName, email (address and phone fields null)
- Demonstrates that only firstName is required, other fields optional
- Add asyncio.run() to execute main()
- Make script executable and add shebang
Validation:
uv run python scripts/seed.pycreates sample member- Sample member has firstName, lastName, email populated
- Sample member has null values for address and phone fields
- Running script twice doesn't create duplicate
- Sample member is queryable via GraphQL
- Script prints confirmation message
Dependencies: Task 10, Task 15
Phase 5: Validation and Documentation
Task 19: Run code quality checks
Objective: Ensure all code meets formatting and linting standards
Steps:
- Run
uv run black .to format all code - Run
uv run isort .to sort imports - Run
uv run ruff check .to verify linting - Fix any reported issues
Validation:
uv run black --check .exits with code 0uv run ruff check .reports no errors- All Python files are consistently formatted
Dependencies: Task 4, Task 18
Task 20: Verify end-to-end functionality
Objective: Test complete user workflow via GraphQL playground
Steps:
- Start server with
uv run uvicorn src.main:app --reload - Open http://localhost:8000/graphql
- Execute createMember mutation with valid data
- Execute members query to list all members
- Execute updateMember mutation to change email
- Execute deleteMember mutation to remove member
- Verify error handling with invalid inputs
Validation:
- All GraphQL operations work via playground
- Sample member exists after seed script
- Error messages are clear and helpful
- Database persists data between server restarts
Dependencies: Task 18, Task 19
Summary
Total tasks: 20 Parallel opportunities:
- Tasks 2, 3, 5 can start together after Task 1
- Task 11 can run in parallel with Tasks 6-10
- Tasks 16-18 can run in parallel
Critical path: Task 1 → Task 2 → Task 6 → Task 7 → Task 8 → Task 9 → Task 10 → Task 15 → Task 20
Estimated completion: Tasks build incrementally; each phase validates previous work.
Verification: After Task 20, run:
uv run pytest # All tests pass
uv run alembic upgrade head # Database schema current
uv run python scripts/seed.py # Sample data loaded
uv run uvicorn src.main:app --reload # Server starts
# Open http://localhost:8000/graphql # GraphiQL works