Skip to content

Commit 5656197

Browse files
committed
add update gtfs_rt feed
1 parent 5bed790 commit 5656197

File tree

6 files changed

+308
-67
lines changed

6 files changed

+308
-67
lines changed

docs/OperationsAPI.yaml

Lines changed: 54 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,38 @@ paths:
5252
description: >
5353
An internal server error occurred.
5454
55+
/v1/operations/feeds/gtfs_rt:
56+
put:
57+
description: Update the specified GTFS-RT feed in the Mobility Database.
58+
tags:
59+
- "operations"
60+
operationId: updateGtfsRtFeed
61+
security:
62+
- ApiKeyAuth: []
63+
requestBody:
64+
description: Payload to update the specified GTFS-RT feed.
65+
required: true
66+
content:
67+
application/json:
68+
schema:
69+
$ref: "#/components/schemas/UpdateRequestGtfsRtFeed"
70+
responses:
71+
200:
72+
description: >
73+
The feed was successfully updated. No content is returned.
74+
204:
75+
description: >
76+
The feed update request was successfully received, but the update process was skipped as the request matches with the source feed.
77+
400:
78+
description: >
79+
The request was invalid.
80+
401:
81+
description: >
82+
The request was not authenticated or has invalid authentication credentials.
83+
500:
84+
description: >
85+
An internal server error occurred.
86+
5587
components:
5688
schemas:
5789
Redirect:
@@ -65,27 +97,16 @@ components:
6597
description: A comment explaining the redirect.
6698
type: string
6799
example: Redirected because of a change of URL.
68-
BasicFeed:
100+
101+
UpdateRequestGtfsRtFeed:
69102
type: object
70-
discriminator:
71-
propertyName: data_type
72-
mapping:
73-
gtfs: '#/components/schemas/GtfsFeed'
74-
gtfs_rt: '#/components/schemas/GtfsRTFeed'
75103
properties:
76104
id:
77105
description: Unique identifier used as a key for the feeds table.
78106
type: string
79107
example: mdb-1210
80-
data_type:
81-
$ref: "#/components/schemas/DataType"
82108
status:
83-
$ref: "#/components/schemas/FeedStatus"
84-
created_at:
85-
description: The date and time the feed was added to the database, in ISO 8601 date-time format.
86-
type: string
87-
example: 2023-07-10T22:06:00Z
88-
format: date-time
109+
$ref: "#/components/schemas/FeedStatus"
89110
external_ids:
90111
$ref: "#/components/schemas/ExternalIds"
91112
provider:
@@ -104,46 +125,35 @@ components:
104125
feed_contact_email:
105126
description: Use to contact the feed producer.
106127
type: string
107-
format: email
108128
109129
source_info:
110130
$ref: "#/components/schemas/SourceInfo"
111131
redirects:
112132
type: array
113133
items:
114134
$ref: "#/components/schemas/Redirect"
135+
entity_types:
136+
type: array
137+
items:
138+
$ref: "#/components/schemas/EntityType"
139+
feed_references:
140+
description:
141+
A list of the GTFS feeds that the real time source is associated with, represented by their MDB source IDs.
142+
type: array
143+
items:
144+
type: string
145+
example: "mdb-20"
146+
# This is a temporary fix as the operational status is not visible yet.
147+
operational_status_action:
148+
type: string
149+
enum:
150+
- no_change
151+
- wip
152+
- clear
115153
required:
116154
- id
117-
- data_type
118155
- status
119-
120-
GtfsFeed:
121-
allOf:
122-
- $ref: "#/components/schemas/BasicFeed"
123-
- type: object
124-
# TODO add this properties when implementing the get endpoint
125-
# properties:
126-
# locations:
127-
# $ref: "#/components/schemas/Locations"
128-
# latest_dataset:
129-
# $ref: "#/components/schemas/LatestDataset"
130-
131-
UpdateRequestGtfsRtFeed:
132-
allOf:
133-
- $ref: "#/components/schemas/BasicFeed"
134-
- type: object
135-
properties:
136-
entity_types:
137-
type: array
138-
items:
139-
$ref: "#/components/schemas/EntityType"
140-
feed_references:
141-
description:
142-
A list of the GTFS feeds that the real time source is associated with, represented by their MDB source IDs.
143-
type: array
144-
items:
145-
type: string
146-
example: "mdb-20"
156+
- entity_types
147157

148158
UpdateRequestGtfsFeed:
149159
type: object

functions-python/helpers/database.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828

2929

