Skip to content

Commit

Permalink
feat: implement feeds endpoint (#79)
Browse files Browse the repository at this point in the history
* Develop feeds endpoint #32
* Develop feeds/{id} endpoint #35
* Develop metadata endpoint #51
  • Loading branch information
aronza authored Aug 20, 2023
1 parent 0c8dbf5 commit c761c16
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 39 deletions.
11 changes: 10 additions & 1 deletion api/src/database/database.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import uuid
from typing import Type

from google.cloud.sql.connector import Connector
from sqlalchemy import create_engine, inspect
Expand Down Expand Up @@ -88,13 +89,16 @@ def close_session(self):
self.logger.error(f"Session closing failed with exception: \n {e}")
return self.is_connected()

def select(self, model: Base, conditions: list = None, attributes: list = None, update_session: bool = True):
def select(self, model: Type[Base], conditions: list = None, attributes: list = None, update_session: bool = True,
limit: int = None, offset: int = None):
"""
Executes a query on the database
:param model: the sqlalchemy model to query
:param conditions: list of conditions (filters for the query)
:param attributes: list of model's attribute names that you want to fetch. If not given, fetches all attributes.
:param update_session: option to update session before running the query (defaults to True)
:param limit: the optional number of rows to limit the query with
:param offset: the optional number of rows to offset the query with
:return: None if database is inaccessible, the results of the query otherwise
"""
try:
Expand All @@ -106,6 +110,10 @@ def select(self, model: Base, conditions: list = None, attributes: list = None,
query = query.filter(condition)
if attributes:
query = query.options(load_only(*attributes))
if limit is not None:
query = query.limit(limit)
if offset is not None:
query = query.offset(offset)
return query.all()
except Exception as e:
self.logger.error(f'SELECT query failed with exception: \n{e}')
Expand Down Expand Up @@ -186,3 +194,4 @@ def merge_relationship(
return False


DB_ENGINE = Database()
34 changes: 31 additions & 3 deletions api/src/feeds/impl/feeds_api_impl.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
from datetime import date
from typing import List

from fastapi import HTTPException

from database_gen.sqlacodegen_models import Feed, Externalid, t_redirectingid
from feeds_gen.apis.feeds_api_base import BaseFeedsApi
from feeds_gen.models.basic_feed import BasicFeed
from feeds_gen.models.bounding_box import BoundingBox
from feeds_gen.models.external_id import ExternalId
from feeds_gen.models.extra_models import TokenModel
from feeds_gen.models.feed_log import FeedLog
from feeds_gen.models.gtfs_dataset import GtfsDataset
Expand All @@ -12,6 +16,8 @@
from feeds_gen.models.latest_dataset import LatestDataset
from feeds_gen.models.source_info import SourceInfo

from database.database import DB_ENGINE


class FeedsApiImpl(BaseFeedsApi):
"""
Expand All @@ -20,13 +26,34 @@ class FeedsApiImpl(BaseFeedsApi):
If a method is left blank the associated endpoint will return a 500 HTTP response.
"""

@staticmethod
def map_feed(feed: Feed):
"""
Maps sqlalchemy data model Feed to API data model BasicFeed
"""
redirects = DB_ENGINE.select(t_redirectingid, conditions=[feed.id == t_redirectingid.c.source_id])
external_ids = DB_ENGINE.select(Externalid, conditions=[feed.id == Externalid.feed_id])

return BasicFeed(id=feed.stable_id, data_type=feed.data_type, status=feed.status,
feed_name=feed.feed_name, note=feed.note, provider=feed.provider,
redirects=[redirect.target_id for redirect in redirects],
external_ids=[ExternalId(external_id=ext_id.associated_id, source=ext_id.source)
for ext_id in external_ids],
source_info=SourceInfo(producer_url=feed.producer_url,
authentication_type=feed.authentication_type,
authentication_info_url=feed.authentication_info_url,
api_key_parameter_name=feed.api_key_parameter_name,
license_url=feed.license_url))

def get_feed(
self,
id: str,
) -> BasicFeed:
"""Get the specified feed from the Mobility Database."""
return BasicFeed(id="gtfsFeedFoo", data_type=None, status=None, external_ids=[], provider="providerFoo",
feed_name="feedFoo", note="note", source_info=SourceInfo())
feeds = DB_ENGINE.select(Feed, conditions=[Feed.stable_id == id])
if len(feeds) == 1:
return self.map_feed(feeds[0])
raise HTTPException(status_code=404, detail=f"Feed {id} not found")

def get_feed_logs(
id: str,
Expand All @@ -47,7 +74,7 @@ def get_feeds(
sort: str,
) -> List[BasicFeed]:
"""Get some (or all) feeds from the Mobility Database."""
return [self.get_feed("gtfsFeedFoo")]
return [self.map_feed(feed) for feed in DB_ENGINE.select(Feed, limit=limit, offset=offset)]

def get_gtfs_feed(
self,
Expand Down Expand Up @@ -85,6 +112,7 @@ def get_gtfs_feeds(
bounding_filter_method: str,
) -> List[GtfsFeed]:
"""Get some (or all) GTFS feeds from the Mobility Database."""
print("In get_gtfs_feeds endpoint")
return [self.get_gtfs_feed("foo")]

def get_gtfs_rt_feed(
Expand Down
2 changes: 1 addition & 1 deletion api/src/feeds/impl/metadata_api_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ def get_metadata(
self,
) -> Metadata:
"""Get metadata about this API."""
return Metadata(version="0.0.0")
return Metadata(version="1.0.0")
14 changes: 5 additions & 9 deletions api/tests/test_datasets_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@

from fastapi.testclient import TestClient


from feeds.models.dataset import Dataset # noqa: F401


def test_datasets_gtfs_get(client: TestClient):
"""Test case for datasets_gtfs_get
Expand All @@ -17,7 +13,7 @@ def test_datasets_gtfs_get(client: TestClient):
}
response = client.request(
"GET",
"/datasets/gtfs",
"/v1/datasets/gtfs",
headers=headers,
params=params,
)
Expand All @@ -37,12 +33,12 @@ def test_datasets_gtfs_id_get(client: TestClient):
}
response = client.request(
"GET",
"/datasets/gtfs/{id}".format(id='dataset_0'),
"/v1/datasets/gtfs/{id}".format(id='dataset_0'),
headers=headers,
)

# uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200
assert response.status_code == 200


def test_feeds_gtfs_id_datasets_get(client: TestClient):
Expand All @@ -56,11 +52,11 @@ def test_feeds_gtfs_id_datasets_get(client: TestClient):
}
response = client.request(
"GET",
"/feeds/gtfs/{id}/datasets".format(id='feed_0'),
"/v1/feeds/gtfs/{id}/datasets".format(id='feed_0'),
headers=headers,
params=params,
)

# uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200
assert response.status_code == 200

21 changes: 8 additions & 13 deletions api/tests/test_feeds_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,8 @@
from fastapi.testclient import TestClient


from feeds.models.basic_feed import BasicFeed # noqa: F401
from feeds.models.gtfs_feed import GtfsFeed # noqa: F401


def test_feeds_get(client: TestClient):
"""Test case for feeds_get
"""
params = [("limit", 10), ("offset", 0), ("filter", 'status=active'), ("sort", '+provider')]
Expand All @@ -18,13 +13,13 @@ def test_feeds_get(client: TestClient):
}
response = client.request(
"GET",
"/feeds",
"/v1/feeds",
headers=headers,
params=params,
)

# uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200
assert response.status_code == 200


def test_feeds_gtfs_get(client: TestClient):
Expand All @@ -38,13 +33,13 @@ def test_feeds_gtfs_get(client: TestClient):
}
response = client.request(
"GET",
"/feeds/gtfs",
"/v1/feeds/gtfs",
headers=headers,
params=params,
)

# uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200
assert response.status_code == 200


def test_feeds_gtfs_id_get(client: TestClient):
Expand All @@ -58,12 +53,12 @@ def test_feeds_gtfs_id_get(client: TestClient):
}
response = client.request(
"GET",
"/feeds/gtfs/{id}".format(id='feed_0'),
"/v1/feeds/gtfs/{id}".format(id='feed_0'),
headers=headers,
)

# uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200
assert response.status_code == 200


def test_feeds_id_get(client: TestClient):
Expand All @@ -77,10 +72,10 @@ def test_feeds_id_get(client: TestClient):
}
response = client.request(
"GET",
"/feeds/{id}".format(id='feed_0'),
"/v1/feeds/{id}".format(id='feed_0'),
headers=headers,
)

# uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200
assert response.status_code == 200

7 changes: 2 additions & 5 deletions api/tests/test_metadata_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
from fastapi.testclient import TestClient


from feeds.models.metadata import Metadata # noqa: F401


def test_metadata_get(client: TestClient):
"""Test case for metadata_get
Expand All @@ -17,10 +14,10 @@ def test_metadata_get(client: TestClient):
}
response = client.request(
"GET",
"/metadata",
"/v1/metadata",
headers=headers,
)

# uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200
assert response.status_code == 200

8 changes: 3 additions & 5 deletions docs/DatabaseCatalogAPI.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -277,12 +277,10 @@ components:

external_ids:
$ref: "#/components/schemas/ExternalIds"
providers:
provider:
description: A commonly used name for the transit provider included in the feed.
type: array
items:
type: string
example: London Transit Commission
type: string
example: London Transit Commission
feed_name:
description: >
An optional description of the data feed, e.g to specify if the data feed is an aggregate of
Expand Down
3 changes: 1 addition & 2 deletions scripts/api-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,4 @@
# relative path
SCRIPT_PATH="$(dirname -- "${BASH_SOURCE[0]}")"
PORT=8080

(cd $SCRIPT_PATH/../api/src && uvicorn feeds_gen.main:app --host 0.0.0.0 --port $PORT)
(cd $SCRIPT_PATH/../api/src && uvicorn feeds_gen.main:app --host 0.0.0.0 --port $PORT --env-file ../../config/.env.local)

0 comments on commit c761c16

Please sign in to comment.