-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
475 added acceptable content types #498
base: master
Are you sure you want to change the base?
Changes from all commits
41498a0
d41254d
dd978b8
5f6f77a
a29b687
d8740f9
01f8080
842e196
bc6f35a
f26ae49
e273218
f87a08b
841c910
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,18 @@ | ||
import cgi | ||
import os | ||
import tempfile | ||
import typing | ||
from typing import Any | ||
from typing import AsyncIterator, Union, Optional | ||
from typing import overload | ||
|
||
import itertools | ||
import json | ||
import pathlib | ||
|
||
from typing import Any | ||
from typing import AsyncIterator, Union, Optional | ||
from typing import overload | ||
from typing import Dict | ||
from typing import Iterator | ||
|
||
from authlib.oauth2.rfc6750.errors import InsufficientScopeError | ||
|
||
from starlette.datastructures import URL, Headers | ||
from starlette.requests import Request | ||
from starlette.responses import Response | ||
|
||
|
@@ -22,16 +22,15 @@ | |
from spinta.accesslog import AccessLog | ||
from spinta.accesslog import log_async_response | ||
from spinta.auth import check_scope | ||
from spinta.backends import check_type_value | ||
from spinta.backends.helpers import get_select_prop_names | ||
from spinta.backends.helpers import get_select_tree | ||
from spinta.backends.components import Backend, BackendFeatures | ||
from spinta.components import Context, Node, UrlParams, Action, DataItem, Namespace, Model, Property, DataStream, DataSubItem | ||
from spinta.datasets.backends.helpers import detect_backend_from_content_type, get_stream_for_direct_upload | ||
from spinta.renderer import render | ||
from spinta.types.datatype import DataType, Object, Array, File, Ref, ExternalRef, Denorm, Inherit, BackRef | ||
from spinta.urlparams import get_model_by_name | ||
from spinta.utils.aiotools import agroupby | ||
from spinta.utils.aiotools import aslice, alist, aiter | ||
from spinta.utils.aiotools import agroupby, aslice, alist, aiter | ||
from spinta.utils.errors import report_error | ||
from spinta.utils.nestedstruct import flatten_value | ||
from spinta.utils.streams import splitlines | ||
|
@@ -40,7 +39,6 @@ | |
from spinta.types.namespace import traverse_ns_models | ||
from spinta.core.ufuncs import asttoexpr | ||
from spinta.formats.components import Format | ||
from spinta.types.text.components import Text | ||
|
||
if typing.TYPE_CHECKING: | ||
from spinta.backends.postgresql.components import WriteTransaction | ||
|
@@ -49,6 +47,7 @@ | |
STREAMING_CONTENT_TYPES = [ | ||
'application/x-jsonlines', | ||
'application/x-ndjson', | ||
'application/json' | ||
] | ||
|
||
|
||
|
@@ -74,20 +73,28 @@ async def push( | |
scope = scope.prop | ||
|
||
stop_on_error = not params.fault_tolerant | ||
if is_streaming_request(request): | ||
content_type = get_content_type_from_request(request) | ||
temp_file = tempfile.NamedTemporaryFile(delete=False) | ||
context.attach('uploaded_file', temp_file) | ||
if is_streaming_request(content_type): | ||
stream = _read_request_stream( | ||
context, request, scope, action, stop_on_error, | ||
) | ||
else: | ||
stream = _read_request_body( | ||
context, request, scope, action, params, stop_on_error, | ||
) | ||
backend = detect_backend_from_content_type(context, content_type) | ||
if backend: | ||
async for line in request.stream(): | ||
temp_file.write(line) | ||
rows = commands.getall(context, scope, backend, file_path=temp_file.name) | ||
stream = get_stream_for_direct_upload(context, rows, request, params, content_type) | ||
else: | ||
stream = _read_request_body( | ||
context, request, scope, action, params, stop_on_error, | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will never be called. |
||
dstream = push_stream(context, stream, | ||
stop_on_error=stop_on_error, | ||
params=params) | ||
|
||
dstream = log_async_response(context, dstream) | ||
|
||
batch = False | ||
if params.summary: | ||
status_code, response = await _summary_response(context, dstream) | ||
|
@@ -105,6 +112,8 @@ async def push( | |
dstream, | ||
) | ||
headers = prepare_headers(context, scope, response, action, is_batch=batch) | ||
if temp_file: | ||
os.unlink(temp_file.name) | ||
return render(context, request, scope, params, response, | ||
action=action, status_code=status_code, headers=headers) | ||
|
||
|
@@ -140,7 +149,7 @@ async def push_stream( | |
context: Context, | ||
stream: AsyncIterator[DataItem], | ||
stop_on_error: bool = True, | ||
params: UrlParams = None | ||
params: UrlParams = None, | ||
) -> AsyncIterator[DataItem]: | ||
|
||
cmds = { | ||
|
@@ -211,15 +220,20 @@ def _stream_group_key(data: DataItem): | |
return data.model, data.prop, data.backend, data.action | ||
|
||
|
||
def is_streaming_request(request: Request): | ||
def get_content_type_from_request(request: Request): | ||
content_type = request.headers.get('content-type') | ||
if content_type: | ||
content_type = cgi.parse_header(content_type)[0] | ||
return content_type | ||
|
||
|
||
def is_streaming_request(content_type): | ||
return content_type in STREAMING_CONTENT_TYPES | ||
|
||
|
||
async def is_batch(request: Request, node: Node): | ||
if is_streaming_request(request): | ||
content_type = get_content_type_from_request(request) | ||
if is_streaming_request(content_type): | ||
return True | ||
|
||
ct = request.headers.get('content-type') | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,16 @@ | ||
from typing import Any | ||
|
||
from spinta.components import Property, Model | ||
from spinta.components import Model | ||
from spinta.core.ufuncs import Env | ||
from spinta.datasets.enums import Level | ||
from spinta.cli.helpers.store import prepare_manifest | ||
from spinta.components import Property, Context | ||
from spinta.datasets.backends.notimpl.components import BackendNotImplemented | ||
from spinta.datasets.components import ExternalBackend | ||
from spinta.datasets.keymaps.components import KeyMap | ||
from spinta.exceptions import GivenValueCountMissmatch | ||
from spinta.exceptions import GivenValueCountMissmatch, NoMatchingBackendDetected | ||
from spinta.types.datatype import Ref, Array | ||
from spinta.formats.helpers import get_response_type_as_format_class | ||
|
||
|
||
def handle_ref_key_assignment(keymap: KeyMap, env: Env, value: Any, ref: Ref) -> dict: | ||
|
@@ -80,3 +85,29 @@ def extract_values_from_row(row: Any, model: Model, keys: list): | |
if len(return_list) == 1: | ||
return_list = return_list[0] | ||
return return_list | ||
|
||
|
||
def detect_backend_from_content_type(context, content_type): | ||
config = context.get('config') | ||
backends = config.components['backends'] | ||
for backend in backends.values(): | ||
if issubclass(backend, ExternalBackend) and not issubclass(backend, BackendNotImplemented): | ||
if backend.accept_types and content_type in backend.accept_types: | ||
return backend() | ||
raise NoMatchingBackendDetected | ||
|
||
|
||
def get_stream_for_direct_upload( | ||
context: Context, | ||
rows, | ||
request, | ||
params, | ||
content_type | ||
): | ||
from spinta.commands.write import write | ||
store = prepare_manifest(context) | ||
manifest = store.manifest | ||
root = manifest.objects['ns'][''] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if access rights are checked here? We should limit client to only write to a specified namespace or model. And here, we just take root namespace, which probably gives permission to write data to any namespace? |
||
fmt = get_response_type_as_format_class(context, request, params, content_type) | ||
stream = write(context, root, rows, changed=True, fmt=fmt) | ||
return stream |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
application/json
should not be here, this is not a streaming content type.