Skip to content

Commit 68a89de

Browse files
[v2.0.1] CircleCI, 500 Error code, Readme updates (#27)
* readme updates * version bumped * docstring tools for developer guide * circleci configurations * pytest test key validation * added 500 error code to retry * updated publish script
1 parent 22827ba commit 68a89de

File tree

8 files changed

+161
-43
lines changed

8 files changed

+161
-43
lines changed

.circleci/config.yml

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# CircleCI jobs are only enabled to on Pull Requests and commits to master branch.
2+
# "Only build pull requests" enabled in Project's Advanced Settings.
3+
version: 2.1
4+
jobs:
5+
build_test:
6+
docker:
7+
- image: cimg/python:3.6
8+
resource_class: small
9+
steps:
10+
- checkout # checkout source code to working directory
11+
- run:
12+
name: Install Environment Dependencies
13+
command: | # install env dependencies
14+
set -e
15+
pip install --upgrade pip
16+
pip install -r docs/dev_requirements.txt
17+
- run:
18+
name: Black Formatting Check # Only validation, without re-formatting
19+
command: |
20+
black --check -t py36 .
21+
- run:
22+
name: isort Import Ordering Check # Only validation, without re-formatting
23+
command: |
24+
isort --check-only --profile black .
25+
- run:
26+
name: Flake8 Lint Check # Uses setup.cfg for configuration
27+
command: |
28+
flake8 . --count --statistics
29+
- run:
30+
name: Pylint Lint Check # Uses .pylintrc for configuration
31+
command: |
32+
pylint scaleapi
33+
- run:
34+
name: Build Package # create whl and install package
35+
command: |
36+
set -e
37+
python setup.py sdist bdist_wheel
38+
pip install --no-cache-dir dist/*.whl
39+
- run:
40+
name: Pytest Test Cases
41+
command: | # Run test suite, uses SCALE_TEST_API_KEY env variable
42+
pytest -v
43+
- run:
44+
name: Twine PyPI Check
45+
command: | # Validate distribution and setup.py configuration
46+
twine check --strict dist/*
47+
pypi_publish:
48+
docker:
49+
- image: cimg/python:3.6
50+
steps:
51+
- checkout # checkout source code to working directory
52+
- run:
53+
name: Validate Tag Version # Check if the tag name matches the package version
54+
command: |
55+
PKG_VERSION=$(sed -n 's/^__version__ = //p' scaleapi/_version.py | sed -e 's/^"//' -e 's/"$//')
56+
57+
if [[ "$CIRCLE_TAG" != "v${PKG_VERSION}" ]]; then
58+
echo "ERROR: Tag name ($CIRCLE_TAG) must match package version (v${PKG_VERSION})."
59+
exit 1;
60+
fi
61+
- run:
62+
name: Validate SDK Version Increment # Check if the version is already on PyPI
63+
command: |
64+
PKG_VERSION=$(sed -n 's/^__version__ = //p' scaleapi/_version.py | sed -e 's/^"//' -e 's/"$//')
65+
66+
if pip install "scaleapi>=${PKG_VERSION}" > /dev/null 2>&1;
67+
then
68+
echo "ERROR: You need to increment to a new version before publishing!"
69+
echo "Version (${PKG_VERSION}) already exists on PyPI."
70+
exit 1;
71+
fi
72+
- run:
73+
name: Install Environment Dependencies
74+
command: | # install env dependencies
75+
set -e
76+
pip install --upgrade pip
77+
pip install twine
78+
- run:
79+
name: Build and Validate
80+
command: | # create whl, validate with twine
81+
set -e
82+
python setup.py sdist bdist_wheel
83+
twine check --strict dist/*
84+
- run:
85+
name: Publish to PyPI
86+
command: |
87+
if test -z "${TWINE_USERNAME}" || test -z "${TWINE_PASSWORD}" ; then
88+
echo "ERROR: Please assign TWINE_USERNAME and TWINE_PASSWORD as environment variables"
89+
exit 1
90+
fi
91+
twine upload dist/*
92+
workflows:
93+
build_test_publish:
94+
jobs:
95+
- build_test
96+
- pypi_publish:
97+
requires:
98+
- build_test
99+
filters:
100+
tags:
101+
only: /^v\d+\.\d+\.\d+$/ # Runs only for tags with the format [v1.2.3]
102+
branches:
103+
ignore: /.*/ # Runs for none of the branches

README.rst

+45-30
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ _____
3131
.. code-block:: python
3232
3333
import scaleapi
34-
client = scaleapi.ScaleClient('YOUR_API_KEY_HERE')
34+
35+
client = scaleapi.ScaleClient("YOUR_API_KEY_HERE")
3536
3637
Tasks
3738
_____
@@ -62,22 +63,28 @@ __ https://docs.scale.com/reference
6263
6364
from scaleapi.tasks import TaskType
6465
65-
client.create_task(
66-
TaskType.ImageAnnotation,
67-
project = 'test_project',
66+
payload = dict(
67+
project = "test_project",
6868
callback_url = "http://www.example.com/callback",
69-
instruction= "Draw a box around each baby cow and big cow.",
69+
instruction = "Draw a box around each baby cow and big cow.",
7070
attachment_type = "image",
7171
attachment = "http://i.imgur.com/v4cBreD.jpg",
72+
unique_id = "c235d023af73",
7273
geometries = {
7374
"box": {
74-
"objects_to_annotate": ["Baby Cow", "Big Cow"],
75-
"min_height": 10,
76-
"min_width": 10
75+
"objects_to_annotate": ["Baby Cow", "Big Cow"],
76+
"min_height": 10,
77+
"min_width": 10,
7778
}
78-
}
79+
},
7980
)
8081
82+
try:
83+
client.create_task(TaskType.ImageAnnotation, **payload)
84+
except ScaleDuplicateTask as err:
85+
print(err.message) # If unique_id is already used for a different task
86+
87+
8188
Retrieve a task
8289
^^^^^^^^^^^^^^^
8390

@@ -87,8 +94,8 @@ __ https://docs.scale.com/reference#retrieve-tasks
8794

8895
.. code-block :: python
8996
90-
task = client.get_task('30553edd0b6a93f8f05f0fee')
91-
print(task.status) # Task status ('pending', 'completed', 'error', 'canceled')
97+
task = client.get_task("30553edd0b6a93f8f05f0fee")
98+
print(task.status) # Task status ("pending", "completed", "error", "canceled")
9299
print(task.response) # If task is complete
93100
94101
List Tasks
@@ -100,9 +107,9 @@ Retrieve a list of `Task` objects, with filters for: ``project_name``, ``batch_n
100107

101108
``get_tasks()`` is a **generator** method and yields ``Task`` objects.
102109

103-
`A generator is another type of function, returns an iterable that you can loop over like a list.
110+
*A generator is another type of function, returns an iterable that you can loop over like a list.
104111
However, unlike lists, generators do not store the content in the memory.
105-
That helps you to process a large number of objects without increasing memory usage.`
112+
That helps you to process a large number of objects without increasing memory usage.*
106113

107114
If you will iterate through the tasks and process them once, using a generator is the most efficient method.
108115
However, if you need to process the list of tasks multiple times, you can wrap the generator in a ``list(...)``
@@ -157,9 +164,9 @@ __ https://docs.scale.com/reference#batch-creation
157164
.. code-block:: python
158165
159166
client.create_batch(
160-
project = 'test_project',
167+
project = "test_project",
161168
callback = "http://www.example.com/callback",
162-
name = 'batch_name_01_07_2021'
169+
name = "batch_name_01_07_2021"
163170
)
164171
165172
Finalize Batch
@@ -171,7 +178,11 @@ __ https://docs.scale.com/reference#batch-finalization
171178

172179
.. code-block:: python
173180
174-
client.finalize_batch(batch_name = 'batch_name_01_07_2021')
181+
client.finalize_batch(batch_name="batch_name_01_07_2021")
182+
183+
# Alternative method
184+
batch = client.get_batch(batch_name="batch_name_01_07_2021")
185+
batch.finalize()
175186
176187
Check Batch Status
177188
^^^^^^^^^^^^^^^^^^
@@ -182,10 +193,10 @@ __ https://docs.scale.com/reference#batch-status
182193

183194
.. code-block:: python
184195
185-
client.batch_status(batch_name = 'batch_name_01_07_2021')
196+
client.batch_status(batch_name = "batch_name_01_07_2021")
186197
187198
# Alternative via Batch.get_status()
188-
batch = client.get_batch('batch_name_01_07_2021')
199+
batch = client.get_batch("batch_name_01_07_2021")
189200
batch.get_status() # Refreshes tasks_{status} attributes of Batch
190201
print(batch.tasks_pending, batch.tasks_completed)
191202
@@ -198,7 +209,7 @@ __ https://docs.scale.com/reference#batch-retrieval
198209

199210
.. code-block:: python
200211
201-
client.get_batch(batch_name = 'batch_name_01_07_2021')
212+
client.get_batch(batch_name = "batch_name_01_07_2021")
202213
203214
List Batches
204215
^^^^^^^^^^^^
@@ -207,9 +218,9 @@ Retrieve a list of Batches. Optional parameters are ``project_name``, ``batch_st
207218

208219
``get_batches()`` is a **generator** method and yields ``Batch`` objects.
209220

210-
`A generator is another type of function, returns an iterable that you can loop over like a list.
221+
*A generator is another type of function, returns an iterable that you can loop over like a list.
211222
However, unlike lists, generators do not store the content in the memory.
212-
That helps you to process a large number of objects without increasing memory usage.`
223+
That helps you to process a large number of objects without increasing memory usage.*
213224

214225
When wrapped in a ``list(...)`` statement, it returns a list of Batches by loading them into the memory.
215226

@@ -229,7 +240,7 @@ __ https://docs.scale.com/reference#batch-list
229240
counter = 0
230241
for batch in batches:
231242
counter += 1
232-
print(f'Downloading batch {counter} | {batch.name} | {batch.project}')
243+
print(f"Downloading batch {counter} | {batch.name} | {batch.project}")
233244
234245
# Alternative for accessing as a Batch list
235246
batch_list = list(batches)
@@ -247,12 +258,16 @@ __ https://docs.scale.com/reference#project-creation
247258

248259
.. code-block:: python
249260
250-
client.create_project(
251-
project_name = 'test_project',
252-
type = 'imageannotation,
253-
params = {'instruction':'Please label the kittens'}
261+
from scaleapi.tasks import TaskType
262+
263+
project = client.create_project(
264+
project_name = "Test_Project",
265+
task_type = TaskType.ImageAnnotation,
266+
params = {"instruction": "Please label the kittens"},
254267
)
255268
269+
print(project.name) # Test_Project
270+
256271
Retrieve Project
257272
^^^^^^^^^^^^^^^^
258273

@@ -262,7 +277,7 @@ __ https://docs.scale.com/reference#project-retrieval
262277

263278
.. code-block:: python
264279
265-
client.get_project(project_name = 'test_project')
280+
client.get_project(project_name = "test_project")
266281
267282
List Projects
268283
^^^^^^^^^^^^^
@@ -290,9 +305,9 @@ __ https://docs.scale.com/reference#project-update-parameters
290305
.. code-block :: python
291306
292307
data = client.update_project(
293-
project_name='test_project',
308+
project_name="test_project",
294309
patch = false,
295-
instruction='update: Please label all the stuff',
310+
instruction="update: Please label all the stuff",
296311
)
297312
298313
Error handling
@@ -319,7 +334,7 @@ For example:
319334
from scaleapi.exceptions import ScaleException
320335
321336
try:
322-
client.create_task(TaskType.TextCollection, attachment='Some parameters are missing.')
337+
client.create_task(TaskType.TextCollection, attachment="Some parameters are missing.")
323338
except ScaleException as err:
324339
print(err.code) # 400
325340
print(err.message) # Parameter is invalid, reason: "attachments" is required

docs/dev_requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ pre-commit==2.11.1
44
isort>=5.7.0
55
pytest>=6.2.2
66
pylint>=2.7.2
7+
twine>=3.4.1

docs/developer_guide.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ $ pip install -r docs/dev_requirements.txt
1919
```
2020
### 3. Setup pre-commit
2121

22-
Assure pre-commit<sup>1</sup> is installed:
22+
Assure pre-commit<sup>[1]</sup> is installed:
2323
```bash
2424
$ pre-commit --version
2525
# pre-commit 2.11.1
@@ -52,6 +52,8 @@ Append following lines to the json file:
5252
},
5353
```
5454

55+
In Python SDK we follow [Google's Python Docstring Guide](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings) for comments and docstring of modules, functions and classes. [Python Docstring Generator](https://marketplace.visualstudio.com/items?itemName=njpwerner.autodocstring) is a useful VS Code extension that helps to generate docstrings.
56+
5557
### 5. Running pre-commit Tests Manually
5658

5759
You can run following command to run pre-commit linter for all files, without a commit. It provides a report for issues as well as fixes formatting.

publish.sh

+1-9
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,7 @@ echo "Active Git Branch: ${BRANCH_NAME}" # release-1.0.5
2323
# BRANCH_PREFIX="${strarr[0]}" # release
2424
# BRANCH_VERSION="${strarr[1]}" # 1.0.5
2525

26-
while IFS= read -r line; do
27-
if [[ $line == __version__* ]];
28-
then
29-
IFS=' = ' read -ra strarr <<< "$line"
30-
PKG_VERSION=$( sed -e 's/^"//' -e 's/"$//' <<< "${strarr[1]}" )
31-
echo "SDK Package Version: ${PKG_VERSION}"
32-
break
33-
fi
34-
done < "${DIR}/${VERSION_FILE}"
26+
PKG_VERSION=$(sed -n 's/^__version__ = //p' "${DIR}/${VERSION_FILE}" | sed -e 's/^"//' -e 's/"$//')
3527

3628
if [ "$BRANCH_NAME" != "master" ];
3729
then

scaleapi/_version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
__version__ = "2.0.0"
1+
__version__ = "2.0.1"
22
__package_name__ = "scaleapi"

scaleapi/api.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# Parameters for HTTP retry
1313
HTTP_TOTAL_RETRIES = 3 # Number of total retries
1414
HTTP_RETRY_BACKOFF_FACTOR = 2 # Wait 1, 2, 4 seconds between retries
15-
HTTP_STATUS_FORCE_LIST = [429, 504] # Status codes to force retry
15+
HTTP_STATUS_FORCE_LIST = [429, 500, 504] # Status codes to force retry
1616
HTTP_RETRY_ALLOWED_METHODS = frozenset({"GET", "POST"})
1717

1818

tests/test_client.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,13 @@
1919
TEST_PROJECT_NAME = "scaleapi-python-sdk"
2020

2121
try:
22+
print(f"SDK Version: {scaleapi.__version__}")
2223
test_api_key = os.environ["SCALE_TEST_API_KEY"]
23-
client = scaleapi.ScaleClient(test_api_key, "pytest")
24+
25+
if test_api_key.startswith("test_") or test_api_key.endswith("|test"):
26+
client = scaleapi.ScaleClient(test_api_key, "pytest")
27+
else:
28+
raise Exception("Please provide a valid TEST environment key.")
2429
except KeyError as err:
2530
raise Exception(
2631
"Please set the environment variable SCALE_TEST_API_KEY to run tests."

0 commit comments

Comments
 (0)