Skip to content
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

✨Implement delete file endpoint #4707

Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
3afd5ea
use relative paths in links returned from get_upload_links
bisgaard-itis Aug 25, 2023
b58ec4a
initial implementation of deletion endpoint
bisgaard-itis Aug 25, 2023
8885ee3
further additions to delete endpoint
bisgaard-itis Aug 28, 2023
1482a2b
add user_id automatically in abort/complete endpoints
bisgaard-itis Aug 28, 2023
232a9c9
use storage_client for delete entrypoint
bisgaard-itis Aug 28, 2023
5723e5f
add mock data and massage endpoints a bit
bisgaard-itis Aug 28, 2023
f27b668
add openapi-core to test tools
bisgaard-itis Aug 29, 2023
e33e599
begin to process captures using openapi specs
bisgaard-itis Aug 30, 2023
607cefc
merge master into implement-delete-endpoint
bisgaard-itis Aug 31, 2023
7bf74fd
add jsonref as a dependency
bisgaard-itis Aug 31, 2023
867188f
refactoring
bisgaard-itis Aug 31, 2023
839e812
make sure jsonref is available for tests
bisgaard-itis Aug 31, 2023
34d538f
start adding test cases for different types
bisgaard-itis Aug 31, 2023
2dfbc60
start adding more serious tests
bisgaard-itis Sep 1, 2023
4916492
further changes to support regex patterns
bisgaard-itis Sep 1, 2023
897d520
add a few more tests and minor bugfix
bisgaard-itis Sep 1, 2023
bac96a1
several small changes to improve logging
bisgaard-itis Sep 1, 2023
ee81cfe
add catalog to hosts
bisgaard-itis Sep 1, 2023
ea4c1c5
initial implementation of pytest fixture which makes use of the captures
bisgaard-itis Sep 1, 2023
cb45edd
minor change
bisgaard-itis Sep 4, 2023
8e3b369
fix several minor issues
bisgaard-itis Sep 4, 2023
94de7ed
several changes to make tests work
bisgaard-itis Sep 4, 2023
8421786
several changes to make test pass
bisgaard-itis Sep 4, 2023
b34ea5a
several small changes
bisgaard-itis Sep 4, 2023
b384f48
merge master into implement-delete-file-endpoint
bisgaard-itis Sep 4, 2023
396b347
assert all path parameters are present
bisgaard-itis Sep 4, 2023
30da6ad
cosmetic changes
bisgaard-itis Sep 4, 2023
1f9c59f
make the captures backwards compatible
bisgaard-itis Sep 4, 2023
6ab1327
avoid too many return statements
bisgaard-itis Sep 5, 2023
e07162c
cosmetic change
bisgaard-itis Sep 5, 2023
9b79941
regex_lookup->respx_lookup
bisgaard-itis Sep 5, 2023
d83d533
get rid of red squiggles
bisgaard-itis Sep 5, 2023
8a330df
minor change
bisgaard-itis Sep 5, 2023
ffddbdc
type fixes
bisgaard-itis Sep 5, 2023
e3c7b5f
fix test
bisgaard-itis Sep 5, 2023
12272e9
minor type fixes
bisgaard-itis Sep 5, 2023
36af390
make pylint happy
bisgaard-itis Sep 5, 2023
ce8c369
specify exceptions
bisgaard-itis Sep 5, 2023
4acb51a
checkout .txt requirements
bisgaard-itis Sep 5, 2023
a84b0e7
update aioresponses
bisgaard-itis Sep 5, 2023
a5b4b21
abortion_url -> abort_url
bisgaard-itis Sep 5, 2023
47481b8
only specify path in client request
bisgaard-itis Sep 5, 2023
ca81388
fix call to logger
bisgaard-itis Sep 5, 2023
e4bd1c1
small changes to enhance preprocessing call
bisgaard-itis Sep 5, 2023
ea75aa0
resolve name collisions
bisgaard-itis Sep 5, 2023
01f79e9
further minor changes
bisgaard-itis Sep 5, 2023
e735dbd
further minor changes
bisgaard-itis Sep 5, 2023
817eea9
changes to capture callback
bisgaard-itis Sep 5, 2023
a4c86ce
remove markit
bisgaard-itis Sep 5, 2023
4d3bd7e
fix import
bisgaard-itis Sep 5, 2023
54fb7c7
refix
bisgaard-itis Sep 5, 2023
290cf1b
small change
bisgaard-itis Sep 5, 2023
bf804f8
Merge branch 'master' into implement-delete-file-endpoint
bisgaard-itis Sep 5, 2023
416224f
Merge branch 'master' into implement-delete-file-endpoint
bisgaard-itis Sep 6, 2023
8167280
Merge branch 'master' into implement-delete-file-endpoint
bisgaard-itis Sep 6, 2023
1a31ea7
Merge branch 'master' into implement-delete-file-endpoint
bisgaard-itis Sep 6, 2023
b271478
move jsonref to base res
bisgaard-itis Sep 6, 2023
e581999
regenerate reqs
bisgaard-itis Sep 6, 2023
96025a4
attempt to only import jsonref in testing mode
bisgaard-itis Sep 6, 2023
410ac46
small correction
bisgaard-itis Sep 6, 2023
4fe1bb2
move another import
bisgaard-itis Sep 6, 2023
49fc3fe
yet another minor fix
bisgaard-itis Sep 6, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions services/api-server/requirements/_test.in
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@
--constraint _base.txt



