Skip to content

Commit ee13a5a

Browse files
feat: implement get_feeds endpoint with filtering and pagination support (#908)
* feat: implement get_feeds endpoint with filtering and pagination support * feat: implement ListFeedsRequest model with filtering and pagination support * feat: implement ListFeedsRequest model with filtering and pagination support * feat: implement get_feeds endpoint with filtering, pagination, and location support * feat: implement get_feeds endpoint with filtering, pagination, and location support * added get_feeds200_response.py * fix * Function refactoring to include entity for each datatype * replaced FeedResponse for based_feed model to align with api implementation * fix * fix * Delete functions-python/operations_api/.openapi-generator/FILES not required * Refactored feed validation and eager loading logic; re added ops_gen FILES * linter fix * Enhance feed processing by implementing from_orm methods for GTFS and GTFS-RT feeds; add location handling and improve logging * Update feeds_operations_impl.py lint fix * Update functions-python/helpers/query_helper.py Co-authored-by: David Gamez <[email protected]> * comment fix included usage of source_info --------- Co-authored-by: David Gamez <[email protected]>
1 parent 3e6d4ad commit ee13a5a

File tree

14 files changed

+1072
-33
lines changed

14 files changed

+1072
-33
lines changed

docs/OperationsAPI.yaml

Lines changed: 189 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,65 @@ tags:
2020
description: "Mobility Database Operations"
2121

2222
paths:
23+
/v1/operations/feeds:
24+
get:
25+
description: Get a list of feeds with optional filtering and pagination.
26+
operationId: getFeeds
27+
tags:
28+
- "operations"
29+
parameters:
30+
- name: operation_status
31+
in: query
32+
description: Filter feeds by operational status.
33+
required: false
34+
schema:
35+
type: string
36+
enum: [wip, published]
37+
- name: data_type
38+
in: query
39+
description: Filter feeds by data type.
40+
required: false
41+
schema:
42+
type: string
43+
enum: [gtfs, gtfs_rt]
44+
- name: offset
45+
in: query
46+
description: Number of items to skip for pagination.
47+
required: false
48+
schema:
49+
type: integer
50+
default: 0
51+
- name: limit
52+
in: query
53+
description: Maximum number of items to return.
54+
required: false
55+
schema:
56+
type: integer
57+
default: 50
58+
maximum: 100
59+
responses:
60+
200:
61+
description: List of feeds retrieved successfully.
62+
content:
63+
application/json:
64+
schema:
65+
type: object
66+
properties:
67+
total:
68+
type: integer
69+
offset:
70+
type: integer
71+
limit:
72+
type: integer
73+
feeds:
74+
type: array
75+
items:
76+
$ref: "#/components/schemas/BaseFeed"
77+
401:
78+
description: Unauthorized.
79+
500:
80+
description: Internal Server Error.
81+
2382
/v1/operations/feeds/gtfs:
2483
put:
2584
description: Update the specified GTFS feed in the Mobility Database.
@@ -82,10 +141,121 @@ paths:
82141
The request was not authenticated or has invalid authentication credentials.
83142
500:
84143
description: >
85-
An internal server error occurred.
144+
An internal server error occurred.
86145
87146
components:
88147
schemas:
148+
BaseFeed:
149+
type: object
150+
discriminator:
151+
propertyName: data_type
152+
mapping:
153+
gtfs: '#/components/schemas/GtfsFeedResponse'
154+
gtfs_rt: '#/components/schemas/GtfsRtFeedResponse'
155+
properties:
156+
id:
157+
type: string
158+
description: Unique identifier for the feed.
159+
stable_id:
160+
type: string
161+
description: Stable identifier for the feed.
162+
status:
163+
$ref: "#/components/schemas/FeedStatus"
164+
data_type:
165+
$ref: "#/components/schemas/DataType"
166+
provider:
167+
type: string
168+
description: Name of the transit provider.
169+
feed_name:
170+
type: string
171+
description: Name of the feed.
172+
note:
173+
type: string
174+
description: Additional notes about the feed.
175+
feed_contact_email:
176+
type: string
177+
description: Contact email for the feed.
178+
source_info:
179+
$ref: "#/components/schemas/SourceInfo"
180+
operational_status:
181+
type: string
182+
enum: [wip, published]
183+
description: Current operational status of the feed.
184+
created_at:
185+
type: string
186+
format: date-time
187+
description: When the feed was created.
188+
official:
189+
type: boolean
190+
description: Whether this is an official feed.
191+
official_updated_at:
192+
type: string
193+
format: date-time
194+
description: When the official status was last updated.
195+
locations:
196+
type: array
197+
items:
198+
$ref: "#/components/schemas/Location"
199+
external_ids:
200+
type: array
201+
items:
202+
$ref: "#/components/schemas/ExternalId"
203+
redirects:
204+
type: array
205+
items:
206+
$ref: "#/components/schemas/Redirect"
207+
208+
GtfsFeedResponse:
209+
allOf:
210+
- $ref: "#/components/schemas/BaseFeed"
211+
- type: object
212+
description: GTFS feed response model.
213+
214+
GtfsRtFeedResponse:
215+
allOf:
216+
- $ref: "#/components/schemas/BaseFeed"
217+
- type: object
218+
properties:
219+
entity_types:
220+
type: array
221+
items:
222+
type: string
223+
enum:
224+
- vp
225+
- tu
226+
- sa
227+
example: vp
228+
description: >
229+
The type of realtime entry:
230+
* vp - vehicle positions
231+
* tu - trip updates
232+
* sa - service alerts
233+
feed_references:
234+
description:
235+
A list of the GTFS feeds that the real time source is associated with, represented by their MDB source IDs.
236+
type: array
237+
items:
238+
type: string
239+
example: "mdb-20"
240+
241+
GetFeeds200Response:
242+
type: object
243+
properties:
244+
total:
245+
type: integer
246+
description: Total number of feeds matching the criteria.
247+
offset:
248+
type: integer
249+
description: Current offset for pagination.
250+
limit:
251+
type: integer
252+
description: Maximum number of items per page.
253+
feeds:
254+
type: array
255+
items:
256+
$ref: "#/components/schemas/BaseFeed"
257+
description: List of feeds.
258+
89259
Redirect:
90260
type: object
91261
properties:
@@ -142,8 +312,7 @@ components:
142312
type: array
143313
items:
144314
type: string
145-
example: "mdb-20"
146-
# This is a temporary fix as the operational status is not visible yet.
315+
example: "mdb-20"
147316
operational_status_action:
148317
type: string
149318
enum:
@@ -189,7 +358,6 @@ components:
189358
type: array
190359
items:
191360
$ref: "#/components/schemas/Redirect"
192-
# This is a temporary fix as the operational status is not visible yet.
193361
operational_status_action:
194362
type: string
195363
enum:
@@ -278,7 +446,7 @@ components:
278446

279447
DataType:
280448
description: >
281-
Describes data type of a fee. Should be one of
449+
Describes data type of a feed. Should be one of
282450
* `gtfs` GTFS feed.
283451
* `gtfs_rt` GTFS-RT feed.
284452
* `gbfs` GBFS feed.
@@ -303,20 +471,26 @@ components:
303471
- 2
304472
example: 2
305473

306-
parameters:
307-
feed_id_path_param:
308-
name: id
309-
in: path
310-
description: The feed ID of the requested feed.
311-
required: True
312-
schema:
313-
type: string
314-
example: mdb-1210
474+
Location:
475+
type: object
476+
properties:
477+
country_code:
478+
type: string
479+
description: ISO 3166-1 alpha-2 country code.
480+
country:
481+
type: string
482+
description: English name of the country.
483+
subdivision_name:
484+
type: string
485+
description: Name of the subdivision (state/province/region).
486+
municipality:
487+
type: string
488+
description: Name of the municipality.
315489

316490
securitySchemes:
317491
ApiKeyAuth:
318492
type: apiKey
319-
name: X-API-KEY
493+
name: x-api-key
320494
in: header
321495

322496
security:

functions-python/helpers/query_helper.py

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import logging
12
from typing import Type
23

34
from shared.database_gen.sqlacodegen_models import (
@@ -6,6 +7,9 @@
67
Gtfsfeed,
78
Gbfsfeed,
89
)
10+
from sqlalchemy import and_
11+
from sqlalchemy.orm import Session, joinedload
12+
from sqlalchemy.orm.query import Query
913

1014
feed_mapping = {"gtfs_rt": Gtfsrealtimefeed, "gtfs": Gtfsfeed, "gbfs": Gbfsfeed}
1115

@@ -18,10 +22,114 @@ def get_model(data_type: str | None) -> Type[Feed]:
1822

1923

2024
def query_feed_by_stable_id(
21-
session, stable_id: str, data_type: str | None
25+
session: Session, stable_id: str, data_type: str | None
2226
) -> Gtfsrealtimefeed | Gtfsfeed | Gbfsfeed:
2327
"""
2428
Query the feed by stable id
2529
"""
2630
model = get_model(data_type)
2731
return session.query(model).filter(model.stable_id == stable_id).first()
32+
33+
34+
def get_eager_loading_options(model: Type[Feed]):
35+
"""
36+
Get the appropriate eager loading options based on the model type.
37+
38+
Args:
39+
model: The SQLAlchemy model class
40+
41+
Returns:
42+
List of joinedload options for the query
43+
"""
44+
if model == Gtfsrealtimefeed:
45+
logging.info("Adding GTFS-RT specific eager loading")
46+
return [
47+
joinedload(Gtfsrealtimefeed.locations),
48+
joinedload(Gtfsrealtimefeed.entitytypes),
49+
joinedload(Gtfsrealtimefeed.gtfs_feeds),
50+
joinedload(Gtfsrealtimefeed.externalids),
51+
joinedload(Gtfsrealtimefeed.redirectingids),
52+
]
53+
elif model == Gtfsfeed:
54+
logging.info("Adding GTFS specific eager loading")
55+
return [
56+
joinedload(Gtfsfeed.locations),
57+
joinedload(Gtfsfeed.externalids),
58+
joinedload(Gtfsfeed.redirectingids),
59+
]
60+
else:
61+
logging.info("Adding base Feed eager loading")
62+
return [
63+
joinedload(Feed.locations),
64+
joinedload(Feed.externalids),
65+
joinedload(Feed.redirectingids),
66+
joinedload(Gtfsrealtimefeed.entitytypes),
67+
joinedload(Gtfsrealtimefeed.gtfs_feeds),
68+
]
69+
70+
71+
def get_feeds_query(
72+
db_session: Session,
73+
operation_status: str | None = None,
74+
data_type: str | None = None,
75+
limit: int | None = None,
76+
offset: int | None = None,
77+
) -> Query:
78+
"""
79+
Build a consolidated query for feeds with filtering options.
80+
81+
Args:
82+
db_session: SQLAlchemy session
83+
operation_status: Optional filter for operational status (wip or published)
84+
data_type: Optional filter for feed type (gtfs or gtfs_rt)
85+
limit: Maximum number of items to return
86+
offset: Number of items to skip
87+
88+
Returns:
89+
Query: SQLAlchemy query object
90+
"""
91+
try:
92+
logging.info(
93+
"Building query with params: data_type=%s, operation_status=%s",
94+
data_type,
95+
operation_status,
96+
)
97+
98+
if data_type == "gtfs":
99+
model = Gtfsfeed
100+
elif data_type == "gtfs_rt":
101+
model = Gtfsrealtimefeed # Force concrete model
102+
else:
103+
model = Feed
104+
105+
logging.info(f"Using concrete model: {model.__name__}")
106+
107+
conditions = []
108+
109+
if operation_status:
110+
conditions.append(model.operational_status == operation_status)
111+
logging.info("Added operational_status filter: %s", operation_status)
112+
113+
query = db_session.query(model)
114+
logging.info("Created base query with model %s", model.__name__)
115+
116+
eager_loading_options = get_eager_loading_options(model)
117+
query = query.options(*eager_loading_options)
118+
119+
if conditions:
120+
query = query.filter(and_(*conditions))
121+
logging.info("Applied conditions: %s", conditions)
122+
123+
query = query.order_by(model.provider, model.stable_id)
124+
125+
if offset is not None:
126+
query = query.offset(offset)
127+
if limit is not None:
128+
query = query.limit(limit)
129+
130+
logging.info("Generated SQL Query: %s", query)
131+
return query
132+
133+
except Exception as e:
134+
logging.error("Error building query: %s", str(e))
135+
raise

0 commit comments

Comments
 (0)