3030
def set_cascade(mapper, class_):
31+
"""
32+
Set cascade for relationships in Gtfsfeed.
33+
This allows to delete/add the relationships when their respective relation array changes.
34+
"""
3135
if class_.__name__ == "Gtfsfeed":
3236
for rel in class_.__mapper__.relationships:
3337
if rel.key in [

functions-python/operations_api/.openapi-generator/FILES

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,11 @@ src/feeds_operations_gen/apis/operations_api_base.py
55
src/feeds_operations_gen/main.py
66
src/feeds_operations_gen/models/__init__.py
77
src/feeds_operations_gen/models/authentication_type.py
8-
src/feeds_operations_gen/models/basic_feed.py
98
src/feeds_operations_gen/models/data_type.py
109
src/feeds_operations_gen/models/entity_type.py
1110
src/feeds_operations_gen/models/external_id.py
1211
src/feeds_operations_gen/models/extra_models.py
1312
src/feeds_operations_gen/models/feed_status.py
14-
src/feeds_operations_gen/models/gtfs_feed.py
1513
src/feeds_operations_gen/models/redirect.py
1614
src/feeds_operations_gen/models/source_info.py
1715
src/feeds_operations_gen/models/update_request_gtfs_feed.py

functions-python/operations_api/src/feeds_operations/impl/feeds_operations_impl.py

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,27 @@
1616

1717
import logging
1818
import os
19-
from typing import Annotated
19+
from typing import Annotated, Type
2020

21+
from deepdiff import DeepDiff
2122
from fastapi import HTTPException
2223
from pydantic import Field
2324
from starlette.responses import Response
2425

25-
from database_gen.sqlacodegen_models import Gtfsfeed
26+
from database_gen.sqlacodegen_models import Gtfsfeed, t_feedsearch
2627
from feeds_operations.impl.models.update_request_gtfs_feed_impl import (
2728
UpdateRequestGtfsFeedImpl,
2829
)
29-
from .request_validator import validate_request
3030
from feeds_operations_gen.apis.operations_api_base import BaseOperationsApi
3131
from feeds_operations_gen.models.data_type import DataType
3232
from feeds_operations_gen.models.update_request_gtfs_feed import UpdateRequestGtfsFeed
33-
from helpers.database import start_db_session
33+
from feeds_operations_gen.models.update_request_gtfs_rt_feed import (
34+
UpdateRequestGtfsRtFeed,
35+
)
36+
from helpers.database import start_db_session, refresh_materialized_view
3437
from helpers.query_helper import query_feed_by_stable_id
35-
from deepdiff import DeepDiff
38+
from .models.update_request_gtfs_rt_feed_impl import UpdateRequestGtfsRtFeedImpl
39+
from .request_validator import validate_request
3640

3741
logging.basicConfig(level=logging.INFO)
3842

@@ -44,20 +48,22 @@ class OperationsApiImpl(BaseOperationsApi):
4448

4549
@staticmethod
4650
def detect_changes(
47-
feed: Gtfsfeed, update_request_gtfs_feed: UpdateRequestGtfsFeed
51+
feed: Gtfsfeed,
52+
update_request_feed: UpdateRequestGtfsFeed | UpdateRequestGtfsRtFeed,
53+
impl_class: Type[UpdateRequestGtfsFeedImpl] | Type[UpdateRequestGtfsRtFeedImpl],
4854
) -> DeepDiff:
4955
"""
5056
Detect changes between the feed and the update request.
5157
"""
5258
# Normalize the feed and the update request and compare them
53-
copy_feed = UpdateRequestGtfsFeedImpl.from_orm(feed)
59+
copy_feed = impl_class.from_orm(feed)
5460
# Temporary solution to update the operational status
5561
copy_feed.operational_status_action = (
56-
update_request_gtfs_feed.operational_status_action
62+
update_request_feed.operational_status_action
5763
)
5864
diff = DeepDiff(
5965
copy_feed.model_dump(),
60-
update_request_gtfs_feed.model_dump(),
66+
update_request_feed.model_dump(),
6167
ignore_order=True,
6268
)
6369
if diff.affected_paths:
@@ -68,7 +74,7 @@ def detect_changes(
6874
logging.info("Detect update changes: no changes detected")
6975
return diff
7076

71-
@validate_request(UpdateRequestGtfsFeed, "update_request_gtfs_feed")
77+
@validate_request(Type[UpdateRequestGtfsFeed], "update_request_gtfs_feed")
7278
async def update_gtfs_feed(
7379
self,
7480
update_request_gtfs_feed: Annotated[
@@ -84,36 +90,67 @@ async def update_gtfs_feed(
8490
- 500: Internal server error.
8591
"""
8692
...
93+
return await self._update_feed(update_request_gtfs_feed, DataType.GTFS)
94+
95+
@validate_request(Type[UpdateRequestGtfsRtFeed], "update_request_gtfs_rt_feed")
96+
async def update_gtfs_rt_feed(
97+
self,
98+
update_request_gtfs_rt_feed: Annotated[
99+
UpdateRequestGtfsRtFeed,
100+
Field(description="Payload to update the specified GTFS-RT feed."),
101+
],
102+
) -> Response:
103+
"""Update the specified GTFS-RT feed in the Mobility Database.
104+
returns:
105+
- 200: Feed updated successfully.
106+
- 204: No changes detected.
107+
- 400: Feed ID not found.
108+
- 500: Internal server error.
109+
"""
110+
return await self._update_feed(update_request_gtfs_rt_feed, DataType.GTFS_RT)
111+
112+
async def _update_feed(
113+
self,
114+
update_request_feed: UpdateRequestGtfsFeed | UpdateRequestGtfsRtFeed,
115+
data_type: DataType,
116+
) -> Response:
117+
"""
118+
Update the specified feed in the Mobility Database
119+
"""
87120
session = None
88121
try:
89122
session = start_db_session(os.getenv("FEEDS_DATABASE_URL"))
90123
feed: Gtfsfeed = query_feed_by_stable_id(
91-
session, update_request_gtfs_feed.id, DataType.GTFS.name
124+
session, update_request_feed.id, data_type.name
92125
)
93126
if feed is None:
94127
raise HTTPException(
95128
status_code=400,
96-
detail=f"Feed ID not found: {update_request_gtfs_feed.id}",
129+
detail=f"Feed ID not found: {update_request_feed.id}",
97130
)
98131

99132
logging.info(
100-
f"Feed ID: {id} attempting to update with the following request: {update_request_gtfs_feed}"
133+
f"Feed ID: {id} attempting to update with the following request: {update_request_feed}"
101134
)
102-
diff = self.detect_changes(feed, update_request_gtfs_feed)
135+
impl_class = (
136+
UpdateRequestGtfsFeedImpl
137+
if data_type == DataType.GTFS
138+
else UpdateRequestGtfsRtFeedImpl
139+
)
140+
diff = self.detect_changes(feed, update_request_feed, impl_class)
103141
if len(diff.affected_paths) > 0 or (
104-
update_request_gtfs_feed.operational_status_action is not None
105-
and update_request_gtfs_feed.operational_status_action != "no_change"
142+
update_request_feed.operational_status_action is not None
143+
and update_request_feed.operational_status_action != "no_change"
106144
):
107-
UpdateRequestGtfsFeedImpl.to_orm(
108-
update_request_gtfs_feed, feed, session
109-
)
145+
impl_class.to_orm(update_request_feed, feed, session)
110146
# This is a temporary solution as the operational_status is not visible in the diff
111147
feed.operational_status = (
112148
feed.operational_status
113-
if update_request_gtfs_feed.operational_status_action == "no_change"
114-
else update_request_gtfs_feed.operational_status_action
149+
if update_request_feed.operational_status_action == "no_change"
150+
else update_request_feed.operational_status_action
115151
)
116152
session.add(feed)
153+
refresh_materialized_view(session, t_feedsearch.name)
117154
session.commit()
118155
logging.info(
119156
f"Feed ID: {id} updated successfully with the following changes: {diff.values()}"
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from feeds_operations_gen.models.entity_type import EntityType
2+
from database_gen.sqlacodegen_models import Entitytype as EntityTypeOrm
3+
4+
5+
class EntityTypeImpl(EntityType):
6+
"""Implementation of the EntityType model.
7+
This class converts a SQLAlchemy row DB object with the gtfs feed fields to a Pydantic model.
8+
"""
9+
10+
class Config:
11+
"""Pydantic configuration.
12+
Enabling `from_attributes` method to create a model instance from a SQLAlchemy row object.
13+
"""
14+
15+
from_attributes = True
16+
17+
@classmethod
18+
def from_orm(cls, obj: EntityTypeOrm | None) -> EntityType | None:
19+
"""
20+
Convert a SQLAlchemy row object to a Pydantic model.
21+
"""
22+
if obj is None:
23+
return None
24+
return EntityType(obj.name)
25+
26+
@classmethod
27+
def to_orm(cls, entity_type: EntityType) -> EntityTypeOrm:
28+
"""
29+
Convert a Pydantic model to a SQLAlchemy row object.
30+
"""
31+
return EntityTypeOrm(name=entity_type.name)

0 commit comments

Comments
 (0)