udata-hydra-csvapi 0.4.3.dev3__py3-none-any.whl → 0.4.3.dev5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- api_tabular/core/cors.py +33 -17
- api_tabular/core/query.py +12 -27
- api_tabular/metrics/app.py +3 -2
- api_tabular/tabular/app.py +3 -2
- {udata_hydra_csvapi-0.4.3.dev3.dist-info → udata_hydra_csvapi-0.4.3.dev5.dist-info}/METADATA +1 -1
- {udata_hydra_csvapi-0.4.3.dev3.dist-info → udata_hydra_csvapi-0.4.3.dev5.dist-info}/RECORD +9 -9
- {udata_hydra_csvapi-0.4.3.dev3.dist-info → udata_hydra_csvapi-0.4.3.dev5.dist-info}/WHEEL +0 -0
- {udata_hydra_csvapi-0.4.3.dev3.dist-info → udata_hydra_csvapi-0.4.3.dev5.dist-info}/licenses/LICENSE +0 -0
- {udata_hydra_csvapi-0.4.3.dev3.dist-info → udata_hydra_csvapi-0.4.3.dev5.dist-info}/top_level.txt +0 -0
api_tabular/core/cors.py
CHANGED
|
@@ -1,24 +1,40 @@
|
|
|
1
1
|
from aiohttp import web
|
|
2
|
+
from aiohttp.web_urldispatcher import MatchInfoError
|
|
3
|
+
|
|
4
|
+
CORS_HEADERS = {
|
|
5
|
+
# Using "*" ensures the Nginx cache remains consistent for all origins
|
|
6
|
+
"Access-Control-Allow-Origin": "*",
|
|
7
|
+
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
|
8
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With",
|
|
9
|
+
"Access-Control-Expose-Headers": "*",
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _accepts_method(match_info: web.UrlMappingMatchInfo, method: str) -> bool:
|
|
14
|
+
# No route registers OPTIONS, so an existing path resolves to a 405 listing its allowed methods
|
|
15
|
+
return (
|
|
16
|
+
isinstance(match_info, MatchInfoError)
|
|
17
|
+
and isinstance(match_info.http_exception, web.HTTPMethodNotAllowed)
|
|
18
|
+
and method in match_info.http_exception.allowed_methods
|
|
19
|
+
)
|
|
2
20
|
|
|
3
21
|
|
|
4
22
|
@web.middleware
|
|
5
|
-
async def
|
|
6
|
-
"""
|
|
7
|
-
Middleware to handle CORS and the OPTIONS preflight.
|
|
8
|
-
"""
|
|
23
|
+
async def _preflight_middleware(request: web.Request, handler):
|
|
24
|
+
"""Answer an OPTIONS preflight with a 204, only for a route accepting the requested method."""
|
|
9
25
|
if request.method == "OPTIONS":
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
26
|
+
requested_method = request.headers.get("Access-Control-Request-Method", "GET")
|
|
27
|
+
if _accepts_method(request.match_info, requested_method):
|
|
28
|
+
return web.Response(status=204)
|
|
29
|
+
return await handler(request)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def add_cors_headers(request: web.Request, response: web.StreamResponse) -> None:
|
|
33
|
+
# on_response_prepare also fires for raised HTTP errors, unhandled 500s and streamed responses
|
|
34
|
+
response.headers.update(CORS_HEADERS)
|
|
14
35
|
|
|
15
|
-
# Add CORS headers to the response
|
|
16
|
-
# Using "*" ensures the Nginx cache remains consistent for all origins
|
|
17
|
-
response.headers["Access-Control-Allow-Origin"] = "*"
|
|
18
|
-
response.headers["Access-Control-Allow-Methods"] = "GET, OPTIONS"
|
|
19
|
-
response.headers["Access-Control-Allow-Headers"] = (
|
|
20
|
-
"Content-Type, Authorization, X-Requested-With"
|
|
21
|
-
)
|
|
22
|
-
response.headers["Access-Control-Expose-Headers"] = "*"
|
|
23
36
|
|
|
24
|
-
|
|
37
|
+
def setup_cors(app: web.Application) -> None:
|
|
38
|
+
"""Enable CORS on an app: answer OPTIONS preflights and add CORS headers to every response."""
|
|
39
|
+
app.middlewares.append(_preflight_middleware)
|
|
40
|
+
app.on_response_prepare.append(add_cors_headers)
|
api_tabular/core/query.py
CHANGED
|
@@ -163,35 +163,20 @@ def split_top_level(s: str) -> list[str]:
|
|
|
163
163
|
|
|
164
164
|
|
|
165
165
|
def find_arg_val(param: str) -> tuple[str, str]:
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
value_pattern = r'\."[^"]*"$'
|
|
170
|
-
if param.count('"') == 0:
|
|
171
|
-
# no quote, we expect col__op.val
|
|
172
|
-
_split = param.split(".")
|
|
173
|
-
if len(_split) != 2:
|
|
174
|
-
raise ValueError(f"argument '{param}' could not be parsed")
|
|
175
|
-
return tuple[str, str](_split) # ty: ignore[invalid-argument-type]
|
|
176
|
-
elif param.count('"') == 4:
|
|
177
|
-
# 4 quotes, we expect "col.umn"__op."val.ue"
|
|
178
|
-
col_op = re.findall(column_operator_pattern, param)
|
|
179
|
-
val = re.findall(value_pattern, param)
|
|
180
|
-
if len(col_op) != 1 or len(val) != 1:
|
|
181
|
-
raise ValueError(f"argument '{param}' could not be parsed")
|
|
166
|
+
# the operator is not checked here, add_filter validates it like for unquoted columns
|
|
167
|
+
quoted_column = re.match(r'^"([^"]*)"__', param)
|
|
168
|
+
if quoted_column:
|
|
182
169
|
# we drop the double quotes around the column as they will be added back in get_column_and_operator
|
|
183
|
-
|
|
184
|
-
|
|
170
|
+
column_prefix = f"{quoted_column.group(1)}__"
|
|
171
|
+
operator_value = param[quoted_column.end() :]
|
|
185
172
|
else:
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
# case "col.umn"__op.val
|
|
194
|
-
return col_op[0].replace('"', ""), param.split(".")[-1]
|
|
173
|
+
column_prefix, operator_value = "", param
|
|
174
|
+
operator, dot, value = operator_value.partition(".")
|
|
175
|
+
# a quoted value is kept as is, an unquoted one can't contain a dot
|
|
176
|
+
value_is_valid = re.fullmatch(r'"[^"]*"', value) or not re.search(r'[".]', value)
|
|
177
|
+
if not dot or '"' in operator or not value_is_valid:
|
|
178
|
+
raise ValueError(f"argument '{param}' could not be parsed")
|
|
179
|
+
return column_prefix + operator, value
|
|
195
180
|
|
|
196
181
|
|
|
197
182
|
def parse_operator(query: str, operator: str, top_level: bool = False):
|
api_tabular/metrics/app.py
CHANGED
|
@@ -7,7 +7,7 @@ from aiohttp import ClientSession, web
|
|
|
7
7
|
from aiohttp_swagger import setup_swagger
|
|
8
8
|
|
|
9
9
|
from api_tabular import config
|
|
10
|
-
from api_tabular.core.cors import
|
|
10
|
+
from api_tabular.core.cors import setup_cors
|
|
11
11
|
from api_tabular.core.data import stream_data
|
|
12
12
|
from api_tabular.core.error import QueryException, handle_exception
|
|
13
13
|
from api_tabular.core.health import check_health
|
|
@@ -108,7 +108,8 @@ async def app_factory():
|
|
|
108
108
|
async def on_cleanup(app):
|
|
109
109
|
await app["csession"].close()
|
|
110
110
|
|
|
111
|
-
app = web.Application(
|
|
111
|
+
app = web.Application()
|
|
112
|
+
setup_cors(app)
|
|
112
113
|
app.add_routes(routes)
|
|
113
114
|
app.on_startup.append(on_startup)
|
|
114
115
|
app.on_cleanup.append(on_cleanup)
|
api_tabular/tabular/app.py
CHANGED
|
@@ -7,7 +7,7 @@ from aiohttp import ClientSession, web
|
|
|
7
7
|
from aiohttp_swagger import setup_swagger
|
|
8
8
|
|
|
9
9
|
from api_tabular import config
|
|
10
|
-
from api_tabular.core.cors import
|
|
10
|
+
from api_tabular.core.cors import setup_cors
|
|
11
11
|
from api_tabular.core.health import check_health
|
|
12
12
|
from api_tabular.core.sentry import get_sentry_kwargs
|
|
13
13
|
from api_tabular.core.swagger import build_swagger_file
|
|
@@ -173,7 +173,8 @@ async def app_factory():
|
|
|
173
173
|
async def on_cleanup(app):
|
|
174
174
|
await app["csession"].close()
|
|
175
175
|
|
|
176
|
-
app = web.Application(
|
|
176
|
+
app = web.Application()
|
|
177
|
+
setup_cors(app)
|
|
177
178
|
app.add_routes(routes)
|
|
178
179
|
app.on_startup.append(on_startup)
|
|
179
180
|
app.on_cleanup.append(on_cleanup)
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
api_tabular/__init__.py,sha256=sYG6CMz-twob-QLyfsPYRc6cWDtqWXvq_N5Dxk-U5qA,2263
|
|
2
2
|
api_tabular/config_default.toml,sha256=cX506NdqHW7gK_2aZjzLiz0Z4gMSl1tU_tI4i4BvuXs,421
|
|
3
3
|
api_tabular/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
api_tabular/core/cors.py,sha256=
|
|
4
|
+
api_tabular/core/cors.py,sha256=xX4qSbv06pVT-NrohQlysFPSR9z7Pj2THz18TCbvJ90,1707
|
|
5
5
|
api_tabular/core/data.py,sha256=5j-3d_el7LMoHpn6V__UdhYHD6GiJbwdH67jrnOYWZQ,1459
|
|
6
6
|
api_tabular/core/error.py,sha256=xRt6fkQfsiupv0tWWzdX0DJjAKbw1n0tRxFyt15VBLA,1062
|
|
7
7
|
api_tabular/core/health.py,sha256=NeFT1C00onvm3CbgesFAcN1N8bmelbQdEYFbmmftICQ,647
|
|
8
|
-
api_tabular/core/query.py,sha256=
|
|
8
|
+
api_tabular/core/query.py,sha256=Qf34vtzt_kPJBYIrHX2QsV-K3ADrbDeLeb8xC__cs-k,9616
|
|
9
9
|
api_tabular/core/sentry.py,sha256=fN5MrDZszLnzLXF3b_nUymPVB0ffNn4PI7xS1GtJNyE,948
|
|
10
10
|
api_tabular/core/swagger.py,sha256=mMEESM4UlgK-voyRTKrddVuUrlUMfTDOQtK1VrVp7Ak,14748
|
|
11
11
|
api_tabular/core/url.py,sha256=af7VKfrMtKMzlHrGgtLDLuRBim4qFv7xlt2PYxAyW2g,761
|
|
12
12
|
api_tabular/core/utils.py,sha256=Ghd9Un0HugFg4oEcEb5yB5FEop7tDSoX4OCzR8mcM-Y,1922
|
|
13
13
|
api_tabular/core/version.py,sha256=OKLg0l1CpDRHV2hsqVgtSihoo3tg0aMG6kzMILtQ-bY,250
|
|
14
14
|
api_tabular/metrics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
-
api_tabular/metrics/app.py,sha256=
|
|
15
|
+
api_tabular/metrics/app.py,sha256=W2htxUO54bTfQLReJhEY3JtsbLWaefC71DmjV9SvUII,4291
|
|
16
16
|
api_tabular/tabular/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
-
api_tabular/tabular/app.py,sha256=
|
|
17
|
+
api_tabular/tabular/app.py,sha256=6lwujb2Pf_70QQlTKL8bhQTnhYinRyDlF_yTdit0pQw,6528
|
|
18
18
|
api_tabular/tabular/utils.py,sha256=BL2nl04-3imBQG-RZiYvVMjxoBORi4lwrKq8NiWecLY,4487
|
|
19
|
-
udata_hydra_csvapi-0.4.3.
|
|
20
|
-
udata_hydra_csvapi-0.4.3.
|
|
21
|
-
udata_hydra_csvapi-0.4.3.
|
|
22
|
-
udata_hydra_csvapi-0.4.3.
|
|
23
|
-
udata_hydra_csvapi-0.4.3.
|
|
19
|
+
udata_hydra_csvapi-0.4.3.dev5.dist-info/licenses/LICENSE,sha256=fb-HAYw94ffh--z1cZrWbpk6YKM0p9WFG7kdd0K7uGQ,1062
|
|
20
|
+
udata_hydra_csvapi-0.4.3.dev5.dist-info/METADATA,sha256=2VEJANlzoHtKJt5iU3MCEw1H-jPyirRWHCreKOTFDHg,21835
|
|
21
|
+
udata_hydra_csvapi-0.4.3.dev5.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
22
|
+
udata_hydra_csvapi-0.4.3.dev5.dist-info/top_level.txt,sha256=MAaK09hxwPhIBBC0i1TdEBAgdu3W_gXnAZqk767gVak,12
|
|
23
|
+
udata_hydra_csvapi-0.4.3.dev5.dist-info/RECORD,,
|
|
File without changes
|
{udata_hydra_csvapi-0.4.3.dev3.dist-info → udata_hydra_csvapi-0.4.3.dev5.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{udata_hydra_csvapi-0.4.3.dev3.dist-info → udata_hydra_csvapi-0.4.3.dev5.dist-info}/top_level.txt
RENAMED
|
File without changes
|