Skip to content

Commit 427ade8

Browse files
GitHKAndrei Neagusanderegg
authored
Feature export/import study (backend) (#2053)
* adding module * first basic version which exports * added openapi spec for import, still wip * first version of the full download * installed parfive [still not sure about this one] * moved dat to yaml * changed export format * correctly updated dependencies * updated openapi spec * added support for uploading osparcprojects * added upload endpoint * upgraded parfive and refavtored code * reverting to former implementation * reverted version as well * export now saves all files from storage * added max_upload_size to 10 GB default * added defaults for local env * removed unused * refactored importers into formatters * refactored code to use formatters and models * refactored code structure * moved LinkAndFile to Pydantic model * split storge from relative file path * added data shuffling to project data and files * renamed models to avoid confusion * refactored from classmethod * changed data types to allow for import/export of projects * first startable project after export/import * pylint: imports and params cleanup * fixed codeclimate * trying to bypass codeclimate * fix pylint * exporter * put back temp dir removal * importing a project also creates its files refactor logging to debug * added failing test which is skipped * found a way to bypass py38 requirement this is only adequate for testing * wait for all services in simcore * reverting change back * fixes issue with thumbnails * trying to tix schema * adminer is not used * when directorv0 had an issue, there were several errors * fixed error * the imported project uuid is returned * must upgrade parfive after python upgrade * removed unecessary request and used app instead * if an error occurs during import a rollback is done * replaced test export V1; thumbnail format changed * removed adminer * changed project data files to json * renamed module * renamed function * removed comment * updated docstring * added more checks wen files are downloed from storage * codeclimate is anoying * removed redoundant checks * minor refactoring * major refactor * refactored and cleared up information * format changed * more logging for easier debugging * replaced format again * got half of the test to work proerly * added compressed version to test * import export test finalized * added comment to clarifay extra str type * changed import path to google api spec * wrapping exporter errors so they can be centralized * mode refactor * updated and fixed confusing comments * Update services/web/server/src/simcore_service_webserver/exporter/export_import.py Co-authored-by: Sylvain <[email protected]> * updated confusing parfive message * parfive aiofiles support message fixed * await on all uplaods in parallel * updated type * semplified project structure * updated export import format of files * remove outdated comments * fixed circular dependency import * refactored imports * refactored hwo responses are handled adds more solidity * fixed pylint * fixed logic style * removed dependency on studies_dispatcher mdoule * added missing type * file might not exist * archiving module 100 test coverage * reverting change as it breaks the comp pipelines * added export and import permissions * refactored to semplify * refactored type * in case of errors * added comments to clear up how the export works * trying to fix codeclimate * patching for ptsv debug * archiving is now done via shutil * pyyaml is required by other modules * returned as before * fix pylint * compression can no longer be disabled * fixing to comply to codeclimante rules * only the compressed version is now supported * fixed openapi specs * there is an issue respecting the google spec * fuxed type of error raised * replaced exceptons with ExporterException * fix comment * minor refactor * export is now POST no longer get * ensuring correct propagation of CancelledError * logging as warning * downloader timeout bumped to 1h max * migrated constant to configration * refactored exporter config to use Pydantic * removed section which no longer exists * updated docstring * fixed test * migrated configuration to Pydantic and decoupled module * adding back missing module * making setup function detectable in testing * added missing section Co-authored-by: Andrei Neagu <[email protected]> Co-authored-by: Sylvain <[email protected]>
1 parent 9e0d6df commit 427ade8

36 files changed

+2614
-38
lines changed

api/specs/webserver/openapi-projects.yaml

+54
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,60 @@ paths:
194194
default:
195195
$ref: "#/components/responses/DefaultErrorResponse"
196196

197+
/projects/{project_id}/export:
198+
parameters:
199+
- name: project_id
200+
in: path
201+
required: true
202+
schema:
203+
type: string
204+
- name: compressed
205+
in: query
206+
required: false
207+
schema:
208+
type: boolean
209+
post:
210+
tags:
211+
- exporter
212+
summary: creates an archive of the project and downloads it
213+
operationId: export_project
214+
responses:
215+
"200":
216+
description: creates an archive from a project file
217+
content:
218+
application/zip:
219+
schema:
220+
type: string
221+
format: binary
222+
default:
223+
$ref: "#/components/responses/DefaultErrorResponse"
224+
225+
/projects/import:
226+
post:
227+
tags:
228+
- exporter
229+
summary: Create new project from an archive
230+
operationId: import_project
231+
requestBody:
232+
content:
233+
multipart/form-data:
234+
schema:
235+
type: object
236+
properties:
237+
fileName:
238+
type: string
239+
format: binary
240+
responses:
241+
"201":
242+
description: creates a new project from an archive
243+
content:
244+
application/json:
245+
schema:
246+
#TODO: change this with an OK response
247+
$ref: "#/components/schemas/ProjectEnveloped"
248+
default:
249+
$ref: "#/components/responses/DefaultErrorResponse"
250+
197251
/projects/{project_id}/close:
198252
parameters:
199253
- name: project_id

api/specs/webserver/openapi.yaml

+7-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,6 @@ paths:
125125
/groups/sparc/classifiers/scicrunch-resources:search:
126126
$ref: "./openapi-groups.yaml#/paths/~1groups~1sparc~1classifiers~1scicrunch-resources:search"
127127

128-
129128
# DATA STORAGE SERVICES ----------------------------------------------------------
130129

131130
/storage/locations:
@@ -169,6 +168,13 @@ paths:
169168
/projects/{project_id}/state:
170169
$ref: "./openapi-projects.yaml#/paths/~1projects~1{project_id}~1state"
171170

171+
# FIXME: /projects/{project_id}:export dose not work
172+
/projects/{project_id}/export:
173+
$ref: "./openapi-projects.yaml#/paths/~1projects~1{project_id}~1export"
174+
175+
/projects:import:
176+
$ref: "./openapi-projects.yaml#/paths/~1projects~1import"
177+
172178
/projects/{project_id}:close:
173179
$ref: "./openapi-projects.yaml#/paths/~1projects~1{project_id}~1close"
174180

packages/models-library/src/models_library/basic_types.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from enum import Enum
22

3-
from pydantic import conint, constr, confloat
3+
from pydantic import confloat, conint, constr
44

55
PortInt = conint(gt=0, lt=65535)
66

packages/models-library/src/models_library/projects.py

+13-6
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
Models a study's project document
33
"""
44
from copy import deepcopy
5-
from typing import Dict, List, Optional, Any
5+
from typing import Any, Dict, List, Optional, Union
66
from uuid import UUID
77

8-
from pydantic import BaseModel, EmailStr, Extra, Field, HttpUrl
8+
from pydantic import BaseModel, EmailStr, Extra, Field, HttpUrl, validator
99

1010
from .basic_regex import DATE_RE
1111
from .projects_access import AccessRights, GroupID
@@ -40,7 +40,9 @@ class Project(BaseModel):
4040
description="longer one-line description about the project",
4141
examples=["Dabbling in temporal transitions ..."],
4242
)
43-
thumbnail: HttpUrl = Field(
43+
# NOTE: str is necessary because HttpUrl will not accept and empty string and the
44+
# frontend sometimes sends this empty string, which is removed by the validator
45+
thumbnail: Union[str, HttpUrl] = Field(
4446
...,
4547
description="url of the project thumbnail",
4648
examples=["https://placeimg.com/171/96/tech/grayscale/?0.jpg"],
@@ -88,9 +90,14 @@ class Project(BaseModel):
8890
quality: Dict[str, Any] = {}
8991

9092
# Dev only
91-
dev: Optional[Dict] = Field(
92-
description="object used for development purposes only"
93-
)
93+
dev: Optional[Dict] = Field(description="object used for development purposes only")
94+
95+
@classmethod
96+
@validator("thumbnail")
97+
def null_thumbnail(cls, v):
98+
if isinstance(v, str) and v == "":
99+
return None
100+
return v
94101

95102
class Config:
96103
description = "Document that stores metadata, pipeline and UI setup of a study"

packages/service-library/src/servicelib/rest_middlewares.py

+3
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,9 @@ async def _middleware_handler(request: web.Request, handler):
172172

173173
resp = await handler(request)
174174

175+
if isinstance(resp, web.FileResponse): # allows for files to be downloaded
176+
return resp
177+
175178
if not isinstance(resp, web.Response):
176179
response = create_data_response(
177180
data=resp, skip_internal_error_details=_is_prod,

services/director-v2/src/simcore_service_director_v2/utils/client_decorators.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ async def wrapper_func(*args, **kwargs) -> httpx.Response:
7575
service_name,
7676
resp.reason_phrase,
7777
resp.status_code,
78-
resp.text(),
78+
resp.text,
7979
)
8080
raise HTTPException(status.HTTP_503_SERVICE_UNAVAILABLE)
8181

services/web/server/requirements/_base.in

+1
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,6 @@ python-socketio
3030
semantic_version
3131
aiodebug
3232
json2html
33+
parfive
3334

3435
pydantic[email]

services/web/server/requirements/_base.txt

+2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ multidict==4.7.6 # via aiohttp, yarl
5151
openapi-core==0.12.0 # via -r requirements/../../../../packages/service-library/requirements/_base.in
5252
openapi-spec-validator==0.2.9 # via openapi-core
5353
pamqp==2.3.0 # via aiormq
54+
parfive==1.0.2 # via -r requirements/_base.in
5455
passlib==1.7.2 # via -r requirements/_base.in
5556
prometheus-client==0.9.0 # via -r requirements/../../../../packages/service-library/requirements/_base.in
5657
prompt-toolkit==3.0.8 # via click-repl
@@ -68,6 +69,7 @@ six==1.15.0 # via click-repl, cryptography, isodate, jsonschema, o
6869
sqlalchemy[postgresql_psycopg2binary]==1.3.20 # via -c requirements/../../../../packages/models-library/requirements/../../../requirements/constraints.txt, -c requirements/../../../../packages/postgres-database/requirements/../../../requirements/constraints.txt, -c requirements/../../../../packages/service-library/requirements/../../../requirements/constraints.txt, -c requirements/../../../../requirements/constraints.txt, -r requirements/../../../../packages/postgres-database/requirements/_base.in, -r requirements/../../../../packages/service-library/requirements/_base.in, aiopg
6970
strict-rfc3339==0.7 # via openapi-core
7071
tenacity==6.2.0 # via -r requirements/../../../../packages/service-library/requirements/_base.in
72+
tqdm==4.54.1 # via parfive
7173
trafaret==2.0.2 # via -r requirements/../../../../packages/service-library/requirements/_base.in
7274
typing-extensions==3.7.4.2 # via aiohttp, yarl
7375
ujson==3.1.0 # via -r requirements/../../../../packages/service-library/requirements/_base.in, aiohttp-swagger

0 commit comments

Comments
 (0)