From 2ba10349f9e91862cdd1c52196c9c1792ef5134b Mon Sep 17 00:00:00 2001 From: rllin Date: Mon, 9 Nov 2020 10:33:13 -0800 Subject: [PATCH 01/12] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 341b8e938..ea8d2bee3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ # Changelog -## Version 2.4.9 (2020-09-10) +## Version 2.4.9 (2020-09-11) ### Fix * 2.4.8 was broken for > Python 3.6 ### Added From 445d51d6390f4ce8539b483ce1cec738f8ce3fb5 Mon Sep 17 00:00:00 2001 From: rllin Date: Mon, 9 Nov 2020 10:33:52 -0800 Subject: [PATCH 02/12] Update CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea8d2bee3..d6e346f7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,11 @@ # Changelog -## Version 2.4.9 (2020-09-11) +## Version 2.4.9 (2020-11-09) ### Fix * 2.4.8 was broken for > Python 3.6 ### Added * include new `Project.enable_model_assisted_labeling` method for turning on [model-assisted labeling](https://labelbox.com/docs/automation/model-assisted-labeling) -## Version 2.4.8 (2020-09-10) +## Version 2.4.8 (2020-11-06) ### Fix * fix failing `next` call https://github.com/Labelbox/labelbox-python/issues/74 From ad551bf1344edcc9c3b634a6da0f7a3e03ed7d87 Mon Sep 17 00:00:00 2001 From: rllin Date: Mon, 9 Nov 2020 10:35:03 -0800 Subject: [PATCH 03/12] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8c0b73379..6baf71b7d 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="labelbox", - version="2.4.8", + version="2.4.9", author="Labelbox", author_email="engineering@labelbox.com", description="Labelbox Python API", From 2eba3e87d4864c0dc4b3632e89cacfe09b27b1d8 Mon Sep 17 00:00:00 2001 From: Hyeong-Jin Edward Kim Date: Fri, 8 Jan 2021 19:05:19 +0200 Subject: [PATCH 04/12] add python_version to backports-datetime-fromisoformat --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6baf71b7d..4532e2563 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ packages=setuptools.find_packages(), install_requires=[ "backoff==1.10.0", - "backports-datetime-fromisoformat==1.0.0", + "backports-datetime-fromisoformat==1.0.0; python_version < '3.7.0'", "dataclasses==0.7; python_version < '3.7.0'", "ndjson==0.3.1", "requests>=2.22.0", From 3241470a97b76ae446aa18f0ceb458f8eb267242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florijan=20Stamenkovi=C4=87?= Date: Tue, 19 Jan 2021 14:07:08 +0100 Subject: [PATCH 05/12] Fix build GitHub action --- .github/workflows/python-package.yml | 4 ++-- labelbox/schema/project.py | 13 ++++++------- tests/integration/test_toggle_mal.py | 5 +++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index dd4572911..997c57036 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -27,9 +27,9 @@ jobs: - name: set environment for branch run: | if [[ "${{github.base_ref}}" == "master" || "${{github.ref}}" == "refs/heads/master" ]]; then - echo "::set-env name=LABELBOX_TEST_ENVIRON::prod" + echo "LABELBOX_TEST_ENVIRON=prod" >> $GITHUB_ENV else - echo "::set-env name=LABELBOX_TEST_ENVIRON::staging" + echo "LABELBOX_TEST_ENVIRON=staging" >> $GITHUB_ENV fi - uses: actions/checkout@v2 diff --git a/labelbox/schema/project.py b/labelbox/schema/project.py index e2a6927c3..c4bff741a 100644 --- a/labelbox/schema/project.py +++ b/labelbox/schema/project.py @@ -367,7 +367,7 @@ def create_prediction(self, label, data_row, prediction_model=None): res = self.client.execute(query_str, params) return Prediction(self.client, res["createPrediction"]) - def enable_model_assisted_labeling(self, toggle: bool=True) -> bool: + def enable_model_assisted_labeling(self, toggle: bool = True) -> bool: """ Turns model assisted labeling either on or off based on input Args: @@ -385,15 +385,13 @@ def enable_model_assisted_labeling(self, toggle: bool=True) -> bool: id, showingPredictionsToLabelers } } - }""" % (project_param, show_param,project_param, show_param) + }""" % (project_param, show_param, project_param, show_param) - params = { - project_param: self.uid, - show_param: toggle - } + params = {project_param: self.uid, show_param: toggle} res = self.client.execute(query_str, params) - return res["project"]["showPredictionsToLabelers"]["showingPredictionsToLabelers"] + return res["project"]["showPredictionsToLabelers"][ + "showingPredictionsToLabelers"] def upload_annotations( self, @@ -460,6 +458,7 @@ def _is_url_valid(url: Union[str, Path]) -> bool: raise ValueError( f'Invalid annotations given of type: {type(annotations)}') + class LabelingParameterOverride(DbObject): priority = Field.Int("priority") number_of_labels = Field.Int("number_of_labels") diff --git a/tests/integration/test_toggle_mal.py b/tests/integration/test_toggle_mal.py index 7cf84614c..9ce5493c8 100644 --- a/tests/integration/test_toggle_mal.py +++ b/tests/integration/test_toggle_mal.py @@ -2,12 +2,13 @@ from labelbox import Project import pytest + def test_enable_model_assisted_labeling(project): response = project.enable_model_assisted_labeling() assert response == True response = project.enable_model_assisted_labeling(True) assert response == True - + response = project.enable_model_assisted_labeling(False) - assert response == False \ No newline at end of file + assert response == False From 85e60979b15c4bd6782ed229aa831af355e204ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florijan=20Stamenkovi=C4=87?= Date: Tue, 19 Jan 2021 15:45:47 +0100 Subject: [PATCH 06/12] Remove parallel testing during build --- .github/workflows/python-package.yml | 4 ++-- tox.ini | 6 ------ 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 997c57036..a070e6ab0 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -58,7 +58,7 @@ jobs: mypy -p labelbox --pretty --show-error-codes - name: Install package and test dependencies run: | - pip install tox==3.18.1 tox-gh-actions==1.3.0 pytest-parallel==0.1.0 + pip install tox==3.18.1 # TODO: replace tox.ini with what the Makefile does # to make sure local testing is @@ -73,4 +73,4 @@ jobs: # randall+staging-python@labelbox.com LABELBOX_TEST_API_KEY_STAGING: ${{ secrets.STAGING_LABELBOX_API_KEY }} run: | - pytest --workers 2 -svv + pytest -svv diff --git a/tox.ini b/tox.ini index 3297034c3..0ff6b5bca 100644 --- a/tox.ini +++ b/tox.ini @@ -2,12 +2,6 @@ [tox] envlist = py36, py37, py38 -[gh-actions] -python = - 3.6: py36 - 3.7: py37 - 3.8: py38 - [testenv] # install pytest in the virtualenv where commands will be executed deps = From c4fae6fe5d247eb17aee2be78ff6238aab8ee838 Mon Sep 17 00:00:00 2001 From: Alex Cota Date: Tue, 19 Jan 2021 10:04:59 -0800 Subject: [PATCH 07/12] Modify files for sphinx auto docs (#91) * Modify files for sphinx auto docs * Fix Python class names and kwargs * Update upload_annotations docstrings in project.py * Fix project.upload_annotations docstring * Add missing files and udpate .gitignore * Move Sphinx documentation files to 'docs' directory * Add Sphinx requirements * Add docs/README.md * Add Sphinx theme to requirements * Fix docs/source/conf.py * Add google-api-core (for retry) to requirements.txt * Update README.md Co-authored-by: Florijan Stamenkovic --- .gitignore | 6 + docs/Makefile | 20 +++ docs/README.md | 42 ++++++ docs/make.bat | 35 +++++ docs/requirements.txt | 3 + docs/source/conf.py | 56 ++++++++ docs/source/index.rst | 171 +++++++++++++++++++++++++ labelbox/client.py | 60 +++++---- labelbox/exceptions.py | 1 + labelbox/pagination.py | 5 +- labelbox/schema/asset_metadata.py | 7 +- labelbox/schema/benchmark.py | 19 ++- labelbox/schema/bulk_import_request.py | 24 +++- labelbox/schema/data_row.py | 22 +++- labelbox/schema/dataset.py | 25 ++-- labelbox/schema/enums.py | 2 + labelbox/schema/label.py | 18 ++- labelbox/schema/labeling_frontend.py | 22 +++- labelbox/schema/ontology.py | 26 ++-- labelbox/schema/organization.py | 16 ++- labelbox/schema/prediction.py | 39 +++--- labelbox/schema/project.py | 67 +++++++--- labelbox/schema/review.py | 16 ++- labelbox/schema/task.py | 14 +- labelbox/schema/user.py | 15 +++ labelbox/schema/webhook.py | 8 ++ requirements.txt | 1 + 27 files changed, 626 insertions(+), 114 deletions(-) create mode 100644 docs/Makefile create mode 100644 docs/README.md create mode 100644 docs/make.bat create mode 100644 docs/requirements.txt create mode 100644 docs/source/conf.py create mode 100644 docs/source/index.rst diff --git a/.gitignore b/.gitignore index b15995323..ca9669026 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,9 @@ dmypy.json # macos files .DS_STORE + +# Sphinx Docs build +docs/build/ +# and source files +docs/source/_static +docs/source/_templates diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 000000000..d0c3cbf10 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..57cbf138b --- /dev/null +++ b/docs/README.md @@ -0,0 +1,42 @@ +# Labelbox Python SDK API Documentation + +The Labelbox Python API documentation is generated from source code comments +using Sphinx (https://www.sphinx-doc.org/). + +## Preparing the Sphinx environment + +To generate the documentation install Sphinx and Sphinxcontrib-Napoleon. The +easiest way to do it is using a Python virtual env and pip: + +``` +# create a virtual environment +python3 -m venv labelbox_docs_venv + +# activate the venv +source ./labelbox_docs_venv/bin/activate + +# upgrade venv pip and setuptools +pip install --upgrade pip setuptools + +# install Sphinx and necessary contrib from requriements +pip install -r labelbox_root/docs/requirements.txt + +# install Labelbox dependencies +pip install -r labelbox_root/requirements.txt +``` + +There are other ways to do prepare the environment, but we highly recommend +using a Python virtual environment. + +## Generating Labelbox SDK API documentation + +With the Sphinx environment prepared, enter the docs folder: + +``` +cd labelbox_root/docs/ +``` + +Run the make build tool, instructing it to build docs as HTML: +``` +make html +``` diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 000000000..6247f7e23 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 000000000..ac42c4269 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,3 @@ +Sphinx==3.4.3 +sphinxcontrib-napoleon==0.7 +sphinx-rtd-theme==0.5.1 diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 000000000..131ae59bc --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,56 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +sys.path.insert(0, os.path.abspath('../..')) + + +# -- Project information ----------------------------------------------------- + +project = 'Labelbox Python API reference' +copyright = '2021, Labelbox' +author = 'Alexandra Cota' + +release = '2.4' + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.viewcode', + 'sphinxcontrib.napoleon' +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# 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 +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 000000000..4bf477382 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,171 @@ +Labelbox Python API reference +=================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + +Client +---------------------- + +.. automodule:: labelbox.client + :members: + :special-members: __init__ + :exclude-members: upload_data, upload_file + :show-inheritance: + +AssetMetadata +-------------------------------------- + +.. automodule:: labelbox.schema.asset_metadata + :members: + :show-inheritance: + +Benchmark +-------------------------------- + +.. automodule:: labelbox.schema.benchmark + :members: + :show-inheritance: + +BulkImportRequest +-------------------------------------------- + +.. automodule:: labelbox.schema.bulk_import_request + :members: + :exclude-members: create_from_local_file, create_from_objects, create_from_url, from_name + :show-inheritance: + +DataRow +-------------------------------- + +.. automodule:: labelbox.schema.data_row + :members: + :show-inheritance: + +Dataset +------------------------------ + +.. automodule:: labelbox.schema.dataset + :members: + :show-inheritance: + +Label +---------------------------- + +.. automodule:: labelbox.schema.label + :members: + :show-inheritance: + +LabelingFrontend +----------------------------------------- + +.. automodule:: labelbox.schema.labeling_frontend + :members: LabelingFrontend + :exclude-members: LabelingFrontendOptions + :show-inheritance: + +LabelingFrontendOptions +----------------------------------------- +.. automodule:: labelbox.schema.labeling_frontend + :members: LabelingFrontendOptions + :exclude-members: LabelingFrontend + :show-inheritance: + :noindex: + +LabelingParameterOverride +----------------------------------------- +.. automodule:: labelbox.schema.project + :members: LabelingParameterOverride + :show-inheritance: + :noindex: + +Ontology +------------------------------- + +.. automodule:: labelbox.schema.ontology + :members: + :exclude-members: OntologyEntity, Classification, Tool, Option + :show-inheritance: + +Organization +----------------------------------- + +.. automodule:: labelbox.schema.organization + :members: + :show-inheritance: + +Prediction +--------------------------------- + +.. automodule:: labelbox.schema.prediction + :members: Prediction + :exclude-members: PredictionModel + :show-inheritance: + +PredictionModel +--------------------------------- +.. automodule:: labelbox.schema.prediction + :members: PredictionModel + :exclude-members: Prediction + :show-inheritance: + :noindex: + +Project +------------------------------ + +.. automodule:: labelbox.schema.project + :members: + :exclude-members: LabelerPerformance, LabelingParameterOverride + :show-inheritance: + +Review +----------------------------- + +.. automodule:: labelbox.schema.review + :members: + :show-inheritance: + +Task +--------------------------- + +.. automodule:: labelbox.schema.task + :members: + :show-inheritance: + +User +--------------------------- + +.. automodule:: labelbox.schema.user + :members: + :show-inheritance: + +Webhook +------------------------------ + +.. automodule:: labelbox.schema.webhook + :members: + :show-inheritance: + +Exceptions +-------------------------- + +.. automodule:: labelbox.exceptions + :members: + :show-inheritance: + +Pagination +-------------------------- + +.. automodule:: labelbox.pagination + :members: + :special-members: __init__ + :show-inheritance: + +Enums +---------------------------- + +.. automodule:: labelbox.schema.enums + :members: + :show-inheritance: + diff --git a/labelbox/client.py b/labelbox/client.py index 96ba7ccf0..204c763a5 100644 --- a/labelbox/client.py +++ b/labelbox/client.py @@ -26,9 +26,11 @@ class Client: - """ A Labelbox client. Contains info necessary for connecting to - a Labelbox server (URL, authentication key). Provides functions for - querying and creating top-level data objects (Projects, Datasets). + """ A Labelbox client. + + Contains info necessary for connecting to a Labelbox server (URL, + authentication key). Provides functions for querying and creating + top-level data objects (Projects, Datasets). """ def __init__(self, @@ -36,23 +38,20 @@ def __init__(self, endpoint='https://api.labelbox.com/graphql'): """ Creates and initializes a Labelbox Client. - Logging is defaulted to level WARNING. To receive more verbose - output to console, update logging.level to the - appropriate level. + Logging is defaulted to level WARNING. To receive more verbose + output to console, update `logging.level` to the appropriate level. - >>> import logger - >>> logging.basicConfig(level = logging.INFO) - >>> client = Client("") + >>> import logger + >>> logging.basicConfig(level = logging.INFO) + >>> client = Client("") Args: - api_key (str): API key. If None, the key is obtained from - the "LABELBOX_API_KEY" environment variable. + api_key (str): API key. If None, the key is obtained from the "LABELBOX_API_KEY" environment variable. endpoint (str): URL of the Labelbox server to connect to. Raises: labelbox.exceptions.AuthenticationError: If no `api_key` is provided as an argument or via the environment variable. - """ if api_key is None: if _LABELBOX_API_KEY not in os.environ: @@ -74,8 +73,10 @@ def __init__(self, labelbox.exceptions.InternalServerError)) def execute(self, query, params=None, timeout=10.0): """ Sends a request to the server for the execution of the - given query. Checks the response for errors and wraps errors - in appropriate labelbox.exceptions.LabelboxError subtypes. + given query. + + Checks the response for errors and wraps errors + in appropriate `labelbox.exceptions.LabelboxError` subtypes. Args: query (str): The query to execute. @@ -219,7 +220,6 @@ def upload_file(self, path: str) -> str: str, the URL of uploaded data. Raises: labelbox.exceptions.LabelboxError: If upload failed. - """ content_type, _ = mimetypes.guess_type(path) filename = os.path.basename(path) @@ -336,8 +336,8 @@ def get_dataset(self, dataset_id): def get_user(self): """ Gets the current User database object. + >>> user = client.get_user() - """ return self._get_single(User, None) @@ -434,16 +434,15 @@ def _create(self, db_object_type, data): return db_object_type(self, res) def create_dataset(self, **kwargs): - """ Creates a Dataset object on the server. Attribute values are - passed as keyword arguments: + """ Creates a Dataset object on the server. + + Attribute values are passed as keyword arguments. - >>> project = client.get_project("") - >>> dataset = client.create_dataset(name="", projects=project) + >>> project = client.get_project("") + >>> dataset = client.create_dataset(name="", projects=project) - Kwargs: - Keyword arguments with new Dataset attribute values. - Keys are attribute names (in Python, snake-case convention) and - values are desired attribute values. + Args: + **kwargs: Keyword arguments with Dataset attribute values. Returns: A new Dataset object. Raises: @@ -453,15 +452,14 @@ def create_dataset(self, **kwargs): return self._create(Dataset, kwargs) def create_project(self, **kwargs): - """ Creates a Project object on the server. Attribute values are - passed as keyword arguments: + """ Creates a Project object on the server. + + Attribute values are passed as keyword arguments. - >>> project = client.create_project(name="", description="") + >>> project = client.create_project(name="", description="") - Kwargs: - Keyword arguments with new Project attribute values. - Keys are attribute names (in Python, snake-case convention) and - values are desired attribute values. + Args: + **kwargs: Keyword arguments with Project attribute values. Returns: A new Project object. Raises: diff --git a/labelbox/exceptions.py b/labelbox/exceptions.py index 9dc8a75cf..9a7292b33 100644 --- a/labelbox/exceptions.py +++ b/labelbox/exceptions.py @@ -31,6 +31,7 @@ class ResourceNotFoundError(LabelboxError): def __init__(self, db_object_type, params): """ Constructor. + Args: db_object_type (type): A labelbox.schema.DbObject subtype. params (dict): Dict of params identifying the sought resource. diff --git a/labelbox/pagination.py b/labelbox/pagination.py index 566edffa2..5b8b6719a 100644 --- a/labelbox/pagination.py +++ b/labelbox/pagination.py @@ -4,16 +4,17 @@ class PaginatedCollection: """ An iterable collection of database objects (Projects, Labels, etc...). + Implements automatic (transparent to the user) paginated fetching during iteration. Intended for use by library internals and not by the end user. - For a list of attributes see __init__(...) documentation. The params of __init__ map exactly to object attributes. """ def __init__(self, client, query, params, dereferencing, obj_class): """ Creates a PaginatedCollection. - Params: + + Args: client (labelbox.Client): the client used for fetching data from DB. query (str): Base query used for pagination. It must contain two '%d' placeholders, the first for pagination 'skip' clause and diff --git a/labelbox/schema/asset_metadata.py b/labelbox/schema/asset_metadata.py index f0990834f..c5aa0ff9c 100644 --- a/labelbox/schema/asset_metadata.py +++ b/labelbox/schema/asset_metadata.py @@ -3,8 +3,11 @@ class AssetMetadata(DbObject): - """ AssetMetadata is a datatype to provide extra context about an asset - while labeling. + """ Asset metadata (AKA Attachments) provides extra context about an asset while labeling. + + Attributes: + meta_type (str): IMAGE, VIDEO, TEXT, or IMAGE_OVERLAY + meta_value (str): URL to an external file or a string of text """ VIDEO = "VIDEO" IMAGE = "IMAGE" diff --git a/labelbox/schema/benchmark.py b/labelbox/schema/benchmark.py index fe5075fc7..f703c2519 100644 --- a/labelbox/schema/benchmark.py +++ b/labelbox/schema/benchmark.py @@ -3,12 +3,21 @@ class Benchmark(DbObject): - """ Benchmarks (also known as Golden Standard) is a quality assurance tool - for training data. Training data quality is the measure of accuracy and - consistency of the training data. Benchmarks works by interspersing data - to be labeled, for which there is a benchmark label, to each person labeling. - These labeled data are compared against their respective benchmark and an + """ Represents a benchmark label. + + The Benchmarks tool works by interspersing data to be labeled, for + which there is a benchmark label, to each person labeling. These + labeled data are compared against their respective benchmark and an accuracy score between 0 and 100 percent is calculated. + + Attributes: + created_at (datetime) + last_activity (datetime) + average_agreement (float) + completed_count (int) + + created_by (Relationship): `ToOne` relationship to User + reference_label (Relationship): `ToOne` relationship to Label """ created_at = Field.DateTime("created_at") created_by = Relationship.ToOne("User", False, "created_by") diff --git a/labelbox/schema/bulk_import_request.py b/labelbox/schema/bulk_import_request.py index b1ff198ce..a8d54e40b 100644 --- a/labelbox/schema/bulk_import_request.py +++ b/labelbox/schema/bulk_import_request.py @@ -92,6 +92,19 @@ def _send_create_file_command( class BulkImportRequest(DbObject): + """Represents the import job when importing annotations. + + Attributes: + name (str) + state (Enum): FAILED, RUNNING, or FINISHED (Refers to the whole import job) + input_file_url (str): URL to your web-hosted NDJSON file + error_file_url (str): NDJSON that contains error messages for failed annotations + status_file_url (str): NDJSON that contains status for each annotation + created_at (datetime): UTC timestamp for date BulkImportRequest was created + + project (Relationship): `ToOne` relationship to Project + created_by (Relationship): `ToOne` relationship to User + """ name = Field.String("name") state = Field.Enum(BulkImportRequestState, "state") input_file_url = Field.String("input_file_url") @@ -103,8 +116,7 @@ class BulkImportRequest(DbObject): created_by = Relationship.ToOne("User", False, "created_by") def refresh(self) -> None: - """ - Synchronizes values of all fields with the database. + """Synchronizes values of all fields with the database. """ query_str, params = query.get_single(BulkImportRequest, self.uid) res = self.client.execute(query_str, params) @@ -112,7 +124,8 @@ def refresh(self) -> None: self._set_field_values(res) def wait_until_done(self, sleep_time_seconds: int = 30) -> None: - """ + """Blocks import job until certain conditions are met. + Blocks until the BulkImportRequest.state changes either to `BulkImportRequestState.FINISHED` or `BulkImportRequestState.FAILED`, periodically refreshing object's state. @@ -196,8 +209,9 @@ def create_from_url(cls, client, project_id: str, name: str, def create_from_objects(cls, client, project_id: str, name: str, predictions: Iterable[dict]) -> 'BulkImportRequest': """ - Creates a BulkImportRequest from an iterable of dictionaries conforming to - JSON predictions format, e.g.: + Creates a `BulkImportRequest` from an iterable of dictionaries. + + Conforms to JSON predictions format, e.g.: ``{ "uuid": "9fd9a92e-2560-4e77-81d4-b2e955800092", "schemaId": "ckappz7d700gn0zbocmqkwd9i", diff --git a/labelbox/schema/data_row.py b/labelbox/schema/data_row.py index f8d12361c..78c068298 100644 --- a/labelbox/schema/data_row.py +++ b/labelbox/schema/data_row.py @@ -5,8 +5,21 @@ class DataRow(DbObject, Updateable, BulkDeletable): - """ A DataRow represents a single piece of data. For example, if you have - a CSV with 100 rows, you will have 1 Dataset and 100 DataRows. + """ Internal Labelbox representation of a single piece of data (e.g. image, video, text). + + Attributes: + external_id (str): User-generated file name or identifier + row_data (str): Paths to local files are uploaded to Labelbox's server. + Otherwise, it's treated as an external URL. + updated_at (datetime) + created_at (datetime) + + dataset (Relationship): `ToOne` relationship to Dataset + created_by (Relationship): `ToOne` relationship to User + organization (Relationship): `ToOne` relationship to Organization + labels (Relationship): `ToMany` relationship to Label + metadata (Relationship): `ToMany` relationship to AssetMetadata + predictions (Relationship): `ToMany` relationship to Prediction """ external_id = Field.String("external_id") row_data = Field.String("row_data") @@ -36,7 +49,8 @@ def __init__(self, *args, **kwargs): self.metadata.supports_sorting = False def create_metadata(self, meta_type, meta_value): - """ Creates an asset metadata for this DataRow. + """ Attaches asset metadata to a DataRow. + >>> datarow.create_metadata("TEXT", "This is a text message") Args: @@ -44,7 +58,7 @@ def create_metadata(self, meta_type, meta_value): VIDEO, IMAGE, TEXT. meta_value (str): Asset metadata value. Returns: - AssetMetadata DB object. + `AssetMetadata` DB object. """ meta_type_param = "metaType" meta_value_param = "metaValue" diff --git a/labelbox/schema/dataset.py b/labelbox/schema/dataset.py index 750b2e013..7a7f0ecd4 100644 --- a/labelbox/schema/dataset.py +++ b/labelbox/schema/dataset.py @@ -8,8 +8,19 @@ class Dataset(DbObject, Updateable, Deletable): - """ A dataset is a collection of DataRows. For example, if you have a CSV with - 100 rows, you will have 1 Dataset and 100 DataRows. + """ A Dataset is a collection of DataRows. + + Attributes: + name (str) + description (str) + updated_at (datetime) + created_at (datetime) + + projects (Relationship): `ToMany` relationship to Project + data_rows (Relationship): `ToMany` relationship to DataRow + created_by (Relationship): `ToOne` relationship to User + organization (Relationship): `ToOne` relationship to Organization + """ name = Field.String("name") description = Field.String("description") @@ -27,12 +38,10 @@ def create_data_row(self, **kwargs): >>> dataset.create_data_row(row_data="http://my_site.com/photos/img_01.jpg") - Kwargs: - Key-value arguments containing new `DataRow` data. - At a minimum `kwargs` must contain `row_data`. The value for - `row_data` is a string. If it is a path to an existing local - file then it is uploaded to Labelbox's server. Otherwise it is - treated as an external URL. + Args: + **kwargs: Key-value arguments containing new `DataRow` data. At a minimum, + must contain `row_data`. + Raises: InvalidQueryError: If `DataRow.row_data` field value is not provided in `kwargs`. diff --git a/labelbox/schema/enums.py b/labelbox/schema/enums.py index b6943cef9..438fc496a 100644 --- a/labelbox/schema/enums.py +++ b/labelbox/schema/enums.py @@ -2,6 +2,8 @@ class BulkImportRequestState(Enum): + """ State of the import job when importing annotations (RUNNING, FAILED, or FINISHED). + """ RUNNING = "RUNNING" FAILED = "FAILED" FINISHED = "FINISHED" diff --git a/labelbox/schema/label.py b/labelbox/schema/label.py index ef968b15e..f25ae6c76 100644 --- a/labelbox/schema/label.py +++ b/labelbox/schema/label.py @@ -7,6 +7,19 @@ class Label(DbObject, Updateable, BulkDeletable): """ Label represents an assessment on a DataRow. For example one label could contain 100 bounding boxes (annotations). + + Attributes: + label (str) + seconds_to_label (float) + agreement (float) + benchmark_agreement (float) + is_benchmark_reference (bool) + + project (Relationship): `ToOne` relationship to Project + data_row (Relationship): `ToOne` relationship to DataRow + reviews (Relationship): `ToMany` relationship to Review + created_by (Relationship): `ToOne` relationship to User + """ def __init__(self, *args, **kwargs): @@ -36,9 +49,8 @@ def bulk_delete(labels): def create_review(self, **kwargs): """ Creates a Review for this label. - Kwargs: - Review attributes. At a minimum a `Review.score` field - value must be provided. + Args: + **kwargs: Review attributes. At a minimum, a `Review.score` field value must be provided. """ kwargs[Entity.Review.label.name] = self kwargs[Entity.Review.project.name] = self.project() diff --git a/labelbox/schema/labeling_frontend.py b/labelbox/schema/labeling_frontend.py index 2dbbe813c..13fe76635 100644 --- a/labelbox/schema/labeling_frontend.py +++ b/labelbox/schema/labeling_frontend.py @@ -3,9 +3,18 @@ class LabelingFrontend(DbObject): - """ Is a type representing an HTML / JavaScript UI that is used to generate - labels. “Image Labeling” is the default Labeling Frontend that comes in every + """ Label editor. + + Represents an HTML / JavaScript UI that is used to generate + labels. “Editor” is the default Labeling Frontend that comes in every organization. You can create new labeling frontends for an organization. + + Attributes: + name (str) + description (str) + iframe_url_path (str) + + projects (Relationship): `ToMany` relationship to Project """ name = Field.String("name") description = Field.String("description") @@ -16,6 +25,15 @@ class LabelingFrontend(DbObject): class LabelingFrontendOptions(DbObject): + """ Label interface options. + + Attributes: + customization_options (str) + + project (Relationship): `ToOne` relationship to Project + labeling_frontend (Relationship): `ToOne` relationship to LabelingFrontend + organization (Relationship): `ToOne` relationship to Organization + """ customization_options = Field.String("customization_options") project = Relationship.ToOne("Project") diff --git a/labelbox/schema/ontology.py b/labelbox/schema/ontology.py index 301507884..9b28f6d01 100644 --- a/labelbox/schema/ontology.py +++ b/labelbox/schema/ontology.py @@ -1,4 +1,3 @@ -"""Client side object for interacting with the ontology.""" import abc from dataclasses import dataclass @@ -65,15 +64,20 @@ def from_json(cls, json_dict): class Ontology(DbObject): - """ A ontology specifies which tools and classifications are available - to a project. - - NOTE: This is read only for now. - - >>> project = client.get_project(name="") - >>> ontology = project.ontology() - >>> ontology.normalized - + """An ontology specifies which tools and classifications are available + to a project. This is read only for now. + + Attributes: + name (str) + description (str) + updated_at (datetime) + created_at (datetime) + normalized (json) + object_schema_count (int) + classification_schema_count (int) + + projects (Relationship): `ToMany` relationship to Project + created_by (Relationship): `ToOne` relationship to User """ name = Field.String("name") @@ -93,6 +97,7 @@ def __init__(self, *args, **kwargs) -> None: self._classifications: Optional[List[Classification]] = None def tools(self) -> List[Tool]: + """Get list of tools (AKA objects) in an Ontology.""" if self._tools is None: self._tools = [ Tool.from_json(tool) for tool in self.normalized['tools'] @@ -100,6 +105,7 @@ def tools(self) -> List[Tool]: return self._tools # type: ignore def classifications(self) -> List[Classification]: + """Get list of classifications in an Ontology.""" if self._classifications is None: self._classifications = [ Classification.from_json(classification) diff --git a/labelbox/schema/organization.py b/labelbox/schema/organization.py index 5a4465189..c23d72133 100644 --- a/labelbox/schema/organization.py +++ b/labelbox/schema/organization.py @@ -3,9 +3,19 @@ class Organization(DbObject): - """ An Organization is a group of Users associated with data created by - Users within that Organization. Typically all Users within an Organization - have access to data created by any User in the same Organization. + """ An Organization is a group of Users. + + It is associated with data created by Users within that Organization. + Typically all Users within an Organization have access to data created by any User in the same Organization. + + Attributes: + updated_at (datetime) + created_at (datetime) + name (str) + + users (Relationship): `ToMany` relationship to User + projects (Relationship): `ToMany` relationship to Project + webhooks (Relationship): `ToMany` relationship to Webhook """ # RelationshipManagers in Organization use the type in Query (and diff --git a/labelbox/schema/prediction.py b/labelbox/schema/prediction.py index a59e0ca86..309f5928f 100644 --- a/labelbox/schema/prediction.py +++ b/labelbox/schema/prediction.py @@ -3,15 +3,19 @@ class PredictionModel(DbObject): - """ A prediction model represents a specific version of a model. + """ A PredictionModel creates a Prediction. Legacy editor only. - NOTE: This is used for the legacy editor [1], if you wish to - import annotations, refer to [2] + Refer to BulkImportRequest if using the new Editor. + Attributes: + updated_at (datetime) + created_at (datetime) + name (str) + slug (str) + version (int) - [1] https://labelbox.com/docs/legacy/import-model-prediction - [2] https://labelbox.com/docs/automation/model-assisted-labeling - + created_by (Relationship): `ToOne` relationship to User + organization (Relationship): `ToOne` relationship to Organization """ updated_at = Field.DateTime("updated_at") created_at = Field.DateTime("created_at") @@ -27,15 +31,20 @@ class PredictionModel(DbObject): class Prediction(DbObject): - """ A prediction created by a PredictionModel. - - NOTE: This is used for the legacy editor [1], if you wish to - import annotations, refer to [2] - - - [1] https://labelbox.com/docs/legacy/import-model-prediction - [2] https://labelbox.com/docs/automation/model-assisted-labeling - + """ A prediction created by a PredictionModel. Legacy editor only. + + Refer to BulkImportRequest if using the new Editor. + + Attributes: + updated_at (datetime) + created_at (datetime) + label (str) + agreement (float) + + organization (Relationship): `ToOne` relationship to Organization + prediction_model (Relationship): `ToOne` relationship to PredictionModel + data_row (Relationship): `ToOne` relationship to DataRow + project (Relationship): `ToOne` relationship to Project """ updated_at = Field.DateTime("updated_at") created_at = Field.DateTime("created_at") diff --git a/labelbox/schema/project.py b/labelbox/schema/project.py index c4bff741a..b23c18539 100644 --- a/labelbox/schema/project.py +++ b/labelbox/schema/project.py @@ -27,6 +27,29 @@ class Project(DbObject, Updateable, Deletable): """ A Project is a container that includes a labeling frontend, an ontology, datasets and labels. + + Attributes: + name (str) + description (str) + updated_at (datetime) + created_at (datetime) + setup_complete (datetime) + last_activity_time (datetime) + auto_audit_number_of_labels (int) + auto_audit_percentage (float) + + datasets (Relationship): `ToMany` relationship to Dataset + created_by (Relationship): `ToOne` relationship to User + organization (Relationship): `ToOne` relationship to Organization + reviews (Relationship): `ToMany` relationship to Review + labeling_frontend (Relationship): `ToOne` relationship to LabelingFrontend + labeling_frontend_options (Relationship): `ToMany` relationship to LabelingFrontendOptions + labeling_parameter_overrides (Relationship): `ToMany` relationship to LabelingParameterOverride + webhooks (Relationship): `ToMany` relationship to Webhook + benchmarks (Relationship): `ToMany` relationship to Benchmark + active_prediction_model (Relationship): `ToOne` relationship to PredictionModel + predictions (Relationship): `ToMany` relationship to Prediction + ontology (Relationship): `ToOne` relationship to Ontology """ name = Field.String("name") description = Field.String("description") @@ -55,11 +78,10 @@ class Project(DbObject, Updateable, Deletable): ontology = Relationship.ToOne("Ontology", True) def create_label(self, **kwargs): - """ Creates a label on a Legacy Editor project. Not - supported in the new Editor. + """ Creates a label on a Legacy Editor project. Not supported in the new Editor. - Kwargs: - Label attributes. At the minimum the label `DataRow`. + Args: + **kwargs: Label attributes. At minimum, the label `DataRow`. """ # Copy-paste of Client._create code so we can inject # a connection to Type. Type objects are on their way to being @@ -86,8 +108,7 @@ def create_label(self, **kwargs): return Label(self.client, res["createLabel"]) def labels(self, datasets=None, order_by=None): - """ - Custom relationship expansion method to support limited filtering. + """ Custom relationship expansion method to support limited filtering. Args: datasets (iterable of Dataset): Optional collection of Datasets @@ -129,9 +150,8 @@ def export_labels(self, timeout_seconds=60): Args: timeout_seconds (float): Max waiting time, in seconds. Returns: - URL of the data file with this Project's labels. If the server - didn't generate during the `timeout_seconds` period, None - is returned. + URL of the data file with this Project's labels. If the server didn't + generate during the `timeout_seconds` period, None is returned. """ sleep_time = 2 id_param = "projectId" @@ -186,7 +206,7 @@ def review_metrics(self, net_score): Args: net_score (None or Review.NetScore): Indicates desired metric. Returns: - int, aggregation count of reviews for given net_score. + int, aggregation count of reviews for given `net_score`. """ if net_score not in (None,) + tuple(Entity.Review.NetScore): raise InvalidQueryError( @@ -230,8 +250,8 @@ def setup(self, labeling_frontend, labeling_frontend_options): self.update(setup_complete=timestamp) def set_labeling_parameter_overrides(self, data): - """ Adds labeling parameter overrides to this project. Example: - + """ Adds labeling parameter overrides to this project. + >>> project.set_labeling_parameter_overrides([ >>> (data_row_1, 2, 3), (data_row_2, 1, 4)]) @@ -307,12 +327,12 @@ def extend_reservations(self, queue_type): return res["extendReservations"] def create_prediction_model(self, name, version): - """ Creates a PredictionModel connected to a Legacy Editor - Project. + """ Creates a PredictionModel connected to a Legacy Editor Project. + Args: name (str): The new PredictionModel's name. version (int): The new PredictionModel's version. - Return: + Returns: A newly created PredictionModel. """ PM = Entity.PredictionModel @@ -324,7 +344,8 @@ def create_prediction_model(self, name, version): return model def create_prediction(self, label, data_row, prediction_model=None): - """ Creates a Prediction within a Legacy Editor Project. + """ Creates a Prediction within a Legacy Editor Project. Not supported + in the new Editor. Args: label (str): The `label` field of the new Prediction. @@ -371,11 +392,10 @@ def enable_model_assisted_labeling(self, toggle: bool = True) -> bool: """ Turns model assisted labeling either on or off based on input Args: - toggle (Boolean): True or False boolean + toggle (bool): True or False boolean Returns: True if toggled on or False if toggled off """ - project_param = "project_id" show_param = "show" @@ -401,15 +421,14 @@ def upload_annotations( """ Uploads annotations to a new Editor project. Args: - name: name of the BulkImportRequest job - annotations: + name (str): name of the BulkImportRequest job + annotations (str or Path or Iterable): url that is publicly accessible by Labelbox containing an ndjson file OR local path to an ndjson file OR iterable of annotation rows Returns: BulkImportRequest - """ if isinstance(annotations, str) or isinstance(annotations, Path): @@ -460,6 +479,12 @@ def _is_url_valid(url: Union[str, Path]) -> bool: class LabelingParameterOverride(DbObject): + """ Customizes the order of assets in the label queue. + + Attributes: + priority (int): A prioritization score. + number_of_labels (int): Number of times an asset should be labeled. + """ priority = Field.Int("priority") number_of_labels = Field.Int("number_of_labels") diff --git a/labelbox/schema/review.py b/labelbox/schema/review.py index 8a4ee5275..10ff904d2 100644 --- a/labelbox/schema/review.py +++ b/labelbox/schema/review.py @@ -6,10 +6,24 @@ class Review(DbObject, Deletable, Updateable): """ Reviewing labeled data is a collaborative quality assurance technique. + A Review object indicates the quality of the assigned Label. The aggregated - review numbers can be obtained on a Project object. """ + review numbers can be obtained on a Project object. + + Attributes: + created_at (datetime) + updated_at (datetime) + score (float) + + created_by (Relationship): `ToOne` relationship to User + organization (Relationship): `ToOne` relationship to Organization + project (Relationship): `ToOne` relationship to Project + label (Relationship): `ToOne` relationship to Label + """ class NetScore(Enum): + """ Negative, Zero, or Positive. + """ Negative = auto() Zero = auto() Positive = auto() diff --git a/labelbox/schema/task.py b/labelbox/schema/task.py index 9af3e6f91..a6ec0f3b8 100644 --- a/labelbox/schema/task.py +++ b/labelbox/schema/task.py @@ -11,6 +11,16 @@ class Task(DbObject): """ Represents a server-side process that might take a longer time to process. Allows the Task state to be updated and checked on the client side. + + Attributes: + updated_at (datetime) + created_at (datetime) + name (str) + status (str) + completion_percentage (float) + + created_by (Relationship): `ToOne` relationship to User + organization (Relationship): `ToOne` relationship to Organization """ updated_at = Field.DateTime("updated_at") created_at = Field.DateTime("created_at") @@ -33,9 +43,9 @@ def refresh(self): def wait_till_done(self, timeout_seconds=60): """ Waits until the task is completed. Periodically queries the server to update the task attributes. + Args: - timeout_seconds (float): Maximum time this method can block, in - seconds. Defaults to one minute. + timeout_seconds (float): Maximum time this method can block, in seconds. Defaults to one minute. """ check_frequency = 2 # frequency of checking, in seconds while True: diff --git a/labelbox/schema/user.py b/labelbox/schema/user.py index 33b15df79..f8487fa52 100644 --- a/labelbox/schema/user.py +++ b/labelbox/schema/user.py @@ -5,6 +5,21 @@ class User(DbObject): """ A User is a registered Labelbox user (for example you) associated with data they create or import and an Organization they belong to. + + Attributes: + updated_at (datetime) + created_at (datetime) + email (str) + name (str) + nickname (str) + intercom_hash (str) + picture (str) + is_viewer (bool) + is_external_viewer (bool) + + organization (Relationship): `ToOne` relationship to Organization + created_tasks (Relationship): `ToMany` relationship to Task + projects (Relationship): `ToMany` relationship to Project """ updated_at = Field.DateTime("updated_at") created_at = Field.DateTime("created_at") diff --git a/labelbox/schema/webhook.py b/labelbox/schema/webhook.py index bb37481ae..a83b93fb1 100644 --- a/labelbox/schema/webhook.py +++ b/labelbox/schema/webhook.py @@ -7,6 +7,14 @@ class Webhook(DbObject, Updateable): """ Represents a server-side rule for sending notifications to a web-server whenever one of several predefined actions happens within a context of a Project or an Organization. + + Attributes: + updated_at (datetime) + created_at (datetime) + url (str) + topics (str): LABEL_CREATED, LABEL_UPDATED, LABEL_DELETED + status (str): ACTIVE, INACTIVE, REVOKED + """ # Status diff --git a/requirements.txt b/requirements.txt index 07429a0ca..d6e1b9c66 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ requests==2.22.0 ndjson==0.3.1 backoff==1.10.0 +google-api-core>=1.22.1 From 45852a54d1561a4c16d7ddf03c225f66cd3846d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florijan=20Stamenkovi=C4=87?= Date: Wed, 20 Jan 2021 10:29:19 +0100 Subject: [PATCH 08/12] Remove trailing whitespace (#97) --- labelbox/client.py | 22 +++++++++++----------- labelbox/exceptions.py | 2 +- labelbox/pagination.py | 4 ++-- labelbox/schema/benchmark.py | 8 ++++---- labelbox/schema/bulk_import_request.py | 8 ++++---- labelbox/schema/data_row.py | 10 +++++----- labelbox/schema/dataset.py | 6 +++--- labelbox/schema/label.py | 2 +- labelbox/schema/labeling_frontend.py | 2 +- labelbox/schema/organization.py | 4 ++-- labelbox/schema/prediction.py | 4 ++-- labelbox/schema/project.py | 16 ++++++++-------- labelbox/schema/review.py | 4 ++-- labelbox/schema/webhook.py | 2 +- 14 files changed, 47 insertions(+), 47 deletions(-) diff --git a/labelbox/client.py b/labelbox/client.py index 204c763a5..c8ca5b1d4 100644 --- a/labelbox/client.py +++ b/labelbox/client.py @@ -26,11 +26,11 @@ class Client: - """ A Labelbox client. - - Contains info necessary for connecting to a Labelbox server (URL, - authentication key). Provides functions for querying and creating - top-level data objects (Projects, Datasets). + """ A Labelbox client. + + Contains info necessary for connecting to a Labelbox server (URL, + authentication key). Provides functions for querying and creating + top-level data objects (Projects, Datasets). """ def __init__(self, @@ -73,8 +73,8 @@ def __init__(self, labelbox.exceptions.InternalServerError)) def execute(self, query, params=None, timeout=10.0): """ Sends a request to the server for the execution of the - given query. - + given query. + Checks the response for errors and wraps errors in appropriate `labelbox.exceptions.LabelboxError` subtypes. @@ -336,7 +336,7 @@ def get_dataset(self, dataset_id): def get_user(self): """ Gets the current User database object. - + >>> user = client.get_user() """ return self._get_single(User, None) @@ -435,7 +435,7 @@ def _create(self, db_object_type, data): def create_dataset(self, **kwargs): """ Creates a Dataset object on the server. - + Attribute values are passed as keyword arguments. >>> project = client.get_project("") @@ -452,8 +452,8 @@ def create_dataset(self, **kwargs): return self._create(Dataset, kwargs) def create_project(self, **kwargs): - """ Creates a Project object on the server. - + """ Creates a Project object on the server. + Attribute values are passed as keyword arguments. >>> project = client.create_project(name="", description="") diff --git a/labelbox/exceptions.py b/labelbox/exceptions.py index 9a7292b33..14153586d 100644 --- a/labelbox/exceptions.py +++ b/labelbox/exceptions.py @@ -31,7 +31,7 @@ class ResourceNotFoundError(LabelboxError): def __init__(self, db_object_type, params): """ Constructor. - + Args: db_object_type (type): A labelbox.schema.DbObject subtype. params (dict): Dict of params identifying the sought resource. diff --git a/labelbox/pagination.py b/labelbox/pagination.py index 5b8b6719a..14803da27 100644 --- a/labelbox/pagination.py +++ b/labelbox/pagination.py @@ -4,7 +4,7 @@ class PaginatedCollection: """ An iterable collection of database objects (Projects, Labels, etc...). - + Implements automatic (transparent to the user) paginated fetching during iteration. Intended for use by library internals and not by the end user. For a list of attributes see __init__(...) documentation. The params of @@ -13,7 +13,7 @@ class PaginatedCollection: def __init__(self, client, query, params, dereferencing, obj_class): """ Creates a PaginatedCollection. - + Args: client (labelbox.Client): the client used for fetching data from DB. query (str): Base query used for pagination. It must contain two diff --git a/labelbox/schema/benchmark.py b/labelbox/schema/benchmark.py index f703c2519..bc8c9e98a 100644 --- a/labelbox/schema/benchmark.py +++ b/labelbox/schema/benchmark.py @@ -4,9 +4,9 @@ class Benchmark(DbObject): """ Represents a benchmark label. - - The Benchmarks tool works by interspersing data to be labeled, for - which there is a benchmark label, to each person labeling. These + + The Benchmarks tool works by interspersing data to be labeled, for + which there is a benchmark label, to each person labeling. These labeled data are compared against their respective benchmark and an accuracy score between 0 and 100 percent is calculated. @@ -15,7 +15,7 @@ class Benchmark(DbObject): last_activity (datetime) average_agreement (float) completed_count (int) - + created_by (Relationship): `ToOne` relationship to User reference_label (Relationship): `ToOne` relationship to Label """ diff --git a/labelbox/schema/bulk_import_request.py b/labelbox/schema/bulk_import_request.py index a8d54e40b..783ae69c7 100644 --- a/labelbox/schema/bulk_import_request.py +++ b/labelbox/schema/bulk_import_request.py @@ -101,7 +101,7 @@ class BulkImportRequest(DbObject): error_file_url (str): NDJSON that contains error messages for failed annotations status_file_url (str): NDJSON that contains status for each annotation created_at (datetime): UTC timestamp for date BulkImportRequest was created - + project (Relationship): `ToOne` relationship to Project created_by (Relationship): `ToOne` relationship to User """ @@ -125,7 +125,7 @@ def refresh(self) -> None: def wait_until_done(self, sleep_time_seconds: int = 30) -> None: """Blocks import job until certain conditions are met. - + Blocks until the BulkImportRequest.state changes either to `BulkImportRequestState.FINISHED` or `BulkImportRequestState.FAILED`, periodically refreshing object's state. @@ -209,8 +209,8 @@ def create_from_url(cls, client, project_id: str, name: str, def create_from_objects(cls, client, project_id: str, name: str, predictions: Iterable[dict]) -> 'BulkImportRequest': """ - Creates a `BulkImportRequest` from an iterable of dictionaries. - + Creates a `BulkImportRequest` from an iterable of dictionaries. + Conforms to JSON predictions format, e.g.: ``{ "uuid": "9fd9a92e-2560-4e77-81d4-b2e955800092", diff --git a/labelbox/schema/data_row.py b/labelbox/schema/data_row.py index 78c068298..df46f1902 100644 --- a/labelbox/schema/data_row.py +++ b/labelbox/schema/data_row.py @@ -5,21 +5,21 @@ class DataRow(DbObject, Updateable, BulkDeletable): - """ Internal Labelbox representation of a single piece of data (e.g. image, video, text). + """ Internal Labelbox representation of a single piece of data (e.g. image, video, text). Attributes: external_id (str): User-generated file name or identifier - row_data (str): Paths to local files are uploaded to Labelbox's server. + row_data (str): Paths to local files are uploaded to Labelbox's server. Otherwise, it's treated as an external URL. updated_at (datetime) created_at (datetime) - + dataset (Relationship): `ToOne` relationship to Dataset created_by (Relationship): `ToOne` relationship to User organization (Relationship): `ToOne` relationship to Organization labels (Relationship): `ToMany` relationship to Label metadata (Relationship): `ToMany` relationship to AssetMetadata - predictions (Relationship): `ToMany` relationship to Prediction + predictions (Relationship): `ToMany` relationship to Prediction """ external_id = Field.String("external_id") row_data = Field.String("row_data") @@ -50,7 +50,7 @@ def __init__(self, *args, **kwargs): def create_metadata(self, meta_type, meta_value): """ Attaches asset metadata to a DataRow. - + >>> datarow.create_metadata("TEXT", "This is a text message") Args: diff --git a/labelbox/schema/dataset.py b/labelbox/schema/dataset.py index 7a7f0ecd4..994e840a9 100644 --- a/labelbox/schema/dataset.py +++ b/labelbox/schema/dataset.py @@ -8,7 +8,7 @@ class Dataset(DbObject, Updateable, Deletable): - """ A Dataset is a collection of DataRows. + """ A Dataset is a collection of DataRows. Attributes: name (str) @@ -39,7 +39,7 @@ def create_data_row(self, **kwargs): >>> dataset.create_data_row(row_data="http://my_site.com/photos/img_01.jpg") Args: - **kwargs: Key-value arguments containing new `DataRow` data. At a minimum, + **kwargs: Key-value arguments containing new `DataRow` data. At a minimum, must contain `row_data`. Raises: @@ -70,7 +70,7 @@ def create_data_rows(self, items): it is a `str`, then it is interpreted as a local file path. The file is uploaded to Labelbox and a DataRow referencing it is created. If an item is a `dict`, then it should map `DataRow` fields (or their - names) to values. At the minimum an `item` passed as a `dict` must + names) to values. At the minimum an `item` passed as a `dict` must contain a `DataRow.row_data` key and value. >>> dataset.create_data_rows([ diff --git a/labelbox/schema/label.py b/labelbox/schema/label.py index f25ae6c76..91d077f45 100644 --- a/labelbox/schema/label.py +++ b/labelbox/schema/label.py @@ -14,7 +14,7 @@ class Label(DbObject, Updateable, BulkDeletable): agreement (float) benchmark_agreement (float) is_benchmark_reference (bool) - + project (Relationship): `ToOne` relationship to Project data_row (Relationship): `ToOne` relationship to DataRow reviews (Relationship): `ToMany` relationship to Review diff --git a/labelbox/schema/labeling_frontend.py b/labelbox/schema/labeling_frontend.py index 13fe76635..a7fb7f943 100644 --- a/labelbox/schema/labeling_frontend.py +++ b/labelbox/schema/labeling_frontend.py @@ -8,7 +8,7 @@ class LabelingFrontend(DbObject): Represents an HTML / JavaScript UI that is used to generate labels. “Editor” is the default Labeling Frontend that comes in every organization. You can create new labeling frontends for an organization. - + Attributes: name (str) description (str) diff --git a/labelbox/schema/organization.py b/labelbox/schema/organization.py index c23d72133..64b46a78c 100644 --- a/labelbox/schema/organization.py +++ b/labelbox/schema/organization.py @@ -4,10 +4,10 @@ class Organization(DbObject): """ An Organization is a group of Users. - + It is associated with data created by Users within that Organization. Typically all Users within an Organization have access to data created by any User in the same Organization. - + Attributes: updated_at (datetime) created_at (datetime) diff --git a/labelbox/schema/prediction.py b/labelbox/schema/prediction.py index 309f5928f..29ea2d986 100644 --- a/labelbox/schema/prediction.py +++ b/labelbox/schema/prediction.py @@ -15,7 +15,7 @@ class PredictionModel(DbObject): version (int) created_by (Relationship): `ToOne` relationship to User - organization (Relationship): `ToOne` relationship to Organization + organization (Relationship): `ToOne` relationship to Organization """ updated_at = Field.DateTime("updated_at") created_at = Field.DateTime("created_at") @@ -40,7 +40,7 @@ class Prediction(DbObject): created_at (datetime) label (str) agreement (float) - + organization (Relationship): `ToOne` relationship to Organization prediction_model (Relationship): `ToOne` relationship to PredictionModel data_row (Relationship): `ToOne` relationship to DataRow diff --git a/labelbox/schema/project.py b/labelbox/schema/project.py index b23c18539..411ec2258 100644 --- a/labelbox/schema/project.py +++ b/labelbox/schema/project.py @@ -28,7 +28,7 @@ class Project(DbObject, Updateable, Deletable): """ A Project is a container that includes a labeling frontend, an ontology, datasets and labels. - Attributes: + Attributes: name (str) description (str) updated_at (datetime) @@ -150,7 +150,7 @@ def export_labels(self, timeout_seconds=60): Args: timeout_seconds (float): Max waiting time, in seconds. Returns: - URL of the data file with this Project's labels. If the server didn't + URL of the data file with this Project's labels. If the server didn't generate during the `timeout_seconds` period, None is returned. """ sleep_time = 2 @@ -250,8 +250,8 @@ def setup(self, labeling_frontend, labeling_frontend_options): self.update(setup_complete=timestamp) def set_labeling_parameter_overrides(self, data): - """ Adds labeling parameter overrides to this project. - + """ Adds labeling parameter overrides to this project. + >>> project.set_labeling_parameter_overrides([ >>> (data_row_1, 2, 3), (data_row_2, 1, 4)]) @@ -328,7 +328,7 @@ def extend_reservations(self, queue_type): def create_prediction_model(self, name, version): """ Creates a PredictionModel connected to a Legacy Editor Project. - + Args: name (str): The new PredictionModel's name. version (int): The new PredictionModel's version. @@ -346,7 +346,7 @@ def create_prediction_model(self, name, version): def create_prediction(self, label, data_row, prediction_model=None): """ Creates a Prediction within a Legacy Editor Project. Not supported in the new Editor. - + Args: label (str): The `label` field of the new Prediction. data_row (DataRow): The DataRow for which the Prediction is created. @@ -392,7 +392,7 @@ def enable_model_assisted_labeling(self, toggle: bool = True) -> bool: """ Turns model assisted labeling either on or off based on input Args: - toggle (bool): True or False boolean + toggle (bool): True or False boolean Returns: True if toggled on or False if toggled off """ @@ -401,7 +401,7 @@ def enable_model_assisted_labeling(self, toggle: bool = True) -> bool: query_str = """mutation toggle_model_assisted_labelingPyApi($%s: ID!, $%s: Boolean!) { project(where: {id: $%s }) { - showPredictionsToLabelers(show: $%s) { + showPredictionsToLabelers(show: $%s) { id, showingPredictionsToLabelers } } diff --git a/labelbox/schema/review.py b/labelbox/schema/review.py index 10ff904d2..a9ae6d9ae 100644 --- a/labelbox/schema/review.py +++ b/labelbox/schema/review.py @@ -8,7 +8,7 @@ class Review(DbObject, Deletable, Updateable): """ Reviewing labeled data is a collaborative quality assurance technique. A Review object indicates the quality of the assigned Label. The aggregated - review numbers can be obtained on a Project object. + review numbers can be obtained on a Project object. Attributes: created_at (datetime) @@ -22,7 +22,7 @@ class Review(DbObject, Deletable, Updateable): """ class NetScore(Enum): - """ Negative, Zero, or Positive. + """ Negative, Zero, or Positive. """ Negative = auto() Zero = auto() diff --git a/labelbox/schema/webhook.py b/labelbox/schema/webhook.py index a83b93fb1..65802600c 100644 --- a/labelbox/schema/webhook.py +++ b/labelbox/schema/webhook.py @@ -67,7 +67,7 @@ def create(client, topics, url, secret, project): def update(self, topics=None, url=None, status=None): """ Updates this Webhook. - + Args: topics (list of str): The new topics value, optional. url (str): The new URL value, optional. From d35940211c81cd38f3add36155f55639b959abcf Mon Sep 17 00:00:00 2001 From: Alex Cota Date: Wed, 20 Jan 2021 15:05:13 -0800 Subject: [PATCH 09/12] Add .readthedocs.yml (#96) * Add .readthedocs.yml * Remove author * Remove autodoc dependency --- .readthedocs.yml | 23 +++++++++++++++++++++++ docs/source/conf.py | 1 - 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 .readthedocs.yml diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 000000000..3631baa72 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,23 @@ +# .readthedocs.yml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Read the docs config file version (Required) +version: 2 + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/source/conf.py + +# Build all formats (epub, pdf, htmlzip) +formats: + - pdf + +# Optionally set the version of Python and requirements required to build your docs +python: + version: 3.7 + install: + - requirements: requirements.txt + - requirements: docs/requirements.txt + - method: setuptools + path: . \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index 131ae59bc..f0d27fe03 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -19,7 +19,6 @@ project = 'Labelbox Python API reference' copyright = '2021, Labelbox' -author = 'Alexandra Cota' release = '2.4' From 07a7d3f3125f01e30a6ef54937cd4371087af27d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florijan=20Stamenkovi=C4=87?= Date: Mon, 25 Jan 2021 18:07:18 +0100 Subject: [PATCH 10/12] Fix minor bugs and tidy tests up (#98) --- .github/workflows/python-package.yml | 4 ++-- CHANGELOG.md | 7 ++++++ docs/source/conf.py | 6 +---- labelbox/client.py | 32 ++++++++++++++++++++----- labelbox/orm/db_object.py | 8 ++++--- labelbox/schema/project.py | 2 +- tests/integration/test_client_errors.py | 6 +++-- tests/integration/test_data_rows.py | 5 +++- tests/integration/test_data_upload.py | 6 ++++- tests/integration/test_label.py | 1 + tests/integration/test_logger.py | 20 ---------------- tests/integration/test_sorting.py | 8 +++---- tests/integration/test_webhook.py | 4 ++++ 13 files changed, 64 insertions(+), 45 deletions(-) delete mode 100644 tests/integration/test_logger.py diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index a070e6ab0..49b92d33c 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -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: @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index d6e346f7c..148d2e103 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/docs/source/conf.py b/docs/source/conf.py index f0d27fe03..9cbbae670 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -14,7 +14,6 @@ import sys sys.path.insert(0, os.path.abspath('../..')) - # -- Project information ----------------------------------------------------- project = 'Labelbox Python API reference' @@ -28,9 +27,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. @@ -41,7 +38,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 diff --git a/labelbox/client.py b/labelbox/client.py index c8ca5b1d4..14384da84 100644 --- a/labelbox/client.py +++ b/labelbox/client.py @@ -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) @@ -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) @@ -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) diff --git a/labelbox/orm/db_object.py b/labelbox/orm/db_object.py index d6453f64f..45feedd9f 100644 --- a/labelbox/orm/db_object.py +++ b/labelbox/orm/db_object.py @@ -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) @@ -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) diff --git a/labelbox/schema/project.py b/labelbox/schema/project.py index 411ec2258..b8afa1ec9 100644 --- a/labelbox/schema/project.py +++ b/labelbox/schema/project.py @@ -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)) diff --git a/tests/integration/test_client_errors.py b/tests/integration/test_client_errors.py index d1dacd5fe..9fad57ffb 100644 --- a/tests/integration/test_client_errors.py +++ b/tests/integration/test_client_errors.py @@ -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 @@ -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} diff --git a/tests/integration/test_data_rows.py b/tests/integration/test_data_rows.py index 0b4563f0d..59be6309b 100644 --- a/tests/integration/test_data_rows.py +++ b/tests/integration/test_data_rows.py @@ -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()) @@ -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 diff --git a/tests/integration/test_data_upload.py b/tests/integration/test_data_upload.py index 6d2226522..60ce78272 100644 --- a/tests/integration/test_data_upload.py +++ b/tests/integration/test_data_upload.py @@ -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 diff --git a/tests/integration/test_label.py b/tests/integration/test_label.py index e2319fe4a..8399f1334 100644 --- a/tests/integration/test_label.py +++ b/tests/integration/test_label.py @@ -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 diff --git a/tests/integration/test_logger.py b/tests/integration/test_logger.py deleted file mode 100644 index c8b9dcf8b..000000000 --- a/tests/integration/test_logger.py +++ /dev/null @@ -1,20 +0,0 @@ -from labelbox import Client -import pytest -import logging - - -def test_client_log(caplog, project): - """ - This file tests that the logger will properly output to the console after updating logging level - - The default level is set to WARNING - - There is an expected output after setting logging level to DEBUG - """ - - project.export_labels() - assert '' == caplog.text - - with caplog.at_level(logging.DEBUG): - project.export_labels() - assert "label export, waiting for server..." in caplog.text diff --git a/tests/integration/test_sorting.py b/tests/integration/test_sorting.py index a10b32a43..07dba6128 100644 --- a/tests/integration/test_sorting.py +++ b/tests/integration/test_sorting.py @@ -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") @@ -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) diff --git a/tests/integration/test_webhook.py b/tests/integration/test_webhook.py index 194760b3c..66fb86312 100644 --- a/tests/integration/test_webhook.py +++ b/tests/integration/test_webhook.py @@ -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) From 03f29520cce48aa067dfcd7d1dbdacde0e371891 Mon Sep 17 00:00:00 2001 From: Matt Sokoloff Date: Fri, 5 Feb 2021 11:36:50 -0500 Subject: [PATCH 11/12] added sdk version to headers (#100) * added sdk version to headers * Update client.py * Update _version.py * format code * format * google format * parse __init__.py for version authored-by: Matt Sokoloff --- labelbox/__init__.py | 3 ++- labelbox/client.py | 4 +++- setup.py | 8 +++++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/labelbox/__init__.py b/labelbox/__init__.py index 680e44068..0b876d51c 100644 --- a/labelbox/__init__.py +++ b/labelbox/__init__.py @@ -1,4 +1,5 @@ name = "labelbox" +__version__ = "2.4.10" from labelbox.client import Client from labelbox.schema.bulk_import_request import BulkImportRequest @@ -14,4 +15,4 @@ from labelbox.schema.asset_metadata import AssetMetadata from labelbox.schema.webhook import Webhook from labelbox.schema.prediction import Prediction, PredictionModel -from labelbox.schema.ontology import Ontology +from labelbox.schema.ontology import Ontology \ No newline at end of file diff --git a/labelbox/client.py b/labelbox/client.py index 14384da84..cd4bc97a2 100644 --- a/labelbox/client.py +++ b/labelbox/client.py @@ -19,6 +19,7 @@ from labelbox.schema.user import User from labelbox.schema.organization import Organization from labelbox.schema.labeling_frontend import LabelingFrontend +from labelbox import __version__ as SDK_VERSION logger = logging.getLogger(__name__) @@ -66,7 +67,8 @@ def __init__(self, self.headers = { 'Accept': 'application/json', 'Content-Type': 'application/json', - 'Authorization': 'Bearer %s' % api_key + 'Authorization': 'Bearer %s' % api_key, + 'X-User-Agent': f'python-sdk {SDK_VERSION}' } @retry.Retry(predicate=retry.if_exception_type( diff --git a/setup.py b/setup.py index 4532e2563..0cbea9283 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,17 @@ import setuptools +with open('labelbox/__init__.py') as fid: + for line in fid: + if line.startswith('__version__'): + SDK_VERSION = line.strip().split()[-1][1:-1] + break + with open("README.md", "r") as fh: long_description = fh.read() setuptools.setup( name="labelbox", - version="2.4.9", + version=SDK_VERSION, author="Labelbox", author_email="engineering@labelbox.com", description="Labelbox Python API", From 77bf52638a44ff577210b3695db1ca49e1f9c38a Mon Sep 17 00:00:00 2001 From: Matt Sokoloff Date: Fri, 5 Feb 2021 14:13:19 -0500 Subject: [PATCH 12/12] User agent (#101) * added sdk version to headers * Update client.py * Update _version.py * format code * format * google format * parse __init__.py for version * Updated changelog authored-by: Matt Sokoloff --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 148d2e103..a1350462d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ * 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.10 (2021-01-05) +### Added +* SDK version added to request headers + ## Version 2.4.9 (2020-11-09) ### Fix * 2.4.8 was broken for > Python 3.6