Skip to content

Commit 96c51dc

Browse files
authored
Add command for tilesets activity (#174)
* MAPSAPI-1488: Tilesets activity command * MAPSAPI-1488: Fix pre-commit repo + address flake warnings * MAPSAPI-1488: Fix tests * MAPSAPI-1488: Pin importlib-metadata==4.8.3, flake8==5.0.4 * MAPSAPI-1488: Clearer documentation
1 parent d2f56a2 commit 96c51dc

File tree

9 files changed

+221
-22
lines changed

9 files changed

+221
-22
lines changed

.pre-commit-config.yaml

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
repos:
2-
-
3-
repo: 'https://github.com/ambv/black'
4-
# 18.6b1
5-
rev: 22.3.0
6-
hooks:
7-
- id: black
8-
args: ['--safe']
9-
-
10-
repo: 'https://gitlab.com/pycqa/flake8'
11-
rev: 3.9.0
12-
hooks:
13-
- id: flake8
14-
args: [
15-
# E501 let black handle all line length decisions
16-
# W503 black conflicts with "line break before operator" rule
17-
# E203 black conflicts with "whitespace before ':'" rule
18-
# E722 bare excepts need to be addressed
19-
'--ignore=E501,W503,E203,E722']
2+
-
3+
repo: 'https://github.com/ambv/black'
4+
# 18.6b1
5+
rev: 22.3.0
6+
hooks:
7+
- id: black
8+
args: ['--safe']
9+
-
10+
repo: 'https://github.com/PyCQA/flake8'
11+
rev: 5.0.4
12+
hooks:
13+
- id: flake8
14+
args: [
15+
# E501 let black handle all line length decisions
16+
# W503 black conflicts with "line break before operator" rule
17+
# E203 black conflicts with "whitespace before ':'" rule
18+
# E722 bare excepts need to be addressed
19+
'--ignore=E501,W503,E203,E722']

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ python:
33
- '3.6'
44
- '3.7'
55
install:
6-
- pip install -U setuptools importlib-metadata
6+
- pip install -U setuptools "importlib-metadata==4.8.3"
77
- pip install -r requirements.txt -e .[test]
88
script:
99
- python --version

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
=======
44

5+
# 1.9.0 (2023-06-14)
6+
- Added command `tilesets list-activity` that returns activity data for a user's tilesets
7+
58
# 1.8.1 (2022-08-29)
69
- Bug Fix: Fix setup script to have `geojson` package in setup.py install requirements
710

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ export MAPBOX_ACCESS_TOKEN=my.token
8181
* [`jobs`](#jobs)
8282
* [`list`](#list)
8383
* [`tilejson`](#tilejson)
84+
* Activity
85+
* [`list-activity`](#list-activity)
8486

8587
### upload-source
8688

@@ -401,3 +403,19 @@ tilesets tilejson <tileset_id>
401403
Flags:
402404

403405
* `--secure`: By default, resource URLs in the retrieved TileJSON (such as in the "tiles" array) will use the HTTP scheme. Include this query parameter in your request to receive HTTPS resource URLs instead.
406+
407+
### list-activity
408+
409+
Lists total request counts for a user's tilesets in the past 30 days. Returns a pagination key `next` if there are more results than the return limit that can be passed into another command as the `start` argument.
410+
411+
```shell
412+
tilesets list-activity <account>
413+
```
414+
415+
Flags:
416+
417+
* `--sortby [requests|modified]` [optional]: Sorting key (default: requests)
418+
* `--orderby [asc|desc]` [optional]: Ordering key (default: desc)
419+
* `--limit [1-500]` [optional]: The maximum number of results to return (default: 100)
420+
* `--indent` [optional]: Indent size for JSON output.
421+
* `--start` [optional]: Pagination key from the `next` value in a response that has more results than the limit.

mapbox_tilesets/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""mapbox_tilesets package"""
22

3-
__version__ = "1.8.1"
3+
__version__ = "1.9.0"

mapbox_tilesets/scripts/cli.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import builtins
33
import json
44
import tempfile
5+
from urllib.parse import urlencode, urlparse, parse_qs
56

67
import click
78
import cligj
@@ -799,3 +800,80 @@ def estimate_area(features, precision, no_validation=False, force_1cm=False):
799800
}
800801
)
801802
)
803+
804+
805+
@cli.command("list-activity")
806+
@click.argument("username", required=True, type=str)
807+
@click.option(
808+
"--sortby",
809+
required=False,
810+
type=click.Choice(["requests", "modified"]),
811+
default="requests",
812+
help="Sort the results by request count or modified timestamps (default: 'requests')",
813+
)
814+
@click.option(
815+
"--orderby",
816+
required=False,
817+
type=click.Choice(["asc", "desc"]),
818+
default="desc",
819+
help="Order results by asc or desc for the sort key (default: 'desc')",
820+
)
821+
@click.option(
822+
"--limit",
823+
required=False,
824+
type=click.IntRange(1, 500),
825+
default=100,
826+
help="The maximum number of results to return, from 1 to 500 (default: 100)",
827+
)
828+
@click.option(
829+
"--start",
830+
required=False,
831+
type=str,
832+
help="Pagination key from the `next` value in a response that has more results than the limit.",
833+
)
834+
@click.option("--token", "-t", required=False, type=str, help="Mapbox access token")
835+
@click.option("--indent", type=int, default=None, help="Indent for JSON output")
836+
def list_activity(
837+
username,
838+
sortby=None,
839+
orderby=None,
840+
limit=None,
841+
start=None,
842+
token=None,
843+
indent=None,
844+
):
845+
"""List tileset activity for an account. The response is an ordered array of data about the user's tilesets and their
846+
total requests over the past 30 days. The sorting and ordering can be configured through cli arguments, defaulting to
847+
descending request counts.
848+
849+
tilesets list-activity <username>
850+
"""
851+
mapbox_api = utils._get_api()
852+
mapbox_token = utils._get_token(token)
853+
s = utils._get_session()
854+
855+
params = {
856+
"access_token": mapbox_token,
857+
"sortby": sortby,
858+
"orderby": orderby,
859+
"limit": limit,
860+
"start": start,
861+
}
862+
params = {k: v for k, v in params.items() if v}
863+
query_string = urlencode(params)
864+
url = f"{mapbox_api}/activity/v1/{username}/tilesets?{query_string}"
865+
866+
r = s.get(url)
867+
if r.status_code == 200:
868+
if r.headers.get("Link"):
869+
url = re.findall(r"<(.*)>;", r.headers.get("Link"))[0]
870+
query = urlparse(url).query
871+
start = parse_qs(query)["start"][0]
872+
873+
result = {
874+
"data": r.json(),
875+
"next": start,
876+
}
877+
click.echo(json.dumps(result, indent=indent))
878+
else:
879+
raise errors.TilesetsError(r.text)

mapbox_tilesets/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def load_module(modulename):
1616
"""Dynamically imports a module and throws a readable exception if not found"""
1717
try:
1818
module = importlib.import_module(modulename)
19-
except (ImportError):
19+
except ImportError:
2020
raise ValueError(
2121
f"Couldn't find {modulename}. Check installation steps in the readme for help: https://github.com/mapbox/tilesets-cli/blob/master/README.md"
2222
) from None

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def read(fname):
4646
],
4747
"test": [
4848
"codecov",
49-
"pytest==4.6.11",
49+
"pytest==6.2.5",
5050
"pytest-cov",
5151
"pre-commit",
5252
"black==22.3.0",

tests/test_cli_activity.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import json
2+
import pytest
3+
4+
from unittest import mock
5+
6+
from click.testing import CliRunner
7+
8+
from mapbox_tilesets.scripts.cli import list_activity
9+
10+
DEFAULT_ENDPOINT = "https://api.mapbox.com/activity/v1/test/tilesets?access_token=pk.eyJ1IjoidGVzdC11c2VyIn0K&sortby=requests&orderby=desc&limit=100"
11+
12+
13+
@pytest.mark.usefixtures("token_environ")
14+
@mock.patch("requests.Session.get")
15+
def test_cli_list_activity(mock_request_get, MockResponse):
16+
runner = CliRunner()
17+
18+
message = [
19+
{
20+
"id": "penny.map-one",
21+
"request_count": 500,
22+
"last_modified": "2023-06-14T20:18:54.809Z",
23+
},
24+
{
25+
"id": "penny.map-two",
26+
"request_count": 400,
27+
"last_modified": "2023-06-14T20:18:54.809Z",
28+
},
29+
{
30+
"id": "penny.map-three",
31+
"request_count": 300,
32+
"last_modified": "2023-06-14T20:18:54.809Z",
33+
},
34+
]
35+
# sends expected request
36+
mock_request_get.return_value = MockResponse(message)
37+
MockResponse.headers = {"Link": '<meow.com?start=foo>; rel="next"'}
38+
result = runner.invoke(list_activity, ["test"])
39+
mock_request_get.assert_called_with(DEFAULT_ENDPOINT)
40+
assert json.loads(result.output) == {"data": message, "next": "foo"}
41+
assert result.exit_code == 0
42+
43+
44+
@pytest.mark.usefixtures("token_environ")
45+
@mock.patch("requests.Session.get")
46+
def test_cli_list_activity_bad_token(mock_request_get, MockResponse):
47+
runner = CliRunner()
48+
49+
message = {"message": "Not Found"}
50+
# sends expected request
51+
mock_request_get.return_value = MockResponse(message, status_code=404)
52+
result = runner.invoke(list_activity, ["test"])
53+
mock_request_get.assert_called_with(DEFAULT_ENDPOINT)
54+
assert result.exit_code == 1
55+
assert result.exception
56+
57+
58+
@pytest.mark.usefixtures("token_environ")
59+
@mock.patch("requests.Session.get")
60+
def test_cli_list_activity_arguments_valid(mock_request_get, MockResponse):
61+
runner = CliRunner()
62+
runner.invoke(
63+
list_activity,
64+
[
65+
"test",
66+
"--sortby",
67+
"modified",
68+
"--orderby",
69+
"asc",
70+
"--limit",
71+
"5",
72+
"--start",
73+
"foo",
74+
],
75+
)
76+
# Arguments are passed into the query string
77+
mock_request_get.assert_called_with(
78+
"https://api.mapbox.com/activity/v1/test/tilesets?access_token=pk.eyJ1IjoidGVzdC11c2VyIn0K&sortby=modified&orderby=asc&limit=5&start=foo"
79+
)
80+
81+
82+
@pytest.mark.usefixtures("token_environ")
83+
@mock.patch("requests.Session.get")
84+
@pytest.mark.parametrize(
85+
"extra_args",
86+
[
87+
(["--sortby", "likes"]),
88+
(["--orderby", "chaos"]),
89+
(["--limit", "0"]),
90+
(["--limit", "501"]),
91+
],
92+
)
93+
def test_cli_list_activity_arguments_invalid(
94+
mock_request_get, MockResponse, extra_args
95+
):
96+
runner = CliRunner()
97+
result = runner.invoke(list_activity, ["test"] + extra_args)
98+
# Invalid argument values should error
99+
mock_request_get.assert_not_called()
100+
assert result.exit_code == 2

0 commit comments

Comments
 (0)