Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
0fb7bbe
Fix error tests
florijanstamenkovic Jan 20, 2021
e4de4fe
Improve DbObject equality testing
florijanstamenkovic Jan 20, 2021
5389da0
Make to-one relationship fetching more robust
florijanstamenkovic Jan 20, 2021
085c6f3
Fix Project.labels ordering
florijanstamenkovic Jan 20, 2021
e03ffd7
Mark tests as xfail (server-side issues) or skip (with added TODO) to…
florijanstamenkovic Jan 20, 2021
de57a9e
Skip slow tests when running tox
florijanstamenkovic Jan 20, 2021
7f90eab
Delete test_logger as it tests Pytest and not Labelbox
florijanstamenkovic Jan 20, 2021
35a2d77
Update CHANGELOG
florijanstamenkovic Jan 20, 2021
256de7a
Perform YAPF
florijanstamenkovic Jan 20, 2021
2ce1532
Add pytest as GH action dep
florijanstamenkovic Jan 20, 2021
a6b6363
Replace pytest invocation with tox in GitHub action
florijanstamenkovic Jan 20, 2021
4f90219
Add all Py versions to GitHub build workflow
florijanstamenkovic Jan 20, 2021
51ccc24
Fix tox invocation in GitHub workflow
florijanstamenkovic Jan 21, 2021
b733f15
Remove unnecessary dependency in GH workflow
florijanstamenkovic Jan 21, 2021
15a0593
Change pytest flags in GitHub build
florijanstamenkovic Jan 22, 2021
ca4e2a2
Remove 'not slow' from tox pytest invocation
florijanstamenkovic Jan 25, 2021
b59c494
Treat 'upstream connect error' as something to retry after
florijanstamenkovic Jan 25, 2021
9e04b13
Increase number of requests in the API call limit test
florijanstamenkovic Jan 25, 2021
1d89f59
Skipping API limit test due to flakyness
florijanstamenkovic Jan 25, 2021
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 .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
max-parallel: 1
matrix:
# TODO: unlock parallel testing by using more API keys
python-version: [3.6]
python-version: [3.6, 3.7, 3.8]

steps:

Expand Down Expand Up @@ -73,4 +73,4 @@ jobs:
# randall+staging-python@labelbox.com
LABELBOX_TEST_API_KEY_STAGING: ${{ secrets.STAGING_LABELBOX_API_KEY }}
run: |
pytest -svv
tox -e py -- -svv
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
# Changelog

## In progress
### Fix
* Custom queries with bad syntax now raise adequate exceptions (InvalidQuery)
* Comparing a Labelbox object (e.g. Project) to None doesn't raise an exception
* Adding `order_by` to `Project.labels` doesn't raise an exception

## Version 2.4.9 (2020-11-09)
### Fix
* 2.4.8 was broken for > Python 3.6
Expand Down
6 changes: 1 addition & 5 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import sys
sys.path.insert(0, os.path.abspath('../..'))


# -- Project information -----------------------------------------------------

project = 'Labelbox Python API reference'
Expand All @@ -29,9 +28,7 @@
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.viewcode',
'sphinxcontrib.napoleon'
'sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinxcontrib.napoleon'
]

# Add any paths that contain templates here, relative to this directory.
Expand All @@ -42,7 +39,6 @@
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']


# -- Options for HTML output -------------------------------------------------

# The theme to use for HTML and HTML Help pages. See the documentation for
Expand Down
32 changes: 26 additions & 6 deletions labelbox/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ def convert_value(value):
error_502 = '502 Bad Gateway'
if error_502 in response.text:
raise labelbox.exceptions.InternalServerError(error_502)
if "upstream connect error or disconnect/reset before headers" \
in response.text:
raise labelbox.exceptions.InternalServerError(
"Connection reset")
raise labelbox.exceptions.LabelboxError(
"Failed to parse response as JSON: %s" % response.text)

Expand Down Expand Up @@ -186,11 +190,27 @@ def check_errors(keywords, *path):
if response_msg.startswith("You have exceeded"):
raise labelbox.exceptions.ApiLimitError(response_msg)

prisma_error = check_errors(["INTERNAL_SERVER_ERROR"], "extensions",
"code")
if prisma_error:
raise labelbox.exceptions.InternalServerError(
prisma_error["message"])
resource_not_found_error = check_errors(["RESOURCE_NOT_FOUND"],
"extensions", "exception",
"code")
if resource_not_found_error is not None:
# Return None and let the caller methods raise an exception
# as they already know which resource type and ID was requested
return None

# A lot of different error situations are now labeled serverside
# as INTERNAL_SERVER_ERROR, when they are actually client errors.
# TODO: fix this in the server API
internal_server_error = check_errors(["INTERNAL_SERVER_ERROR"],
"extensions", "code")
if internal_server_error is not None:
message = internal_server_error.get("message")

if message.startswith("Syntax Error"):
raise labelbox.exceptions.InvalidQueryError(message)

else:
raise labelbox.exceptions.InternalServerError(message)

