diff --git a/.gitignore b/.gitignore index a081fb2..9bc59cf 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ .cache /.vscode/ .DS_Store +/build/ \ No newline at end of file diff --git a/README.rst b/README.rst index a0ebe25..e2905fe 100644 --- a/README.rst +++ b/README.rst @@ -216,7 +216,7 @@ __ https://docs.scale.com/reference#project-retrieval .. code-block:: python - client.get_projet(project_name = 'test_project') + client.get_project(project_name = 'test_project') List Projects ^^^^^^^^^^^^^ diff --git a/build/lib/scaleapi/__init__.py b/build/lib/scaleapi/__init__.py deleted file mode 100644 index ba18f02..0000000 --- a/build/lib/scaleapi/__init__.py +++ /dev/null @@ -1,188 +0,0 @@ -import requests - -from .tasks import Task -from .batches import Batch - -TASK_TYPES = [ - 'annotation', - 'audiotranscription', - 'categorization', - 'comparison', - 'cuboidannotation', - 'datacollection', - 'imageannotation', - 'lineannotation', - 'namedentityrecognition', - 'pointannotation', - 'polygonannotation', - 'segmentannotation', - 'transcription', - 'videoannotation', - 'videoboxannotation', - 'videocuboidannotation' -] -SCALE_ENDPOINT = 'https://api.scale.com/v1/' -DEFAULT_LIMIT = 100 -DEFAULT_OFFSET = 0 - - -class ScaleException(Exception): - def __init__(self, message, errcode): - super(ScaleException, self).__init__( - ' {}'.format(errcode, message)) - self.code = errcode - - -class ScaleInvalidRequest(ScaleException, ValueError): - pass - - -class Paginator(list): - def __init__(self, docs, total, limit, offset, has_more, next_token=None): - super(Paginator, self).__init__(docs) - self.docs = docs - self.total = total - self.limit = limit - self.offset = offset - self.has_more = has_more - self.next_token = next_token - - -class Tasklist(Paginator): - pass - - -class Batchlist(Paginator): - pass - - -class ScaleClient(object): - def __init__(self, api_key): - self.api_key = api_key - - def _getrequest(self, endpoint, params=None): - """Makes a get request to an endpoint. - - If an error occurs, assumes that endpoint returns JSON as: - { 'status_code': XXX, - 'error': 'I failed' } - """ - params = params or {} - r = requests.get(SCALE_ENDPOINT + endpoint, - headers={"Content-Type": "application/json"}, - auth=(self.api_key, ''), params=params) - - if r.status_code == 200: - return r.json() - else: - try: - error = r.json()['error'] - except ValueError: - error = r.text - if r.status_code == 400: - raise ScaleInvalidRequest(error, r.status_code) - else: - raise ScaleException(error, r.status_code) - - def _postrequest(self, endpoint, payload=None): - """Makes a post request to an endpoint. - - If an error occurs, assumes that endpoint returns JSON as: - { 'status_code': XXX, - 'error': 'I failed' } - """ - payload = payload or {} - r = requests.post(SCALE_ENDPOINT + endpoint, json=payload, - headers={"Content-Type": "application/json"}, - auth=(self.api_key, '')) - - if r.status_code == 200: - return r.json() - else: - try: - error = r.json()['error'] - except ValueError: - error = r.text - if r.status_code == 400: - raise ScaleInvalidRequest(error, r.status_code) - else: - raise ScaleException(error, r.status_code) - - def fetch_task(self, task_id): - """Fetches a task. - - Returns the associated task. - """ - return Task(self._getrequest('task/%s' % task_id), self) - - def cancel_task(self, task_id): - """Cancels a task. - - Returns the associated task. - Raises a ScaleException if it has already been canceled. - """ - return Task(self._postrequest('task/%s/cancel' % task_id), self) - - def tasks(self, **kwargs): - """Returns a list of your tasks. - Returns up to 100 at a time, to get more, use the next_token param passed back. - - Note that offset is deprecated. - - start/end_time are ISO8601 dates, the time range of tasks to fetch. - status can be 'completed', 'pending', or 'canceled'. - type is the task type. - limit is the max number of results to display per page, - next_token can be use to fetch the next page of tasks. - customer_review_status can be 'pending', 'fixed', 'accepted' or 'rejected'. - offset (deprecated) is the number of results to skip (for showing more pages). - """ - allowed_kwargs = {'start_time', 'end_time', 'status', 'type', 'project', - 'batch', 'limit', 'offset', 'completed_before', 'completed_after', - 'next_token', 'customer_review_status', 'updated_before', 'updated_after'} - for key in kwargs: - if key not in allowed_kwargs: - raise ScaleInvalidRequest('Illegal parameter %s for ScaleClient.tasks()' - % key, None) - response = self._getrequest('tasks', params=kwargs) - docs = [Task(json, self) for json in response['docs']] - return Tasklist(docs, response['total'], response['limit'], - response['offset'], response['has_more'], response.get('next_token')) - - def create_task(self, task_type, **kwargs): - endpoint = 'task/' + task_type - taskdata = self._postrequest(endpoint, payload=kwargs) - return Task(taskdata, self) - - def create_batch(self, project, batch_name, callback): - payload = dict(project=project, name=batch_name, callback=callback) - batchdata = self._postrequest('batches', payload) - return Batch(batchdata, self) - - def get_batch(self, batch_name: str): - batchdata = self._getrequest('batches/%s' % batch_name) - return Batch(batchdata, self) - - def list_batches(self, **kwargs): - allowed_kwargs = {'start_time', 'end_time', 'status', 'project', - 'batch', 'limit', 'offset', } - for key in kwargs: - if key not in allowed_kwargs: - raise ScaleInvalidRequest('Illegal parameter %s for ScaleClient.tasks()' - % key, None) - response = self._getrequest('tasks', params=kwargs) - docs = [Batch(doc, self) for doc in response['docs']] - return Batchlist( - docs, response['total'], response['limit'], response['offset'], - response['has_more'], response.get('next_token'), - ) - - -def _AddTaskTypeCreator(task_type): - def create_task_wrapper(self, **kwargs): - return self.create_task(task_type, **kwargs) - setattr(ScaleClient, 'create_' + task_type + '_task', create_task_wrapper) - - -for taskType in TASK_TYPES: - _AddTaskTypeCreator(taskType) diff --git a/build/lib/scaleapi/batches.py b/build/lib/scaleapi/batches.py deleted file mode 100644 index 55ec6c8..0000000 --- a/build/lib/scaleapi/batches.py +++ /dev/null @@ -1,26 +0,0 @@ -class Batch(object): - def __init__(self, param_dict, client): - self.param_dict = param_dict - self.name = param_dict['name'] - self.pending = None - self.completed = None - self.error = None - self.canceled = None - self.client = client - - def __hash__(self): - return hash(self.name) - - def __str__(self): - return 'Batch(name=%s)' % self.name - - def __repr__(self): - return 'Batch(%s)' % self.param_dict - - def finalize(self): - return self.client._postrequest("batches/%s/finalize" % self.name) - - def get_status(self): - res = self.client._getrequest("batches/%s/status" % self.name) - for stat in ["pending", "completed", "error", "canceled"]: - setattr(self, stat, res.get(stat, 0)) diff --git a/build/lib/scaleapi/projects.py b/build/lib/scaleapi/projects.py deleted file mode 100644 index e619a07..0000000 --- a/build/lib/scaleapi/projects.py +++ /dev/null @@ -1,14 +0,0 @@ -class Project(object): - def __init__(self, param_dict, client): - self.param_dict = param_dict - self.name = param_dict['name'] - self.client = client - - def __hash__(self): - return hash(self.name) - - def __str__(self): - return 'Project(name=%s)' % self.name - - def __repr__(self): - return 'Project(%s)' % self.param_dict diff --git a/build/lib/scaleapi/tasks.py b/build/lib/scaleapi/tasks.py deleted file mode 100644 index a94579a..0000000 --- a/build/lib/scaleapi/tasks.py +++ /dev/null @@ -1,30 +0,0 @@ -class Task(object): - """Task class, containing task information.""" - - def __init__(self, param_dict, client): - self.client = client - self.param_dict = param_dict - self.id = param_dict['task_id'] - - def __getattr__(self, name): - if name in self.param_dict: - return self.param_dict[name] - if name in self.params: - return self.params[name] - raise AttributeError("'%s' object has no attribute %s" - % (type(self).__name__, name)) - - def __hash__(self): - return hash(self.id) - - def __str__(self): - return 'Task(id=%s)' % self.id - - def __repr__(self): - return 'Task(%s)' % self.param_dict - - def refresh(self): - self.param_dict = self.client._getrequest('task/%s' % self.id) - - def cancel(self): - self.client.cancel_task(self.id) diff --git a/pypi_update_guide.md b/pypi_update_guide.md index 88d63eb..000329f 100644 --- a/pypi_update_guide.md +++ b/pypi_update_guide.md @@ -10,7 +10,7 @@ _Creating and deploying a new package version is easy_ **Step 0: Critical - Bump Project Version** -In `setup.py`, you need to specify a new project version. +In `_version.py`, you need to specify a new project version. We use [semantic versioning](https://packaging.python.org/guides/distributing-packages-using-setuptools/#semantic-versioning-preferred). If you are adding a meaningful feature, bump the minor version. If you are fixing a bug, bump the incremental version. diff --git a/scaleapi/__init__.py b/scaleapi/__init__.py index 3ea1dcd..4048d0b 100644 --- a/scaleapi/__init__.py +++ b/scaleapi/__init__.py @@ -1,8 +1,10 @@ import requests +import platform from .tasks import Task from .batches import Batch from .projects import Project +from ._version import __version__ TASK_TYPES = [ 'annotation', @@ -63,6 +65,10 @@ class Batchlist(Paginator): class ScaleClient(object): def __init__(self, api_key): self.api_key = api_key + self._headers = { + "Content-Type": "application/json", + "User-Agent": _generate_useragent() + } def _getrequest(self, endpoint, params=None): """Makes a get request to an endpoint. @@ -73,7 +79,7 @@ def _getrequest(self, endpoint, params=None): """ params = params or {} r = requests.get(SCALE_ENDPOINT + endpoint, - headers={"Content-Type": "application/json"}, + headers=self._headers, auth=(self.api_key, ''), params=params) if r.status_code == 200: @@ -97,7 +103,7 @@ def _postrequest(self, endpoint, payload=None): """ payload = payload or {} r = requests.post(SCALE_ENDPOINT + endpoint, json=payload, - headers={"Content-Type": "application/json"}, + headers=self._headers, auth=(self.api_key, '')) if r.status_code == 200: @@ -195,7 +201,7 @@ def create_project(self, project_name, type, params): projectdata = self._postrequest('projects', payload) return Project(projectdata, self) - def get_projet(self, project_name): + def get_project(self, project_name): projectdata = self._getrequest('projects/%s' % project_name) return Project(projectdata, self) @@ -209,9 +215,18 @@ def update_project(self, project_name, **kwargs): if key not in allowed_kwargs: raise ScaleInvalidRequest('Illegal parameter %s for ScaleClient.update_project()' % key, None) - projectdata = self._postrequest('projects/%s/setParams' % project_name) + projectdata = self._postrequest('projects/%s/setParams' % project_name, payload=kwargs) return projectdata +def _generate_useragent(): + try: + python_version = platform.python_version() + os_platform = platform.platform() + + user_agent = '%s/%s Python/%s OS/%s' % (__name__, __version__, python_version, os_platform) + return user_agent + except: + return "scaleapi-python-client" def _AddTaskTypeCreator(task_type): def create_task_wrapper(self, **kwargs): diff --git a/scaleapi/_version.py b/scaleapi/_version.py new file mode 100644 index 0000000..976498a --- /dev/null +++ b/scaleapi/_version.py @@ -0,0 +1 @@ +__version__ = "1.0.3" diff --git a/setup.py b/setup.py index 6df8f0d..b264c51 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,6 @@ import sys import warnings +import os.path try: from setuptools import setup @@ -25,10 +26,22 @@ install_requires.append('idna') install_requires.append('requests[security]') +def read(rel_path): + here = os.path.abspath(os.path.dirname(__file__)) + with open(os.path.join(here, rel_path), 'r') as fp: + return fp.read() + +def get_version(rel_path): + for line in read(rel_path).splitlines(): + if line.startswith('__version__'): + delim = '"' if '"' in line else "'" + return line.split(delim)[1] + raise RuntimeError("Unable to find a valid __version__ string in %s." % rel_path) + setup( name='scaleapi', packages=['scaleapi'], - version='1.0.2', + version=get_version("scaleapi/_version.py"), description='The official Python client library for Scale AI, the Data Platform for AI', author='Scale AI', author_email='support@scale.com',