diff --git a/README.rst b/README.rst index 9307e90..fedd616 100644 --- a/README.rst +++ b/README.rst @@ -70,6 +70,10 @@ Services - Retrieve TileJSON metadata for a tileset - Retrieve a single marker image without any background map +- **Tilesets V1** `examples <./docs/tilesets.md#tilesets>`__, `website `__ + + - Read metadata for raster and vector tilesets + Please note that there may be some lag between the release of new Mapbox web services and releases of this package. diff --git a/docs/index.rst b/docs/index.rst index 76c0a0b..707ca0e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -68,6 +68,10 @@ Services - Retrieve TileJSON metadata for a tileset - Retrieve a single marker image without any background map +- **Tilesets V1** `examples <./docs/tilesets.md#tilesets>`__, `website `__ + + - Read metadata for raster and vector tilesets + Please note that there may be some lag between the release of new Mapbox web services and releases of this package. @@ -116,6 +120,7 @@ Documentation mapmatching.md static_style.md tilequery.md + tilesets.md maps.md api/mapbox.rst api/mapbox.services.rst diff --git a/docs/tilesets.md b/docs/tilesets.md new file mode 100644 index 0000000..eec0a60 --- /dev/null +++ b/docs/tilesets.md @@ -0,0 +1,43 @@ +# Tilesets + +The `Tilesets` class provides access to the Mapbox Tilesets API. You can import it from either the `mapbox` module or the `mapbox.services.tilesets` module. + +__mapbox__: + +```python +>>> from mapbox import Tilesets +``` + +__mapbox.services.tilesets__: + +```python +>>> from mapbox.services.tilesets import Tilesets +``` + +See https://www.mapbox.com/api-documentation/#tilesets for general documentation of the API. + +Use of the Tilesets API requires an access token, which you should set in your environment. For more information, see the [access tokens](access_tokens.md) documentation. + +## Tilesets Method + +The public method of the `Tilesets` class provides access to the Tilesets API and returns an instance of [`requests.Response`](http://docs.python-requests.org/en/latest/api/#requests.Response). + +## Usage: Listing Tilesets + +Instantiate `Tilesets`. + +```python +>>> tilesets = Tilesets() +``` + +Call the `list` method, passing in values for optional arguments as necessary - `tileset_type`, `visibility`, `sortby`, and `limit`. + +```python +>>> response = tilesets.list() +``` + +Evaluate whether the request succeeded, and retrieve the tileset object from the response object. + +```python +>>> if response.status_code == 200: +... tileset_object = response.get_json() diff --git a/mapbox/__init__.py b/mapbox/__init__.py index 5431645..ed5aa6b 100644 --- a/mapbox/__init__.py +++ b/mapbox/__init__.py @@ -1,16 +1,17 @@ # mapbox __version__ = "0.18.0" +from .services.analytics import Analytics from .services.datasets import Datasets from .services.directions import Directions +from .services.matrix import DirectionsMatrix from .services.geocoding import ( Geocoder, InvalidCountryCodeError, InvalidPlaceTypeError) +from .services.maps import Maps from .services.mapmatching import MapMatcher -from .services.matrix import DirectionsMatrix -from .services.surface import Surface from .services.static import Static from .services.static_style import StaticStyle -from .services.uploads import Uploader -from .services.analytics import Analytics +from .services.surface import Surface from .services.tilequery import Tilequery -from .services.maps import Maps +from .services.tilesets import Tilesets +from .services.uploads import Uploader diff --git a/mapbox/errors.py b/mapbox/errors.py index 4c18eae..e481f08 100644 --- a/mapbox/errors.py +++ b/mapbox/errors.py @@ -81,6 +81,50 @@ class InvalidRowError(ValidationError): class InvalidFileFormatError(ValidationError): pass +class InvalidTilesetTypeError(ValidationError): + """InvalidTilesetTypeError + + Parameters + ---------- + message : str, optional + A human-readable string describing the error. + """ + + pass + + +class InvalidVisibilityError(ValidationError): + """InvalidVisibilityError + + Parameters + ---------- + message : str, optional + A human-readable string describing the error. + """ + + pass + + +class InvalidSortbyError(ValidationError): + """InvalidSortbyError + + Parameters + ---------- + message : str, optional + A human-readable string describing the error. + """ + + pass + + +class InvalidLimitError(ValidationError): + """InvalidLimitError + + Parameters + ---------- + message : str, optional + A human-readable string describing the error. + """ class InvalidFeatureFormatError(ValidationError): pass diff --git a/mapbox/services/tilesets.py b/mapbox/services/tilesets.py new file mode 100644 index 0000000..cefb2c1 --- /dev/null +++ b/mapbox/services/tilesets.py @@ -0,0 +1,175 @@ +"""The Tilesets class provides access to Mapbox's Tilesets API.""" + +from mapbox.errors import ( + InvalidTilesetTypeError, + InvalidVisibilityError, + InvalidSortbyError, + InvalidLimitError +) + +from mapbox.services.base import Service + +from uritemplate import URITemplate + +class Tilesets(Service): + """Access to Tilesets API V1 + + Attributes + ---------- + api_name : str + The API's name. + + api_version : str + The API's version number. + + valid_tileset_types : list + The possible values for tileset_type. + + valid_visibilities : list + The possible values for visibility. + + valid_sortbys : list + The possible values for sortby. + + base_uri : str + The API's base URI, currently https://api.mapbox.com/tilesets/v1 + """ + + api_name = "tilesets" + + api_version = "v1" + + valid_tileset_types = [ + "raster", + "vector" + ] + + valid_visibilities = [ + "private", + "public" + ] + + valid_sortbys = [ + "created", + "modified" + ] + + @property + def base_uri(self): + """Forms base URI.""" + + return "https://{}/{}/{}".format( + self.host, + self.api_name, + self.api_version + ) + + def _validate_tileset_type(self, tileset_type): + """Validates tileset type, raising error if invalid.""" + + if tileset_type not in self.valid_tileset_types: + raise InvalidTilesetTypeError( + "{} is not a valid tileset type".format(tileset_type) + ) + + return tileset_type + + def _validate_visibility(self, visibility): + """Validates visibility, raising error if invalid.""" + + if visibility not in self.valid_visibilities: + raise InvalidVisibilityError( + "{} is not a valid value for visibility".format(visibility) + ) + + return visibility + + def _validate_sortby(self, sortby): + """Validates sortby, raising error if invalid.""" + + if sortby not in self.valid_sortbys: + raise InvalidSortbyError( + "{} is not a valid value for sortby".format(sortby) + ) + + return sortby + + def _validate_limit(self, limit): + """Validates limit, raising error if invalid.""" + + if (limit < 1) or (limit > 500): + raise InvalidLimitError( + "{} is not a valid value for limit".format(limit) + ) + + return limit + + def list(self, tileset_type=None, visibility=None, + sortby=None, limit=None): + """Lists all tilesets for an account. + + tileset_type : str, optional + Filter results by tileset type. + + Valid values are raster or vector. + + visibility : str, optional + Filter results by visibility. + + Valid values are private or public. + + Private tilesets require an access token + belonging to the owner, while public + tilesets may be requested with an access + token belonging to any user. + + sortby : str, optional + Sort results by timestamp. + + Valid values are created or modified + + limit : int, optional + The maximum number of objects to return + (pagination), where 1 is the minimum value + and 500 is the maxium value. + + The default value is 100. + + Returns + ------- + request.Response + The response object with a tileset object. + """ + + # Build URI resource path. + + path_part = "/{username}" + path_values = dict(username=self.username) + uri = URITemplate(self.base_uri + path_part).expand(**path_values) + + # Validate tileset_type, visibility, sortby, and limit + # and build URI query parameters. + + query_parameters = dict() + + if tileset_type: + tileset_type = self._validate_tileset_type(tileset_type) + query_parameters["type"] = tileset_type + + if visibility: + visibility = self._validate_visibility(visibility) + query_parameters["visibility"] = visibility + + if sortby: + sortby = self._validate_sortby(sortby) + query_parameters["sortby"] = sortby + + if limit: + limit = self._validate_limit(limit) + query_parameters["limit"] = str(limit) + + # Send HTTP GET request. + + response = self.session.get(uri, params=query_parameters) + + return response diff --git a/tests/test_tilesets.py b/tests/test_tilesets.py new file mode 100644 index 0000000..634f7d7 --- /dev/null +++ b/tests/test_tilesets.py @@ -0,0 +1,313 @@ +from mapbox.errors import ( + InvalidTilesetTypeError, + InvalidVisibilityError, + InvalidSortbyError, + InvalidLimitError +) + +from mapbox.services.tilesets import Tilesets + +from base64 import b64encode + +from pytest import ( + mark, + raises +) + +from responses import ( + activate, + add, + GET +) + + +USERNAME = b64encode(b"{\"u\":\"user\"}").decode("utf-8") +ACCESS_TOKEN = "pk.{}.test".format(USERNAME) + + +def test_object_attributes(): + tilesets = Tilesets() + + assert tilesets.api_name + assert tilesets.api_version + assert tilesets.valid_tileset_types + assert tilesets.valid_visibilities + assert tilesets.valid_sortbys + assert tilesets.base_uri + + +def test_validate_tileset_type_invalid(): + tilesets = Tilesets() + + with raises(InvalidTilesetTypeError) as exception: + tileset_type = "invalid" + result = tilesets._validate_tileset_type(tileset_type) + + +@mark.parametrize("tileset_type", ["raster", "vector"]) +def test_validate_tileset_type_valid(tileset_type): + tilesets = Tilesets() + result = tilesets._validate_tileset_type(tileset_type) + assert result == tileset_type + + +def test_validate_visibility_invalid(): + tilesets = Tilesets() + + with raises(InvalidVisibilityError) as exception: + visibility = "invalid" + result = tilesets._validate_visibility(visibility) + + +@mark.parametrize("visibility", ["private", "public"]) +def test_validate_visibility_valid(visibility): + tilesets = Tilesets() + result = tilesets._validate_visibility(visibility) + assert result == visibility + + +def test_validate_sortby_invalid(): + tilesets = Tilesets() + + with raises(InvalidSortbyError) as exception: + sortby = "invalid" + result = tilesets._validate_sortby(sortby) + + +@mark.parametrize("sortby", ["created", "modified"]) +def test_validate_sortby_valid(sortby): + tilesets = Tilesets() + result = tilesets._validate_sortby(sortby) + assert result == sortby + + +@mark.parametrize("limit", [0, 501]) +def test_validate_limit_invalid(limit): + tilesets = Tilesets() + + with raises(InvalidLimitError) as exception: + result = tilesets._validate_limit(limit) + + +@mark.parametrize("limit", [1, 250, 500]) +def test_validate_limit_valid(limit): + tilesets = Tilesets() + result = tilesets._validate_limit(limit) + assert result == limit + + +@activate +def test_list(): + add( + method=GET, + url="https://api.mapbox.com" + + "/tilesets/v1" + + "/user" + + "?access_token={}".format(ACCESS_TOKEN), + match_querystring=True, + body="{\"key\": \"value\"}", + status=200 + ) + + tilesets = Tilesets(access_token=ACCESS_TOKEN) + response = tilesets.list() + assert response.status_code == 200 + + +@activate +@mark.parametrize("tileset_type", ["raster", "vector"]) +def test_list_with_tileset_type(tileset_type): + add( + method=GET, + url="https://api.mapbox.com" + + "/tilesets/v1" + + "/user" + + "?access_token={}".format(ACCESS_TOKEN) + + "&type={}".format(tileset_type), + match_querystring=True, + body="{\"key\": \"value\"}", + status=200 + ) + + tilesets = Tilesets(access_token=ACCESS_TOKEN) + response = tilesets.list(tileset_type=tileset_type) + assert response.status_code == 200 + + +@activate +@mark.parametrize("visibility", ["private", "public"]) +def test_list_with_visibility(visibility): + add( + method=GET, + url="https://api.mapbox.com" + + "/tilesets/v1" + + "/user" + + "?access_token={}".format(ACCESS_TOKEN) + + "&visibility={}".format(visibility), + match_querystring=True, + body="{\"key\": \"value\"}", + status=200 + ) + + tilesets = Tilesets(access_token=ACCESS_TOKEN) + response = tilesets.list(visibility=visibility) + assert response.status_code == 200 + + +@activate +@mark.parametrize("sortby", ["created", "modified"]) +def test_list_with_sortby(sortby): + add( + method=GET, + url="https://api.mapbox.com" + + "/tilesets/v1" + + "/user" + + "?access_token={}".format(ACCESS_TOKEN) + + "&sortby={}".format(sortby), + match_querystring=True, + body="{\"key\": \"value\"}", + status=200 + ) + + tilesets = Tilesets(access_token=ACCESS_TOKEN) + response = tilesets.list(sortby=sortby) + assert response.status_code == 200 + + +@activate +@mark.parametrize("limit", [1, 250, 500]) +def test_list_with_limit(limit): + add( + method=GET, + url="https://api.mapbox.com" + + "/tilesets/v1" + + "/user" + + "?access_token={}".format(ACCESS_TOKEN) + + "&limit={}".format(limit), + match_querystring=True, + body="{\"key\": \"value\"}", + status=200 + ) + + tilesets = Tilesets(access_token=ACCESS_TOKEN) + response = tilesets.list(limit=limit) + assert response.status_code == 200 + + +@activate +def test_list_with_tileset_type_and_visibility(): + add( + method=GET, + url="https://api.mapbox.com" + + "/tilesets/v1" + + "/user" + + "?access_token={}".format(ACCESS_TOKEN) + + "&type=vector" + + "&visibility=public", + match_querystring=True, + body="{\"key\": \"value\"}", + status=200 + ) + + tilesets = Tilesets(access_token=ACCESS_TOKEN) + response = tilesets.list(tileset_type="vector", visibility="public") + assert response.status_code == 200 + + +@activate +def test_list_with_tileset_type_and_sortby(): + add( + method=GET, + url="https://api.mapbox.com" + + "/tilesets/v1" + + "/user" + + "?access_token={}".format(ACCESS_TOKEN) + + "&type=vector" + + "&sortby=created", + match_querystring=True, + body="{\"key\": \"value\"}", + status=200 + ) + + tilesets = Tilesets(access_token=ACCESS_TOKEN) + response = tilesets.list(tileset_type="vector", sortby="created") + assert response.status_code == 200 + + +@activate +def test_list_with_tileset_type_and_limit(): + add( + method=GET, + url="https://api.mapbox.com" + + "/tilesets/v1" + + "/user" + + "?access_token={}".format(ACCESS_TOKEN) + + "&type=vector" + + "&limit=500", + match_querystring=True, + body="{\"key\": \"value\"}", + status=200 + ) + + tilesets = Tilesets(access_token=ACCESS_TOKEN) + response = tilesets.list(tileset_type="vector", limit=500) + assert response.status_code == 200 + + +@activate +def test_list_with_visibility_and_sortby(): + add( + method=GET, + url="https://api.mapbox.com" + + "/tilesets/v1" + + "/user" + + "?access_token={}".format(ACCESS_TOKEN) + + "&visibility=public" + + "&sortby=created", + match_querystring=True, + body="{\"key\": \"value\"}", + status=200 + ) + + tilesets = Tilesets(access_token=ACCESS_TOKEN) + response = tilesets.list(visibility="public", sortby="created") + assert response.status_code == 200 + + +@activate +def test_list_with_visibility_and_limit(): + add( + method=GET, + url="https://api.mapbox.com" + + "/tilesets/v1" + + "/user" + + "?access_token={}".format(ACCESS_TOKEN) + + "&visibility=public" + + "&limit=500", + match_querystring=True, + body="{\"key\": \"value\"}", + status=200 + ) + + tilesets = Tilesets(access_token=ACCESS_TOKEN) + response = tilesets.list(visibility="public", limit=500) + assert response.status_code == 200 + + +@activate +def test_list_with_sortby_and_limit(): + add( + method=GET, + url="https://api.mapbox.com" + + "/tilesets/v1" + + "/user?access_token={}".format(ACCESS_TOKEN) + + "&sortby=created" + + "&limit=500", + match_querystring=True, + body="{\"key\": \"value\"}", + status=200 + ) + + tilesets = Tilesets(access_token=ACCESS_TOKEN) + response = tilesets.list(sortby="created", limit=500) + assert response.status_code == 200