if len(errors) > 0:
logger.warning("Unparsed errors on query execution: %r", errors)
Expand Down Expand Up @@ -297,7 +317,7 @@ def _get_single(self, db_object_type, uid):
"""
query_str, params = query.get_single(db_object_type, uid)
res = self.execute(query_str, params)
res = res[utils.camel_case(db_object_type.type_name())]
res = res and res.get(utils.camel_case(db_object_type.type_name()))
if res is None:
raise labelbox.exceptions.ResourceNotFoundError(
db_object_type, params)
Expand Down
8 changes: 5 additions & 3 deletions labelbox/orm/db_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ def __str__(self):
return "<%s %s>" % (self.type_name().split(".")[-1], attribute_values)

def __eq__(self, other):
return self.type_name() == other.type_name() and self.uid == other.uid
return (isinstance(other, DbObject) and
self.type_name() == other.type_name() and self.uid == other.uid)

def __hash__(self):
return 7541 * hash(self.type_name()) + hash(self.uid)
Expand Down Expand Up @@ -152,8 +153,9 @@ def _to_one(self):

query_string, params = query.relationship(self.source, rel, None, None)
result = self.source.client.execute(query_string, params)
result = result[utils.camel_case(type(self.source).type_name())]
result = result[rel.graphql_name]
result = result and result.get(
utils.camel_case(type(self.source).type_name()))
result = result and result.get(rel.graphql_name)
if result is None:
return None
return rel.destination_type(self.source.client, result)
Expand Down
2 changes: 1 addition & 1 deletion labelbox/schema/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def labels(self, datasets=None, order_by=None):
id_param = "projectId"
query_str = """query GetProjectLabelsPyApi($%s: ID!)
{project (where: {id: $%s})
{labels (skip: %%d first: %%d%s%s) {%s}}}""" % (
{labels (skip: %%d first: %%d %s %s) {%s}}}""" % (
id_param, id_param, where, order_by_str,
query.results_query_part(Label))

Expand Down
6 changes: 4 additions & 2 deletions tests/integration/test_client_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,9 @@ def test_invalid_attribute_error(client, rand_gen):
project.delete()


@pytest.mark.skip
@pytest.mark.slow
# TODO improve consistency
@pytest.mark.skip(reason="Inconsistent test")
def test_api_limit_error(client, rand_gen):
project_id = client.create_project(name=rand_gen(str)).uid

Expand All @@ -114,7 +116,7 @@ def get(arg):
return e

with Pool(300) as pool:
results = pool.map(get, list(range(1000)))
results = pool.map(get, list(range(2000)))

assert labelbox.exceptions.ApiLimitError in {type(r) for r in results}

Expand Down
5 changes: 4 additions & 1 deletion tests/integration/test_data_rows.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ def test_data_row_bulk_creation(dataset, rand_gen):

data_rows[0].delete()


@pytest.mark.slow
def test_data_row_large_bulk_creation(dataset, rand_gen):
# Do a longer task and expect it not to be complete immediately
with NamedTemporaryFile() as fp:
fp.write("Test data".encode())
Expand All @@ -62,7 +65,7 @@ def test_data_row_bulk_creation(dataset, rand_gen):
data_rows = len(list(dataset.data_rows())) == 5003


@pytest.mark.skip
@pytest.mark.xfail(reason="DataRow.dataset() relationship not set")
def test_data_row_single_creation(dataset, rand_gen):
client = dataset.client
assert len(list(dataset.data_rows())) == 0
Expand Down
6 changes: 5 additions & 1 deletion tests/integration/test_data_upload.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import pytest
import requests


def test_file_uplad(client, rand_gen):
# TODO it seems that at some point Google Storage (gs prefix) started being
# returned, and we can't just download those with requests. Fix this
@pytest.mark.skip
def test_file_upload(client, rand_gen):
data = rand_gen(str)
url = client.upload_data(data.encode())
assert requests.get(url).text == data
1 change: 1 addition & 0 deletions tests/integration/test_label.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def test_labels(label_pack):
assert list(data_row.labels()) == []


# TODO check if this is supported or not
@pytest.mark.skip
def test_label_export(label_pack):
project, dataset, data_row, label = label_pack
Expand Down
20 changes: 0 additions & 20 deletions tests/integration/test_logger.py

This file was deleted.

8 changes: 4 additions & 4 deletions tests/integration/test_sorting.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
from labelbox import Project


@pytest.mark.skip
@pytest.mark.xfail(reason="Relationship sorting not implemented correctly "
"on the server-side")
def test_relationship_sorting(client):
a = client.create_project(name="a", description="b")
b = client.create_project(name="b", description="c")
Expand All @@ -29,7 +30,6 @@ def get(order_by):
c.delete()


@pytest.mark.xfail(reason="Sorting not supported on top-level fetches")
def test_top_level_sorting(client):
# TODO support sorting on top-level fetches
with pytest.raises(TypeError):
client.get_projects(order_by=Project.name.asc)
client.get_projects(order_by=Project.name.asc)
4 changes: 4 additions & 0 deletions tests/integration/test_webhook.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import pytest

from labelbox import Webhook


# TODO investigate why this fails
@pytest.mark.skip
def test_webhook_create_update(project, rand_gen):
client = project.client
url = "https:/" + rand_gen(str)
Expand Down