Skip to content

Commit a909c6d

Browse files
committed
webserver api
1 parent 9aeaede commit a909c6d

File tree

4 files changed

+138
-10
lines changed

4 files changed

+138
-10
lines changed

services/web/server/requirements/_test.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ coverage
1616
docker
1717
Faker
1818
fastapi[standard]
19+
fastapi-pagination
1920
flaky
2021
hypothesis
2122
jsonref

services/web/server/requirements/_test.txt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,16 @@ alembic==1.8.1
1414
# via
1515
# -c requirements/_base.txt
1616
# -r requirements/_test.in
17+
annotated-types==0.7.0
18+
# via
19+
# -c requirements/_base.txt
20+
# pydantic
1721
anyio==4.3.0
1822
# via
1923
# -c requirements/_base.txt
2024
# httpx
25+
# starlette
26+
# watchfiles
2127
async-timeout==4.0.3
2228
# via
2329
# -c requirements/_base.txt
@@ -77,6 +83,8 @@ fastapi==0.115.6
7783
# via -r requirements/_test.in
7884
fastapi-cli==0.0.5
7985
# via fastapi
86+
fastapi-pagination==0.12.34
87+
# via -r requirements/_test.in
8088
flaky==3.8.1
8189
# via -r requirements/_test.in
8290
frozenlist==1.4.1
@@ -92,14 +100,18 @@ h11==0.14.0
92100
# via
93101
# -c requirements/_base.txt
94102
# httpcore
103+
# uvicorn
95104
httpcore==1.0.7
96105
# via
97106
# -c requirements/_base.txt
98107
# httpx
108+
httptools==0.6.4
109+
# via uvicorn
99110
httpx==0.28.1
100111
# via
101112
# -c requirements/../../../../requirements/constraints.txt
102113
# -c requirements/_base.txt
114+
# fastapi
103115
# respx
104116
hypothesis==6.91.0
105117
# via -r requirements/_test.in
@@ -109,6 +121,7 @@ idna==3.3
109121
# via
110122
# -c requirements/_base.txt
111123
# anyio
124+
# email-validator
112125
# httpx
113126
# requests
114127
# yarl
@@ -175,6 +188,7 @@ pydantic==2.10.2
175188
# -c requirements/../../../../requirements/constraints.txt
176189
# -c requirements/_base.txt
177190
# fastapi
191+
# fastapi-pagination
178192
pydantic-core==2.27.1
179193
# via
180194
# -c requirements/_base.txt
@@ -253,6 +267,10 @@ requests==2.32.2
253267
# docker
254268
respx==0.22.0
255269
# via -r requirements/_test.in
270+
rich==13.4.2
271+
# via
272+
# -c requirements/_base.txt
273+
# typer
256274
setuptools==69.1.1
257275
# via
258276
# -c requirements/_base.txt
@@ -310,6 +328,7 @@ typing-extensions==4.12.2
310328
# -c requirements/_base.txt
311329
# asyncpg-stubs
312330
# fastapi
331+
# fastapi-pagination
313332
# mypy
314333
# pydantic
315334
# pydantic-core

services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6024,6 +6024,54 @@ paths:
60246024
$ref: '#/components/schemas/FileLocation'
60256025
type: array
60266026
title: Response List Storage Locations
6027+
/v0/storage/locations/{location_id}/paths:
6028+
get:
6029+
tags:
6030+
- storage
6031+
summary: List Storage Paths
6032+
description: Lists the files/directories in WorkingDirectory
6033+
operationId: list_storage_paths
6034+
parameters:
6035+
- name: location_id
6036+
in: path
6037+
required: true
6038+
schema:
6039+
type: integer
6040+
title: Location Id
6041+
- name: size
6042+
in: query
6043+
required: false
6044+
schema:
6045+
type: integer
6046+
minimum: 1
6047+
exclusiveMaximum: true
6048+
default: 20
6049+
title: Size
6050+
maximum: 50
6051+
- name: cursor
6052+
in: query
6053+
required: false
6054+
schema:
6055+
anyOf:
6056+
- type: string
6057+
- type: 'null'
6058+
title: Cursor
6059+
- name: fileFilter
6060+
in: query
6061+
required: false
6062+
schema:
6063+
anyOf:
6064+
- type: string
6065+
format: path
6066+
- type: 'null'
6067+
title: Filefilter
6068+
responses:
6069+
'200':
6070+
description: Successful Response
6071+
content:
6072+
application/json:
6073+
schema:
6074+
$ref: '#/components/schemas/CursorPage_FileMetaDataGet_'
60276075
/v0/storage/locations/{location_id}/datasets:
60286076
get:
60296077
tags:
@@ -8362,6 +8410,47 @@ components:
83628410
required:
83638411
- priceDollars
83648412
title: CreateWalletPayment
8413+
CursorPage_FileMetaDataGet_:
8414+
properties:
8415+
items:
8416+
items:
8417+
$ref: '#/components/schemas/FileMetaDataGet'
8418+
type: array
8419+
title: Items
8420+
total:
8421+
anyOf:
8422+
- type: integer
8423+
- type: 'null'
8424+
title: Total
8425+
description: Total items
8426+
current_page:
8427+
anyOf:
8428+
- type: string
8429+
- type: 'null'
8430+
title: Current Page
8431+
description: Cursor to refetch the current page
8432+
current_page_backwards:
8433+
anyOf:
8434+
- type: string
8435+
- type: 'null'
8436+
title: Current Page Backwards
8437+
description: Cursor to refetch the current page starting from the last item
8438+
previous_page:
8439+
anyOf:
8440+
- type: string
8441+
- type: 'null'
8442+
title: Previous Page
8443+
description: Cursor for the previous page
8444+
next_page:
8445+
anyOf:
8446+
- type: string
8447+
- type: 'null'
8448+
title: Next Page
8449+
description: Cursor for the next page
8450+
type: object
8451+
required:
8452+
- items
8453+
title: CursorPage[FileMetaDataGet]
83658454
DatCoreFileLink:
83668455
properties:
83678456
store:

services/web/server/src/simcore_service_webserver/storage/_rest.py

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
StorageAsyncJobStatus,
2626
)
2727
from models_library.projects_nodes_io import LocationID
28+
from models_library.utils.change_case import camel_to_snake
2829
from models_library.utils.fastapi_encoders import jsonable_encoder
2930
from pydantic import AnyUrl, BaseModel, ByteSize, TypeAdapter
3031
from servicelib.aiohttp import status
@@ -45,12 +46,12 @@
4546
)
4647
from servicelib.request_keys import RQT_USERID_KEY
4748
from servicelib.rest_responses import unwrap_envelope
48-
from simcore_service_webserver.rabbitmq import get_rabbitmq_rpc_client
4949
from yarl import URL
5050

5151
from .._meta import API_VTAG
5252
from ..login.decorators import login_required
5353
from ..models import RequestContext
54+
from ..rabbitmq import get_rabbitmq_rpc_client
5455
from ..security.decorators import permission_required
5556
from ._exception_handlers import handle_data_export_exceptions
5657
from .schemas import StorageFileIDStr
@@ -91,7 +92,7 @@ def _to_storage_url(request: web.Request) -> URL:
9192

9293
return (
9394
url.joinpath(fastapi_encoded_suffix, encoded=True)
94-
.with_query(request.query)
95+
.with_query({camel_to_snake(k): v for k, v in request.query.items()})
9596
.update_query(user_id=userid)
9697
)
9798

@@ -123,18 +124,29 @@ class _ResponseTuple(NamedTuple):
123124

124125

125126
async def _forward_request_to_storage(
126-
request: web.Request, method: str, body: dict[str, Any] | None = None, **kwargs
127+
request: web.Request,
128+
method: str,
129+
body: dict[str, Any] | None = None,
130+
**kwargs,
127131
) -> _ResponseTuple:
128132
url = _to_storage_url(request)
129133
session = get_client_session(request.app)
130134

131135
async with session.request(
132136
method.upper(), url, ssl=False, json=body, **kwargs
133137
) as resp:
134-
if resp.status >= status.HTTP_400_BAD_REQUEST:
135-
raise web.HTTPException(reason=await resp.text())
136-
payload = await resp.json()
137-
return _ResponseTuple(payload=payload, status_code=resp.status)
138+
match resp.status:
139+
case status.HTTP_422_UNPROCESSABLE_ENTITY:
140+
raise web.HTTPUnprocessableEntity(
141+
reason=await resp.text(), content_type=resp.content_type
142+
)
143+
case status.HTTP_404_NOT_FOUND:
144+
raise web.HTTPNotFound(reason=await resp.text())
145+
case _ if resp.status >= status.HTTP_400_BAD_REQUEST:
146+
raise web.HTTPError(reason=await resp.text())
147+
case _:
148+
payload = await resp.json()
149+
return _ResponseTuple(payload=payload, status_code=resp.status)
138150

139151

140152
# ---------------------------------------------------------------------
@@ -152,6 +164,16 @@ async def list_storage_locations(request: web.Request) -> web.Response:
152164
return create_data_response(payload, status=resp_status)
153165

154166

167+
@routes.get(
168+
f"{_storage_locations_prefix}/{{location_id}}/paths", name="list_storage_paths"
169+
)
170+
@login_required
171+
@permission_required("storage.files.*")
172+
async def list_paths(request: web.Request) -> web.Response:
173+
payload, resp_status = await _forward_request_to_storage(request, "GET", body=None)
174+
return create_data_response(payload, status=resp_status)
175+
176+
155177
@routes.get(
156178
_storage_locations_prefix + "/{location_id}/datasets", name="list_datasets_metadata"
157179
)
@@ -423,7 +445,6 @@ class _PathParams(BaseModel):
423445
@permission_required("storage.files.*")
424446
@handle_data_export_exceptions
425447
async def get_async_jobs(request: web.Request) -> web.Response:
426-
427448
_req_ctx = RequestContext.model_validate(request)
428449

429450
rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app)
@@ -449,7 +470,6 @@ async def get_async_jobs(request: web.Request) -> web.Response:
449470
@permission_required("storage.files.*")
450471
@handle_data_export_exceptions
451472
async def get_async_job_status(request: web.Request) -> web.Response:
452-
453473
_req_ctx = RequestContext.model_validate(request)
454474
rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app)
455475

@@ -503,7 +523,6 @@ async def abort_async_job(request: web.Request) -> web.Response:
503523
@permission_required("storage.files.*")
504524
@handle_data_export_exceptions
505525
async def get_async_job_result(request: web.Request) -> web.Response:
506-
507526
_req_ctx = RequestContext.model_validate(request)
508527

509528
rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app)

0 commit comments

Comments
 (0)