# Project Structure Reference

## Standard Directory Layout

Replace `<svc>` with the Python package name (underscores, e.g. `ows_analytics` for service `ows-analytics`).

```
ows-<domain>/
├── application.py            # WSGI entry point — ddtrace + Sentry init + route import
├── dev.py                    # Local dev runner (Flask debug server, port 5000)
├── Dockerfile                # Multi-stage: base → deploy (+ pr_tests if needed)
├── Jenkinsfile               # CI/CD pipeline
├── Makefile                  # Dev/CI commands
├── uwsgi-start.sh            # uWSGI startup script (referenced by Dockerfile ENTRYPOINT)
├── software-catalog.yaml     # Datadog service catalog definition
├── requirements.txt          # Production deps — ALL VERSIONS PINNED, internal PyPI first
├── requirements-dev.txt      # Dev/test deps — ALL VERSIONS PINNED
├── .flake8                   # Flake8 config
├── setup.cfg                 # isort config
├── mypy.ini                  # Type checking config
├── .env.shadow               # Committed placeholder for .env (never commit real .env)
├── spec/
│   └── ows-<domain>-1.0.0.yaml   # OpenAPI spec (Swagger)
├── <svc>/
│   ├── __init__.py
│   ├── api.py                # Flask app factory: create app, configure logging, Snowflake pool
│   ├── config.py             # ALL config: env vars, secrets, URL constants, pool sizes
│   ├── handlers.py           # Route handlers (core) + global error handler
│   ├── [domain]_handlers.py  # Additional handler files per domain area (if large)
│   ├── features.py           # Feature flag checks (pythonfeatures / Split.io)
│   ├── connectors/
│   │   ├── __init__.py
│   │   ├── snowflake.py      # Snowflake pool, fetchall, fetchall_nocache
│   │   └── redis.py          # Redis client (fakeredis fallback when REDIS_HOST absent)
│   ├── constants/
│   │   ├── __init__.py
│   │   └── error.py          # Pre-built error response constants
│   ├── logic/                # Business logic — calls connectors, returns plain dicts/lists
│   │   ├── __init__.py
│   │   └── <domain>.py       # One module per domain (chart.py, artist.py, etc.)
│   ├── models/               # (Optional) Heavy data transformation / aggregation
│   │   └── __init__.py
│   ├── queries/              # (Optional) AbstractSnowflakeQuery subclasses + SQL files
│   │   ├── __init__.py
│   │   └── sql/              # .sql files, macros/ subdirectory
│   ├── schemas/              # Marshmallow output schemas (serialization)
│   │   ├── __init__.py
│   │   ├── base.py           # BaseSchema with normalize() classmethod
│   │   └── <domain>.py       # One file per endpoint group (inner Request + Response)
│   ├── services/             # HTTP clients for other microservices
│   │   ├── __init__.py
│   │   └── request.py        # owsrequest wrapper with Sentry capture on non-200
│   ├── utils/
│   │   ├── __init__.py
│   │   ├── cache.py          # @cache_in_redis decorator + key helpers
│   │   └── db/
│   │       ├── __init__.py
│   │       └── mapper.py     # map_db_result_with_column_names()
│   └── validation/           # (Optional) Access decorators, dev auth override
│       ├── __init__.py
│       └── access.py         # @verify_profile decorator
└── tests/
    ├── conftest.py           # Session-scoped autouse fixtures (mock permissions, dates)
    ├── unit/
    │   ├── conftest.py       # Flask test client, mock Snowflake execute, mock Redis
    │   ├── test_handlers.py  # Handler tests (one class per endpoint group)
    │   └── logic/
    │       └── test_<domain>.py
    └── integration/
        ├── conftest.py       # assert_endpoint() helper, pre-defined header sets
        └── test_api_endpoints.py
```

## File Responsibilities (strict separation)

| File | Owns | Must NOT contain |
|---|---|---|
| `application.py` | ddtrace init, Sentry init, route import | Business logic, config values |
| `config.py` | All env vars, all secrets, all URL constants | App instance, import of Flask |
| `api.py` | Flask app creation, logging setup, SQLAlchemy setup | Route decorators, business logic |
| `handlers.py` | `@app.route` decorators, param extraction, return | Business logic, SQL, direct DB calls |
| `logic/<domain>.py` | Business logic, orchestration | Route decorators, Flask request object |
| `connectors/` | Connection management, raw queries | Business logic, serialization |
| `schemas/` | marshmallow serialization/deserialization | DB queries, business logic |
| `config.py` | Route path strings (e.g. `HEALTH_CHECK_PATH = "/hello/"`) | App instance |

## Key Conventions

- The Python package name is the service name with hyphens replaced by underscores and the `ows-` prefix kept: `ows-analytics` → `ows_analytics`.
- `handlers.py` imports `app` from `<svc>.api` — never creates a new Flask instance.
- Routes are registered as a **side-effect of importing** handler modules in `application.py`. No Flask blueprints.
- All route path strings are defined in `config.py`. Handlers use `@app.route(config.MY_PATH)`.
- `__init__.py` files are empty (no re-exports that create circular imports).