aioresponses
alembic
asgi_lifespan
click
docker
faker
jsonref
moto[server] # mock out tests based on AWS-S3
pytest
pytest-asyncio
Expand All @@ -27,4 +28,3 @@ pytest-runner
respx
sqlalchemy[mypy] # adds Mypy / Pep-484 Support for ORM Mappings SEE https://docs.sqlalchemy.org/en/20/orm/extensions/mypy.html
types-boto3
aioresponses
33 changes: 17 additions & 16 deletions services/api-server/requirements/_test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,19 @@ aws-sam-translator==1.55.0
# cfn-lint
aws-xray-sdk==2.12.0
# via moto
boto3==1.28.26
boto3==1.28.40
# via
# aws-sam-translator
# moto
boto3-stubs==1.28.26
boto3-stubs==1.28.40
# via types-boto3
botocore==1.31.26
botocore==1.31.40
bisgaard-itis marked this conversation as resolved.
Show resolved Hide resolved
# via
# aws-xray-sdk
# boto3
# moto
# s3transfer
botocore-stubs==1.31.23
botocore-stubs==1.31.40
# via boto3-stubs
certifi==2023.5.7
# via
Expand Down Expand Up @@ -104,7 +104,7 @@ exceptiongroup==1.1.1
# -c requirements/_base.txt
# anyio
# pytest
faker==19.3.0
faker==19.3.1
# via -r requirements/_test.in
flask==2.1.3
# via
Expand Down Expand Up @@ -170,6 +170,8 @@ jsonpickle==3.0.2
# via jschema-to-python
jsonpointer==2.4
# via jsonpatch
jsonref==1.1.0
# via -r requirements/_test.in
jsonschema==3.2.0
# via
# -c requirements/./constraints.txt
Expand Down Expand Up @@ -200,7 +202,7 @@ multidict==6.0.4
# -c requirements/_base.txt
# aiohttp
# yarl
mypy==1.5.0
mypy==1.5.1
# via sqlalchemy
mypy-extensions==1.0.0
# via mypy
Expand All @@ -221,7 +223,7 @@ pbr==5.11.1
# via
# jschema-to-python
# sarif-om
pluggy==1.2.0
pluggy==1.3.0
# via pytest
pyasn1==0.5.0
# via
Expand All @@ -237,7 +239,7 @@ pyrsistent==0.19.3
# via
# -c requirements/_base.txt
# jsonschema
pytest==7.4.0
pytest==7.4.1
# via
# -r requirements/_test.in
# pytest-asyncio
Expand All @@ -248,7 +250,7 @@ pytest-asyncio==0.21.1
# via -r requirements/_test.in
pytest-cov==4.1.0
# via -r requirements/_test.in
pytest-docker==2.0.0
pytest-docker==2.0.1
# via -r requirements/_test.in
pytest-mock==3.11.1
# via -r requirements/_test.in
Expand Down Expand Up @@ -285,7 +287,7 @@ rsa==4.9
# via
# -c requirements/../../../requirements/constraints.txt
# python-jose
s3transfer==0.6.1
s3transfer==0.6.2
# via boto3
sarif-om==1.0.4
# via cfn-lint
Expand Down Expand Up @@ -318,20 +320,19 @@ tomli==2.0.1
# coverage
# mypy
# pytest
types-awscrt==0.18.0
# via
# botocore-stubs
# types-s3transfer
types-awscrt==0.19.1
# via botocore-stubs
types-boto3==1.0.2
# via -r requirements/_test.in
types-pyyaml==6.0.12.11
# via responses
types-s3transfer==0.6.1
types-s3transfer==0.6.2
# via boto3-stubs
typing-extensions==4.6.3
# via
# -c requirements/_base.txt
# alembic
# boto3-stubs
# mypy
# sqlalchemy2-stubs
urllib3==1.26.16
Expand All @@ -341,7 +342,7 @@ urllib3==1.26.16
# docker
# requests
# responses
websocket-client==1.6.1
websocket-client==1.6.2
# via docker
werkzeug==2.1.2
# via
Expand Down
12 changes: 6 additions & 6 deletions services/api-server/requirements/_tools.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ astroid==2.15.6
# via pylint
black==23.7.0
# via -r requirements/../../../requirements/devenv.txt
build==0.10.0
build==1.0.0
# via pip-tools
bump2version==1.0.1
# via -r requirements/../../../requirements/devenv.txt
Expand All @@ -28,7 +28,7 @@ distlib==0.3.7
# via virtualenv
filelock==3.12.2
# via virtualenv
identify==2.5.26
identify==2.5.27
# via pre-commit
isort==5.12.0
# via
Expand Down Expand Up @@ -70,7 +70,7 @@ platformdirs==3.10.0
# black
# pylint
# virtualenv
pre-commit==3.3.3
pre-commit==3.4.0
# via -r requirements/../../../requirements/devenv.txt
pylint==2.17.5
# via -r requirements/../../../requirements/devenv.txt
Expand All @@ -83,7 +83,7 @@ pyyaml==6.0.1
# -c requirements/_test.txt
# pre-commit
# watchdog
ruff==0.0.284
ruff==0.0.287
# via -r requirements/../../../requirements/devenv.txt
tomli==2.0.1
# via
Expand All @@ -100,11 +100,11 @@ typing-extensions==4.6.3
# -c requirements/_base.txt
# -c requirements/_test.txt
# astroid
virtualenv==20.24.3
virtualenv==20.24.4
# via pre-commit
watchdog==3.0.0
# via -r requirements/_tools.in
wheel==0.41.1
wheel==0.41.2
# via pip-tools
wrapt==1.15.0
# via
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@
from ..._meta import API_VTAG
from ...models.pagination import Page, PaginationParams
from ...models.schemas.errors import ErrorGet
from ...models.schemas.files import ClientFile, ClientFileUploadSchema, File
from ...models.schemas.files import (
ClientFile,
ClientFileUploadData,
File,
FileUploadData,
UploadLinks,
)
from ...services.storage import StorageApi, StorageFileMetaData, to_file_api_model
from ..dependencies.authentication import get_current_user_id
from ..dependencies.services import get_api_client
Expand Down Expand Up @@ -187,7 +193,7 @@ async def upload_files(files: list[UploadFile] = FileParam(...)):

