Skip to content

Commit eb8052f

Browse files
committed
UUID chunk endpoint
1 parent 81de953 commit eb8052f

6 files changed

Lines changed: 37 additions & 59 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
- [#1126](https://github.com/LayerManager/layman/issues/1126) Endpoint [Workspace Map](https://github.com/LayerManager/layman/blob/v2.1.0/doc/rest.md#workspace-map) (with methods GET, PATCH, DELETE) was removed and replaced with endpoint [Map](doc/rest.md#map) (with methods GET, PATCH, DELETE) to use UUID-based URL `/rest/maps/{uuid}` instead of workspace&name-based URL.
2424
- [#1126](https://github.com/LayerManager/layman/issues/1126) Endpoint [Workspace Layer](https://github.com/LayerManager/layman/blob/v2.1.0/doc/rest.md#workspace-layer) (with methods GET, PATCH, DELETE) was removed and replaced with endpoint [Layer](doc/rest.md#layer) (with methods GET, PATCH, DELETE) to use UUID-based URL `/rest/layers/{uuid}` instead of workspace&name-based URL.
2525
- [#1126](https://github.com/LayerManager/layman/issues/1126) Endpoint [Workspace Layers](https://github.com/LayerManager/layman/blob/v2.1.0/doc/rest.md#workspace-layers) (with methods GET, POST, DELETE) was unified into endpoint [Layers](doc/rest.md#get-layers); `workspace` is now supplied via request query/body parameter instead of URL path.
26+
- [#1126](https://github.com/LayerManager/layman/issues/1126) Endpoint [Workspace Layer Chunk](https://github.com/LayerManager/layman/blob/v2.1.0/doc/rest.md#workspace-layer-chunk) (with methods GET, POST) was removed and replaced with endpoint [Layer Chunk](doc/rest.md#layer-chunk) to use UUID-based URL `/rest/layers/{uuid}/chunk` instead of workspace&name-based URL.
2627

2728
## v2.1.0
2829
2025-05-02
@@ -185,7 +186,7 @@
185186
- [#165](https://github.com/LayerManager/layman/issues/165) POST Workspace [Layers](https://github.com/LayerManager/layman/blob/v2.1.0/doc/rest.md#post-workspace-layers)/[Maps](doc/rest.md#post-workspace-maps) and PATCH Workspace [Layer](https://github.com/LayerManager/layman/blob/v2.1.0/doc/rest.md#patch-workspace-layer)/[Map](https://github.com/LayerManager/layman/blob/v2.1.0/doc/rest.md#patch-workspace-map) saves [role names](doc/models.md#role) mentioned in `access_rights.read` and `access_rights.write` parameters into [prime DB schema](doc/data-storage.md#postgresql).
186187
- [#165](https://github.com/LayerManager/layman/issues/165) Many requests respect roles in access rights:
187188
- [GET](https://github.com/LayerManager/layman/blob/v2.1.0/doc/rest.md#get-workspace-layer)/[PATCH](https://github.com/LayerManager/layman/blob/v2.1.0/doc/rest.md#patch-workspace-layer)/[DELETE](https://github.com/LayerManager/layman/blob/v2.1.0/doc/rest.md#delete-workspace-layer) Workspace Layer
188-
- GET Workspace Layer [Thumbnail](https://github.com/LayerManager/layman/blob/v2.1.0/doc/rest.md#get-workspace-layer-thumbnail)/[Style](https://github.com/LayerManager/layman/blob/v2.1.0/doc/rest.md#get-workspace-layer-style)/[Metadata Comparison](doc/rest.md#get-workspace-layer-metadata-comparison)/[Chunk](doc/rest.md#get-workspace-layer-chunk)
189+
- GET Workspace Layer [Thumbnail](https://github.com/LayerManager/layman/blob/v2.1.0/doc/rest.md#get-workspace-layer-thumbnail)/[Style](https://github.com/LayerManager/layman/blob/v2.1.0/doc/rest.md#get-workspace-layer-style)/[Metadata Comparison](doc/rest.md#get-workspace-layer-metadata-comparison)/[Chunk](https://github.com/LayerManager/layman/blob/v2.1.0/doc/rest.md#get-workspace-layer-chunk)
189190
- [GET](https://github.com/LayerManager/layman/blob/v2.1.0/doc/rest.md#get-workspace-map)/[PATCH](https://github.com/LayerManager/layman/blob/v2.1.0/doc/rest.md#patch-workspace-map)/[DELETE](https://github.com/LayerManager/layman/blob/v2.1.0/doc/rest.md#delete-workspace-map) Workspace Map
190191
- GET Workspace Map [File](https://github.com/LayerManager/layman/blob/v2.1.0/doc/rest.md#get-workspace-map-file)/[Thumbnail](https://github.com/LayerManager/layman/blob/v2.1.0/doc/rest.md#get-workspace-map-thumbnail)/[Metadata Comparison](doc/rest.md#get-workspace-map-metadata-comparison)
191192
- GET Workspace [Layers](https://github.com/LayerManager/layman/blob/v2.1.0/doc/rest.md#get-workspace-layers)/[Maps](doc/rest.md#get-workspace-maps)

doc/async-file-upload.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,12 @@ const onFormSubmit = (event) => {
104104
)
105105
);
106106

107-
// find out layer name
108-
const layername = resp_json[0]['name'];
107+
// find out layer uuid
108+
const layer_uuid = resp_json[0]['uuid'];
109109

110110
// set up resumable.js instance
111111
const resumable = new Resumable({
112-
target: `/rest/workspaces/${form_data.get('workspace')}/layers/${layername}/chunk`,
112+
target: `/rest/layers/${layer_uuid}/chunk`,
113113
query: {
114114
'layman_original_parameter': 'file'
115115
},

doc/rest.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
|[Layer](models.md#layer)|`/rest/layers/<uuid>`|[GET](#get-layer)| x | [PATCH](#patch-layer) | [DELETE](#delete-layer) |
99
|Layer Thumbnail|`/rest/layers/<uuid>/thumbnail`|[GET](#get-layer-thumbnail)| x | x | x |
1010
|Layer Style|`/rest/layers/<uuid>/style`|[GET](#get-layer-style)| x | x | x |
11-
|Workspace Layer Chunk|`/rest/workspaces/<workspace_name>/layers/<layername>/chunk`|[GET](#get-workspace-layer-chunk)| [POST](#post-workspace-layer-chunk) | x | x |
11+
|Layer Chunk|`/rest/layers/<uuid>/chunk`|[GET](#get-layer-chunk)| [POST](#post-layer-chunk) | x | x |
1212
|Workspace Layer Metadata Comparison|`/rest/workspaces/<workspace_name>/layers/<layername>/metadata-comparison`|[GET](#get-workspace-layer-metadata-comparison) | x | x | x |
1313
|Maps|`/rest/maps`|[GET](#get-maps)| x | x | x |
1414
|[Map](models.md#map)|`/rest/maps/<uuid>`|[GET](#get-map)| x | [PATCH](#patch-map) | [DELETE](#delete-map) |
@@ -122,7 +122,7 @@ Response to this request may be returned sooner than the processing chain is fin
122122
It is possible to upload data files asynchronously, which is suitable for large files. This can be done in three steps:
123123
1. Send POST Layers request with **file** parameter filled by file names that you want to upload
124124
2. Read set of files accepted to upload from POST Layers response, **files_to_upload** property. The set of accepted files will be either equal to or subset of file names sent in **file** parameter.
125-
3. Send [POST Workspace Layer Chunk](#post-workspace-layer-chunk) requests using Resumable.js to upload files.
125+
3. Send [POST Layer Chunk](#post-layer-chunk) requests using Resumable.js to upload files.
126126

127127
Check [Asynchronous file upload](async-file-upload.md) example.
128128

@@ -148,7 +148,7 @@ Body parameters:
148148
- any of above types in single ZIP file (.zip)
149149
- file names, i.e. array of strings
150150
- it is allowed to publish time-series layer by setting time_regex parameter and sending one or more main raster files (compressed in one archive or uncompressed) with the same extension, color interpretation, pixel size, nodata value, mask flags, and data type name. Filename can be at most 210 characters long. Supported characters are 26 Latin letters `a-zA-Z` (with or without diacritics), numbers, underscores, dashes, dots, and spaces. Other Latin characters (e.g. ligatures `ß` or `Æ`) and other than Latin scripts (e.g. Cyrillic or Chinese) are not supported. Files are stored and published with slugified filenames (diacritic is removed from letters, and space ` ` is converted to underscore `_`).
151-
- if file names are provided, files must be uploaded subsequently using [POST Workspace Layer Chunk](#post-workspace-layer-chunk)
151+
- if file names are provided, files must be uploaded subsequently using [POST Layer Chunk](#post-layer-chunk)
152152
- in case of raster data input, following input combinations of bands and color interpretations are supported:
153153
- 1 band: Gray
154154
- 1 band: Palette
@@ -230,7 +230,7 @@ JSON array of objects representing posted layers with following structure:
230230
- **name**: String. Name of the layer.
231231
- **uuid**: String. UUID of the layer.
232232
- **url**: String. URL of the layer. It points to [GET Layer](#get-layer).
233-
- *files_to_upload*: List of objects. It's present only if **file** parameter contained file names. Each object represents one file that server expects to be subsequently uploaded using [POST Workspace Layer Chunk](#post-workspace-layer-chunk). Each object has following properties:
233+
- *files_to_upload*: List of objects. It's present only if **file** parameter contained file names. Each object represents one file that server expects to be subsequently uploaded using [POST Layer Chunk](#post-layer-chunk). Each object has following properties:
234234
- **file**: name of the file, equal to one of file name from **file** parameter
235235
- **layman_original_parameter**: name of the request parameter that contained the file name; currently, the only possible value is `file`
236236

@@ -366,7 +366,7 @@ Body parameters:
366366
- If provided, current data file will be deleted and replaced by this file. GeoServer feature types, DB table, normalized raster file, and thumbnail will be deleted and created again using the new file.
367367
- same file types as in [POST Layers](#post-layers) are expected
368368
- only one of `file` or `external_table_uri` can be set
369-
- if file names are provided, files must be uploaded subsequently using [POST Workspace Layer Chunk](#post-workspace-layer-chunk)
369+
- if file names are provided, files must be uploaded subsequently using [POST Layer Chunk](#post-layer-chunk)
370370
- if published file has empty bounding box (i.e. no features), its bounding box on WMS/WFS endpoint is set to the whole World
371371
- if QML style is used (either directly within this request, or indirectly from previous state on server), it must list all attributes contained in given data file
372372
- it is allowed to publish time-series layer - see [POST Layers](#post-layers)
@@ -461,7 +461,7 @@ Content-Type:
461461
- `application/x-qgis-layer-settings` for QML
462462

463463

464-
## Workspace Layer Chunk
464+
## Layer Chunk
465465
Layer Chunk endpoint enables to upload layer data files asynchronously by splitting them into small parts called *chunks* that are uploaded independently. The endpoint is expected to be operated using [Resumable.js](https://github.com/23/resumable.js/) library. Resumable.js can split and upload files by chunks using [HTML File API](https://developer.mozilla.org/en-US/docs/Web/API/File), widely supported by major browsers.
466466

467467
Check [Asynchronous file upload](async-file-upload.md) example.
@@ -472,8 +472,8 @@ The endpoint is activated after [POST Layers](#post-layers) or [PATCH Layer](#pa
472472
- layer is deleted
473473

474474
### URL
475-
`/rest/<workspace_name>/layers/<layername>/chunk`
476-
### GET Workspace Layer Chunk
475+
`/rest/layers/<uuid>/chunk`
476+
### GET Layer Chunk
477477
Test if file chunk is already uploaded on the server.
478478

479479
#### Request
@@ -487,7 +487,7 @@ Content-Type: `application/json`
487487

488488
HTTP status code 200 if chunk is already uploaded on the server, otherwise 404.
489489

490-
### POST Workspace Layer Chunk
490+
### POST Layer Chunk
491491
Upload file chunk to the server.
492492

493493
#### Request

src/layman/layer/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def get_layer_patch_keys():
3838

3939

4040
from ..common import InternalSourceTypeDef
41-
from .rest_workspace_layer_chunk import bp as workspace_layer_chunk_bp
41+
from .rest_layer_chunk import bp as layer_chunk_bp
4242
from .rest_layer_thumbnail import bp as layer_thumbnail_bp
4343
from .rest_layer_style import bp as layer_style_bp
4444
from .rest_workspace_layer_metadata_comparison import bp as workspace_layer_metadata_comparison_bp
@@ -52,14 +52,14 @@ def get_layer_patch_keys():
5252
'name': PUBLICATION_TYPE_NAME,
5353
'rest_path_name': LAYER_REST_PATH_NAME,
5454
'workspace_blueprints': [ # blueprints to register
55-
workspace_layer_chunk_bp,
5655
workspace_layer_metadata_comparison_bp,
5756
],
5857
'blueprints': [ # blueprints to register
5958
layers_bp,
6059
layer_bp,
6160
layer_thumbnail_bp,
6261
layer_style_bp,
62+
layer_chunk_bp,
6363
],
6464
# see also .util.TASKS_TO_LAYER_INFO_KEYS
6565
'internal_sources': OrderedDict([ # internal sources to process when new source is published

src/layman/layer/rest_workspace_layer_chunk.py renamed to src/layman/layer/rest_layer_chunk.py

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,25 @@
33
from flask import Blueprint, jsonify, request, current_app as app, g
44

55
from layman import LaymanError
6-
from layman.util import check_workspace_name_decorator, get_publication_uuid
6+
from layman.util import check_uuid_decorator
77
from layman.authn import authenticate
8-
from layman.authz import authorize_workspace_publications_decorator
9-
from . import util, LAYER_REST_PATH_NAME
8+
from layman.authz import authorize_uuid_publication_decorator
9+
from . import LAYER_REST_PATH_NAME, LAYER_TYPE
1010
from .filesystem import input_chunk
11-
from .filesystem.util import LAYER_TYPE
1211

13-
bp = Blueprint('rest_workspace_layer_chunk', __name__)
12+
bp = Blueprint('rest_layer_chunk', __name__)
1413

1514

1615
@bp.before_request
17-
@check_workspace_name_decorator
18-
@util.check_layername_decorator
16+
@check_uuid_decorator
1917
@authenticate
20-
@authorize_workspace_publications_decorator
18+
@authorize_uuid_publication_decorator(expected_publication_type=LAYER_TYPE)
2119
def before_request():
2220
pass
2321

2422

25-
@bp.route(f"/{LAYER_REST_PATH_NAME}/<layername>/chunk", methods=['POST'])
26-
def post(workspace, layername):
23+
@bp.route(f"/{LAYER_REST_PATH_NAME}/<uuid>/chunk", methods=['POST'])
24+
def post(uuid):
2725
app.logger.info(f"POST Layer Chunk, actor={g.user}")
2826

2927
total_chunks = request.form.get('resumableTotalChunks', type=int)
@@ -45,19 +43,17 @@ def post(workspace, layername):
4543
chunk.seek(0, os.SEEK_SET)
4644
app.logger.info(f"POST Layer Chunk, size = {chunk_size}")
4745

48-
publ_uuid = get_publication_uuid(workspace, LAYER_TYPE, layername)
49-
input_chunk.save_layer_file_chunk(publ_uuid, parameter_name,
46+
input_chunk.save_layer_file_chunk(uuid, parameter_name,
5047
filename, chunk,
5148
chunk_number, total_chunks)
52-
# time.sleep(5)
5349

5450
return jsonify({
5551
'message': 'Chunk saved.'
5652
}), 200
5753

5854

59-
@bp.route(f"/{LAYER_REST_PATH_NAME}/<layername>/chunk", methods=['GET'])
60-
def get(workspace, layername):
55+
@bp.route(f"/{LAYER_REST_PATH_NAME}/<uuid>/chunk", methods=['GET'])
56+
def get(uuid):
6157
app.logger.info(f"GET Layer Chunk, actor={g.user}")
6258

6359
chunk_number = request.args.get('resumableChunkNumber', default=1,
@@ -67,8 +63,7 @@ def get(workspace, layername):
6763
parameter_name = request.args.get('layman_original_parameter', default='error',
6864
type=str)
6965

70-
publ_uuid = get_publication_uuid(workspace, LAYER_TYPE, layername)
71-
chunk_exists = input_chunk.layer_file_chunk_exists(publ_uuid, parameter_name, filename, chunk_number)
66+
chunk_exists = input_chunk.layer_file_chunk_exists(uuid, parameter_name, filename, chunk_number)
7267

7368
if chunk_exists:
7469
result = jsonify({

test_tools/process_client.py

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
layer_keys_to_check,
8282
'sample/layman.layer/small_layer.geojson',
8383
'rest_workspace_layer_metadata_comparison.get',
84-
'rest_workspace_layer_chunk.post',
84+
'rest_layer_chunk.post',
8585
),
8686
None: PublicationTypeDef('publicationname',
8787
'rest_publications.get',
@@ -152,18 +152,10 @@ def raise_if_not_complete_status(response):
152152
raise LaymanError(55, data=resp_json)
153153

154154

155-
def upload_file_chunks(publication_type,
156-
workspace,
157-
name,
158-
file_paths,
159-
):
160-
publication_type_def = PUBLICATION_TYPES_DEF[publication_type]
155+
def upload_file_chunks(uuid, file_paths):
161156
time.sleep(0.5)
162157
with app.app_context():
163-
chunk_url = url_for(publication_type_def.post_workspace_publication_chunk,
164-
workspace=workspace,
165-
**{publication_type_def.url_param_name: name},
166-
)
158+
chunk_url = url_for('rest_layer_chunk.post', uuid=uuid)
167159

168160
file_chunks = [('file', file_name) for file_name in file_paths]
169161
for file_type, file_name in file_chunks:
@@ -341,21 +333,19 @@ def publish_publication(publication_type,
341333
timeout=HTTP_TIMEOUT,
342334
)
343335
raise_layman_error(response)
344-
assert response.json()[0]['name'] == name or not name, f'name={name}, response.name={response.json()[0]["name"]}'
345-
name = name or response.json()[0]['name']
336+
result = response.json()[0]
337+
assert result['name'] == name or not name, f'name={name}, response.name={result["name"]}'
338+
name = name or result['name']
346339

347340
if with_chunks and not do_not_upload_chunks:
348-
upload_file_chunks(publication_type,
349-
workspace,
350-
name,
351-
file_paths, )
341+
upload_file_chunks(result['uuid'], file_paths)
352342

353343
if not do_not_upload_chunks:
354344
wait_for_publication_status(workspace, publication_type, name, check_response_fn=check_response_fn,
355345
headers=headers, raise_if_not_complete=raise_if_not_complete)
356346
if temp_dir:
357347
shutil.rmtree(temp_dir)
358-
return response.json()[0]
348+
return result
359349

360350

361351
def publish_workspace_publication(publication_type,
@@ -900,7 +890,7 @@ def patch_publication_by_uuid(publication_type,
900890
assert all(key in response.json() for key in expected_resp_keys), f'uuid={uuid}, response={response.json()}'
901891

902892
if with_chunks and not do_not_upload_chunks:
903-
upload_file_chunks_by_uuid(publication_type, uuid, file_paths,)
893+
upload_file_chunks(uuid, file_paths)
904894

905895
if not do_not_upload_chunks:
906896
wait_for_publication_status_by_uuid(uuid, publication_type,
@@ -918,14 +908,6 @@ def patch_publication_by_uuid(publication_type,
918908
patch_layer = partial(patch_publication_by_uuid, LAYER_TYPE)
919909

920910

921-
def upload_file_chunks_by_uuid(publication_type, uuid, file_paths):
922-
with app.app_context():
923-
pub_info = layman_util.get_publication_info_by_uuid(uuid, context={'keys': ['workspace', 'name']})
924-
workspace = pub_info.get('_workspace')
925-
name = pub_info.get('name')
926-
return upload_file_chunks(publication_type, workspace, name, file_paths)
927-
928-
929911
def wait_for_publication_status_by_uuid(uuid, publication_type, *, check_response_fn=None, headers=None,
930912
raise_if_not_complete=True, sleeping_time=0.5):
931913
with app.app_context():

0 commit comments

Comments
 (0)