Skip to content

Commit 24853fc

Browse files
authored
Add download_attachment() method (#381)
1 parent acd0571 commit 24853fc

File tree

3 files changed

+61
-1
lines changed

3 files changed

+61
-1
lines changed

README.rst

+5-1
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,11 @@ The history of a bucket can also be purged with:
418418
Attachments
419419
-----------
420420

421-
If the `kinto-attachment plugin <https://github.com/Kinto/kinto-attachment/>`_ is enabled, it is possible to add attachments on records:
421+
If the `kinto-attachment plugin <https://github.com/Kinto/kinto-attachment/>`_ is enabled, it is possible to fetch, add, or remove attachments on records:
422+
423+
.. code-block:: python
424+
425+
filepath = client.download_attachment(record_obj)
422426
423427
.. code-block:: python
424428

src/kinto_http/client.py

+27
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,33 @@ def purge_history(self, *, bucket=None, safe=True, if_match=None) -> List[Dict]:
880880
resp, _ = self.session.request("delete", endpoint, headers=headers)
881881
return resp["data"]
882882

883+
@retry_timeout
884+
def download_attachment(
885+
self,
886+
record,
887+
filepath=None,
888+
chunk_size=8 * 1024,
889+
):
890+
if "attachment" not in record:
891+
raise ValueError("Specified record has no attachment")
892+
893+
server_info = self.server_info()
894+
base_url = server_info["capabilities"]["attachments"]["base_url"]
895+
location = record["attachment"]["location"]
896+
url = base_url + location
897+
898+
if filepath is None:
899+
filepath = record["attachment"]["filename"]
900+
elif os.path.isdir(filepath):
901+
filepath = os.path.join(filepath, record["attachment"]["filename"])
902+
903+
with open(filepath, "wb") as f:
904+
with requests.get(url, stream=True) as r:
905+
r.raise_for_status()
906+
for chunk in r.iter_content(chunk_size=chunk_size):
907+
f.write(chunk)
908+
return filepath
909+
883910
@retry_timeout
884911
def add_attachment(
885912
self,

tests/test_client.py

+29
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import os
12
import re
23

34
import pytest
@@ -1394,6 +1395,34 @@ def test_purging_of_history(client_setup: Client):
13941395
client.session.request.assert_called_with("delete", url, headers=None)
13951396

13961397

1398+
def test_download_attachment(client_setup: Client, mocker: MockerFixture):
1399+
client = client_setup
1400+
1401+
client.session.request.return_value = (
1402+
{"capabilities": {"attachments": {"base_url": "https://cdn/"}}},
1403+
{},
1404+
)
1405+
1406+
mock_requests_get = mocker.patch("kinto_http.requests.get")
1407+
mock_response = mocker.MagicMock()
1408+
mock_response.iter_content = mocker.MagicMock(return_value=[b"chunk1", b"chunk2", b"chunk3"])
1409+
mock_response.raise_for_status = mocker.MagicMock()
1410+
mock_requests_get.return_value.__enter__.return_value = mock_response
1411+
1412+
with pytest.raises(ValueError):
1413+
client.download_attachment({})
1414+
1415+
record = {"attachment": {"location": "file.bin", "filename": "local.bin"}}
1416+
1417+
path = client.download_attachment(record)
1418+
assert path == "local.bin"
1419+
with open(path) as f:
1420+
assert f.read() == "chunk1chunk2chunk3"
1421+
1422+
path = client.download_attachment(record, filepath="/tmp")
1423+
assert os.path.exists("/tmp/local.bin")
1424+
1425+
13971426
def test_add_attachment_guesses_mimetype(record_setup: Client, tmp_path):
13981427
client = record_setup
13991428
mock_response(client.session)

0 commit comments

Comments
 (0)