Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 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
16 changes: 15 additions & 1 deletion api_tabular/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import aiohttp_cors

from aiohttp import web, ClientSession
from aiohttp_swagger import setup_swagger

from sentry_sdk.integrations.aiohttp import AioHttpIntegration
from api_tabular import config
Expand All @@ -11,7 +12,7 @@
get_resource_data,
get_resource_data_streamed,
)
from api_tabular.utils import build_sql_query_string, build_link_with_page, url_for
from api_tabular.utils import build_sql_query_string, build_link_with_page, url_for, build_swagger_file
from api_tabular.error import QueryException

routes = web.RouteTableDef()
Expand Down Expand Up @@ -58,6 +59,16 @@ async def resource_profile(request):
return web.json_response(resource)


@routes.get(r"/api/resources/{rid}/swagger/", name="swagger")
async def resource_swagger(request):
resource_id = request.match_info["rid"]
resource = await get_resource(
request.app["csession"], resource_id, ["profile:csv_detective"]
)
swagger_string = build_swagger_file(resource['profile']['columns'], resource_id)
return web.Response(body=swagger_string)


@routes.get(r"/api/resources/{rid}/data/", name="data")
async def resource_data(request):
resource_id = request.match_info["rid"]
Expand Down Expand Up @@ -159,6 +170,9 @@ async def on_cleanup(app):
)
for route in list(app.router.routes()):
cors.add(route)

setup_swagger(app, swagger_url=config.DOC_PATH, ui_version=3, swagger_from_file="ressource_app_swagger.yaml")

return app


Expand Down
5 changes: 3 additions & 2 deletions api_tabular/config_default.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
PGREST_ENDPOINT = "http://localhost:8080"
SERVER_NAME = 'localhost:8005'
SCHEME = 'http'
SERVER_NAME = "localhost:8005"
SCHEME = "http"
SENTRY_DSN = ""
PAGE_SIZE_DEFAULT = 20
PAGE_SIZE_MAX = 50
BATCH_SIZE = 50000
DOC_PATH = "/api/doc"
8 changes: 7 additions & 1 deletion api_tabular/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import sentry_sdk
import aiohttp_cors

from aiohttp_swagger import setup_swagger
from aiohttp import web, ClientSession

from sentry_sdk.integrations.aiohttp import AioHttpIntegration
Expand Down Expand Up @@ -57,6 +58,9 @@ async def get_object_data_streamed(

@routes.get(r"/api/{model}/data/")
async def metrics_data(request):
"""
Retrieve metric data for a specified model with optional filtering and sorting.
"""
model = request.match_info["model"]
query_string = request.query_string.split("&") if request.query_string else []
page = int(request.query.get("page", "1"))
Expand All @@ -70,7 +74,6 @@ async def metrics_data(request):
offset = page_size * (page - 1)
else:
offset = 0

try:
sql_query = build_sql_query_string(query_string, page_size, offset)
except ValueError:
Expand Down Expand Up @@ -145,6 +148,9 @@ async def on_cleanup(app):
)
for route in list(app.router.routes()):
cors.add(route)

setup_swagger(app, swagger_url=config.DOC_PATH, ui_version=3, swagger_from_file="metrics_swagger.yaml")

return app


Expand Down
239 changes: 239 additions & 0 deletions api_tabular/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import yaml
from aiohttp.web_request import Request

from api_tabular import config
Expand Down Expand Up @@ -59,3 +60,241 @@ def url_for(request: Request, route: str, *args, **kwargs):
if kwargs.pop("_external", None):
return external_url(router[route].url_for(**kwargs))
return router[route].url_for(**kwargs)


