forked from RolnickLab/ami-data-companion
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathantenna_api_server.py
More file actions
210 lines (156 loc) · 5.73 KB
/
Copy pathantenna_api_server.py
File metadata and controls
210 lines (156 loc) · 5.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
"""Mock Antenna API server for integration testing.
This module provides a FastAPI application that mocks the Antenna API endpoints
used by the worker. It allows tests to validate the API contract without
requiring an actual Antenna server.
"""
from fastapi import FastAPI, HTTPException
from trapdata.antenna.schemas import (
AntennaJobListItem,
AntennaJobsListResponse,
AntennaPipelineProcessingTask,
AntennaTaskResult,
AntennaTasksListResponse,
AsyncPipelineRegistrationRequest,
AsyncPipelineRegistrationResponse,
)
app = FastAPI()
# State management for tests
_jobs_queue: dict[int, list[AntennaPipelineProcessingTask]] = {}
_posted_results: dict[int, list[AntennaTaskResult]] = {}
_projects: list[dict] = []
_registered_pipelines: dict[int, list[str]] = {} # project_id -> pipeline slugs
_last_get_jobs_service_name: str = ""
@app.get("/api/v2/jobs")
def get_jobs(
pipeline__slug: str,
ids_only: int,
incomplete_only: int,
processing_service_name: str = "",
):
"""Return available job IDs.
Args:
pipeline__slug: Pipeline slug filter
ids_only: If 1, return only job IDs
incomplete_only: If 1, return only incomplete jobs
processing_service_name: Name of the processing service making the request
Returns:
AntennaJobsListResponse with list of job IDs
"""
global _last_get_jobs_service_name
_last_get_jobs_service_name = processing_service_name
# Return all jobs in queue (for testing, we return all registered jobs)
job_ids = list(_jobs_queue.keys())
results = [AntennaJobListItem(id=job_id) for job_id in job_ids]
return AntennaJobsListResponse(results=results)
@app.get("/api/v2/jobs/{job_id}/tasks")
def get_tasks(job_id: int, batch: int):
"""Return batch of tasks (atomically remove from queue).
Args:
job_id: Job ID to fetch tasks for
batch: Number of tasks to return
Returns:
AntennaTasksListResponse with batch of tasks
"""
if job_id not in _jobs_queue:
return AntennaTasksListResponse(tasks=[])
# Get up to `batch` tasks and remove them from queue
tasks = _jobs_queue[job_id][:batch]
_jobs_queue[job_id] = _jobs_queue[job_id][batch:]
return AntennaTasksListResponse(tasks=tasks)
@app.post("/api/v2/jobs/{job_id}/result/")
def post_results(job_id: int, payload: list[dict]):
"""Store posted results for test validation.
Args:
job_id: Job ID to post results for
payload: List of AntennaTaskResult dicts
Returns:
Success status
"""
if job_id not in _posted_results:
_posted_results[job_id] = []
# Parse each result dict into AntennaTaskResult
for result_dict in payload:
task_result = AntennaTaskResult(**result_dict)
_posted_results[job_id].append(task_result)
return {"status": "ok"}
@app.get("/api/v2/projects/")
def get_projects():
"""Return list of projects the user has access to.
Returns:
Paginated response with list of projects
"""
return {"results": _projects}
@app.post("/api/v2/projects/{project_id}/pipelines/")
def register_pipelines(project_id: int, payload: dict):
"""Register pipelines for a project.
Args:
project_id: Project ID to register pipelines for
payload: AsyncPipelineRegistrationRequest as dict
Returns:
AsyncPipelineRegistrationResponse
"""
# Validate request
request = AsyncPipelineRegistrationRequest(**payload)
# Check if project exists
project_ids = [p["id"] for p in _projects]
if project_id not in project_ids:
raise HTTPException(status_code=404, detail="Project not found")
# Track registered pipelines
if project_id not in _registered_pipelines:
_registered_pipelines[project_id] = []
created = []
for pipeline in request.pipelines:
if pipeline.slug not in _registered_pipelines[project_id]:
_registered_pipelines[project_id].append(pipeline.slug)
created.append(pipeline.slug)
return AsyncPipelineRegistrationResponse(
pipelines_created=created,
pipelines_updated=[],
processing_service_id=1,
)
# Test helper methods
def setup_job(job_id: int, tasks: list[AntennaPipelineProcessingTask]):
"""Populate job queue for testing.
Args:
job_id: Job ID to setup
tasks: List of tasks to add to the queue
"""
_jobs_queue[job_id] = tasks.copy()
def get_posted_results(job_id: int) -> list[AntennaTaskResult]:
"""Retrieve results posted by worker.
Args:
job_id: Job ID to get results for
Returns:
List of posted task results
"""
return _posted_results.get(job_id, [])
def setup_projects(projects: list[dict]):
"""Setup projects for testing.
Args:
projects: List of project dicts with 'id' and 'name' fields
"""
_projects.clear()
_projects.extend(projects)
def get_registered_pipelines(project_id: int) -> list[str]:
"""Get list of pipeline slugs registered for a project.
Args:
project_id: Project ID to get pipelines for
Returns:
List of pipeline slugs
"""
return _registered_pipelines.get(project_id, [])
def get_last_get_jobs_service_name() -> str:
"""Return the processing_service_name received by the last get_jobs call.
Returns:
The processing_service_name value from the most recent GET /jobs request,
or an empty string if no request has been made since the last reset().
"""
return _last_get_jobs_service_name
def reset():
"""Clear all state between tests."""
global _last_get_jobs_service_name
_jobs_queue.clear()
_posted_results.clear()
_projects.clear()
_registered_pipelines.clear()
_last_get_jobs_service_name = ""