Skip to content

Commit 727bb95

Browse files
Add support for files endpoint (#38)
* Add support for files endpoint * add readme * fix readme * nit * simplify upload endpoint * Extended Readme with a task payload example Co-authored-by: Fatih Kurtoglu <fatih.kurtoglu@scale.com>
1 parent 1ce41cd commit 727bb95

File tree

6 files changed

+178
-8
lines changed

6 files changed

+178
-8
lines changed

README.rst

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,60 @@ __ https://docs.scale.com/reference#project-update-parameters
337337
instruction="update: Please label all the stuff",
338338
)
339339
340+
Files
341+
________
342+
343+
Files are a way of uploading local files directly to Scale storage or importing files before creating tasks.
344+
345+
The ``file.attachment_url`` can be used in place of attachments in task payload.
346+
347+
Upload Files
348+
^^^^^^^^^^^^^^
349+
350+
Upload a file. Check out `Scale's API documentation`__ for more information.
351+
352+
__ https://docs.scale.com/reference#file-upload-1
353+
354+
.. code-block:: python
355+
356+
with open(file_name, 'rb') as f:
357+
my_file = client.upload_file(
358+
file=f,
359+
project_name = "test_project",
360+
)
361+
362+
Import Files
363+
^^^^^^^^^^^^^^
364+
365+
Import a file from a URL. Check out `Scale's API documentation`__ for more information.
366+
367+
__ https://docs.scale.com/reference#file-import-1
368+
369+
.. code-block:: python
370+
371+
my_file = client.import_file(
372+
file_url="http://i.imgur.com/v4cBreD.jpg",
373+
project_name = "test_project",
374+
)
375+
376+
377+
After the files are successfully uploaded to Scale's storage, you can access the URL as ``my_file.attachment_url``, which will have a prefix like ``scaledata://``.
378+
379+
The attribute can be passed to the task payloads, in the ``attachment`` parameter.
380+
381+
.. code-block:: python
382+
383+
task_payload = dict(
384+
...
385+
...
386+
attachment_type = "image",
387+
attachment = my_file.attachment_url,
388+
...
389+
...
390+
)
391+
392+
393+
340394
Error handling
341395
______________
342396

scaleapi/__init__.py

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
from typing import Dict, Generator, Generic, List, TypeVar, Union
1+
from typing import IO, Dict, Generator, Generic, List, TypeVar, Union
22

33
from scaleapi.batches import Batch, BatchStatus
44
from scaleapi.exceptions import ScaleInvalidRequest
5+
from scaleapi.files import File
56
from scaleapi.projects import Project
67

78
from ._version import __version__ # noqa: F401
@@ -317,7 +318,13 @@ def create_task(self, task_type: TaskType, **kwargs) -> Task:
317318
taskdata = self.api.post_request(endpoint, body=kwargs)
318319
return Task(taskdata, self)
319320

320-
def create_batch(self, project: str, batch_name: str, callback: str = "") -> Batch:
321+
def create_batch(
322+
self,
323+
project: str,
324+
batch_name: str,
325+
callback: str = "",
326+
instruction_batch: bool = False,
327+
) -> Batch:
321328
"""Create a new Batch within a project.
322329
https://docs.scale.com/reference#batch-creation
323330
@@ -329,12 +336,21 @@ def create_batch(self, project: str, batch_name: str, callback: str = "") -> Bat
329336
callback (str, optional):
330337
Email to notify, or URL to POST to
331338
when a batch is complete.
339+
instruction_batch (bool):
340+
Only applicable for self serve projects.
341+
Create an instruction batch by setting
342+
the instruction_batch flag to true.
332343
333344
Returns:
334345
Batch: Created batch object
335346
"""
336347
endpoint = "batches"
337-
payload = dict(project=project, name=batch_name, callback=callback)
348+
payload = dict(
349+
project=project,
350+
name=batch_name,
351+
instruction_batch=instruction_batch,
352+
callback=callback,
353+
)
338354
batchdata = self.api.post_request(endpoint, body=payload)
339355
return Batch(batchdata, self)
340356

