import sys
if sys.version_info[0] >= 3:
from urllib.parse import urlencode, parse_qs
else:
from urllib import urlencode
from urlparse import parse_qs
import json
from requests_oauthlib import OAuth1
import requests
[docs]class Client(object):
"""Api client for the Shapeways API http://developers.shapeways.com
The API uses OAuth v1 to authenticate clients, so the following steps
must be used.
1. Create a client
2. Connect to API and get request token and authentication url
3. Send user to authentication url
4. Verify callback from authentication url
Example:
.. code:: python
client = Client("key", "secret")
url = client.connect()
# redirect user to `url`
# capture response url from authentication callback
client.verify_url(response_url)
# make api requests
info = client.get_api_info()
"""
__slots__ = [
"base_url", "api_version", "consumer_key", "consumer_secret",
"oauth_token", "oauth_secret", "oauth", "callback_url",
]
def __init__(
self, consumer_key, consumer_secret, callback_url=None,
oauth_token=None, oauth_secret=None
):
"""Constructor for a new :class:`shapeways.client.Client`
:param consumer_key: The API key for your app
:type consumer_key: str
:param consumer_secret: The API secret key for your app
:type consumer_secret: str
:param callback_url: The url that should be redirected to after
successful authentication with Shapeways OAuth
:type callback_url: str
:param oauth_token: The OAuth token obtained from calls
to connect/verify
:type oauth_token: str
:param oauth_secret: The OAuth secret obtained from calls
to connect/verify
:type oauth_secret: str
"""
self.consumer_key = consumer_key
self.consumer_secret = consumer_secret
self.callback_url = callback_url
self.base_url = "https://api.shapeways.com"
self.api_version = "v1"
self.oauth_token = oauth_token
self.oauth_secret = oauth_secret
self.oauth = OAuth1(
self.consumer_key,
client_secret=consumer_secret,
callback_uri=self.callback_url,
resource_owner_key=self.oauth_token,
resource_owner_secret=self.oauth_secret,
)
[docs] def url(self, path):
"""Generate the full url for an API path
.. code:: python
client = Client("key", "secret")
url = client.url("/api/")
# "https://api.shapeways.com/api/v1"
:param path: The API path to get the url for
:type path: str
:returns: the full url to ``path``
:rtype: str
"""
if not path.startswith("/"):
path = "/%s" % path
if not path.endswith("/"):
path += "/"
return "%s%s%s" % (self.base_url, path, self.api_version)
[docs] def connect(self):
"""Get an OAuth request token and authentication url
:returns: the authentication url that the user must visit or None
on error
:rtype: str or None
"""
response = requests.post(
url=self.url("/oauth1/request_token/"), auth=self.oauth
)
data = parse_qs(response.text)
self.oauth_secret = data.get("oauth_token_secret", [None])[0]
return data.get("authentication_url", [None])[0]
[docs] def verify_url(self, url):
"""Parse parameters and properly call :meth:`shapeways.client.Client.verify`
If you already have the ``oauth_token`` and ``oauth_verifier`` parameters
parsed, use :meth:`shapeways.client.Client.verify` directly instead.
:param url: The response url or query string from the
authentication callback
:type url: str
"""
url, _, qs = url.rpartition("?")
data = parse_qs(qs)
self.verify(
data.get("oauth_token", [None])[0],
data.get("oauth_verifier", [None])[0]
)
[docs] def verify(self, oauth_token, oauth_verifier):
"""Get an access token and setup OAuth credentials for further use
If you have the full url or query string from the authentication
callback then you can use :meth:`shapeways.client.Client.verify_url`
which will parse the correct parameters from the query string and
call :meth:`shapeways.client.Client.verify`
:param oauth_token: the ``oauth_token`` parameter from the
authentication callback
:type oauth_token: str
:param oauth_verifier: the ``oauth_verifier`` parameter from the
authentication callback
:type oauth_verifier: str
"""
access_oauth = OAuth1(
self.consumer_key,
client_secret=self.consumer_secret,
resource_owner_key=oauth_token,
resource_owner_secret=self.oauth_secret,
verifier=oauth_verifier
)
response = requests.post(
url=self.url("/oauth1/access_token/"),
auth=access_oauth
)
data = parse_qs(response.text)
self.oauth_token = data.get("oauth_token", [None])[0]
self.oauth_secret = data.get("oauth_token_secret", [None])[0]
self.oauth = OAuth1(
self.consumer_key,
client_secret=self.consumer_secret,
resource_owner_key=self.oauth_token,
resource_owner_secret=self.oauth_secret,
)
def _get(self, path, params=None):
"""Fetch the results from an API GET call to ``path``
:param path: the api path to fetch e.g. ``/api/``
:type path: str
:param params: dict of query string parameters to use
:type params: dict or None
:returns: the results from the api call
:rtype: dict
"""
response = requests.get(
url =self.url(path), auth=self.oauth, params=params
)
return response.json()
def _delete(self, url, params=None):
"""Fetch the results from an API DELETE call to ``path``
:param path: the api path to fetch e.g. ``/api/``
:type path: str
:param params: dict of query string parameters to use
:type params: dict or None
:returns: the results from the api call
:rtype: dict
"""
response = requests.delete(
url=self.url(url), auth=self.oauth, params=params
)
return response.json()
def _post(self, url, body=None, params=None):
"""Fetch the results from an API POST call to ``path``
:param path: the api path to fetch e.g. ``/api/``
:type path: str
:param body: the POST body to use
:type body: str or None
:param params: dict of query string parameters to use
:type params: dict or None
:returns: the results from the api call
:rtype: dict
"""
response = requests.post(
url=self.url(url), auth=self.oauth, params=params, data=body
)
return response.json()
def _put(self, url, body=None, params=None):
"""Fetch the results from an API PUT call to ``path``
:param path: the api path to fetch e.g. ``/api/``
:type path: str
:param body: the PUT body to use
:type body: str or None
:param params: dict of query string parameters to use
:type params: dict or None
:returns: the results from the api call
:rtype: dict
"""
response = requests.put(
url=self.url(url), auth=self.oauth, params=params, data=body
)
return response.json()
[docs] def get_api_info(self):
"""Make an API call `GET /api/v1
<https://developers.shapeways.com/docs?li=dh_docs#GET_-api-v1-1>`_
:returns: api info
:rtype: dict
"""
return self._get("/api/")
[docs] def get_cart(self):
"""Make an API call `GET /orders/cart/v1
<https://developers.shapeways.com/docs?li=dh_docs#GET_-orders-cart-v1>`_
:returns: items currently in the cart
:rtype: dict
"""
return self._get("/orders/cart/")
[docs] def get_material(self, material_id):
"""Make an API call `GET /materials/{material_id}/v1
<https://developers.shapeways.com/docs?li=dh_docs#GET_-materials-materialId-v1>`_
:param material_id: the id of the material to fetch
:type material_id: int
:returns: specific materials info
:rtype: dict
"""
return self._get("/materials/%s/" % material_id)
[docs] def get_materials(self):
"""Make an API call `GET /materials/v1
<https://developers.shapeways.com/docs?li=dh_docs#GET_-materials-v1>`_
:returns: information about all materials
:rtype: dict
"""
return self._get("/materials/")
[docs] def get_models(self, page=None):
"""Make an API call `GET /models/v1
<https://developers.shapeways.com/docs?li=dh_docs#GET_-models-v1>`_
:returns: information about all user's models
:rtype: dict
"""
params = None
if page is not None:
params = {
"page": int(page)
}
return self._get("/models/", params=params)
[docs] def get_model(self, model_id):
"""Make an API call `GET /models/{model_id}/v1
<https://developers.shapeways.com/docs?li=dh_docs#GET_-models-modelId-v1>`_
:param model_id: the id of the model to fetch
:type mode_id: int
:returns: data for a specific model
:rtype: dict
"""
return self._get("/models/%s/" % model_id)
[docs] def get_model_info(self, model_id):
"""Make an API call `GET /models/{model_id}/info/v1
<https://developers.shapeways.com/docs?li=dh_docs#GET_-models-modelId-info-v1>`_
:param model_id: the id of the model to fetch
:type mode_id: int
:returns: information for a specific model
:rtype: dict
"""
return self._get("/models/%s/info/" % model_id)
[docs] def delete_model(self, model_id):
"""Make an API call `DELETE /models/{model_id}/v1
<https://developers.shapeways.com/docs?li=dh_docs#DELETE_-models-modelId-v1>`_
:param model_id: the id of the model to delete
:type mode_id: int
:returns: information whether or not it was successful
:rtype: dict
"""
return self._delete("/models/%s/" % model_id)
[docs] def get_printers(self):
"""Make an API call `GET /printers/v1
<https://developers.shapeways.com/docs?li=dh_docs#GET_-printers-v1>`_
:returns: information about all printers
:rtype: dict
"""
return self._get("/printers/")
[docs] def get_printer(self, printer_id):
"""Make an API call `GET /printers/{printer_id}/v1
<https://developers.shapeways.com/docs?li=dh_docs#GET_-printers-printerId-v1>`_
:param printer_id: the printer to fetch information for
:type printer_id: int
:returns: information about a specific printer
:rtype: dict
"""
return self._get("/printers/%s/" % printer_id)
[docs] def get_categories(self):
"""Make an API call `GET /categories/v1
<https://developers.shapeways.com/docs?li=dh_docs#GET_-categories-v1>`_
:returns: information about all categories
:rtype: dict
"""
return self._get("/categories/")
[docs] def get_category(self, category_id):
"""Make an API call `GET /categories/{category_id}/v1
<https://developers.shapeways.com/docs?li=dh_docs#GET_-categories-categoryId-v1>`_
:param category_id: the category to fetch information for
:type category_id: int
:returns: information about a specific category
:rtype: dict
"""
return self._get("/categories/%s/" % category_id)
[docs] def get_price(self, params):
"""Make an API call `POST /price/v1
<https://developers.shapeways.com/docs?li=dh_docs#POST_-price-v1>`_
Required Parameters:
1. ``volume`` - float
2. ``area`` - float
3. ``xBoundMin`` - float
4. ``xBoundMax`` - float
5. ``yBoundMin`` - float
6. ``yBoundMax`` - float
7. ``zBoundMin`` - float
8. ``zBoundMax`` - float
Optional Parameters:
1. ``materials`` - list
:param params: dict of necessary parameters to make the api call
:type params: dict
:returns: pricing information for the ``params`` given
:rtype: dict
:raises: :class:`Exception` when any of the required parameters
are missing
"""
required = [
"volume", "area", "xBoundMin", "xBoundMax",
"yBoundMin", "yBoundMax", "zBoundMin", "zBoundMax"
]
missing = []
for prop in required:
if prop not in params:
missing.append(prop)
if missing:
raise Exception("get_price missing required parameters: %r" % missing)
return self._post("/price/", body=json.dumps(params))
[docs] def add_to_cart(self, params):
"""Make an API call `POST /orders/cart/v1
<https://developers.shapeways.com/docs?li=dh_docs#POST_-orders-cart-v1>`_
Required Parameters:
1. ``modelId`` - int
Optional Parameters:
1. ``materialId`` - int
2. ``quantity`` - int
:param params: dict of necessary parameters to make the api call
:type params: dict
:returns: whether or not the call was successful
:rtype: dict
:raises: :class:`Exception` when the required parameter is missing
"""
if "modelId" not in params:
raise Exception("add_to_cart missing required parameter ['modelId']")
return self._post("/orders/cart/", body=json.dumps(params))
[docs] def add_model_file(self, model_id, params):
"""Make an API call `POST /models/{model_id}/files/v1
<https://developers.shapeways.com/docs?li=dh_docs#POST_-models-modelId-files-v1>`_
Required Parameters:
1. ``file`` - str (the file data)
2. ``fileName`` - str
3. ``hasRightsToModel`` - bool
4. ``acceptTermsAndConditions`` - bool
Optional Parameters:
1. ``uploadScale`` - float
:param model_id: the id of the model to upload the file for
:type model_id: int
:param params: dict of necessary parameters to make the api call
:type params: dict
:returns: file upload information
:rtype: dict
:raises: :class:`Exception` when any of the required parameters
are missing
"""
required = [
"file", "fileName", "hasRightsToModel", "acceptTermsAndConditions"
]
missing = []
for prop in required:
if prop not in params:
missing.append(prop)
if missing:
raise Exception("add_model_file missing required parameters %r" % missing)
return self._post(
"/models/%s/files/" % model_id, body=json.dumps(params)
)
[docs] def add_model_photo(self, model_id, params):
"""Make an API call `POST /models/{model_id}/photos/v1
<https://developers.shapeways.com/docs?li=dh_docs#POST_-models-modelId-photos-v1>`_
Required Parameters:
1. ``file`` - str (the file data)
Optional Parameters:
1. ``title`` - str
2. ``description`` - str
3. ``materialId`` - int
4. ``isDefault`` - bool
:param model_id: the id of the model to upload the photo for
:type model_id: int
:param params: dict of necessary parameters to make the api call
:type params: dict
:returns: photo upload information
:rtype: dict
:raises: :class:`Exception` when the required parameter is missing
"""
if "file" not in params:
raise Exception("add_model_photo missing required parameter ['file']")
return self._post(
"/models/%s/photos/" % model_id, body=json.dumps(params)
)
[docs] def get_model_file(self, model_id, file_version, include_file=False):
"""Make an API call `GET /models/{model_id}/files/{file_version}/v1
<https://developers.shapeways.com/docs?li=dh_docs#GET_-models-modelId-files-fileVersion-v1>`_
:param model_id: the id of the model to get the file from
:type model_id: int
:param file_version: the file version of the file to fetch
:type file_version: int
:param include_file: whether or not to include the raw file data
in the response
:type include_file: bool
:returns: the file information
:rtype: dict
"""
params = {
"file": int(include_file),
}
return self._get(
"/models/%s/files/%s/" % (model_id, file_version),
params=params
)
[docs] def update_model_info(self, model_id, params):
"""Make an API call `PUT /models/{model_id}/info/v1
<https://developers.shapeways.com/docs?li=dh_docs#PUT_-models-modelId-info-v1>`_
Optional Parameters:
1. ``uploadScale`` - float
2. ``title`` - str
3. ``description`` - str
4. ``isPublic`` - bool
5. ``isForSale`` - bool
6. ``isDownloadable`` - bool
7. ``tags`` - list
8. ``materials`` - dict
9. ``defaultMaterialId`` - int
10. ``categories`` - list
:param model_id: the id of the model to get the file from
:type model_id: int
:param params: dict of necessary parameters to make the api call
:type params: dict
:returns: the model information
:rtype: dict
"""
return self._put(
"/models/%s/info/" % model_id, body=json.dumps(params)
)
[docs] def add_model(self, params):
"""Make an API call `POST /models/v1
<https://developers.shapeways.com/docs?li=dh_docs#POST_-models-v1>`_
Required Parameters:
1. ``file`` - str (the file data)
2. ``fileName`` - str
3. ``hasRightsToModel`` - bool
4. ``acceptTermsAndConditions`` - bool
Optional Parameters:
1. ``uploadScale`` - float
2. ``title`` - str
3. ``description`` - str
4. ``isPublic`` - bool
5. ``isForSale`` - bool
6. ``isDownloadable`` - bool
7. ``tags`` - list
8. ``materials`` - dict
9. ``defaultMaterialId`` - int
10. ``categories`` - list
:param params: dict of necessary parameters to make the api call
:type params: dict
:returns: model upload information
:rtype: dict
:raises: :class:`Exception` when any of the required parameters
are missing
"""
required = ["file", "fileName", "hasRightsToModel", "acceptTermsAndConditions"]
missing = []
for prop in required:
if prop not in params:
missing.append(prop)
if missing:
raise Exception("add_model missing required parameters: %r" % missing)
return self._post("/models/", body=json.dumps(params))