def swagger_parameters(resource_columns):
parameters_list = [
{
'name': 'rid',
'in': 'path',
'description': 'ID of resource to return',
'required': True,
'schema': {
'type': 'string'
}
},
{
'name': 'page',
'in': 'query',
'description': 'Specific page',
'required': False,
'schema': {
'type': 'string'
}
},
{
'name': 'page_size',
'in': 'query',
'description': 'Number of results per page',
'required': False,
'schema': {
'type': 'string'
}
}
]
for key, value in resource_columns.items():
parameters_list.extend(
[
{
'name': f'sort ascending {key}',
'in': 'query',
'description': f'{key}__sort=asc.',
'required': False,
'schema': {
'type': 'string'
}
},
{
'name': f'sort descending {key}',
'in': 'query',
'description': f'{key}__sort=desc.',
'required': False,
'schema': {
'type': 'string'
}
}
]
)
if value['python_type'] == 'string':
parameters_list.extend(
[
{
'name': f'exact {key}',
'in': 'query',
'description': f'{key}__exact=value.',
'required': False,
'schema': {
'type': 'string'
}
},
{
'name': f'contains {key}',
'in': 'query',
'description': f'{key}__contains=value.',
'required': False,
'schema': {
'type': 'string'
}
}
]
)
elif value['python_type'] == 'float':
parameters_list.extend(
[
{
'name': f'{key} less',
'in': 'query',
'description': f'{key}__less=value.',
'required': False,
'schema': {
'type': 'string'
}
},
{
'name': f'{key} greater',
'in': 'query',
'description': f'{key}__greater=value.',
'required': False,
'schema': {
'type': 'string'
}
}
]
)
return parameters_list


def swagger_component(resource_columns):
resource_prop_dict = {}
for key, value in resource_columns.items():
type = 'string'
if value['python_type'] == 'float':
type = 'integer'
resource_prop_dict.update({
f'{key}': {
'type': f'{type}'
}
})
component_dict = {
'schemas': {
'ResourceData': {
'type': 'object',
'properties': {
'data': {
'type': 'array',
'items': {
'$ref': '#/components/schemas/Resource'
}
},
'link': {
'type': 'object',
'properties': {
'profile': {
'description': 'Link to the profile endpoint of the resource',
'type': 'string'
},
'next': {
'description': 'Pagination link to the next page of the resource data',
'type': 'string'
},
'prev': {
'description': 'Pagination link to the previous page of the resource data',
'type': 'string'
}
}
},
'meta': {
'type': 'object',
'properties': {
'page': {
'description': 'Current page',
'type': 'integer'
},
'page_size': {
'description': 'Number of results per page',
'type': 'integer'
},
'total': {
'description': 'Total number of results',
'type': 'integer'
}
}
}
}
},
'Resource': {
'type': 'object',
'properties': resource_prop_dict
}
}
}
return component_dict


def build_swagger_file(resource_columns, rid):
parameters_list = swagger_parameters(resource_columns)
component_dict = swagger_component(resource_columns)
swagger_dict = {
'openapi': '3.0.3',
'info': {
'title': 'Resource data API',
'description': 'Retrieve data for a specified resource with optional filtering and sorting.',
'version': '1.0.0'
},
'tags': {
'name': 'Data retrieval',
'description': 'Retrieve data for a specified resource'
},
'paths': {
f'/api/resources/{rid}/data/': {
'get': {
'description': 'Returns resource data based on ID.',
'summary': 'Find resource by ID',
'operationId': 'getResourceById',
'responses': {
'200': {
'description': 'successful operation',
'content': {
'application/json': {
'schema': {
'$ref': '#/components/schemas/ResourceData'
}
}
}
},
'400': {
'description': 'Invalid query string'
},
'404': {
'description': 'Resource not found'
}
}
},
'parameters': parameters_list
},
f'/api/resources/{rid}/data/csv/': {
'get': {
'description': 'Returns resource data based on ID as a CSV file.',
'summary': 'Find resource by ID in CSV',
'operationId': 'getResourceByIdCSV',
'responses': {
'200': {
'description': 'successful operation',
'content': {
'text/csv': {}
}
},
'400': {
'description': 'Invalid query string'
},
'404': {
'description': 'Resource not found'
}
}
},
'parameters': parameters_list
}
},
'components': component_dict
}
return yaml.dump(swagger_dict)
Loading