@router.post(
"/content",
response_model=ClientFileUploadSchema,
response_model=ClientFileUploadData,
include_in_schema=API_SERVER_DEV_FEATURES_ENABLED,
)
@cancel_on_disconnect
Expand All @@ -212,20 +218,18 @@ async def get_upload_links(
file_size=ByteSize(client_file.filesize),
is_directory=False,
)

query = f"{upload_links.links.complete_upload.query}".removesuffix(":complete")
url = request.url_for(
completion_url: URL = request.url_for(
"complete_multipart_upload", file_id=file_meta.id
).include_query_params(**dict(item.split("=") for item in query.split("&")))
upload_links.links.complete_upload = parse_obj_as(AnyUrl, f"{url}")

query = f"{upload_links.links.abort_upload.query}".removesuffix(":abort")
url = request.url_for(
"abort_multipart_upload", file_id=file_meta.id
).include_query_params(**dict(item.split("=") for item in query.split("&")))
upload_links.links.abort_upload = parse_obj_as(AnyUrl, f"{url}")

return ClientFileUploadSchema(file_id=file_meta.id, upload_schema=upload_links)
)
abortion_url: URL = request.url_for("abort_multipart_upload", file_id=file_meta.id)
bisgaard-itis marked this conversation as resolved.
Show resolved Hide resolved
upload_data: FileUploadData = FileUploadData(
chunk_size=upload_links.chunk_size,
urls=upload_links.urls,
links=UploadLinks(
complete_upload=completion_url.path, abort_upload=abortion_url.path
),
)
return ClientFileUploadData(file_id=file_meta.id, upload_schema=upload_data)


