Skip to content

Commit 357a65e

Browse files
authored
feat: operation api OpenAPI sync with Mobility Database Catalog API (#1415)
1 parent ac5a1ba commit 357a65e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1561
-769
lines changed

.github/copilot-instructions.md

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Mobility Feed API - AI Coding Assistant Instructions
2+
3+
## Project Architecture
4+
5+
This is a **mobility data API service** built with FastAPI, serving open mobility data from across the world. The architecture follows a **code-generation pattern** with clear separation between generated and implementation code.
6+
7+
### Core Components
8+
9+
- **`api/`**: Main FastAPI application with spec-first development using OpenAPI Generator
10+
- **`functions-python/`**: Google Cloud Functions for data processing (batch jobs, validation, analytics)
11+
- **`web-app/`**: Frontend application
12+
- **PostgreSQL + PostGIS**: Database with geospatial support for mobility data
13+
14+
### Key Generated vs Implementation Split
15+
16+
- **Generated code** (never edit): `api/src/feeds_gen/` and `api/src/shared/database_gen/`
17+
- **Implementation code**: `api/src/feeds/impl/` contains actual business logic
18+
- **Schema source**: `docs/DatabaseCatalogAPI.yaml` drives code generation
19+
20+
## Critical Development Workflows
21+
22+
### Initial Setup
23+
```bash
24+
# One-time OpenAPI setup
25+
scripts/setup-openapi-generator.sh
26+
27+
# Install dependencies
28+
cd api && pip3 install -r requirements.txt -r requirements_dev.txt
29+
30+
# Start local database
31+
docker-compose --env-file ./config/.env.local up -d --force-recreate
32+
33+
# Generate API stubs (run after schema changes)
34+
scripts/api-gen.sh
35+
scripts/db-gen.sh
36+
```
37+
38+
### Common Development Commands
39+
```bash
40+
# Start API server (includes Swagger UI at http://localhost:8080/docs/)
41+
scripts/api-start.sh
42+
43+
# Run tests with coverage
44+
scripts/api-tests.sh
45+
# Run specific test file
46+
scripts/api-tests.sh my_test_file.py
47+
48+
# Lint checks (Flake8 + Black)
49+
scripts/lint-tests.sh
50+
51+
# Reset and populate local database
52+
./scripts/docker-localdb-rebuild-data.sh --populate-db
53+
# Include test datasets
54+
./scripts/docker-localdb-rebuild-data.sh --populate-db --populate-test-data
55+
```
56+
57+
## Project-Specific Patterns
58+
59+
### Error Handling Convention
60+
- Use `shared.common.error_handling.InternalHTTPException` for internal errors
61+
- Convert to FastAPI HTTPException using `feeds.impl.error_handling.convert_exception()`
62+
- Store error messages as Finals in `api/src/feeds/impl/error_handling.py`
63+
- Error responses follow: `{"details": "The error message"}`
64+
65+
### Database Patterns
66+
- **Polymorphic inheritance**: `Feed` base class with `GtfsFeed`, `GbfsFeed`, `GtfsRTFeed` subclasses
67+
- **SQLAlchemy ORM**: Models in `shared/database_gen/sqlacodegen_models.py` (generated)
68+
- **Session management**: Use `@with_db_session` decorator for database operations
69+
- **Unique IDs**: Generate with `generate_unique_id()` (36-char UUID4)
70+
71+
### API Implementation Structure
72+
- Endpoints in `feeds/impl/*_api_impl.py` extend generated base classes from `feeds_gen/`
73+
- Filter classes in `shared/feed_filters/` for query parameter handling
74+
- Model implementations in `feeds/impl/models/` extend generated models
75+
76+
### Code Generation Workflow
77+
1. Modify `docs/DatabaseCatalogAPI.yaml` for API changes
78+
2. Run `scripts/api-gen.sh` to regenerate FastAPI stubs
79+
3. Run `scripts/db-gen.sh` for database schema changes
80+
4. Implement business logic in `feeds/impl/` classes
81+
82+
### Testing Patterns
83+
- Tests use empty local test DB (reset with `--use-test-db` flag)
84+
- Coverage reports in `scripts/coverage_reports/`
85+
- Python path configured to `src/` in `pyproject.toml`
86+
87+
### Functions Architecture
88+
- **Google Cloud Functions** in `functions-python/` for background processing
89+
- Shared database models via `database_gen/` symlink
90+
- Each function has its own deployment configuration
91+
- Tasks include: validation reports, batch datasets, GBFS validation, BigQuery ingestion
92+
93+
### Authentication
94+
- **OAuth2 Bearer tokens** for API access
95+
- Refresh tokens from mobilitydatabase.org account
96+
- Access tokens valid for 1 hour
97+
- Test endpoint: `/v1/metadata` with Bearer token
98+
99+
## Integration Points
100+
101+
- **BigQuery**: Analytics data pipeline via `big_query_ingestion/` function
102+
- **PostGIS**: Geospatial queries for location-based feed filtering
103+
- **Liquibase**: Database schema migrations in `liquibase/` directory
104+
- **Docker**: Multi-service setup with PostgreSQL, test DB, and schema documentation
105+
106+
## File Exclusions for AI Context
107+
- Skip `src/feeds_gen/*` and `src/shared/database_gen/*` (generated code)
108+
- Skip `data/` and `data-test/` (database volumes)
109+
- Skip `htmlcov/` (coverage reports)
110+
- Black formatter excludes these paths automatically

.github/workflows/api-deployer.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ jobs:
260260
- uses: actions/download-artifact@v4
261261
with:
262262
name: feeds_operations_gen
263-
path: functions-python/operations_api/src/feeds_operations_gen/
263+
path: functions-python/operations_api/src/feeds_gen/
264264

265265
- name: Build python functions
266266
run: |

.github/workflows/build-test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,5 +140,5 @@ jobs:
140140
uses: actions/upload-artifact@v4
141141
with:
142142
name: feeds_operations_gen
143-
path: functions-python/operations_api/src/feeds_operations_gen/
143+
path: functions-python/operations_api/src/feeds_gen/
144144
overwrite: true

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,7 @@ tf.plan
8080
functions-python/**/*.csv
8181

8282
# Local emulators
83-
.cloudstorage
83+
.cloudstorage
84+
85+
# Project files
86+
*.code-workspace

api/src/shared/db_models/gtfs_dataset_impl.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from shared.db_models.bounding_box_impl import BoundingBoxImpl
66
from shared.db_models.validation_report_impl import ValidationReportImpl
77
from feeds_gen.models.gtfs_dataset import GtfsDataset
8-
from utils.model_utils import compare_java_versions
8+
from shared.db_models.model_utils import compare_java_versions
99

1010

1111
class GtfsDatasetImpl(GtfsDataset):

api/src/shared/db_models/gtfs_rt_feed_impl.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def from_orm(cls, feed: GtfsRTFeedOrm | None) -> GtfsRTFeed | None:
1818
gtfs_rt_feed: GtfsRTFeed = super().from_orm(feed)
1919
if not gtfs_rt_feed:
2020
return None
21-
gtfs_rt_feed.locations = [LocationImpl.from_orm(item) for item in feed.locations]
22-
gtfs_rt_feed.entity_types = [item.name for item in feed.entitytypes]
23-
gtfs_rt_feed.feed_references = [item.stable_id for item in feed.gtfs_feeds]
21+
gtfs_rt_feed.locations = [LocationImpl.from_orm(item) for item in feed.locations] if feed.locations else []
22+
gtfs_rt_feed.entity_types = [item.name for item in feed.entitytypes] if feed.entitytypes else []
23+
gtfs_rt_feed.feed_references = [item.stable_id for item in feed.gtfs_feeds] if feed.gtfs_feeds else []
2424
return gtfs_rt_feed

api/src/shared/db_models/latest_dataset_impl.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from shared.db_models.bounding_box_impl import BoundingBoxImpl
55
from feeds_gen.models.latest_dataset import LatestDataset
66
from feeds_gen.models.latest_dataset_validation_report import LatestDatasetValidationReport
7-
from utils.model_utils import compare_java_versions
7+
from shared.db_models.model_utils import compare_java_versions
88

99

1010
class LatestDatasetImpl(LatestDataset):
File renamed without changes.

api/tests/utils/test_compare_java_versions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import unittest
2-
from utils.model_utils import compare_java_versions
2+
from shared.db_models.model_utils import compare_java_versions
33

44

55
class TestCompareJavaVersions(unittest.TestCase):

0 commit comments

Comments
 (0)