|
1 | 1 | """Tilesets command line interface""" |
| 2 | + |
| 3 | +import base64 |
2 | 4 | import builtins |
3 | 5 | import json |
| 6 | +import re |
4 | 7 | import tempfile |
5 | | -from urllib.parse import urlencode, urlparse, parse_qs |
| 8 | +from urllib.parse import parse_qs, urlencode, urlparse |
6 | 9 |
|
7 | 10 | import click |
8 | 11 | import cligj |
9 | | -import base64 |
10 | | -import re |
11 | 12 | from requests_toolbelt import MultipartEncoder, MultipartEncoderMonitor |
12 | 13 |
|
13 | 14 | import mapbox_tilesets |
14 | | -from mapbox_tilesets import utils, errors |
| 15 | +from mapbox_tilesets import errors, utils |
15 | 16 |
|
16 | 17 |
|
17 | 18 | @click.version_option(version=mapbox_tilesets.__version__, message="%(version)s") |
@@ -629,6 +630,103 @@ def callback(m): |
629 | 630 | raise errors.TilesetsError(resp.text) |
630 | 631 |
|
631 | 632 |
|
| 633 | +@cli.command("upload-raster-source") |
| 634 | +@click.argument("username", required=True, type=str) |
| 635 | +@click.argument("id", required=True, callback=validate_source_id, type=str) |
| 636 | +@click.argument("inputs", nargs=-1, required=True, type=click.File("r")) |
| 637 | +@click.option("--quiet", is_flag=True, help="Don't show progress bar") |
| 638 | +@click.option( |
| 639 | + "--replace", |
| 640 | + is_flag=True, |
| 641 | + help="Upload raster source file", |
| 642 | +) |
| 643 | +@click.option("--token", "-t", required=False, type=str, help="Mapbox access token") |
| 644 | +@click.option("--indent", type=int, default=None, help="Indent for JSON output") |
| 645 | +@click.pass_context |
| 646 | +def upload_source(ctx, username, id, inputs, quiet, replace, token=None, indent=None): |
| 647 | + """Create a new tileset source, or add data to an existing tileset source. |
| 648 | + Optionally, replace an existing tileset source. |
| 649 | +
|
| 650 | + tilesets upload-source <username> <source_id> <path/to/source/data> |
| 651 | + """ |
| 652 | + return _upload_source(ctx, username, id, inputs, quiet, replace, token, indent) |
| 653 | + |
| 654 | + |
| 655 | +def _upload_source(ctx, username, id, inputs, quiet, replace, token=None, indent=None): |
| 656 | + mapbox_api = utils._get_api() |
| 657 | + mapbox_token = utils._get_token(token) |
| 658 | + s = utils._get_session() |
| 659 | + url = ( |
| 660 | + f"{mapbox_api}/tilesets/v1/sources/{username}/{id}?access_token={mapbox_token}" |
| 661 | + ) |
| 662 | + |
| 663 | + method = "post" |
| 664 | + if replace: |
| 665 | + method = "put" |
| 666 | + |
| 667 | + # This does the decoding by hand instead of using pyjwt because |
| 668 | + # pyjwt rejects tokens that don't pad the base64 with = signs. |
| 669 | + token_parts = mapbox_token.split(".") |
| 670 | + if len(token_parts) < 2: |
| 671 | + raise errors.TilesetsError( |
| 672 | + f"Token {mapbox_token} does not contain a payload component" |
| 673 | + ) |
| 674 | + else: |
| 675 | + while len(token_parts[1]) % 4 != 0: |
| 676 | + token_parts[1] = token_parts[1] + "=" |
| 677 | + body = json.loads(base64.b64decode(token_parts[1])) |
| 678 | + if "u" in body: |
| 679 | + if username != body["u"]: |
| 680 | + raise errors.TilesetsError( |
| 681 | + f"Token username {body['u']} does not match username {username}" |
| 682 | + ) |
| 683 | + else: |
| 684 | + raise errors.TilesetsError( |
| 685 | + f"Token {mapbox_token} does not contain a username" |
| 686 | + ) |
| 687 | + |
| 688 | + if len(inputs) > 10: |
| 689 | + raise errors.TilesetsError("Maximum 10 files can be uploaded at once.") |
| 690 | + |
| 691 | + for item in inputs: |
| 692 | + m = MultipartEncoder( |
| 693 | + fields={"file": ("file", open(item.name, "rb"), "multipart/form-data")} |
| 694 | + ) |
| 695 | + if quiet: |
| 696 | + resp = getattr(s, method)( |
| 697 | + url, |
| 698 | + data=m, |
| 699 | + headers={ |
| 700 | + "Content-Disposition": "multipart/form-data", |
| 701 | + "Content-type": m.content_type, |
| 702 | + }, |
| 703 | + ) |
| 704 | + else: |
| 705 | + prog = click.progressbar( |
| 706 | + length=m.len, fill_char="=", width=0, label="upload progress" |
| 707 | + ) |
| 708 | + with prog: |
| 709 | + |
| 710 | + def callback(m): |
| 711 | + prog.pos = m.bytes_read |
| 712 | + prog.update(0) # Step is 0 because we set pos above |
| 713 | + |
| 714 | + monitor = MultipartEncoderMonitor(m, callback) |
| 715 | + resp = getattr(s, method)( |
| 716 | + url, |
| 717 | + data=monitor, |
| 718 | + headers={ |
| 719 | + "Content-Disposition": "multipart/form-data", |
| 720 | + "Content-type": monitor.content_type, |
| 721 | + }, |
| 722 | + ) |
| 723 | + |
| 724 | + if resp.status_code == 200: |
| 725 | + click.echo(json.dumps(resp.json(), indent=indent)) |
| 726 | + else: |
| 727 | + raise errors.TilesetsError(resp.text) |
| 728 | + |
| 729 | + |
632 | 730 | @cli.command("add-source", hidden=True) |
633 | 731 | @click.argument("username", required=True, type=str) |
634 | 732 | @click.argument("id", required=True, type=str) |
|
0 commit comments