@@ -596,3 +612,39 @@ def update_project(self, project_name: str, **kwargs) -> Project:
596612
endpoint = f"projects/{Api.quote_string(project_name)}/setParams"
597613
projectdata = self.api.post_request(endpoint, body=kwargs)
598614
return Project(projectdata, self)
615+
616+
def upload_file(self, file: IO, **kwargs) -> File:
617+
"""Upload file.
618+
Refer to Files API Reference:
619+
https://docs.scale.com/reference#file-upload-1
620+
621+
Args:
622+
file (IO):
623+
File buffer
624+
625+
Returns:
626+
File
627+
"""
628+
629+
endpoint = "files/upload"
630+
files = {"file": file}
631+
filedata = self.api.post_request(endpoint, files=files, data=kwargs)
632+
return File(filedata, self)
633+
634+
def import_file(self, file_url: str, **kwargs) -> File:
635+
"""Import file from a remote url.
636+
Refer to Files API Reference:
637+
https://docs.scale.com/reference#file-import-1
638+
639+
Args:
640+
file_url (str):
641+
File's url
642+
643+
Returns:
644+
File
645+
"""
646+
647+
endpoint = "files/import"
648+
payload = dict(file_url=file_url, **kwargs)
649+
filedata = self.api.post_request(endpoint, body=payload)
650+
return File(filedata, self)

scaleapi/api.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,20 @@ def __init__(self, api_key, user_agent_extension=None):
3030
"Content-Type": "application/json",
3131
"User-Agent": self._generate_useragent(user_agent_extension),
3232
}
33+
self._headers_multipart_form_data = {
34+
"User-Agent": self._generate_useragent(user_agent_extension),
35+
}
3336

3437
@staticmethod
3538
def _http_request(
36-
method, url, headers=None, auth=None, params=None, body=None
39+
method,
40+
url,
41+
headers=None,
42+
auth=None,
43+
params=None,
44+
body=None,
45+
files=None,
46+
data=None,
3747
) -> Response:
3848

3949
https = requests.Session()
@@ -59,6 +69,8 @@ def _http_request(
5969
auth=auth,
6070
params=params,
6171
json=body,
72+
files=files,
73+
data=data,
6274
)
6375

6476
return res
@@ -77,13 +89,21 @@ def _raise_on_respose(res: Response):
7789
raise exception(message, res.status_code)
7890

7991
def _api_request(
80-
self, method, endpoint, headers=None, auth=None, params=None, body=None
92+
self,
93+
method,
94+
endpoint,
95+
headers=None,
96+
auth=None,
97+
params=None,
98+
body=None,
99+
files=None,
100+
data=None,
81101
):
82102
"""Generic HTTP request method with error handling."""
83103

84104
url = f"{SCALE_ENDPOINT}/{endpoint}"
85105

86-
res = self._http_request(method, url, headers, auth, params, body)
106+
res = self._http_request(method, url, headers, auth, params, body, files, data)
87107

88108
json = None
89109
if res.status_code == 200:
@@ -99,10 +119,18 @@ def get_request(self, endpoint, params=None):
99119
"GET", endpoint, headers=self._headers, auth=self._auth, params=params
100120
)
101121

102-
def post_request(self, endpoint, body=None):
122+
def post_request(self, endpoint, body=None, files=None, data=None):
103123
"""Generic POST Request Wrapper"""
104124
return self._api_request(
105-
"POST", endpoint, headers=self._headers, auth=self._auth, body=body
125+
"POST",
126+
endpoint,
127+
headers=self._headers
128+
if files is None
129+
else self._headers_multipart_form_data,
130+
auth=self._auth,
131+
body=body,
132+
files=files,
133+
data=data,
106134
)
107135

108136
@staticmethod

scaleapi/files.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
class File:
2+
"""File class, containing File information."""
3+
4+
def __init__(self, json, client):
5+
self._json = json
6+
self.id = json["id"]
7+
self.attachment_url = json["attachment_url"]
8+
self._client = client
9+
10+
def __hash__(self):
11+
return hash(self.id)
12+
13+
def __str__(self):
14+
return f"File(id={self.id})"
15+
16+
def __repr__(self):
17+
return f"File({self._json})"
18+
19+
def as_dict(self):
20+
"""Returns all attributes as a dictionary"""
21+
return self._json

tests/test_client.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,3 +378,18 @@ def test_get_batches():
378378
# Download all batches to check total count
379379
all_batches = list(client.get_batches(project_name=TEST_PROJECT_NAME))
380380
assert total_batches == len(all_batches)
381+
382+
383+
def test_files_upload():
384+
with open("tests/test_image.png", "rb") as f:
385+
client.upload_file(
386+
file=f,
387+
project_name=TEST_PROJECT_NAME,
388+
)
389+
390+
391+
def test_files_import():
392+
client.import_file(
393+
file_url="https://static.scale.com/uploads/selfserve-sample-image.png",
394+
project_name=TEST_PROJECT_NAME,
395+
)

tests/test_image.png

333 KB
Loading

0 commit comments

Comments
 (0)