From 6379c0df33e5f5220e8b12012356a9b018d468f6 Mon Sep 17 00:00:00 2001 From: dnomadb Date: Mon, 16 Sep 2019 09:20:38 -0700 Subject: [PATCH 01/12] a little standardization re-org --- setup.py | 2 +- tests/test_cli_create.py | 2 +- tests/test_cli_jobs.py | 2 +- tests/test_cli_list.py | 2 +- tests/test_cli_publish.py | 2 +- tests/test_cli_sources.py | 2 +- tests/test_cli_status.py | 2 +- tests/test_cli_update_recipe.py | 2 +- tests/test_cli_validate_recipe.py | 2 +- tests/test_cli_view_recipe.py | 2 +- tests/test_file_validator.py | 2 +- tilesets/{ => scripts}/cli.py | 2 +- tilesets/{scripts => }/utils.py | 0 13 files changed, 12 insertions(+), 12 deletions(-) rename tilesets/{ => scripts}/cli.py (99%) rename tilesets/{scripts => }/utils.py (100%) diff --git a/setup.py b/setup.py index 166f142..f90ce3b 100644 --- a/setup.py +++ b/setup.py @@ -36,6 +36,6 @@ def read(fname): extras_require={"test": ["pytest>=3.6.0", "pytest-cov", "pre-commit"]}, entry_points=""" [console_scripts] - tilesets=tilesets.cli:cli + tilesets=tilesets.scripts.cli:cli """, ) diff --git a/tests/test_cli_create.py b/tests/test_cli_create.py index 08a47ea..a26917e 100644 --- a/tests/test_cli_create.py +++ b/tests/test_cli_create.py @@ -3,7 +3,7 @@ from click.testing import CliRunner import pytest -from tilesets.cli import create +from tilesets.scripts.cli import create class MockResponse: diff --git a/tests/test_cli_jobs.py b/tests/test_cli_jobs.py index fa8823e..2ee61b6 100644 --- a/tests/test_cli_jobs.py +++ b/tests/test_cli_jobs.py @@ -3,7 +3,7 @@ from click.testing import CliRunner import pytest -from tilesets.cli import jobs, job +from tilesets.scripts.cli import jobs, job class MockResponse: diff --git a/tests/test_cli_list.py b/tests/test_cli_list.py index ef1ec18..d7b4bdd 100644 --- a/tests/test_cli_list.py +++ b/tests/test_cli_list.py @@ -3,7 +3,7 @@ from click.testing import CliRunner import pytest -from tilesets.cli import list +from tilesets.scripts.cli import list class MockResponse: diff --git a/tests/test_cli_publish.py b/tests/test_cli_publish.py index 992fff7..cdca6ab 100644 --- a/tests/test_cli_publish.py +++ b/tests/test_cli_publish.py @@ -3,7 +3,7 @@ import pytest -from tilesets.cli import publish +from tilesets.scripts.cli import publish class MockResponse: diff --git a/tests/test_cli_sources.py b/tests/test_cli_sources.py index 0045b78..10c3c17 100644 --- a/tests/test_cli_sources.py +++ b/tests/test_cli_sources.py @@ -4,7 +4,7 @@ import pytest -from tilesets.cli import ( +from tilesets.scripts.cli import ( add_source, view_source, delete_source, diff --git a/tests/test_cli_status.py b/tests/test_cli_status.py index fb9d7b2..ef3111b 100644 --- a/tests/test_cli_status.py +++ b/tests/test_cli_status.py @@ -3,7 +3,7 @@ import pytest -from tilesets.cli import status +from tilesets.scripts.cli import status class MockResponse: diff --git a/tests/test_cli_update_recipe.py b/tests/test_cli_update_recipe.py index 59037d8..506b792 100644 --- a/tests/test_cli_update_recipe.py +++ b/tests/test_cli_update_recipe.py @@ -2,7 +2,7 @@ from unittest import mock import pytest -from tilesets.cli import update_recipe +from tilesets.scripts.cli import update_recipe class MockResponse: diff --git a/tests/test_cli_validate_recipe.py b/tests/test_cli_validate_recipe.py index 0cf26dc..8e56b81 100644 --- a/tests/test_cli_validate_recipe.py +++ b/tests/test_cli_validate_recipe.py @@ -2,7 +2,7 @@ from unittest import mock import pytest -from tilesets.cli import validate_recipe +from tilesets.scripts.cli import validate_recipe class MockResponse: diff --git a/tests/test_cli_view_recipe.py b/tests/test_cli_view_recipe.py index 50ede39..274e8ee 100644 --- a/tests/test_cli_view_recipe.py +++ b/tests/test_cli_view_recipe.py @@ -2,7 +2,7 @@ from unittest import mock import pytest -from tilesets.cli import view_recipe +from tilesets.scripts.cli import view_recipe class MockResponse: diff --git a/tests/test_file_validator.py b/tests/test_file_validator.py index c05433f..962ac57 100644 --- a/tests/test_file_validator.py +++ b/tests/test_file_validator.py @@ -1,6 +1,6 @@ from click.testing import CliRunner -from tilesets.cli import cli +from tilesets.scripts.cli import cli import os filepath = os.path.join(os.path.dirname(__file__)) diff --git a/tilesets/cli.py b/tilesets/scripts/cli.py similarity index 99% rename from tilesets/cli.py rename to tilesets/scripts/cli.py index 2566d2c..4d406a7 100644 --- a/tilesets/cli.py +++ b/tilesets/scripts/cli.py @@ -7,7 +7,7 @@ import click import tilesets import requests -from tilesets.scripts import utils +from tilesets import utils import jsonschema from jsonseq.decode import JSONSeqDecoder from json.decoder import JSONDecodeError diff --git a/tilesets/scripts/utils.py b/tilesets/utils.py similarity index 100% rename from tilesets/scripts/utils.py rename to tilesets/utils.py From 1fa9d681627785a104985940c17077e08242b10c Mon Sep 17 00:00:00 2001 From: dnomadb Date: Mon, 16 Sep 2019 13:22:14 -0700 Subject: [PATCH 02/12] use cligj; diff json handling --- tests/test_cli_sources.py | 14 ++++---- tilesets/scripts/cli.py | 70 ++++++++++++++------------------------- tilesets/utils.py | 24 ++++++++++++++ 3 files changed, 55 insertions(+), 53 deletions(-) diff --git a/tests/test_cli_sources.py b/tests/test_cli_sources.py index 10c3c17..249974a 100644 --- a/tests/test_cli_sources.py +++ b/tests/test_cli_sources.py @@ -36,15 +36,13 @@ def test_cli_add_source(mock_request_post): add_source, ["test-user", "hello-world", "tests/fixtures/valid.ldgeojson"] ) assert result.exit_code == 0 - assert "Validating tests/fixtures/valid.ldgeojson ...\n✔ valid" in result.output - assert ( - "Adding tests/fixtures/valid.ldgeojson to mapbox://tileset-source/test-user/hello-world" - in result.output - ) + assert ( - '{\n "id": "mapbox://tileset-source/test-user/hello-world"\n}\n' - in result.output + result.output + == """Adding Point feature to mapbox://tileset-source/test-user/hello-world\n{"id": "mapbox://tileset-source/test-user/hello-world"}\n""" ) + # assert result.output == """Adding Point feature to mapbox://tileset-source/test-user/hello-world\n\"{\\"id\\":\\"mapbox://tileset-source/test-user/hello-world\\"}\"\n + # """ @pytest.mark.usefixtures("token_environ") @@ -92,4 +90,4 @@ def test_cli_validate_source(): runner = CliRunner() result = runner.invoke(validate_source, ["tests/fixtures/valid.ldgeojson"]) assert result.exit_code == 0 - assert result.output == "Validating tests/fixtures/valid.ldgeojson ...\n✔ valid\n" + assert result.output == "Validating features\n✔ valid\n" diff --git a/tilesets/scripts/cli.py b/tilesets/scripts/cli.py index 4d406a7..353f48e 100644 --- a/tilesets/scripts/cli.py +++ b/tilesets/scripts/cli.py @@ -2,15 +2,16 @@ import os import json +import requests import sys +from io import BytesIO + import click +import cligj + import tilesets -import requests -from tilesets import utils -import jsonschema -from jsonseq.decode import JSONSeqDecoder -from json.decoder import JSONDecodeError +from tilesets import utils, errors def _get_token(token=None): @@ -278,33 +279,15 @@ def update_recipe(tileset, recipe, token=None): @cli.command("validate-source") -@click.argument("source_path", required=True, type=click.Path(exists=True)) -def validate_source(source_path): +@cligj.features_in_arg +def validate_source(features): """Validate your source file. $ tilesets validate-source """ - line_count = 1 - with open(source_path, "r") as inf: - click.echo("Validating {0} ...".format(source_path)) - feature = None - try: - for feature in JSONSeqDecoder().decode(inf): - utils.validate_geojson(feature) - line_count += 1 - except JSONDecodeError: - click.echo( - "Error: Invalid JSON on line {} \n Invalid Content: {} \n".format( - line_count, feature - ) - ) - sys.exit(1) - except jsonschema.exceptions.ValidationError: - click.echo( - "Error: Invalid geojson found on line {} \n Invalid Feature: {} \n Note - Geojson must be line delimited.".format( - line_count, feature - ) - ) - sys.exit(1) + click.echo(f"Validating features", err=True) + + for feature in features: + utils.validate_geojson(feature) click.echo("✔ valid") @@ -312,39 +295,36 @@ def validate_source(source_path): @cli.command("add-source") @click.argument("username", required=True, type=str) @click.argument("id", required=True, type=str) -@click.argument( - "files", - required=True, - type=click.Path(exists=True, file_okay=True, dir_okay=True), - nargs=-1, -) +@cligj.features_in_arg @click.option("--no-validation", is_flag=True, help="Bypass source file validation") @click.option("--token", "-t", required=False, type=str, help="Mapbox access token") +@click.option("--indent", type=int, default=None, help="Indent for JSON output") @click.pass_context -def add_source(ctx, username, id, files, no_validation, token=None): +def add_source(ctx, username, id, features, no_validation, token=None, indent=None): """Create/add a tileset source tilesets add-source """ mapbox_api = _get_api() mapbox_token = _get_token(token) - for f in utils.flatten(files): - url = "{0}/tilesets/v1/sources/{1}/{2}?access_token={3}".format( - mapbox_api, username, id, mapbox_token - ) + + for feature in features: + url = f"{mapbox_api}/tilesets/v1/sources/{username}/{id}?access_token={mapbox_token}" if not no_validation: - ctx.invoke(validate_source, source_path=f) + utils.validate_geojson(feature) click.echo( - "Adding {0} to mapbox://tileset-source/{1}/{2}".format(f, username, id) + f"Adding {feature['geometry']['type']} feature to mapbox://tileset-source/{username}/{id}", + err=True, ) - r = requests.post(url, files={"file": ("tileset-source", open(f, "rb"))}) + with BytesIO(json.dumps(feature).encode("utf-8")) as io: + r = requests.post(url, files={"file": ("tileset-source", io)}) if r.status_code == 200: - utils.print_response(r.text) + click.echo(utils.format_response(r.text, indent=indent)) else: - click.echo(r.text) + raise errors.TilesetsError(r.text) @cli.command("view-source") diff --git a/tilesets/utils.py b/tilesets/utils.py index 43c5541..81ba8b9 100644 --- a/tilesets/utils.py +++ b/tilesets/utils.py @@ -4,6 +4,7 @@ import re from jsonschema import validate +from json.decoder import JSONDecodeError tileset_arg = click.argument("tileset", required=True, type=str) @@ -26,6 +27,29 @@ def flatten(files): yield f +def format_response(text, indent=None): + """Cleanly parse and serialize + different response types + + Parameters + ---------- + text: str + text to format + indent: int or None + Amount to indent formatted json + [default=None] + + Returns + ------- + text: str + formatted text + """ + try: + return json.dumps(json.loads(text), indent=indent, sort_keys=True) + except JSONDecodeError: + return text + + def print_response(text): try: j = json.loads(text) From 43bd046cd8a2aeaaf8f7992970d6b8174527e7b1 Mon Sep 17 00:00:00 2001 From: dnomadb Date: Mon, 16 Sep 2019 13:25:39 -0700 Subject: [PATCH 03/12] install cligj --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 51c2637..108af3f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ boto3==1.9.99 Click==7.0 +cligj==0.5.0 requests==2.21.0 jsonschema==3.0.1 jsonseq==1.0.0 From 928416656ce23f2733b5b31824a52224bb3d25da Mon Sep 17 00:00:00 2001 From: dnomadb Date: Mon, 16 Sep 2019 13:29:05 -0700 Subject: [PATCH 04/12] use error module --- tilesets/errors.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tilesets/errors.py diff --git a/tilesets/errors.py b/tilesets/errors.py new file mode 100644 index 0000000..a100551 --- /dev/null +++ b/tilesets/errors.py @@ -0,0 +1,19 @@ +"""Error handling for the tilesets CLI""" + + +class TilesetsError(Exception): + """Base Tilesets error + Deriving errors from this base isolates module development + problems from Python usage problems. + """ + + exit_code = 1 + + def __init__(self, message): + """Error constructor + Parameters + ---------- + message: str + Error description + """ + self.message = message From 3a9c59bb5d395ffbcdb46ccc5e2ccee4392043e6 Mon Sep 17 00:00:00 2001 From: dnomadb Date: Mon, 16 Sep 2019 13:40:04 -0700 Subject: [PATCH 05/12] file validation tests --- tests/test_file_validator.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/test_file_validator.py b/tests/test_file_validator.py index 962ac57..46e3c82 100644 --- a/tests/test_file_validator.py +++ b/tests/test_file_validator.py @@ -1,7 +1,9 @@ +import os +import pytest + from click.testing import CliRunner from tilesets.scripts.cli import cli -import os filepath = os.path.join(os.path.dirname(__file__)) @@ -12,17 +14,15 @@ def test_validate_ldgeojson(): cli, ["validate-source", filepath + "/fixtures/valid.ldgeojson"] ) assert result.exit_code == 0 - assert "✔ valid" in result.output +@pytest.mark.skip("Should this pass w/ cligj?") def test_validate_invalid_ldgeojson(): runner = CliRunner() result = runner.invoke( cli, ["validate-source", filepath + "/fixtures/invalid.ldgeojson"] ) assert result.exit_code == 1 - output = "Error: Invalid JSON on line 1 \n Invalid Content: None \n\n" - assert output in result.output def test_validate_invalid_geojson(): @@ -31,5 +31,3 @@ def test_validate_invalid_geojson(): cli, ["validate-source", filepath + "/fixtures/invalid-geojson.ldgeojson"] ) assert result.exit_code == 1 - output = "Error: Invalid geojson found on line 1 \n Invalid Feature: {'type': 'Feature', 'geometry': {'type': 'Point'}} \n Note - Geojson must be line delimited.\n" - assert output in result.output From c94c877aaff50fb776245cbc6c138b0561933ed0 Mon Sep 17 00:00:00 2001 From: dnomadb Date: Mon, 16 Sep 2019 13:50:42 -0700 Subject: [PATCH 06/12] unit tests for utils --- tests/test_utils.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 tests/test_utils.py diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..db64f06 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,43 @@ +from tilesets.utils import format_response, validate_tileset_id + + +def test_format_response_json(): + response = '{"bada": "bing"}' + + assert format_response(response) == response + + +def test_format_response_indent_json(): + response = '{"bada": "bing"}' + + assert format_response(response, indent=2) == """{\n \"bada\": \"bing\"\n}""" + + +def test_format_response_indent_backwards_json(): + response = """{\n \"bada\": \"bing\"\n}""" + + assert format_response(response) == '{"bada": "bing"}' + + +def test_format_response_string(): + response = "hi" + + assert format_response(response) == response + + +def test_validate_tileset_id(): + tileset = "iama.test" + + assert validate_tileset_id(tileset) + + +def test_validate_tileset_id_badfmt(): + tileset = "iama.test.ok" + + assert not validate_tileset_id(tileset) + + +def test_validate_tileset_id_toolong(): + tileset = "hellooooooooooooooooooooooooooooooo.hiiiiiiiuuuuuuuuuuuuuuuuuuuuuu" + + assert not validate_tileset_id(tileset) From 1d7350114e5da55014396a466602bf5c30f87a3e Mon Sep 17 00:00:00 2001 From: dnomadb Date: Tue, 24 Sep 2019 17:33:49 -0700 Subject: [PATCH 07/12] cleanup test message testng --- tests/test_cli_create.py | 29 +++++++++++++++++++---------- tilesets/errors.py | 5 +++++ tilesets/scripts/cli.py | 6 +++--- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/tests/test_cli_create.py b/tests/test_cli_create.py index a26917e..48c10e2 100644 --- a/tests/test_cli_create.py +++ b/tests/test_cli_create.py @@ -1,3 +1,5 @@ +import json + from unittest import mock from click.testing import CliRunner @@ -7,12 +9,16 @@ class MockResponse: - def __init__(self, mock_text): - self.text = mock_text + def __init__(self, mock_json): + self.text = json.dumps(mock_json) + self._json = mock_json def MockResponse(self): return self + def json(self): + return self._json + @pytest.mark.usefixtures("token_environ") def test_cli_create_missing_recipe(): @@ -39,7 +45,8 @@ def test_cli_create_missing_name(): def test_cli_create_success(mock_request_post): runner = CliRunner() # sends request to proper endpoints - mock_request_post.return_value = MockResponse('{"message":"mock message"}') + message = {"message": "mock message"} + mock_request_post.return_value = MockResponse(message) result = runner.invoke( create, ["test.id", "--recipe", "tests/fixtures/recipe.json", "--name", "test name"], @@ -53,7 +60,7 @@ def test_cli_create_success(mock_request_post): "recipe": {"minzoom": 0, "maxzoom": 10, "layer_name": "test_layer"}, }, ) - assert '{\n "message": "mock message"\n}\n' in result.output + assert json.loads(result.output) == message @pytest.mark.usefixtures("token_environ") @@ -61,9 +68,9 @@ def test_cli_create_success(mock_request_post): def test_cli_create_success_description(mock_request_post): runner = CliRunner() # sends request with "description" included - mock_request_post.return_value = MockResponse( - '{"message":"mock message with description"}' - ) + + message = {"message": "mock message with description"} + mock_request_post.return_value = MockResponse(message) result = runner.invoke( create, [ @@ -77,6 +84,7 @@ def test_cli_create_success_description(mock_request_post): ], ) assert result.exit_code == 0 + mock_request_post.assert_called_with( "https://api.mapbox.com/tilesets/v1/test.id?access_token=fake-token", json={ @@ -85,7 +93,7 @@ def test_cli_create_success_description(mock_request_post): "recipe": {"minzoom": 0, "maxzoom": 10, "layer_name": "test_layer"}, }, ) - assert '{\n "message": "mock message with description"\n}\n' in result.output + assert json.loads(result.output) == {"message": "mock message with description"} @pytest.mark.usefixtures("token_environ") @@ -119,7 +127,8 @@ def test_cli_create_private_invalid(mock_request_post): @mock.patch("requests.post") def test_cli_use_token_flag(mock_request_post): runner = CliRunner() - mock_request_post.return_value = MockResponse('{"message":"mock message"}') + message = {"message": "mock message"} + mock_request_post.return_value = MockResponse(message) # Provides the flag --token result = runner.invoke( create, @@ -142,4 +151,4 @@ def test_cli_use_token_flag(mock_request_post): "recipe": {"minzoom": 0, "maxzoom": 10, "layer_name": "test_layer"}, }, ) - assert '{\n "message": "mock message"\n}\n' in result.output + assert json.loads(result.output) == message diff --git a/tilesets/errors.py b/tilesets/errors.py index a100551..f70a4ac 100644 --- a/tilesets/errors.py +++ b/tilesets/errors.py @@ -17,3 +17,8 @@ def __init__(self, message): Error description """ self.message = message + + +class TilesetNameError(TilesetsError): + """Not a valid tileset id + """ diff --git a/tilesets/scripts/cli.py b/tilesets/scripts/cli.py index 353f48e..46a5ea9 100644 --- a/tilesets/scripts/cli.py +++ b/tilesets/scripts/cli.py @@ -81,15 +81,15 @@ def create(tileset, recipe, name=None, description=None, privacy=None, token=Non body["private"] = True if privacy == "private" else False if not utils.validate_tileset_id(tileset): - click.echo("Invalid tileset_id, format must match username.tileset") - sys.exit() + raise errors.TilesetNameError if recipe: with open(recipe) as json_recipe: body["recipe"] = json.load(json_recipe) r = requests.post(url, json=body) - utils.print_response(r.text) + + click.echo(json.dumps(r.json())) @cli.command("publish") From e346cb5af15f4f245afbd2b27cf6031c13692120 Mon Sep 17 00:00:00 2001 From: dnomadb Date: Fri, 27 Sep 2019 13:27:06 -0700 Subject: [PATCH 08/12] reformatting cli tests --- tests/conftest.py | 19 ++++++ tests/test_cli_create.py | 21 ++----- tests/test_cli_jobs.py | 54 +++++++---------- tests/test_cli_list.py | 48 ++++++--------- tests/test_cli_publish.py | 17 ++++-- tests/test_cli_sources.py | 57 +++++++----------- tests/test_cli_status.py | 31 ++++------ tests/test_cli_update_recipe.py | 18 ++---- tests/test_cli_validate_recipe.py | 18 +++--- tests/test_cli_view_recipe.py | 43 +++++++++----- tests/test_utils.py | 26 +------- tilesets/scripts/cli.py | 98 +++++++++++++++++-------------- tilesets/utils.py | 56 ------------------ 13 files changed, 204 insertions(+), 302 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 2718a2c..1276711 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,26 @@ import pytest +import json @pytest.fixture(scope="function") def token_environ(monkeypatch): monkeypatch.setenv("MAPBOX_ACCESS_TOKEN", "fake-token") monkeypatch.setenv("MapboxAccessToken", "test-token") + + +class _MockResponse: + def __init__(self, mock_json, status_code=200): + self.text = json.dumps(mock_json) + self._json = mock_json + self.status_code = status_code + + def MockResponse(self): + return self + + def json(self): + return self._json + + +@pytest.fixture +def MockResponse(): + return _MockResponse diff --git a/tests/test_cli_create.py b/tests/test_cli_create.py index 48c10e2..5d7e141 100644 --- a/tests/test_cli_create.py +++ b/tests/test_cli_create.py @@ -8,18 +8,6 @@ from tilesets.scripts.cli import create -class MockResponse: - def __init__(self, mock_json): - self.text = json.dumps(mock_json) - self._json = mock_json - - def MockResponse(self): - return self - - def json(self): - return self._json - - @pytest.mark.usefixtures("token_environ") def test_cli_create_missing_recipe(): runner = CliRunner() @@ -42,10 +30,11 @@ def test_cli_create_missing_name(): @pytest.mark.usefixtures("token_environ") @mock.patch("requests.post") -def test_cli_create_success(mock_request_post): +def test_cli_create_success(mock_request_post, MockResponse): runner = CliRunner() # sends request to proper endpoints message = {"message": "mock message"} + # print(helpers(message)) mock_request_post.return_value = MockResponse(message) result = runner.invoke( create, @@ -65,7 +54,7 @@ def test_cli_create_success(mock_request_post): @pytest.mark.usefixtures("token_environ") @mock.patch("requests.post") -def test_cli_create_success_description(mock_request_post): +def test_cli_create_success_description(mock_request_post, MockResponse): runner = CliRunner() # sends request with "description" included @@ -98,7 +87,7 @@ def test_cli_create_success_description(mock_request_post): @pytest.mark.usefixtures("token_environ") @mock.patch("requests.post") -def test_cli_create_private_invalid(mock_request_post): +def test_cli_create_private_invalid(mock_request_post, MockResponse): runner = CliRunner() # sends request with "description" included mock_request_post.return_value = MockResponse( @@ -125,7 +114,7 @@ def test_cli_create_private_invalid(mock_request_post): @pytest.mark.usefixtures("token_environ") @mock.patch("requests.post") -def test_cli_use_token_flag(mock_request_post): +def test_cli_use_token_flag(mock_request_post, MockResponse): runner = CliRunner() message = {"message": "mock message"} mock_request_post.return_value = MockResponse(message) diff --git a/tests/test_cli_jobs.py b/tests/test_cli_jobs.py index 2ee61b6..69ba9f3 100644 --- a/tests/test_cli_jobs.py +++ b/tests/test_cli_jobs.py @@ -1,88 +1,74 @@ -from unittest import mock +import json +import pytest +from unittest import mock from click.testing import CliRunner -import pytest from tilesets.scripts.cli import jobs, job -class MockResponse: - def __init__(self, mock_text): - self.text = mock_text - self.status_code = 200 - - def MockResponse(self): - return self - - -class MockResponseError: - def __init__(self, mock_text): - self.text = mock_text - self.status_code = 404 - - def MockResponse(self): - return self - - @pytest.mark.usefixtures("token_environ") @mock.patch("requests.get") -def test_cli_job(mock_request_get): +def test_cli_job(mock_request_get, MockResponse): runner = CliRunner() # sends expected request - mock_request_get.return_value = MockResponse('{"message":"mock message"}') + message = {"message": "mock message"} + mock_request_get.return_value = MockResponse(message) result = runner.invoke(jobs, ["test.id"]) mock_request_get.assert_called_with( "https://api.mapbox.com/tilesets/v1/test.id/jobs?access_token=fake-token" ) assert result.exit_code == 0 - assert '{\n "message": "mock message"\n}\n' in result.output + assert json.loads(result.output) == message +# noting for future that this test really is a copy of above @pytest.mark.usefixtures("token_environ") @mock.patch("requests.get") -def test_cli_job_error(mock_request_get): +def test_cli_job_error(mock_request_get, MockResponse): runner = CliRunner() # sends expected request - mock_request_get.return_value = MockResponseError( - '{"message":"mock error message"}' - ) + message = {"message": "mock error message"} + mock_request_get.return_value = MockResponse(message, status_code=404) result = runner.invoke(jobs, ["test.id"]) mock_request_get.assert_called_with( "https://api.mapbox.com/tilesets/v1/test.id/jobs?access_token=fake-token" ) assert result.exit_code == 0 - assert '{\n "message": "mock error message"\n}\n' in result.output + assert json.loads(result.output) == message @pytest.mark.usefixtures("token_environ") @mock.patch("requests.get") -def test_cli_jobs_and_stage(mock_request_get): +def test_cli_jobs_and_stage(mock_request_get, MockResponse): """test jobs + stage endpoint""" runner = CliRunner() # sends expected request - mock_request_get.return_value = MockResponse('{"message":"mock message"}') + message = {"message": "mock message"} + mock_request_get.return_value = MockResponse(message) result = runner.invoke(jobs, ["test.id", "--stage", "complete"]) mock_request_get.assert_called_with( "https://api.mapbox.com/tilesets/v1/test.id/jobs?stage=complete&access_token=fake-token" ) assert result.exit_code == 0 - assert '{\n "message": "mock message"\n}\n' in result.output + assert json.loads(result.output) == message @pytest.mark.usefixtures("token_environ") @mock.patch("requests.get") -def test_cli_single_job(mock_request_get): +def test_cli_single_job(mock_request_get, MockResponse): """test job endpoint""" runner = CliRunner() # sends expected request - mock_request_get.return_value = MockResponse('{"message":"mock message"}') + message = {"message": "mock message"} + mock_request_get.return_value = MockResponse(message) result = runner.invoke(job, ["test.id", "job_id"]) mock_request_get.assert_called_with( "https://api.mapbox.com/tilesets/v1/test.id/jobs/job_id?access_token=fake-token" ) assert result.exit_code == 0 - assert '{\n "message": "mock message"\n}\n' in result.output + assert json.loads(result.output) == message diff --git a/tests/test_cli_list.py b/tests/test_cli_list.py index d7b4bdd..1997c16 100644 --- a/tests/test_cli_list.py +++ b/tests/test_cli_list.py @@ -1,59 +1,47 @@ +import json +import pytest + from unittest import mock from click.testing import CliRunner -import pytest from tilesets.scripts.cli import list -class MockResponse: - def __init__(self, mock_text): - self.text = mock_text - self.status_code = 200 - - def MockResponse(self): - return self - - -class MockResponseError: - def __init__(self, mock_text): - self.text = mock_text - self.status_code = 404 - - def MockResponse(self): - return self - - @pytest.mark.usefixtures("token_environ") @mock.patch("requests.get") -def test_cli_list(mock_request_get): +def test_cli_list(mock_request_get, MockResponse): runner = CliRunner() + message = [ + {"id": "test.tileset-1", "something": "beep"}, + {"id": "test.tileset-2", "something": "boop"}, + ] # sends expected request - mock_request_get.return_value = MockResponse( - '[{"id":"test.tileset-1","something":"beep"},{"id":"test.tileset-2","something":"boop"}]' - ) + mock_request_get.return_value = MockResponse(message) result = runner.invoke(list, ["test"]) mock_request_get.assert_called_with( "https://api.mapbox.com/tilesets/v1/test?access_token=fake-token" ) assert result.exit_code == 0 - assert "test.tileset-1\ntest.tileset-2\n" in result.output + assert result.output == """test.tileset-1\ntest.tileset-2\n""" @pytest.mark.usefixtures("token_environ") @mock.patch("requests.get") -def test_cli_list_verbose(mock_request_get): +def test_cli_list_verbose(mock_request_get, MockResponse): runner = CliRunner() + message = [ + {"id": "test.tileset-1", "something": "beep"}, + {"id": "test.tileset-2", "something": "boop"}, + ] # sends expected request - mock_request_get.return_value = MockResponse( - '[{"id":"test.tileset-1","something":"beep"},{"id":"test.tileset-2","something":"boop"}]' - ) + mock_request_get.return_value = MockResponse(message) result = runner.invoke(list, ["test", "--verbose"]) mock_request_get.assert_called_with( "https://api.mapbox.com/tilesets/v1/test?access_token=fake-token" ) assert result.exit_code == 0 - assert '"something": "beep"\n' in result.output - assert '"something": "boop"\n' in result.output + + assert [json.loads(l.strip()) for l in result.output.split("\n") if l] == message diff --git a/tests/test_cli_publish.py b/tests/test_cli_publish.py index cdca6ab..a8376be 100644 --- a/tests/test_cli_publish.py +++ b/tests/test_cli_publish.py @@ -1,19 +1,24 @@ +import json +import pytest + from click.testing import CliRunner from unittest import mock -import pytest - from tilesets.scripts.cli import publish class MockResponse: - def __init__(self, mock_text, status_code): - self.text = mock_text + def __init__(self, mock_json, status_code): + self.text = json.dumps(mock_json) + self._json = mock_json self.status_code = status_code def MockResponse(self): return self + def json(self): + return self._json + @pytest.mark.usefixtures("token_environ") @mock.patch("requests.post") @@ -21,7 +26,7 @@ def test_cli_publish(mock_request_post): runner = CliRunner() # sends expected request - mock_request_post.return_value = MockResponse('{"message":"mock message"}', 200) + mock_request_post.return_value = MockResponse({"message": "mock message"}, 200) result = runner.invoke(publish, ["test.id"]) mock_request_post.assert_called_with( "https://api.mapbox.com/tilesets/v1/test.id/publish?access_token=fake-token" @@ -37,7 +42,7 @@ def test_cli_publish(mock_request_post): @mock.patch("requests.post") def test_cli_publish_use_token_flag(mock_request_post): runner = CliRunner() - mock_request_post.return_value = MockResponse('{"message":"mock message"}', 200) + mock_request_post.return_value = MockResponse({"message": "mock message"}, 200) # Provides the flag --token result = runner.invoke(publish, ["test.id", "--token", "flag-token"]) mock_request_post.assert_called_with( diff --git a/tests/test_cli_sources.py b/tests/test_cli_sources.py index 249974a..13615d2 100644 --- a/tests/test_cli_sources.py +++ b/tests/test_cli_sources.py @@ -13,24 +13,11 @@ ) -class MockResponse: - def __init__(self, mock_text, status_code): - self.text = mock_text - self.status_code = status_code - - def MockResponse(self): - return self - - def json(self): - return json.loads(self.text) - - @pytest.mark.usefixtures("token_environ") @mock.patch("requests.post") -def test_cli_add_source(mock_request_post): - mock_request_post.return_value = MockResponse( - '{"id":"mapbox://tileset-source/test-user/hello-world"}', 200 - ) +def test_cli_add_source(mock_request_post, MockResponse): + message = {"id": "mapbox://tileset-source/test-user/hello-world"} + mock_request_post.return_value = MockResponse(message, status_code=200) runner = CliRunner() result = runner.invoke( add_source, ["test-user", "hello-world", "tests/fixtures/valid.ldgeojson"] @@ -41,29 +28,24 @@ def test_cli_add_source(mock_request_post): result.output == """Adding Point feature to mapbox://tileset-source/test-user/hello-world\n{"id": "mapbox://tileset-source/test-user/hello-world"}\n""" ) - # assert result.output == """Adding Point feature to mapbox://tileset-source/test-user/hello-world\n\"{\\"id\\":\\"mapbox://tileset-source/test-user/hello-world\\"}\"\n - # """ @pytest.mark.usefixtures("token_environ") @mock.patch("requests.get") -def test_cli_view_source(mock_request_get): - mock_request_get.return_value = MockResponse( - '{"id":"mapbox://tileset-source/test-user/hello-world"}', 200 - ) +def test_cli_view_source(mock_request_get, MockResponse): + message = {"id": "mapbox://tileset-source/test-user/hello-world"} + mock_request_get.return_value = MockResponse(message, status_code=200) runner = CliRunner() result = runner.invoke(view_source, ["test-user", "hello-world"]) + assert result.exit_code == 0 - assert ( - result.output - == '{\n "id": "mapbox://tileset-source/test-user/hello-world"\n}\n' - ) + assert json.loads(result.output) == message @pytest.mark.usefixtures("token_environ") @mock.patch("requests.delete") -def test_cli_delete_source(mock_request_delete): - mock_request_delete.return_value = MockResponse("", 201) +def test_cli_delete_source(mock_request_delete, MockResponse): + mock_request_delete.return_value = MockResponse("", status_code=201) runner = CliRunner() result = runner.invoke(delete_source, ["test-user", "hello-world"]) assert result.exit_code == 0 @@ -72,17 +54,20 @@ def test_cli_delete_source(mock_request_delete): @pytest.mark.usefixtures("token_environ") @mock.patch("requests.get") -def test_cli_view_source_2(mock_request_get): - mock_request_get.return_value = MockResponse( - '[{"id":"mapbox://tileset-source/test-user/hello-world"},\ - {"id":"mapbox://tileset-source/test-user/hola-mundo"}]', - 200, - ) +def test_cli_view_source_2(mock_request_get, MockResponse): + message = [ + {"id": "mapbox://tileset-source/test-user/hello-world"}, + {"id": "mapbox://tileset-source/test-user/hola-mundo"}, + ] + mock_request_get.return_value = MockResponse(message, status_code=200) runner = CliRunner() result = runner.invoke(list_sources, ["test-user"]) + assert result.exit_code == 0 - assert "mapbox://tileset-source/test-user/hello-world" in result.output - assert "mapbox://tileset-source/test-user/hola-mundo" in result.output + assert ( + result.output + == "mapbox://tileset-source/test-user/hello-world\nmapbox://tileset-source/test-user/hola-mundo\n" + ) @pytest.mark.usefixtures("token_environ") diff --git a/tests/test_cli_status.py b/tests/test_cli_status.py index ef3111b..09e797d 100644 --- a/tests/test_cli_status.py +++ b/tests/test_cli_status.py @@ -1,44 +1,39 @@ +import json +import pytest + from click.testing import CliRunner from unittest import mock -import pytest - from tilesets.scripts.cli import status -class MockResponse: - def __init__(self, mock_text): - self.text = mock_text - self.status_code = 200 - - def MockResponse(self): - return self - - @pytest.mark.usefixtures("token_environ") @mock.patch("requests.get") -def test_cli_status(mock_request_get): +def test_cli_status(mock_request_get, MockResponse): runner = CliRunner() - + # helpers.help_me() # sends expected request - mock_request_get.return_value = MockResponse('{"message":"mock message"}') + message = {"message": "mock message"} + mock_request_get.return_value = MockResponse(message) result = runner.invoke(status, ["test.id"]) mock_request_get.assert_called_with( "https://api.mapbox.com/tilesets/v1/test.id/status?access_token=fake-token" ) assert result.exit_code == 0 - assert '{\n "message": "mock message"\n}\n' in result.output + assert json.loads(result.output) == message @pytest.mark.usefixtures("token_environ") @mock.patch("requests.get") -def test_cli_status_use_token_flag(mock_request_get): +def test_cli_status_use_token_flag(mock_request_get, MockResponse): runner = CliRunner() - mock_request_get.return_value = MockResponse('{"message":"mock message"}') + message = {"message": "mock message"} + mock_request_get.return_value = MockResponse(message) # Provides the flag --token result = runner.invoke(status, ["test.id", "--token", "flag-token"]) mock_request_get.assert_called_with( "https://api.mapbox.com/tilesets/v1/test.id/status?access_token=flag-token" ) + assert result.exit_code == 0 - assert '{\n "message": "mock message"\n}\n' in result.output + assert json.loads(result.output) == {"message": "mock message"} diff --git a/tests/test_cli_update_recipe.py b/tests/test_cli_update_recipe.py index 506b792..e3280ae 100644 --- a/tests/test_cli_update_recipe.py +++ b/tests/test_cli_update_recipe.py @@ -5,14 +5,6 @@ from tilesets.scripts.cli import update_recipe -class MockResponse: - def __init__(self): - self.status_code = 201 - - def MockResponse(self): - return self - - @pytest.mark.usefixtures("token_environ") def test_cli_update_recipe_no_recipe(): runner = CliRunner() @@ -23,25 +15,24 @@ def test_cli_update_recipe_no_recipe(): @pytest.mark.usefixtures("token_environ") @mock.patch("requests.patch") -def test_cli_update_recipe(mock_request_patch): +def test_cli_update_recipe(mock_request_patch, MockResponse): runner = CliRunner() # sends expected request - mock_request_patch.return_value = MockResponse() + mock_request_patch.return_value = MockResponse("", status_code=201) result = runner.invoke(update_recipe, ["test.id", "tests/fixtures/recipe.json"]) mock_request_patch.assert_called_with( "https://api.mapbox.com/tilesets/v1/test.id/recipe?access_token=fake-token", json={"minzoom": 0, "maxzoom": 10, "layer_name": "test_layer"}, ) assert result.exit_code == 0 - assert "Updated recipe." in result.output @pytest.mark.usefixtures("token_environ") @mock.patch("requests.patch") -def test_cli_update_recipe2(mock_request_patch): +def test_cli_update_recipe2(mock_request_patch, MockResponse): runner = CliRunner() - mock_request_patch.return_value = MockResponse() + mock_request_patch.return_value = MockResponse("", status_code=201) # Provides the flag --token result = runner.invoke( update_recipe, @@ -52,4 +43,3 @@ def test_cli_update_recipe2(mock_request_patch): json={"minzoom": 0, "maxzoom": 10, "layer_name": "test_layer"}, ) assert result.exit_code == 0 - assert "Updated recipe." in result.output diff --git a/tests/test_cli_validate_recipe.py b/tests/test_cli_validate_recipe.py index 8e56b81..c078945 100644 --- a/tests/test_cli_validate_recipe.py +++ b/tests/test_cli_validate_recipe.py @@ -1,6 +1,8 @@ +import json +import pytest + from click.testing import CliRunner from unittest import mock -import pytest from tilesets.scripts.cli import validate_recipe @@ -24,25 +26,27 @@ def test_cli_validate_recipe_no_recipe(): @pytest.mark.usefixtures("token_environ") @mock.patch("requests.put") -def test_cli_validate_recipe(mock_request_put): +def test_cli_validate_recipe(mock_request_put, MockResponse): runner = CliRunner() # sends expected request - mock_request_put.return_value = MockResponse('{"message":"mock message"}') + message = {"message": "mock message"} + mock_request_put.return_value = MockResponse(message) result = runner.invoke(validate_recipe, ["tests/fixtures/recipe.json"]) mock_request_put.assert_called_with( "https://api.mapbox.com/tilesets/v1/validateRecipe?access_token=fake-token", json={"minzoom": 0, "maxzoom": 10, "layer_name": "test_layer"}, ) assert result.exit_code == 0 - assert '{\n "message": "mock message"\n}\n' in result.output + assert json.loads(result.output) == message @pytest.mark.usefixtures("token_environ") @mock.patch("requests.put") -def test_cli_validate_recipe_use_token_flag(mock_request_put): +def test_cli_validate_recipe_use_token_flag(mock_request_put, MockResponse): runner = CliRunner() - mock_request_put.return_value = MockResponse('{"message":"mock message"}') + message = {"message": "mock message"} + mock_request_put.return_value = MockResponse(message) # Provides the flag --token result = runner.invoke( validate_recipe, ["tests/fixtures/recipe.json", "--token", "flag-token"] @@ -52,4 +56,4 @@ def test_cli_validate_recipe_use_token_flag(mock_request_put): json={"minzoom": 0, "maxzoom": 10, "layer_name": "test_layer"}, ) assert result.exit_code == 0 - assert '{\n "message": "mock message"\n}\n' in result.output + assert json.loads(result.output) == message diff --git a/tests/test_cli_view_recipe.py b/tests/test_cli_view_recipe.py index 274e8ee..abcbe40 100644 --- a/tests/test_cli_view_recipe.py +++ b/tests/test_cli_view_recipe.py @@ -1,43 +1,54 @@ +import json +import pytest + from click.testing import CliRunner from unittest import mock -import pytest from tilesets.scripts.cli import view_recipe -class MockResponse: - def __init__(self, mock_text): - self.text = mock_text - self.status_code = 200 - - def MockResponse(self): - return self - - @pytest.mark.usefixtures("token_environ") @mock.patch("requests.get") -def test_cli_view_recipe(mock_request_get): +def test_cli_view_recipe(mock_request_get, MockResponse): runner = CliRunner() # sends expected request - mock_request_get.return_value = MockResponse('{"fake":"recipe_data"}') + message = {"fake": "recipe_data"} + mock_request_get.return_value = MockResponse(message) result = runner.invoke(view_recipe, ["test.id"]) mock_request_get.assert_called_with( "https://api.mapbox.com/tilesets/v1/test.id/recipe?access_token=fake-token" ) assert result.exit_code == 0 - assert '{\n "fake": "recipe_data"\n}\n' in result.output + assert json.loads(result.output) == message @pytest.mark.usefixtures("token_environ") @mock.patch("requests.get") -def test_cli_view_recipe_use_token_flag(mock_request_get): +def test_cli_view_recipe_use_token_flag(mock_request_get, MockResponse): runner = CliRunner() - mock_request_get.return_value = MockResponse('{"fake":"recipe_data"}') + message = {"fake": "recipe_data"} + mock_request_get.return_value = MockResponse(message) # Provides the flag --token result = runner.invoke(view_recipe, ["test.id", "--token", "flag-token"]) mock_request_get.assert_called_with( "https://api.mapbox.com/tilesets/v1/test.id/recipe?access_token=flag-token" ) assert result.exit_code == 0 - assert '{\n "fake": "recipe_data"\n}\n' in result.output + assert json.loads(result.output) == message + + +@pytest.mark.usefixtures("token_environ") +@mock.patch("requests.get") +def test_cli_view_recipe_raises(mock_request_get, MockResponse): + runner = CliRunner() + + # sends expected request + mock_request_get.return_value = MockResponse("not found", status_code=404) + result = runner.invoke(view_recipe, ["test.id"]) + mock_request_get.assert_called_with( + "https://api.mapbox.com/tilesets/v1/test.id/recipe?access_token=fake-token" + ) + assert result.exit_code == 1 + + assert result.exception.message == '"not found"' diff --git a/tests/test_utils.py b/tests/test_utils.py index db64f06..7210e0c 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,28 +1,4 @@ -from tilesets.utils import format_response, validate_tileset_id - - -def test_format_response_json(): - response = '{"bada": "bing"}' - - assert format_response(response) == response - - -def test_format_response_indent_json(): - response = '{"bada": "bing"}' - - assert format_response(response, indent=2) == """{\n \"bada\": \"bing\"\n}""" - - -def test_format_response_indent_backwards_json(): - response = """{\n \"bada\": \"bing\"\n}""" - - assert format_response(response) == '{"bada": "bing"}' - - -def test_format_response_string(): - response = "hi" - - assert format_response(response) == response +from tilesets.utils import validate_tileset_id def test_validate_tileset_id(): diff --git a/tilesets/scripts/cli.py b/tilesets/scripts/cli.py index 46a5ea9..e4812a9 100644 --- a/tilesets/scripts/cli.py +++ b/tilesets/scripts/cli.py @@ -1,9 +1,7 @@ """Tilesets command line interface""" - import os import json import requests -import sys from io import BytesIO @@ -61,7 +59,10 @@ def cli(): help="set the tileset privacy options", ) @click.option("--token", "-t", required=False, type=str, help="Mapbox access token") -def create(tileset, recipe, name=None, description=None, privacy=None, token=None): +@click.option("--indent", type=int, default=None, help="Indent for JSON output") +def create( + tileset, recipe, name=None, description=None, privacy=None, token=None, indent=None +): """Create a new tileset with a recipe. $ tilesets create @@ -89,13 +90,14 @@ def create(tileset, recipe, name=None, description=None, privacy=None, token=Non r = requests.post(url, json=body) - click.echo(json.dumps(r.json())) + click.echo(json.dumps(r.json(), indent=indent)) @cli.command("publish") @click.argument("tileset", required=True, type=str) @click.option("--token", "-t", required=False, type=str, help="Mapbox access token") -def publish(tileset, token=None): +@click.option("--indent", type=int, default=None, help="Indent for JSON output") +def publish(tileset, token=None, indent=None): """Publish your tileset. tilesets publish @@ -107,18 +109,20 @@ def publish(tileset, token=None): ) r = requests.post(url) if r.status_code == 200: - utils.print_response(r.text) + click.echo(json.dumps(r.json(), indent=indent)) click.echo( - f"You can view the status of your tileset with the `tilesets status {tileset}` command." + f"You can view the status of your tileset with the `tilesets status {tileset}` command.", + err=True, ) else: - utils.print_response(r.text) + raise errors.TilesetsError(f"{r.text}") @cli.command("status") @click.argument("tileset", required=True, type=str) @click.option("--token", "-t", required=False, type=str, help="Mapbox access token") -def status(tileset, token=None): +@click.option("--indent", type=int, default=None, help="Indent for JSON output") +def status(tileset, token=None, indent=None): """View the current queue/processing/complete status of your tileset. tilesets status @@ -129,14 +133,16 @@ def status(tileset, token=None): mapbox_api, tileset, mapbox_token ) r = requests.get(url) - utils.print_response(r.text) + + click.echo(json.dumps(r.json(), indent=indent)) @cli.command("jobs") @click.argument("tileset", required=True, type=str) @click.option("--stage", "-s", required=False, type=str, help="job stage") @click.option("--token", "-t", required=False, type=str, help="Mapbox access token") -def jobs(tileset, stage, token=None): +@click.option("--indent", type=int, default=None, help="Indent for JSON output") +def jobs(tileset, stage, token=None, indent=None): """View all jobs for a particular tileset. tilesets jobs @@ -150,15 +156,18 @@ def jobs(tileset, stage, token=None): url = "{0}/tilesets/v1/{1}/jobs?stage={2}&access_token={3}".format( mapbox_api, tileset, stage, mapbox_token ) + r = requests.get(url) - utils.print_response(r.text) + + click.echo(json.dumps(r.json(), indent=indent)) @cli.command("job") @click.argument("tileset", required=True, type=str) @click.argument("job_id", required=True, type=str) @click.option("--token", "-t", required=False, type=str, help="Mapbox access token") -def job(tileset, job_id, token=None): +@click.option("--indent", type=int, default=None, help="Indent for JSON output") +def job(tileset, job_id, token=None, indent=None): """View a single job for a particular tileset. tilesets job @@ -169,7 +178,8 @@ def job(tileset, job_id, token=None): mapbox_api, tileset, job_id, mapbox_token ) r = requests.get(url) - utils.print_response(r.text) + + click.echo(json.dumps(r.json(), indent=indent)) @cli.command("list") @@ -182,7 +192,8 @@ def job(tileset, job_id, token=None): help="Will print all tileset information", ) @click.option("--token", "-t", required=False, type=str, help="Mapbox access token") -def list(username, verbose, token=None): +@click.option("--indent", type=int, default=None, help="Indent for JSON output") +def list(username, verbose, token=None, indent=None): """List all tilesets for an account. By default the response is a simple list of tileset IDs. If you would like an array of all tileset's information, @@ -198,19 +209,20 @@ def list(username, verbose, token=None): r = requests.get(url) if r.status_code == 200: if verbose: - utils.print_response(r.text) + for tileset in r.json(): + click.echo(json.dumps(tileset, indent=indent)) else: - j = json.loads(r.text) - for tileset in j: + for tileset in r.json(): click.echo(tileset["id"]) else: - click.echo(r.text) + raise errors.TilesetsError(r.txt) @cli.command("validate-recipe") @click.argument("recipe", required=True, type=click.Path(exists=True)) @click.option("--token", "-t", required=False, type=str, help="Mapbox access token") -def validate_recipe(recipe, token=None): +@click.option("--indent", type=int, default=None, help="Indent for JSON output") +def validate_recipe(recipe, token=None, indent=None): """Validate a Recipe JSON document tilesets validate-recipe @@ -221,19 +233,17 @@ def validate_recipe(recipe, token=None): mapbox_api, mapbox_token ) with open(recipe) as json_recipe: - try: - recipe_json = json.load(json_recipe) - except: - click.echo("Error: recipe is not valid json") - sys.exit() + recipe_json = json.load(json_recipe) + r = requests.put(url, json=recipe_json) - utils.print_response(r.text) + click.echo(json.dumps(r.json(), indent=indent)) @cli.command("view-recipe") @click.argument("tileset", required=True, type=str) @click.option("--token", "-t", required=False, type=str, help="Mapbox access token") -def view_recipe(tileset, token=None): +@click.option("--indent", type=int, default=None, help="Indent for JSON output") +def view_recipe(tileset, token=None, indent=None): """View a tileset's recipe JSON tilesets view-recipe @@ -245,16 +255,17 @@ def view_recipe(tileset, token=None): ) r = requests.get(url) if r.status_code == 200: - utils.print_response(r.text) + click.echo(json.dumps(r.json(), indent=indent)) else: - click.echo(r.text) + raise errors.TilesetsError(r.text) @cli.command("update-recipe") @click.argument("tileset", required=True, type=str) @click.argument("recipe", required=True, type=click.Path(exists=True)) @click.option("--token", "-t", required=False, type=str, help="Mapbox access token") -def update_recipe(tileset, recipe, token=None): +@click.option("--indent", type=int, default=None, help="Indent for JSON output") +def update_recipe(tileset, recipe, token=None, indent=None): """Update a Recipe JSON document for a particular tileset tilesets update-recipe @@ -265,17 +276,14 @@ def update_recipe(tileset, recipe, token=None): mapbox_api, tileset, mapbox_token ) with open(recipe) as json_recipe: - try: - recipe_json = json.load(json_recipe) - except: - click.echo("Error: recipe is not valid json") - sys.exit() + recipe_json = json.load(json_recipe) r = requests.patch(url, json=recipe_json) if r.status_code == 201: - click.echo("Updated recipe.") + click.echo("Updated recipe.", err=True) + click.echo(json.dumps(r.json(), indent=indent)) else: - utils.print_response(r.text) + raise errors.TilesetsError(r.text) @cli.command("validate-source") @@ -322,7 +330,7 @@ def add_source(ctx, username, id, features, no_validation, token=None, indent=No r = requests.post(url, files={"file": ("tileset-source", io)}) if r.status_code == 200: - click.echo(utils.format_response(r.text, indent=indent)) + click.echo(json.dumps(r.json(), indent=indent)) else: raise errors.TilesetsError(r.text) @@ -331,7 +339,8 @@ def add_source(ctx, username, id, features, no_validation, token=None, indent=No @click.argument("username", required=True, type=str) @click.argument("id", required=True, type=str) @click.option("--token", "-t", required=False, type=str, help="Mapbox access token") -def view_source(username, id, token=None): +@click.option("--indent", type=int, default=None, help="Indent for JSON output") +def view_source(username, id, token=None, indent=None): """View a Tileset Source's information tilesets view-source @@ -343,9 +352,9 @@ def view_source(username, id, token=None): ) r = requests.get(url) if r.status_code == 200: - utils.print_response(r.text) + click.echo(json.dumps(r.json(), indent=indent)) else: - click.echo(r.text) + raise errors.TilesetsError(r.text) @cli.command("delete-source") @@ -366,7 +375,7 @@ def delete_source(username, id, token=None): if r.status_code == 201: click.echo("Source deleted.") else: - utils.print_response(r.text) + raise errors.TilesetsError(r.text) @cli.command("list-sources") @@ -384,6 +393,7 @@ def list_sources(username, token=None): ) r = requests.get(url) if r.status_code == 200: - utils.print_response(r.text) + for source in r.json(): + click.echo(source["id"]) else: - click.echo(r.text) + raise errors.TilesetsError(r.text) diff --git a/tilesets/utils.py b/tilesets/utils.py index 81ba8b9..0cb3621 100644 --- a/tilesets/utils.py +++ b/tilesets/utils.py @@ -1,62 +1,6 @@ -import os -import click -import json import re from jsonschema import validate -from json.decoder import JSONDecodeError - -tileset_arg = click.argument("tileset", required=True, type=str) - - -def absoluteFilePaths(directory): - for dirpath, _, filenames in os.walk(directory): - for f in filenames: - yield os.path.abspath(os.path.join(dirpath, f)) - - -def flatten(files): - """takes a list of files or directories and converts - all directories into absolute file paths - """ - for f in files: - if os.path.isdir(f): - for dir_file in absoluteFilePaths(f): - yield dir_file - else: - yield f - - -def format_response(text, indent=None): - """Cleanly parse and serialize - different response types - - Parameters - ---------- - text: str - text to format - indent: int or None - Amount to indent formatted json - [default=None] - - Returns - ------- - text: str - formatted text - """ - try: - return json.dumps(json.loads(text), indent=indent, sort_keys=True) - except JSONDecodeError: - return text - - -def print_response(text): - try: - j = json.loads(text) - msg = json.dumps(j, indent=2, sort_keys=True) - click.echo(msg) - except: # tofix: bare except - click.echo("Failure \n" + text) def validate_tileset_id(tileset_id): From 936399658fa67ae91f1cb0afa5c607e3bb2424f9 Mon Sep 17 00:00:00 2001 From: dnomadb Date: Fri, 27 Sep 2019 13:34:42 -0700 Subject: [PATCH 09/12] remove typo --- tests/test_cli_create.py | 2 +- tests/test_cli_status.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_cli_create.py b/tests/test_cli_create.py index 5d7e141..ba96079 100644 --- a/tests/test_cli_create.py +++ b/tests/test_cli_create.py @@ -34,7 +34,7 @@ def test_cli_create_success(mock_request_post, MockResponse): runner = CliRunner() # sends request to proper endpoints message = {"message": "mock message"} - # print(helpers(message)) + mock_request_post.return_value = MockResponse(message) result = runner.invoke( create, diff --git a/tests/test_cli_status.py b/tests/test_cli_status.py index 09e797d..affa336 100644 --- a/tests/test_cli_status.py +++ b/tests/test_cli_status.py @@ -11,7 +11,7 @@ @mock.patch("requests.get") def test_cli_status(mock_request_get, MockResponse): runner = CliRunner() - # helpers.help_me() + # sends expected request message = {"message": "mock message"} mock_request_get.return_value = MockResponse(message) From 9427b079e0d4a12665d68a3d315057fa2bdf5b0f Mon Sep 17 00:00:00 2001 From: dnomadb Date: Fri, 27 Sep 2019 21:27:25 -0700 Subject: [PATCH 10/12] write sources then post --- tilesets/scripts/cli.py | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/tilesets/scripts/cli.py b/tilesets/scripts/cli.py index e4812a9..423c9c9 100644 --- a/tilesets/scripts/cli.py +++ b/tilesets/scripts/cli.py @@ -316,23 +316,27 @@ def add_source(ctx, username, id, features, no_validation, token=None, indent=No mapbox_api = _get_api() mapbox_token = _get_token(token) - for feature in features: - url = f"{mapbox_api}/tilesets/v1/sources/{username}/{id}?access_token={mapbox_token}" - if not no_validation: - utils.validate_geojson(feature) + with BytesIO() as io: + for feature in features: + url = f"{mapbox_api}/tilesets/v1/sources/{username}/{id}?access_token={mapbox_token}" + if not no_validation: + utils.validate_geojson(feature) - click.echo( - f"Adding {feature['geometry']['type']} feature to mapbox://tileset-source/{username}/{id}", - err=True, - ) + click.echo( + f"Adding {feature['geometry']['type']} feature to mapbox://tileset-source/{username}/{id}", + err=True, + ) - with BytesIO(json.dumps(feature).encode("utf-8")) as io: - r = requests.post(url, files={"file": ("tileset-source", io)}) + io.write((json.dumps(feature) + "\n").encode("utf-8")) - if r.status_code == 200: - click.echo(json.dumps(r.json(), indent=indent)) - else: - raise errors.TilesetsError(r.text) + io.seek(0) + + r = requests.post(url, files={"file": ("tileset-source", io)}) + + if r.status_code == 200: + click.echo(json.dumps(r.json(), indent=indent)) + else: + raise errors.TilesetsError(r.text) @cli.command("view-source") From 71edbdcd429e4f558b0f749bed3b5bd326436996 Mon Sep 17 00:00:00 2001 From: dnomadb Date: Mon, 30 Sep 2019 10:05:14 -0700 Subject: [PATCH 11/12] :shh: --- tests/test_cli_sources.py | 3 +-- tilesets/scripts/cli.py | 5 ----- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/test_cli_sources.py b/tests/test_cli_sources.py index 13615d2..0828771 100644 --- a/tests/test_cli_sources.py +++ b/tests/test_cli_sources.py @@ -25,8 +25,7 @@ def test_cli_add_source(mock_request_post, MockResponse): assert result.exit_code == 0 assert ( - result.output - == """Adding Point feature to mapbox://tileset-source/test-user/hello-world\n{"id": "mapbox://tileset-source/test-user/hello-world"}\n""" + result.output == """{"id": "mapbox://tileset-source/test-user/hello-world"}\n""" ) diff --git a/tilesets/scripts/cli.py b/tilesets/scripts/cli.py index 423c9c9..d4460bf 100644 --- a/tilesets/scripts/cli.py +++ b/tilesets/scripts/cli.py @@ -322,11 +322,6 @@ def add_source(ctx, username, id, features, no_validation, token=None, indent=No if not no_validation: utils.validate_geojson(feature) - click.echo( - f"Adding {feature['geometry']['type']} feature to mapbox://tileset-source/{username}/{id}", - err=True, - ) - io.write((json.dumps(feature) + "\n").encode("utf-8")) io.seek(0) From 7a5cef503bce590e82956fe569a1fd2f12bd3699 Mon Sep 17 00:00:00 2001 From: dnomadb Date: Mon, 30 Sep 2019 13:35:20 -0700 Subject: [PATCH 12/12] bump version --- CHANGELOG.md | 5 +++++ setup.py | 2 +- tilesets/__init__.py | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e734b4b..6074a74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 0.3.0 (2019-09-30) +- Feature input abstraction using `cligj` +- Logging refactor: default output is no compact JSON +- Informational printing (non-api responses) directed to stderr + ## 0.2.1 (20190-09-16) - Reformatting using `black` - More robust tileset id checking diff --git a/setup.py b/setup.py index 6871134..609b4f2 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ def read(fname): setup( name="tilesets-cli", - version="0.2.0", + version="0.3.0", description=u"CLI for interacting with and preparing data for the Tilesets API", long_description=long_description, classifiers=[], diff --git a/tilesets/__init__.py b/tilesets/__init__.py index 977883e..f20f554 100644 --- a/tilesets/__init__.py +++ b/tilesets/__init__.py @@ -1,3 +1,3 @@ """tilesets package""" -__version__ = "0.2.1" +__version__ = "0.3.0"