Skip to content

Commit 5fd3533

Browse files
author
Jake Pruitt
authored
Add upload-source command with --replace flag (#94)
1 parent f074a94 commit 5fd3533

File tree

3 files changed

+147
-7
lines changed

3 files changed

+147
-7
lines changed

README.md

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ export MAPBOX_ACCESS_TOKEN=my.token
3636
# Commands
3737

3838
* Tileset Sources
39-
* [`add-source`](#add-source)
39+
* [`upload-source`](#upload-source)
40+
* *deprecated* [`add-source`](#deprecated-add-source)
4041
* [`validate-source`](#validate-source)
4142
* [`view-source`](#view-source)
4243
* [`list-sources`](#list-source)
@@ -56,7 +57,37 @@ export MAPBOX_ACCESS_TOKEN=my.token
5657
* [`list`](#list)
5758
* [`tilejson`](#tilejson)
5859

59-
### add-source
60+
### upload-source
61+
62+
```shell
63+
tilesets upload-source <username> <id> <file>
64+
```
65+
66+
Uploads GeoJSON files to a source for tiling. Accepts line-delimited GeoJSON or GeoJSON feature collections as files or via `stdin`. The CLI automatically converts data to line-delimited GeoJSON prior to uploading. Can be used to add data to a source or to replace all of the data in a source with the `--replace` flag.
67+
68+
Flags:
69+
70+
* `--no-validation` [optional]: do not validate source data locally before uploading
71+
* `--replace` [optional]: delete all existing source data and replace with data from the file
72+
* `--quiet` [optional]: do not display an upload progress bar
73+
74+
Usage
75+
76+
```shell
77+
# single file
78+
tilesets upload-source <username> <id> ./file.geojson
79+
80+
# multiple files
81+
tilesets upload-source <username> <id> file-1.geojson file-4.geojson
82+
83+
# directory of files
84+
# Reading from a directory will not distinguish between GeoJSON files and non GeoJSON files. All source files will be run through our validator unless you pass the `--no-validation` flag.
85+
tilesets upload-source <username> <id> ./path/to/multiple/files/
86+
```
87+
88+
### *deprecated* add-source
89+
90+
*WARNING: add-source is maintained for legacy purposes. Please use the `upload-source` command instead.*
6091

6192
```shell
6293
tilesets add-source <username> <id> <file>

mapbox_tilesets/scripts/cli.py

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -489,17 +489,30 @@ def validate_source(features):
489489
click.echo("✔ valid")
490490

491491

492-
@cli.command("add-source")
492+
@cli.command("upload-source")
493493
@click.argument("username", required=True, type=str)
494494
@click.argument("id", required=True, type=str)
495495
@cligj.features_in_arg
496496
@click.option("--no-validation", is_flag=True, help="Bypass source file validation")
497497
@click.option("--quiet", is_flag=True, help="Don't show progress bar")
498+
@click.option(
499+
"--replace",
500+
is_flag=True,
501+
help="Replace the existing source with the new source file",
502+
)
498503
@click.option("--token", "-t", required=False, type=str, help="Mapbox access token")
499504
@click.option("--indent", type=int, default=None, help="Indent for JSON output")
500505
@click.pass_context
501-
def add_source(
502-
ctx, username, id, features, no_validation, quiet, token=None, indent=None
506+
def upload_source(
507+
ctx, username, id, features, no_validation, quiet, replace, token=None, indent=None
508+
):
509+
return _upload_source(
510+
ctx, username, id, features, no_validation, quiet, replace, token, indent
511+
)
512+
513+
514+
def _upload_source(
515+
ctx, username, id, features, no_validation, quiet, replace, token=None, indent=None
503516
):
504517
"""Create/add a tileset source
505518
@@ -512,6 +525,10 @@ def add_source(
512525
f"{mapbox_api}/tilesets/v1/sources/{username}/{id}?access_token={mapbox_token}"
513526
)
514527

528+
method = "post"
529+
if replace:
530+
method = "put"
531+
515532
with tempfile.TemporaryFile() as file:
516533
for feature in features:
517534
if not no_validation:
@@ -525,7 +542,7 @@ def add_source(
525542
m = MultipartEncoder(fields={"file": ("file", file)})
526543

527544
if quiet:
528-
resp = s.post(
545+
resp = getattr(s, method)(
529546
url,
530547
data=m,
531548
headers={
@@ -544,7 +561,7 @@ def callback(m):
544561
prog.update(0) # Step is 0 because we set pos above
545562

546563
monitor = MultipartEncoderMonitor(m, callback)
547-
resp = s.post(
564+
resp = getattr(s, method)(
548565
url,
549566
data=monitor,
550567
headers={
@@ -559,6 +576,27 @@ def callback(m):
559576
raise errors.TilesetsError(resp.text)
560577

561578

579+
@cli.command("add-source")
580+
@click.argument("username", required=True, type=str)
581+
@click.argument("id", required=True, type=str)
582+
@cligj.features_in_arg
583+
@click.option("--no-validation", is_flag=True, help="Bypass source file validation")
584+
@click.option("--quiet", is_flag=True, help="Don't show progress bar")
585+
@click.option("--token", "-t", required=False, type=str, help="Mapbox access token")
586+
@click.option("--indent", type=int, default=None, help="Indent for JSON output")
587+
@click.pass_context
588+
def add_source(
589+
ctx, username, id, features, no_validation, quiet, token=None, indent=None
590+
):
591+
"""Create/add/replace a tileset source
592+
593+
tilesets add-source <username> <id> <path/to/source/data>
594+
"""
595+
return _upload_source(
596+
ctx, username, id, features, no_validation, quiet, False, token, indent
597+
)
598+
599+
562600
@cli.command("view-source")
563601
@click.argument("username", required=True, type=str)
564602
@click.argument("id", required=True, type=str)

tests/test_cli_sources.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from mapbox_tilesets.scripts.cli import (
99
add_source,
10+
upload_source,
1011
view_source,
1112
delete_source,
1213
validate_source,
@@ -91,6 +92,76 @@ def test_cli_add_source_no_validation(mock_request_post, MockResponse):
9192
)
9293

9394

95+
@pytest.mark.usefixtures("token_environ")
96+
@mock.patch("mapbox_tilesets.scripts.cli.MultipartEncoder")
97+
@mock.patch("mapbox_tilesets.scripts.cli.MultipartEncoderMonitor")
98+
@mock.patch("requests.Session.put")
99+
def test_cli_upload_source_replace(
100+
mock_request_put,
101+
mock_multipart_encoder_monitor,
102+
mock_multipart_encoder,
103+
MockResponse,
104+
MockMultipartEncoding,
105+
):
106+
okay_response = {"id": "mapbox://tileset-source/test-user/hello-world"}
107+
mock_request_put.return_value = MockResponse(okay_response, status_code=200)
108+
109+
expected_json = b'{"type":"Feature","geometry":{"type":"Point","coordinates":[125.6,10.1]},"properties":{"name":"Dinagat Islands"}}\n'
110+
111+
def side_effect(fields):
112+
assert fields["file"][1].read() == expected_json
113+
return MockMultipartEncoding()
114+
115+
mock_multipart_encoder.side_effect = side_effect
116+
117+
runner = CliRunner()
118+
validated_result = runner.invoke(
119+
upload_source,
120+
["test-user", "hello-world", "tests/fixtures/valid.ldgeojson", "--replace"],
121+
)
122+
assert validated_result.exit_code == 0
123+
124+
assert (
125+
validated_result.output
126+
== """{"id": "mapbox://tileset-source/test-user/hello-world"}\n"""
127+
)
128+
129+
130+
@pytest.mark.usefixtures("token_environ")
131+
@mock.patch("mapbox_tilesets.scripts.cli.MultipartEncoder")
132+
@mock.patch("mapbox_tilesets.scripts.cli.MultipartEncoderMonitor")
133+
@mock.patch("requests.Session.put")
134+
def test_cli_upload_source_no_replace(
135+
mock_request_post,
136+
mock_multipart_encoder_monitor,
137+
mock_multipart_encoder,
138+
MockResponse,
139+
MockMultipartEncoding,
140+
):
141+
okay_response = {"id": "mapbox://tileset-source/test-user/hello-world"}
142+
mock_request_post.return_value = MockResponse(okay_response, status_code=200)
143+
144+
expected_json = b'{"type":"Feature","geometry":{"type":"Point","coordinates":[125.6,10.1]},"properties":{"name":"Dinagat Islands"}}\n'
145+
146+
def side_effect(fields):
147+
assert fields["file"][1].read() == expected_json
148+
return MockMultipartEncoding()
149+
150+
mock_multipart_encoder.side_effect = side_effect
151+
152+
runner = CliRunner()
153+
validated_result = runner.invoke(
154+
upload_source,
155+
["test-user", "hello-world", "tests/fixtures/valid.ldgeojson", "--replace"],
156+
)
157+
assert validated_result.exit_code == 0
158+
159+
assert (
160+
validated_result.output
161+
== """{"id": "mapbox://tileset-source/test-user/hello-world"}\n"""
162+
)
163+
164+
94165
@pytest.mark.usefixtures("token_environ")
95166
@mock.patch("requests.Session.get")
96167
def test_cli_view_source(mock_request_get, MockResponse):

0 commit comments

Comments
 (0)