Skip to content

Commit 253bdc2

Browse files
authored
✨♻️ Storage refactoring step3 (ITISFoundation#3144)
1 parent 2bc4d08 commit 253bdc2

File tree

96 files changed

+5909
-4556
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

96 files changed

+5909
-4556
lines changed

api/specs/storage/openapi.yaml

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,11 @@ paths:
243243
$ref: "#/components/schemas/FileMetaEnvelope"
244244
default:
245245
$ref: "#/components/responses/DefaultErrorResponse"
246-
patch:
247-
summary: Update file metadata
248-
operationId: update_file_meta_data
246+
247+
/locations/{location_id}/files/{file_id}:abort:
248+
post:
249+
summary: Asks the server to abort the upload and revert to the last valid version if any
250+
operationId: abort_upload_file
249251
parameters:
250252
- name: file_id
251253
in: path
@@ -263,12 +265,8 @@ paths:
263265
schema:
264266
type: string
265267
responses:
266-
"200":
267-
description: "Returns file metadata"
268-
content:
269-
application/json:
270-
schema:
271-
$ref: "#/components/schemas/FileMetaEnvelope"
268+
"204":
269+
description: Abort OK
272270
default:
273271
$ref: "#/components/responses/DefaultErrorResponse"
274272

@@ -346,6 +344,12 @@ paths:
346344
application/json:
347345
schema:
348346
$ref: "#/components/schemas/PresignedLinkEnveloped"
347+
links:
348+
AbortUpload:
349+
operationId: abort_upload_file
350+
parameters:
351+
path.location_id: "$request.path.location_id"
352+
path.file_id: "$request.path.file_id"
349353
default:
350354
$ref: "#/components/responses/DefaultErrorResponse"
351355
delete:

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

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,26 @@
88

99
import re
1010
from datetime import datetime
11-
from typing import List, Optional, Pattern, Union
11+
from enum import Enum
12+
from typing import Any, Optional, Pattern, Union
1213
from uuid import UUID
1314

14-
from models_library.projects_nodes_io import LocationID, LocationName, StorageFileID
15-
from pydantic import BaseModel, ByteSize, ConstrainedStr, Extra, Field, validator
15+
from models_library.projects_nodes_io import (
16+
LocationID,
17+
LocationName,
18+
NodeID,
19+
SimcoreS3FileID,
20+
StorageFileID,
21+
)
22+
from pydantic import (
23+
BaseModel,
24+
ByteSize,
25+
ConstrainedStr,
26+
Extra,
27+
Field,
28+
root_validator,
29+
validator,
30+
)
1631
from pydantic.networks import AnyUrl
1732

1833
from .basic_regex import DATCORE_DATASET_NAME_RE, S3_BUCKET_NAME_RE
@@ -127,8 +142,8 @@ class FileMetaDataGet(BaseModel):
127142

128143
@validator("location_id", pre=True)
129144
@classmethod
130-
def convert_from_str(cls, v):
131-
if isinstance(v, str):
145+
def ensure_location_is_integer(cls, v):
146+
if v is not None:
132147
return int(v)
133148
return v
134149

@@ -198,14 +213,46 @@ class Config:
198213

199214

200215
class FileMetaDataArray(BaseModel):
201-
__root__: List[FileMetaDataGet] = []
216+
__root__: list[FileMetaDataGet] = []
202217

203218

204219
# /locations/{location_id}/files/{file_id}
205220

206221

222+
class LinkType(str, Enum):
223+
PRESIGNED = "PRESIGNED"
224+
S3 = "S3"
225+
226+
207227
class PresignedLink(BaseModel):
208228
link: AnyUrl
209229

210230

211231
# /simcore-s3/
232+
233+
234+
class FoldersBody(BaseModel):
235+
source: dict[str, Any] = Field(default_factory=dict)
236+
destination: dict[str, Any] = Field(default_factory=dict)
237+
nodes_map: dict[NodeID, NodeID] = Field(default_factory=dict)
238+
239+
@root_validator()
240+
@classmethod
241+
def ensure_consistent_entries(cls, values):
242+
source_node_keys = (
243+
NodeID(n) for n in values["source"].get("workbench", {}).keys()
244+
)
245+
if set(source_node_keys) != set(values["nodes_map"].keys()):
246+
raise ValueError("source project nodes do not fit with nodes_map entries")
247+
destination_node_keys = (
248+
NodeID(n) for n in values["destination"].get("workbench", {}).keys()
249+
)
250+
if set(destination_node_keys) != set(values["nodes_map"].values()):
251+
raise ValueError(
252+
"destination project nodes do not fit with nodes_map values"
253+
)
254+
return values
255+
256+
257+
class SoftCopyBody(BaseModel):
258+
link_id: SimcoreS3FileID

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from pydantic import conint, constr
44

5-
from .basic_regex import UUID_RE_BASE, VERSION_RE
5+
from .basic_regex import UUID_RE, VERSION_RE
66

77
# port number range
88
PortInt = conint(gt=0, lt=65535)
@@ -21,7 +21,7 @@
2121
EnvVarKey = constr(regex=r"[a-zA-Z][a-azA-Z0-9_]*")
2222

2323
# e.g. '5c833a78-1af3-43a7-9ed7-6a63b188f4d8'
24-
UUIDStr = constr(regex=UUID_RE_BASE)
24+
UUIDStr = constr(regex=UUID_RE)
2525

2626

2727
class LogLevel(str, Enum):

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from copy import deepcopy
55
from datetime import datetime
66
from enum import Enum
7-
from typing import Any, Dict, List, Optional
7+
from typing import Any, Optional
88
from uuid import UUID
99

1010
from pydantic import BaseModel, EmailStr, Extra, Field, HttpUrl, constr, validator
@@ -22,7 +22,7 @@
2222
ClassifierID = str
2323

2424
# TODO: for some reason class Workbench(BaseModel): __root__= does not work as I thought ... investigate!
25-
Workbench = Dict[NodeIDStr, Node]
25+
Workbench = dict[NodeIDStr, Node]
2626

2727

2828
# NOTE: careful this is in sync with packages/postgres-database/src/simcore_postgres_database/models/projects.py!!!
@@ -97,6 +97,7 @@ def convert_sql_alchemy_enum(cls, v):
9797
class Config:
9898
orm_mode = True
9999
use_enum_values = True
100+
allow_population_by_field_name = True
100101

101102

102103
class Project(BaseProjectModel):
@@ -121,15 +122,15 @@ class Project(BaseProjectModel):
121122
examples=["2018-07-01T11:13:43Z"],
122123
alias="lastChangeDate",
123124
)
124-
access_rights: Dict[GroupIDStr, AccessRights] = Field(
125+
access_rights: dict[GroupIDStr, AccessRights] = Field(
125126
...,
126127
description="object containing the GroupID as key and read/write/execution permissions as value",
127128
alias="accessRights",
128129
)
129130

130131
# Classification
131-
tags: Optional[List[int]] = []
132-
classifiers: Optional[List[ClassifierID]] = Field(
132+
tags: Optional[list[int]] = []
133+
classifiers: Optional[list[ClassifierID]] = Field(
133134
default_factory=list,
134135
description="Contains the reference to the project classifiers",
135136
examples=["some:id:to:a:classifier"],
@@ -142,20 +143,20 @@ class Project(BaseProjectModel):
142143
ui: Optional[StudyUI] = None
143144

144145
# Quality
145-
quality: Dict[str, Any] = Field(
146+
quality: dict[str, Any] = Field(
146147
{}, description="stores the study quality assessment"
147148
)
148149

149150
# Dev only
150-
dev: Optional[Dict] = Field(description="object used for development purposes only")
151+
dev: Optional[dict] = Field(description="object used for development purposes only")
151152

152153
class Config:
153154
description = "Document that stores metadata, pipeline and UI setup of a study"
154155
title = "osparc-simcore project"
155156
extra = Extra.forbid
156157

157158
@staticmethod
158-
def schema_extra(schema: Dict, _model: "Project"):
159+
def schema_extra(schema: dict, _model: "Project"):
159160
# pylint: disable=unsubscriptable-object
160161

161162
# Patch to allow jsonschema nullable

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

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@
77
"""
88

99
import re
10-
from enum import IntEnum
1110
from pathlib import Path
12-
from typing import Literal, Optional, Pattern, Union
11+
from typing import Optional, Pattern, Union
1312
from uuid import UUID
1413

1514
from pydantic import AnyUrl, BaseModel, ConstrainedStr, Extra, Field, validator
@@ -24,14 +23,8 @@ class NodeIDStr(ConstrainedStr):
2423
regex: Optional[Pattern[str]] = re.compile(UUID_RE)
2524

2625

27-
# NOTE: this trick is used to keep backward compatility simcore.s3 is not a valid python variable name
28-
Location = IntEnum(
29-
value="Location",
30-
names=[("simcore.s3", 0), ("SIMCORE_S3", 0), ("datcore", 1), ("DATCORE", 1)],
31-
)
32-
33-
LocationID = Union[Literal[0], Literal[1]]
34-
LocationName = Union[Literal["simcore.s3"], Literal["datcore"]]
26+
LocationID = int
27+
LocationName = str
3528

3629

3730
class SimcoreS3FileID(ConstrainedStr):

packages/models-library/tests/test_basic_regex.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from models_library.basic_regex import (
1313
DATE_RE,
1414
PUBLIC_VARIABLE_NAME_RE,
15-
UUID_RE_BASE,
15+
UUID_RE,
1616
VERSION_RE,
1717
)
1818
from packaging.version import Version
@@ -30,6 +30,7 @@ def assert_match_and_get_capture(regex_str, test_str, expected) -> Optional[Sequ
3030
assert match is not None
3131
print(regex_str, "captured:", match.group(), "->", match.groups())
3232
else:
33+
assert match
3334
captured = match.groups()
3435
assert captured == expected
3536
return captured
@@ -56,7 +57,7 @@ def test_VERSION_RE(version_str, expected):
5657
],
5758
)
5859
def test_UUID_RE(uuid_str, expected):
59-
assert_match_and_get_capture(UUID_RE_BASE, uuid_str, expected)
60+
assert_match_and_get_capture(UUID_RE, uuid_str, expected)
6061

6162

6263
class webserver_timedate_utils:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"""file_meta_data: remove unused columns, add expiration of upload
2+
3+
Revision ID: 2cc556e5c52d
4+
Revises: cf3bac482ce0
5+
Create Date: 2022-06-26 19:12:13.478593+00:00
6+
7+
"""
8+
import sqlalchemy as sa
9+
from alembic import op
10+
11+
# revision identifiers, used by Alembic.
12+
revision = "2cc556e5c52d"
13+
down_revision = "cf3bac482ce0"
14+
branch_labels = None
15+
depends_on = None
16+
17+
18+
def upgrade():
19+
# ### commands auto generated by Alembic - please adjust! ###
20+
op.add_column(
21+
"file_meta_data", sa.Column("upload_expires_at", sa.DateTime(), nullable=True)
22+
)
23+
op.alter_column(
24+
"file_meta_data", "file_id", existing_type=sa.VARCHAR(), nullable=False
25+
)
26+
op.drop_column("file_meta_data", "display_file_path")
27+
op.drop_column("file_meta_data", "node_name")
28+
op.drop_column("file_meta_data", "raw_file_path")
29+
op.drop_column("file_meta_data", "project_name")
30+
op.drop_column("file_meta_data", "file_name")
31+
op.drop_column("file_meta_data", "file_uuid")
32+
op.drop_column("file_meta_data", "user_name")
33+
# ### end Alembic commands ###
34+
op.create_primary_key("pk_file_meta_data", "file_meta_data", ["file_id"])
35+
36+
37+
def downgrade():
38+
op.drop_constraint("pk_file_meta_data", "file_meta_data", "primary")
39+
# ### commands auto generated by Alembic - please adjust! ###
40+
op.add_column(
41+
"file_meta_data",
42+
sa.Column("user_name", sa.VARCHAR(), autoincrement=False, nullable=True),
43+
)
44+
op.add_column(
45+
"file_meta_data",
46+
sa.Column("file_uuid", sa.VARCHAR(), autoincrement=False, nullable=True),
47+
)
48+
op.add_column(
49+
"file_meta_data",
50+
sa.Column("file_name", sa.VARCHAR(), autoincrement=False, nullable=True),
51+
)
52+
op.add_column(
53+
"file_meta_data",
54+
sa.Column("project_name", sa.VARCHAR(), autoincrement=False, nullable=True),
55+
)
56+
op.add_column(
57+
"file_meta_data",
58+
sa.Column("raw_file_path", sa.VARCHAR(), autoincrement=False, nullable=True),
59+
)
60+
op.add_column(
61+
"file_meta_data",
62+
sa.Column("node_name", sa.VARCHAR(), autoincrement=False, nullable=True),
63+
)
64+
op.add_column(
65+
"file_meta_data",
66+
sa.Column(
67+
"display_file_path", sa.VARCHAR(), autoincrement=False, nullable=True
68+
),
69+
)
70+
op.alter_column(
71+
"file_meta_data", "file_id", existing_type=sa.VARCHAR(), nullable=True
72+
)
73+
op.drop_column("file_meta_data", "upload_expires_at")
74+
# ### end Alembic commands ###
75+
conn = op.get_bind()
76+
for row in conn.execute(sa.DDL("SELECT file_id FROM file_meta_data")):
77+
file_id = row["file_id"]
78+
conn.execute(
79+
sa.DDL(
80+
f"""
81+
UPDATE file_meta_data
82+
SET file_uuid = '{file_id}'
83+
WHERE file_id = '{file_id}'
84+
"""
85+
)
86+
)
87+
op.create_primary_key("pk_file_meta_data", "file_meta_data", ["file_uuid"])

packages/postgres-database/src/simcore_postgres_database/models/file_meta_data.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,14 @@
55
file_meta_data = sa.Table(
66
"file_meta_data",
77
metadata,
8-
sa.Column("file_uuid", sa.String(), primary_key=True),
98
sa.Column("location_id", sa.String()),
109
sa.Column("location", sa.String()),
1110
sa.Column("bucket_name", sa.String()),
1211
sa.Column("object_name", sa.String()),
1312
sa.Column("project_id", sa.String()),
14-
sa.Column("project_name", sa.String()),
1513
sa.Column("node_id", sa.String()),
16-
sa.Column("node_name", sa.String()),
17-
sa.Column("file_name", sa.String()),
1814
sa.Column("user_id", sa.String()),
19-
sa.Column("user_name", sa.String()),
20-
sa.Column("file_id", sa.String()),
21-
sa.Column("raw_file_path", sa.String()),
22-
sa.Column("display_file_path", sa.String()),
15+
sa.Column("file_id", sa.String(), primary_key=True),
2316
sa.Column("created_at", sa.String()),
2417
sa.Column("last_modified", sa.String()),
2518
sa.Column("file_size", sa.BigInteger()),
@@ -38,4 +31,7 @@
3831
doc="If true, this file is a soft link."
3932
"i.e. is another entry with the same object_name",
4033
),
34+
sa.Column(
35+
"upload_expires_at", sa.DateTime(), nullable=True, doc="Timestamp of expiration"
36+
),
4137
)

0 commit comments

Comments
 (0)