Skip to content

Commit

Permalink
Add /@delete/file endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
gitcarbs committed Jan 9, 2025
1 parent 7381dfe commit db9da28
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 10 deletions.
14 changes: 14 additions & 0 deletions guillotina/api/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def _traversed_file_doc(summary, parameters=None, responses=None):
{"name": "UPLOAD-METADATA", "in": "header", "required": False, "schema": {"type": "string"}},
]


# Static File
@configure.service(context=IStaticFile, method="GET", permission="guillotina.AccessContent")
class FileGET(DownloadService):
Expand Down Expand Up @@ -360,3 +361,16 @@ async def render(self):
# for the field to save there by chunks
adapter = get_multi_adapter((self.context, self.request, self.field), IFileManager)
return await adapter.tus_options()


@configure.service(
context=IResource,
method="PATCH",
permission="guillotina.ModifyContent",
name="@delete/{field_name}",
**_traversed_file_doc("Delete the content of a file"),
)
class DeleteFile(TraversableFieldService):
async def __call__(self):
adapter = get_multi_adapter((self.context, self.request, self.field), IFileManager)
return await adapter.delete()
14 changes: 14 additions & 0 deletions guillotina/files/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,3 +253,17 @@ async def copy(self, to_storage_manager, dm):
await to_storage_manager.append(dm, to_storage_manager.iter_data(), 0)
await to_storage_manager.finish(dm)
await dm.finish()

async def delete(self):
file = self.field.get(self.field.context or self.context)
if file is not None:
try:
blob = file._blob
bfile = blob.open("r")
await bfile.async_del()
self.field.set(self.field.context or self.context, None)
self.field.context.register()
return True
except AttributeError:
pass
return False
9 changes: 9 additions & 0 deletions guillotina/files/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,3 +470,12 @@ async def save_file(self, generator, content_type=None, filename=None, extension
async def copy(self, to_manager):
await to_manager.dm.load()
await self.file_storage_manager.copy(to_manager.file_storage_manager, to_manager.dm)

async def delete(self):
await self.dm.load()
await self.dm.start()

result = await self.file_storage_manager.delete()
await self.dm.finish()

return result
8 changes: 6 additions & 2 deletions guillotina/interfaces/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ async def copy(other_manager):
Copy current file to new one
"""

async def delete():
"""
Delete the file
"""


class IBlobMetadata(Interface):

Expand Down Expand Up @@ -218,5 +223,4 @@ class IDBFileField(ICloudFileField):


class IDBFile(IFile):
"""Marker for a DBFile
"""
"""Marker for a DBFile"""
25 changes: 17 additions & 8 deletions guillotina/test_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,13 +342,11 @@ def __call__(self, data):


class IMemoryFileField(IFileField):
"""
"""
""" """


class IInMemoryCloudFile(IFile):
"""
"""
""" """


@configure.adapter(for_=(dict, IMemoryFileField), provides=IJSONToValue)
Expand Down Expand Up @@ -393,11 +391,14 @@ async def iter_data(self, uri=None):
if uri is None:
file = self.field.get(self.field.context or self.context)
uri = file.uri
with open(_tmp_files[uri], "rb") as fi:
chunk = fi.read(1024)
while chunk:
yield chunk
try:
with open(_tmp_files[uri], "rb") as fi:
chunk = fi.read(1024)
while chunk:
yield chunk
chunk = fi.read(1024)
except KeyError:
raise AttributeError("File not found")

async def start(self, dm):
upload_file_id = dm.get("upload_file_id")
Expand Down Expand Up @@ -467,6 +468,14 @@ async def copy(self, to_storage_manager, to_dm):
}
)

async def delete(self):
file = self.field.get(self.field.context or self.context)
if file.uri in _tmp_files:
os.remove(_tmp_files[file.uri])
del _tmp_files[file.uri]
return True
return False


@implementer(IMemoryFileField)
class InMemoryFileField(Object):
Expand Down
34 changes: 34 additions & 0 deletions guillotina/tests/test_attachment.py
Original file line number Diff line number Diff line change
Expand Up @@ -865,3 +865,37 @@ async def test_upload_errors(manager_type, redis_container, container_requester)
},
)
assert status == 412


@pytest.mark.parametrize("manager_type", _pytest_params)
async def test_delete_upload(manager_type, redis_container, container_requester):
async with container_requester as requester:
response, status = await requester(
"POST",
"/db/guillotina/",
data=json.dumps({"@type": "Item", "@behaviors": [IAttachment.__identifier__], "id": "foobar"}),
)
assert status == 201

response, status = await requester(
"PATCH",
"/db/guillotina/foobar/@upload/file",
data=b"X" * 1024 * 1024 * 4,
headers={"x-upload-size": str(1024 * 1024 * 4)},
)
assert status == 200

response, status = await requester("GET", "/db/guillotina/foobar/@download/file")
assert status == 200

response, status = await requester("PATCH", "/db/guillotina/foobar/@delete/file")
assert status == 200
assert response is True

response, status = await requester("PATCH", "/db/guillotina/foobar/@delete/file")
assert status == 200
assert response is False

response, status = await requester("GET", "/db/guillotina/foobar/@download/file")
assert response == {"reason": "File does not exist"}
assert status == 404

0 comments on commit db9da28

Please sign in to comment.