Skip to content

Commit b0556bd

Browse files
committed
Add AGENTS.md
1 parent e66529d commit b0556bd

1 file changed

Lines changed: 254 additions & 0 deletions

File tree

AGENTS.md

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
# AGENTS.md — Coding Agent Guide for `df-leeway`
2+
3+
This file provides instructions for agentic coding tools (AI assistants, automated agents, etc.)
4+
working in this repository.
5+
6+
---
7+
8+
## Project Overview
9+
10+
`opendrift-leeway-webgui` is a Django 4 web application that submits ocean drift simulations via
11+
a Celery task queue. Simulations run inside a Docker container using the OpenDrift Leeway model.
12+
Results are served via Django class-based views and a Django REST Framework API. There is no
13+
JavaScript build step — the frontend is server-side rendered Django templates with static files.
14+
15+
**Stack:** Python 3.10+, Django 4, Celery, Redis, Docker, Django REST Framework, drf-spectacular.
16+
17+
---
18+
19+
## Environment Setup
20+
21+
```bash
22+
# Create and activate a virtual environment
23+
python3 -m venv .venv
24+
source .venv/bin/activate
25+
26+
# Install project + dev dependencies
27+
pip install -e .[dev]
28+
29+
# Apply database migrations (run from opendrift_leeway_webgui/)
30+
python3 manage.py migrate
31+
32+
# Build the custom OpenDrift Docker image (required for tests and simulations)
33+
docker build -t opendrift-leeway-custom opendrift/
34+
```
35+
36+
The application reads runtime config from `/etc/opendrift-leeway-webgui.ini`. For local
37+
development, set `LEEWAY_SECRET_KEY` as an environment variable (any string works locally).
38+
39+
---
40+
41+
## Running the Application
42+
43+
```bash
44+
# Django development server (run from opendrift_leeway_webgui/)
45+
python3 manage.py runserver
46+
47+
# Celery worker (requires Redis running on localhost:6379)
48+
celery -A leeway worker -l INFO
49+
```
50+
51+
---
52+
53+
## Testing
54+
55+
**Prerequisites:** Redis must be running and `opendrift-leeway-custom:latest` Docker image must
56+
exist (see setup above). The integration test runs an actual simulation via Celery.
57+
58+
```bash
59+
# Run all tests
60+
pytest
61+
62+
# Run a single test file
63+
pytest tests/leeway/test_simulation.py
64+
65+
# Run a single test by name
66+
pytest tests/leeway/test_simulation.py::test_simulation
67+
68+
# Run with verbose output
69+
pytest -v tests/leeway/test_simulation.py::test_simulation
70+
71+
# Skip coverage for faster iteration
72+
pytest --no-cov tests/leeway/test_simulation.py
73+
```
74+
75+
`DJANGO_SETTINGS_MODULE` and `LEEWAY_SECRET_KEY=dummy` are injected automatically by
76+
`[tool.pytest.ini_options]` in `pyproject.toml` — no manual env setup needed.
77+
78+
Test files live under `tests/` and follow the `test_<module>.py` naming convention.
79+
Use `@pytest.mark.django_db(transaction=True)` for tests that touch the database.
80+
81+
---
82+
83+
## Linting & Formatting
84+
85+
All tool config lives in `pyproject.toml`. Run the full suite via pre-commit:
86+
87+
```bash
88+
# Run all pre-commit hooks against every file
89+
pre-commit run --all-files
90+
91+
# Run individual tools
92+
black . # format Python (line length 88)
93+
isort . # sort imports
94+
pylint opendrift_leeway_webgui # static analysis
95+
djlint --lint --reformat --quiet opendrift_leeway_webgui # lint/format Django HTML templates
96+
```
97+
98+
CI runs Black, isort, djlint, and pylint as separate parallel jobs on every push and PR.
99+
**All linting must pass before merging.**
100+
101+
Key formatting rules:
102+
- **Black** line length: 88 characters (`skip-magic-trailing-comma = true`)
103+
- **pylint** max line length: 120 characters
104+
- **isort** `multi_line_output = 3` (vertical hanging indent, Black-compatible)
105+
106+
---
107+
108+
## Code Style Guidelines
109+
110+
### Import Ordering
111+
112+
Use `isort` conventions: stdlib → third-party → first-party → relative. Multi-line imports use
113+
vertical hanging indent (isort `multi_line_output = 3`):
114+
115+
```python
116+
import logging
117+
from datetime import timedelta
118+
from pathlib import Path
119+
120+
from django.conf import settings
121+
from rest_framework import mixins
122+
123+
from .models import LeewaySimulation
124+
from .utils import send_result_mail
125+
```
126+
127+
Always use relative imports within sub-packages (`from .models import ...`,
128+
`from ...leeway.models import ...`).
129+
130+
### Naming Conventions
131+
132+
| Kind | Convention | Example |
133+
|---|---|---|
134+
| Modules / packages | `snake_case` | `logging_formatter.py` |
135+
| Classes | `PascalCase` | `LeewaySimulationCreateView` |
136+
| Functions / methods | `snake_case` | `send_result_mail()` |
137+
| Constants | `ALL_CAPS` | `LEEWAY_OBJECT_TYPES` |
138+
| Template files | `snake_case` | `leewaysimulation_detail.html` |
139+
| API versioning | directory-based | `api/v1/` |
140+
141+
### Type Annotations
142+
143+
The codebase does **not** use Python type hints (no mypy in the toolchain). Do not add type
144+
annotations to existing code unless explicitly requested. For documenting parameter and return
145+
types, use Sphinx-style docstring comments:
146+
147+
```python
148+
def format(self, record):
149+
"""
150+
:param record: The log record
151+
:type record: ~logging.LogRecord
152+
:rtype: str
153+
"""
154+
```
155+
156+
### Error Handling
157+
158+
- Use `raise ... from exc` to preserve exception context:
159+
```python
160+
raise ValueError(f'"{value}" is not a valid bool value') from exc
161+
```
162+
- Raise `django.core.exceptions.ImproperlyConfigured` for misconfigured settings.
163+
- Capture subprocess failures via `stderr` and persist them to the model's `traceback` field.
164+
- Use Django form `clean_<field>()` methods for field-level validation.
165+
- Use guard clauses / early returns in views (e.g., return `HttpResponseForbidden` before
166+
proceeding with destructive operations).
167+
168+
### Logging
169+
170+
Always define a module-level logger; never use `print()`:
171+
172+
```python
173+
import logging
174+
175+
logger = logging.getLogger(__name__)
176+
```
177+
178+
### Django Views
179+
180+
- Use **class-based views** throughout; inherit from Django generic views
181+
(`CreateView`, `ListView`, `DetailView`, `DeleteView`, etc.).
182+
- Mix in `LoginRequiredMixin` as the **first** base class for auth-protected views:
183+
```python
184+
class LeewaySimulationDeleteView(LoginRequiredMixin, DeleteView):
185+
```
186+
- Document class attributes with `#:` Sphinx docstring comments:
187+
```python
188+
#: The model for this form view
189+
model = LeewaySimulation
190+
```
191+
192+
### Django REST Framework
193+
194+
- Compose ViewSets from mixins rather than subclassing `ModelViewSet` directly:
195+
```python
196+
class LeewaySimulationViewSet(
197+
mixins.CreateModelMixin,
198+
mixins.ListModelMixin,
199+
viewsets.GenericViewSet,
200+
):
201+
```
202+
- Use `ModelSerializer` subclasses in `serializers.py`.
203+
- API schema is auto-generated by `drf-spectacular` — keep views and serializers annotated
204+
enough for accurate OpenAPI output.
205+
206+
### Pylint Suppressions
207+
208+
Use inline `# pylint: disable=...` comments sparingly and only where genuinely justified.
209+
The project enables `useless-suppression` checking, so remove suppressions that are no longer
210+
needed.
211+
212+
---
213+
214+
## Project Layout
215+
216+
```
217+
opendrift_leeway_webgui/ # Main Django project package
218+
├── core/ # Settings, root URLs, WSGI, utilities
219+
├── leeway/ # Primary app: models, views, forms, tasks, Celery config
220+
│ ├── migrations/ # Auto-generated Django migrations
221+
│ ├── static/ # App-level static files
222+
│ └── templates/ # Django HTML templates (djlint-formatted)
223+
├── api/ # DRF API
224+
│ └── v1/ # Versioned API (serializers, views, urls)
225+
├── simulation/ # Runtime input/output data (not committed)
226+
└── simulation.py # Standalone script executed inside Docker container
227+
tests/
228+
├── conftest.py # Shared pytest fixtures (Celery worker setup/teardown)
229+
└── leeway/
230+
└── test_simulation.py # Integration tests
231+
```
232+
233+
---
234+
235+
## Versioning & Release
236+
237+
The project uses **CalVer** (`YYYY.MM.INC0`) managed by `bumpver`:
238+
239+
```bash
240+
bumpver update # bump version across pyproject.toml and source files
241+
python3 -m build # produce wheel + sdist for PyPI
242+
```
243+
244+
PyPI publishing is automated via `.github/workflows/deployment.yml` and triggers on Git tag push.
245+
246+
---
247+
248+
## CI Overview (`.github/workflows/`)
249+
250+
| Workflow | Trigger | What it does |
251+
|---|---|---|
252+
| `linting.yml` | push / PR | Runs Black, djlint, isort, pylint as parallel jobs |
253+
| `tests.yml` | push / PR | Starts Redis, builds Docker image, runs `pytest` on Python 3.10 & 3.11 |
254+
| `deployment.yml` | push / tag | Builds package; publishes to PyPI on tag push |

0 commit comments

Comments
 (0)