diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..a81c8ee121952cf06bfaf9ff9988edd8cded763c --- /dev/null +++ b/.gitignore @@ -0,0 +1,138 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ diff --git a/README.md b/README.md index 7b441aa205acbb1c1a07cc7a8189f24cb025abb1..ceed0c973f6fe22f1a313d53a4d8f090be70722a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ # esap-userprofile-python-client +A Python client for the ESCAPE ESAP User Profile REST API. diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..adaa6db6e1657e72679abe29edd50933e6dd5416 --- /dev/null +++ b/__init__.py @@ -0,0 +1,2 @@ +from .zooniverse import zooniverse +from .shopping_client import shopping_client diff --git a/setup.py b/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..15fac8d3538c077c158c3e182ad4ae375cc3ff21 --- /dev/null +++ b/setup.py @@ -0,0 +1,23 @@ +import setuptools + +with open("README.md", "r") as fh: + long_description = fh.read() + +setuptools.setup( + name="esap-userprofile-python-client", + version="0.0.1", + author="Hugh Dickinson", + author_email="hugh.dickinson@open.ac.uk", + description="A small example package", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://git.astron.nl/astron-sdc/esap-userprofile-python-client", + packages=setuptools.find_packages(), + install_requires=["pandas", "requests", "panoptes-client"], + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + ], + python_requires=">=3.6", +) diff --git a/shopping_client.py b/shopping_client.py new file mode 100644 index 0000000000000000000000000000000000000000..33e43a75a596f17b19057a19f05e2bd57f345356 --- /dev/null +++ b/shopping_client.py @@ -0,0 +1,25 @@ +import pandas as pd +import requests +import json +import os +import urllib.parse + + +class shopping_client: + + endpoint = "esap-api/accounts/user-profiles/" + + def __init__(self, username, host="http://localhost:5555/"): + self.username = username + self.host = host + self.basket = None + + def get_basket(self, reload=False): + if self.basket is None or reload: + url = urllib.parse.urljoin(self.host, shopping_client.endpoint) + print(url) + response = requests.get(url, dict(user_name=self.username)) + print(response.content) + if response.ok: + self.basket = json.loads(response.content)["results"][0]["shopping_cart"] + return self.basket diff --git a/zooniverse/__init__.py b/zooniverse/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1d4ed23a78c81d6417dacd0f2c6919c8c6b05184 --- /dev/null +++ b/zooniverse/__init__.py @@ -0,0 +1 @@ +from .zooniverse import zooniverse diff --git a/zooniverse/zooniverse.py b/zooniverse/zooniverse.py new file mode 100644 index 0000000000000000000000000000000000000000..3f3ddb464418ed99036fe74bc61b55e45c32ce9f --- /dev/null +++ b/zooniverse/zooniverse.py @@ -0,0 +1,105 @@ +import requests +import json +import io +import getpass +import pandas as pd +from panoptes_client import Panoptes, Project, Workflow +from panoptes_client.panoptes import PanoptesAPIException + + +class zooniverse: + + entity_types = {"workflow": Workflow, "project": Project} + category_converters = { + "subjects": dict(metadata=json.loads, locations=json.loads), + "classifications": dict(metadata=json.loads, annotations=json.loads), + } + + def __init__(self, username, password=None): + self.username = username + self.password = password + if self.password is None: + self.password = getpass.getpass() + + self.panoptes = Panoptes.connect(username=self.username, password=self.password) + + def is_available(self, item, verbose=False): + try: + description = self._get_entity(item).describe_export( + self._get_item_entry(item, "category") + ) + if verbose: + print(description) + return True + except PanoptesAPIException as e: + return False + + def generate(self, item, wait=False): + print("Generating requested export...") + if wait: + print("\t\tWaiting for generation to complete...") + else: + print("\t\tNot waiting for generation to complete...") + response = self._get_entity(item).get_export( + self._get_item_entry(item, "category"), generate=True, wait=wait + ) + if response.ok: + return ( + pd.read_csv( + io.BytesIO(response.content), + converters=zooniverse.category_converters[ + self._get_item_entry(item, "category") + ], + ) + if not wait + else response + ) + else: + return None + + def retrieve(self, item, generate=False, wait=False): + if self.is_available(item) and not generate: + response = self._get_entity(item).get_export( + self._get_item_entry(item, "category"), generate=False, wait=wait + ) + else: + if not generate: + print( + "Requested resource is not available and you have specified generate==False" + ) + return None + else: + print("Generating requested export...") + if wait: + print("\t\tWaiting for generation to complete...") + else: + print("\t\tNot waiting for generation to complete...") + response = self._get_entity(item).get_export( + self._get_item_entry(item, "category"), generate=True, wait=wait + ) + if response.ok: + return ( + pd.read_csv( + io.BytesIO(response.content), + converters=zooniverse.category_converters[ + self._get_item_entry(item, "category") + ], + ) + if not wait + else response + ) + else: + return None + + def _get_entity(self, item): + entity = zooniverse.entity_types[self._get_item_entry(item, "catalog")].find( + int(self._get_item_entry(item, self._catalogue_to_id_string(item))) + ) + return entity + + def _get_item_entry(self, item, entry): + item_data = json.loads(item["item_data"].replace("'", '"')) + return item_data.get(entry, None) + + def _catalogue_to_id_string(self, item): + return self._get_item_entry(item, "catalog") + "_id"