@router.get(
Expand Down Expand Up @@ -264,19 +268,19 @@ async def get_file(

@router.delete(
"/{file_id}",
bisgaard-itis marked this conversation as resolved.
Show resolved Hide resolved
status_code=status.HTTP_204_NO_CONTENT,
responses={**_COMMON_ERROR_RESPONSES},
include_in_schema=API_SERVER_DEV_FEATURES_ENABLED,
)
async def delete_file(
file_id: UUID,
storage_client: Annotated[StorageApi, Depends(get_api_client(StorageApi))],
user_id: Annotated[int, Depends(get_current_user_id)],
storage_client: Annotated[StorageApi, Depends(get_api_client(StorageApi))],
):
assert storage_client # nsoec

msg = f"delete file {file_id=} of {user_id=}. SEE https://github.com/ITISFoundation/osparc-issues/issues/952"
raise NotImplementedError(msg)
file: File = await get_file(
file_id=file_id, storage_client=storage_client, user_id=user_id
)
await storage_client.delete_file(
user_id=user_id, quoted_storage_file_id=file.quoted_storage_file_id
)


@router.post(
Expand All @@ -294,7 +298,7 @@ async def abort_multipart_upload(
assert user_id # nosec
file: File = File(id=file_id, filename=client_file.filename, checksum=None)
abort_link: URL = await storage_client.create_abort_upload_link(
file, query=dict(request.query_params)
file, query={"user_id": str(user_id)}
)
await abort_upload(abort_upload_link=parse_obj_as(AnyUrl, str(abort_link)))

Expand All @@ -318,7 +322,7 @@ async def complete_multipart_upload(

file: File = File(id=file_id, filename=client_file.filename, checksum=None)
complete_link: URL = await storage_client.create_complete_upload_link(
file, dict(request.query_params)
file, {"user_id": str(user_id)}
)

e_tag: ETag = await complete_file_upload(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,16 @@

import aiofiles
from fastapi import UploadFile
from models_library.api_schemas_storage import FileUploadSchema
from models_library.projects_nodes_io import StorageFileID
from pydantic import BaseModel, ByteSize, ConstrainedStr, Field, parse_obj_as, validator
from pydantic import (
AnyUrl,
BaseModel,
ByteSize,
ConstrainedStr,
Field,
parse_obj_as,
validator,
)

from ...utils.hash import create_md5_checksum

Expand Down Expand Up @@ -144,8 +151,17 @@ def quoted_storage_file_id(self) -> str:
return _quote(self.storage_file_id, safe="")


class ClientFileUploadSchema(BaseModel):
class UploadLinks(BaseModel):
abort_upload: str
complete_upload: str


class FileUploadData(BaseModel):
chunk_size: ByteSize
urls: list[AnyUrl]
links: UploadLinks


class ClientFileUploadData(BaseModel):
file_id: UUID = Field(..., description="The file resource id")
upload_schema: FileUploadSchema = Field(
..., description="Schema for uploading file"
)
upload_schema: FileUploadData = Field(..., description="Schema for uploading file")
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ async def get_download_link(
link: AnyUrl = presigned_link.link
return link

async def delete_file(self, user_id: int, quoted_storage_file_id: str) -> None:
response = await self.client.delete(
f"{self.client.base_url}locations/{self.SIMCORE_S3_ID}/files/{quoted_storage_file_id}",
bisgaard-itis marked this conversation as resolved.
Show resolved Hide resolved
params={"user_id": user_id},
)
response.raise_for_status()
bisgaard-itis marked this conversation as resolved.
Show resolved Hide resolved

async def get_upload_links(
self, user_id: int, file_id: UUID, file_name: str
) -> FileUploadSchema:
Expand All @@ -105,6 +112,7 @@ async def get_upload_links(
f"/locations/{self.SIMCORE_S3_ID}/files/{object_path}",
params={"user_id": user_id, "file_size": 0},
)
response.raise_for_status()

enveloped_data = Envelope[FileUploadSchema].parse_obj(response.json())
assert enveloped_data.data # nosec
Expand Down
Loading
Loading