Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
## Current (in progress)

- Add endpoint to stream a CSV response [#5](https://github.com/etalab/api-tabular/pull/5)
- Make URL in links absolute [#7](https://github.com/etalab/api-tabular/pull/7)
20 changes: 10 additions & 10 deletions api_tabular/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
get_resource_data,
get_resource_data_streamed,
)
from api_tabular.utils import build_sql_query_string, build_link_with_page
from api_tabular.utils import build_sql_query_string, build_link_with_page, url_for
from api_tabular.error import QueryException

routes = web.RouteTableDef()
Expand All @@ -22,7 +22,7 @@
)


@routes.get(r"/api/resources/{rid}/")
@routes.get(r"/api/resources/{rid}/", name="meta")
async def resource_meta(request):
resource_id = request.match_info["rid"]
resource = await get_resource(
Expand All @@ -34,12 +34,12 @@ async def resource_meta(request):
"url": resource["url"],
"links": [
{
"href": f"/api/resources/{resource_id}/profile/",
"href": url_for(request, 'profile', rid=resource_id, _external=True),
"type": "GET",
"rel": "profile",
},
{
"href": f"/api/resources/{resource_id}/data/",
"href": url_for(request, 'data', rid=resource_id, _external=True),
"type": "GET",
"rel": "data",
},
Expand All @@ -48,7 +48,7 @@ async def resource_meta(request):
)


@routes.get(r"/api/resources/{rid}/profile/")
@routes.get(r"/api/resources/{rid}/profile/", name="profile")
async def resource_profile(request):
resource_id = request.match_info["rid"]
resource = await get_resource(
Expand All @@ -57,7 +57,7 @@ async def resource_profile(request):
return web.json_response(resource)


@routes.get(r"/api/resources/{rid}/data/")
@routes.get(r"/api/resources/{rid}/data/", name="data")
async def resource_data(request):
resource_id = request.match_info["rid"]
query_string = request.query_string.split("&") if request.query_string else []
Expand Down Expand Up @@ -85,12 +85,12 @@ async def resource_data(request):
request.app["csession"], resource, sql_query
)

next = build_link_with_page(request.path, query_string, page + 1, page_size)
prev = build_link_with_page(request.path, query_string, page - 1, page_size)
next = build_link_with_page(request, query_string, page + 1, page_size)
prev = build_link_with_page(request, query_string, page - 1, page_size)
body = {
"data": response,
"links": {
"profile": f"/api/resources/{resource_id}/profile/",
"profile": url_for(request, 'profile', rid=resource_id, _external=True),
"next": next if page_size + offset < total else None,
"prev": prev if page > 1 else None,
},
Expand All @@ -99,7 +99,7 @@ async def resource_data(request):
return web.json_response(body)


@routes.get(r"/api/resources/{rid}/data/csv/")
@routes.get(r"/api/resources/{rid}/data/csv/", name="csv")
async def resource_data_csv(request):
resource_id = request.match_info["rid"]
query_string = request.query_string.split("&") if request.query_string else []
Expand Down
4 changes: 2 additions & 2 deletions api_tabular/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ async def metrics_data(request):

response, total = await get_object_data(request.app["csession"], model, sql_query)

next = build_link_with_page(request.path, query_string, page + 1, page_size)
prev = build_link_with_page(request.path, query_string, page - 1, page_size)
next = build_link_with_page(request, query_string, page + 1, page_size)
prev = build_link_with_page(request, query_string, page - 1, page_size)
body = {
"data": response,
"links": {
Expand Down
14 changes: 12 additions & 2 deletions api_tabular/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from aiohttp.web_request import Request


def build_sql_query_string(
request_arg: list, page_size: int = None, offset: int = 0
) -> str:
Expand Down Expand Up @@ -33,8 +36,15 @@ def process_total(raw_total: str) -> int:
return int(str_total)


def build_link_with_page(path, query_string, page, page_size):
def build_link_with_page(request: Request, query_string: str, page: int, page_size: int):
q = [string for string in query_string if not string.startswith("page")]
q.extend([f"page={page}", f"page_size={page_size}"])
rebuilt_q = "&".join(q)
return f"{path}?{rebuilt_q}"
return f"{request.scheme}://{request.host}{request.path}?{rebuilt_q}"


def url_for(request: Request, route: str, *args, **kwargs):
router = request.app.router
if kwargs.pop("_external", None):
return f"{request.scheme}://{request.host}{router[route].url_for(**kwargs)}"
return router[route].url_for(**kwargs)
24 changes: 13 additions & 11 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ async def test_api_resource_meta(client, rmock):
"url": "https://example.com",
"links": [
{
"href": f"/api/resources/{RESOURCE_ID}/profile/",
"href": f"http://127.0.0.1:{client.port}/api/resources/{RESOURCE_ID}/profile/",
"type": "GET",
"rel": "profile",
},
{
"href": f"/api/resources/{RESOURCE_ID}/data/",
"href": f"http://127.0.0.1:{client.port}/api/resources/{RESOURCE_ID}/data/",
"type": "GET",
"rel": "data",
},
Expand Down Expand Up @@ -66,7 +66,7 @@ async def test_api_resource_data(client, rmock):
"links": {
"next": None,
"prev": None,
"profile": "/api/resources/60963939-6ada-46bc-9a29-b288b16d969b/profile/",
"profile": f"http://127.0.0.1:{client.port}/api/resources/60963939-6ada-46bc-9a29-b288b16d969b/profile/",
},
"meta": {"page": 1, "page_size": 20, "total": 10},
}
Expand All @@ -88,7 +88,7 @@ async def test_api_resource_data_with_args(client, rmock):
"links": {
"next": None,
"prev": None,
"profile": "/api/resources/60963939-6ada-46bc-9a29-b288b16d969b/profile/",
"profile": f"http://127.0.0.1:{client.port}/api/resources/60963939-6ada-46bc-9a29-b288b16d969b/profile/",
},
"meta": {"page": 1, "page_size": 20, "total": 10},
}
Expand All @@ -110,7 +110,7 @@ async def test_api_resource_data_with_args_case(client, rmock):
"links": {
"next": None,
"prev": None,
"profile": "/api/resources/60963939-6ada-46bc-9a29-b288b16d969b/profile/",
"profile": f"http://127.0.0.1:{client.port}/api/resources/60963939-6ada-46bc-9a29-b288b16d969b/profile/",
},
"meta": {"page": 1, "page_size": 20, "total": 10},
}
Expand Down Expand Up @@ -183,7 +183,7 @@ async def test_api_percent_encoding_arabic(client, rmock):
"links": {
"next": None,
"prev": None,
"profile": "/api/resources/60963939-6ada-46bc-9a29-b288b16d969b/profile/",
"profile": f"http://127.0.0.1:{client.port}/api/resources/60963939-6ada-46bc-9a29-b288b16d969b/profile/",
},
"meta": {"page": 1, "page_size": 20, "total": 10},
}
Expand All @@ -205,7 +205,7 @@ async def test_api_with_unsupported_args(client, rmock):
"links": {
"next": None,
"prev": None,
"profile": "/api/resources/60963939-6ada-46bc-9a29-b288b16d969b/profile/",
"profile": f"http://127.0.0.1:{client.port}/api/resources/60963939-6ada-46bc-9a29-b288b16d969b/profile/",
},
"meta": {"page": 1, "page_size": 20, "total": 10},
}
Expand All @@ -225,9 +225,10 @@ async def test_api_pagination(client, rmock):
body = {
"data": [{"such": "data"}],
"links": {
"next": "/api/resources/60963939-6ada-46bc-9a29-b288b16d969b/data/?page=2&page_size=1",
"next": f"http://127.0.0.1:{client.port}"
"/api/resources/60963939-6ada-46bc-9a29-b288b16d969b/data/?page=2&page_size=1",
"prev": None,
"profile": "/api/resources/60963939-6ada-46bc-9a29-b288b16d969b/profile/",
"profile": f"http://127.0.0.1:{client.port}/api/resources/60963939-6ada-46bc-9a29-b288b16d969b/profile/",
},
"meta": {"page": 1, "page_size": 1, "total": 2},
}
Expand All @@ -246,8 +247,9 @@ async def test_api_pagination(client, rmock):
"data": [{"such": "data"}],
"links": {
"next": None,
"prev": "/api/resources/60963939-6ada-46bc-9a29-b288b16d969b/data/?page=1&page_size=1",
"profile": "/api/resources/60963939-6ada-46bc-9a29-b288b16d969b/profile/",
"prev": f"http://127.0.0.1:{client.port}"
"/api/resources/60963939-6ada-46bc-9a29-b288b16d969b/data/?page=1&page_size=1",
"profile": f"http://127.0.0.1:{client.port}/api/resources/60963939-6ada-46bc-9a29-b288b16d969b/profile/",
},
"meta": {"page": 2, "page_size": 1, "total": 2},
}
Expand Down
24 changes: 24 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

from aiohttp.test_utils import make_mocked_request

from api_tabular.utils import build_link_with_page, url_for


def test_build_link_with_page():
request = make_mocked_request("GET", "/api/test?foo=bar")
link = build_link_with_page(request, query_string=["foo=1", "bar=3"], page=2, page_size=10)
assert link == f"{request.scheme}://{request.host}/api/test?foo=1&bar=3&page=2&page_size=10"


def test_url_for(client):
request = make_mocked_request("GET", "/api/test?foo=bar")
request.app.router = client.app.router
url = url_for(request, 'profile', rid='rid')
assert str(url) == '/api/resources/rid/profile/'


def test_url_for_external(client):
request = make_mocked_request("GET", "/api/test?foo=bar")
request.app.router = client.app.router
url = url_for(request, 'profile', rid='rid', _external=True)
assert str(url) == f'{request.scheme}://{request.host}/api/resources/rid/profile/'