Skip to content
This repository has been archived by the owner on Jun 3, 2024. It is now read-only.

Commit

Permalink
Direction depart time (#368)
Browse files Browse the repository at this point in the history
* directions: specify arrive_by or depart_at parameters

* directions: fix arrive/depart query params

* directions: add basic tests for arrive_by/depart_at

* tiny lint fixes

* tiny lint fixes in tests
  • Loading branch information
remi-dupre committed Jul 19, 2022
1 parent 6d9303e commit b084255
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 8 deletions.
34 changes: 28 additions & 6 deletions idunn/api/directions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from datetime import datetime
from fastapi import HTTPException, Query, Path, Request, Response, Depends
from pydantic import confloat
from typing import Optional

from idunn import settings
from idunn.places import Latlon
Expand All @@ -19,23 +21,34 @@ def directions_request(request: Request, response: Response):

async def get_directions_with_coordinates(
# URL values
f_lon: confloat(ge=-180, le=180) = Path(..., title="Origin point longitude"),
f_lat: confloat(ge=-90, le=90) = Path(..., title="Origin point latitude"),
t_lon: confloat(ge=-180, le=180) = Path(..., title="Destination point longitude"),
t_lat: confloat(ge=-90, le=90) = Path(..., title="Destination point latitude"),
f_lon: confloat(ge=-180, le=180) = Path(title="Origin point longitude"),
f_lat: confloat(ge=-90, le=90) = Path(title="Origin point latitude"),
t_lon: confloat(ge=-180, le=180) = Path(title="Destination point longitude"),
t_lat: confloat(ge=-90, le=90) = Path(title="Destination point latitude"),
# Query parameters
type: str = Query(..., description="Transport mode"),
language: str = "en",
# Time parameters
arrive_by: Optional[datetime] = Query(None, title="Local arrival time"),
depart_at: Optional[datetime] = Query(None, title="Local departure time"),
# Request
request: Request = Depends(directions_request),
):
"""Get directions to get from a point to another."""
from_place = Latlon(f_lat, f_lon)
to_place = Latlon(t_lat, t_lon)

if arrive_by and depart_at:
raise HTTPException(
status_code=400,
detail="`arrive_by` and `depart_at` can't both be specified",
)

if not type:
raise HTTPException(status_code=400, detail='"type" query param is required')

return await directions_client.get_directions(
from_place, to_place, type, language, extra=request.query_params
from_place, to_place, type, language, arrive_by, depart_at, extra=request.query_params
)


Expand All @@ -45,6 +58,9 @@ async def get_directions(
destination: str = Query(..., description="Destination place id."),
type: str = Query(..., description="Transport mode."),
language: str = Query("en", description="User language."),
# Time parameters
arrive_by: Optional[datetime] = Query(None, title="Local arrival time"),
depart_at: Optional[datetime] = Query(None, title="Local departure time"),
# Request
request: Request = Depends(directions_request),
):
Expand All @@ -55,6 +71,12 @@ async def get_directions(
except IdunnPlaceError as exc:
raise HTTPException(status_code=404, detail=exc.message) from exc

if arrive_by and depart_at:
raise HTTPException(
status_code=400,
detail="`arrive_by` and `depart_at` can't both be specified",
)

return await directions_client.get_directions(
from_place, to_place, type, language, extra=request.query_params
from_place, to_place, type, language, arrive_by, depart_at, extra=request.query_params
)
7 changes: 6 additions & 1 deletion idunn/datasources/directions/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
from abc import ABC, abstractmethod, abstractproperty
from datetime import datetime
from fastapi import HTTPException
from pydantic import BaseModel
from typing import Callable, Optional
Expand Down Expand Up @@ -58,6 +59,8 @@ async def get_directions(
to_place: BasePlace,
mode: IdunnTransportMode,
lang: str,
arrive_by: Optional[datetime],
depart_at: Optional[datetime],
extra: Optional[QueryParams] = None,
) -> DirectionsResponse:
idunn_mode = IdunnTransportMode.parse(mode)
Expand All @@ -80,7 +83,9 @@ async def get_directions(
)

# pylint: disable = not-callable
return await method.get_directions(from_place, to_place, idunn_mode, lang, extra)
return await method.get_directions(
from_place, to_place, idunn_mode, lang, arrive_by, depart_at, extra
)


directions_client = DirectionsClient()
3 changes: 3 additions & 0 deletions idunn/datasources/directions/abs_client.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from abc import ABC, abstractmethod
from datetime import datetime
from typing import Optional

from idunn.geocoder.models.params import QueryParams
Expand All @@ -19,6 +20,8 @@ async def get_directions(
to_place: BasePlace,
mode: IdunnTransportMode,
lang: str,
arrive_by: Optional[datetime],
depart_at: Optional[datetime],
extra: Optional[QueryParams] = None,
) -> DirectionsResponse:
...
12 changes: 12 additions & 0 deletions idunn/datasources/directions/hove/client.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import httpx
from datetime import datetime
from fastapi import HTTPException
from typing import Optional

Expand Down Expand Up @@ -36,6 +37,8 @@ async def get_directions(
to_place: BasePlace,
mode: IdunnTransportMode,
_lang: str,
arrive_by: Optional[datetime],
depart_at: Optional[datetime],
_extra: Optional[QueryParams] = None,
) -> HoveResponse:
if not self.API_ENABLED:
Expand All @@ -46,6 +49,7 @@ async def get_directions(

from_place = from_place.get_coord()
to_place = to_place.get_coord()
date_time = arrive_by or depart_at

params = {
"from": f"{from_place['lon']};{from_place['lat']}",
Expand All @@ -65,6 +69,14 @@ async def get_directions(
"direct_path": "only",
}
),
**(
{
"datetime": date_time.isoformat(),
"datetime_represents": "arrival" if arrive_by else "departure",
}
if date_time
else {}
),
}

response = await self.session.get(
Expand Down
5 changes: 5 additions & 0 deletions idunn/datasources/directions/mapbox/client.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import httpx
import logging
from datetime import datetime
from fastapi import HTTPException
from fastapi.responses import JSONResponse
from pydantic import BaseModel
Expand Down Expand Up @@ -48,6 +49,8 @@ async def get_directions(
to_place: BasePlace,
mode: IdunnTransportMode,
lang: str,
arrive_by: Optional[datetime],
depart_at: Optional[datetime],
extra: Optional[QueryParams] = None,
) -> DirectionsResponse:
if not self.API_ENABLED:
Expand All @@ -70,6 +73,8 @@ async def get_directions(
params={
"language": lang,
"access_token": settings["MAPBOX_DIRECTIONS_ACCESS_TOKEN"],
**({"arrive_by": arrive_by.isoformat()} if arrive_by else {}),
**({"depart_at": depart_at.isoformat()} if depart_at else {}),
**MapboxAPIExtraParams(**extra).dict(exclude_none=True),
},
timeout=self.request_timeout,
Expand Down
43 changes: 42 additions & 1 deletion tests/test_directions.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,48 @@ def test_direction_car(mock_directions_car):
assert len(response_data["data"]["routes"][0]["legs"]) == 1
assert len(response_data["data"]["routes"][0]["legs"][0]["steps"]) == 10
assert response_data["data"]["routes"][0]["legs"][0]["mode"] == "CAR"
assert "exclude=ferry" in str(mock_directions_car.calls[0].request.url)
mocked_request_url = str(mock_directions_car.calls[0].request.url)
assert "exclude=ferry" in mocked_request_url

# Check that we leave these at defaults
assert "arrive_by" not in mocked_request_url
assert "depart_at" not in mocked_request_url


@freeze_time("2018-06-14 8:30:00", tz_offset=0)
def test_direction_car_arrive_by(mock_directions_car):
client = TestClient(app)
client.get(
"http://localhost/v1/directions/2.3402355%2C48.8900732%3B2.3688579%2C48.8529869",
params={
"language": "fr",
"type": "driving",
"exclude": "ferry",
"arrive_by": "2018-06-14T10:30:00",
},
)

mocked_request_url = str(mock_directions_car.calls[0].request.url)
assert "arrive_by" in mocked_request_url
assert "depart_at" not in mocked_request_url


@freeze_time("2018-06-14 8:30:00", tz_offset=0)
def test_direction_car_depart_at(mock_directions_car):
client = TestClient(app)
client.get(
"http://localhost/v1/directions/2.3402355%2C48.8900732%3B2.3688579%2C48.8529869",
params={
"language": "fr",
"type": "driving",
"exclude": "ferry",
"depart_at": "2018-06-14T10:30:00",
},
)

mocked_request_url = str(mock_directions_car.calls[0].request.url)
assert "arrive_by" not in mocked_request_url
assert "depart_at" in mocked_request_url


def test_direction_car_with_ids(mock_directions_car):
Expand Down
31 changes: 31 additions & 0 deletions tests/test_directions_hove.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ def test_directions_pt(mock_directions_pt):
assert "from=2.34024;48.89007" in mocked_url
assert "to=2.36886;48.85299" in mocked_url
assert "direct_path=none" in mocked_url
assert "datetime" not in mocked_url


def test_directions_hove_not_configured():
Expand All @@ -74,3 +75,33 @@ def test_directions_hove_not_configured():
params={"type": "publictransport"},
)
assert response.status_code == 501


def test_direction_hove_arrive_by(mock_directions_pt):
client = TestClient(app)
client.get(
"http://localhost/v1/directions/2.3402355%2C48.8900732%3B2.3688579%2C48.8529869",
params={
"type": "publictransport",
"arrive_by": "2018-06-14T10:30:00",
},
)

mocked_request_url = str(mock_directions_pt.calls[0].request.url)
assert "datetime" in mocked_request_url
assert "datetime_represents=arrival" in mocked_request_url


def test_direction_hove_depart_at(mock_directions_pt):
client = TestClient(app)
client.get(
"http://localhost/v1/directions/2.3402355%2C48.8900732%3B2.3688579%2C48.8529869",
params={
"type": "publictransport",
"depart_at": "2018-06-14T10:30:00",
},
)

mocked_request_url = str(mock_directions_pt.calls[0].request.url)
assert "datetime" in mocked_request_url
assert "datetime_represents=departure" in mocked_request_url

0 comments on commit b084255

Please sign in to comment.