-
Notifications
You must be signed in to change notification settings - Fork 69
/
serve.py
152 lines (122 loc) · 4.5 KB
/
serve.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
"""scripts/serve.py
Use Flask development server to serve up raster files or database locally.
"""
from typing import Optional, Any, Tuple, Sequence, cast
import os
import tempfile
import logging
import click
import tqdm
from terracotta.scripts.click_types import RasterPattern, RasterPatternType
from terracotta.scripts.http_utils import find_open_port
logger = logging.getLogger(__name__)
@click.command(
"serve", short_help="Serve rasters through a local Flask development server."
)
@click.option(
"-d", "--database", required=False, default=None, help="Database to serve from."
)
@click.option(
"-r",
"--raster-pattern",
type=RasterPattern(),
required=False,
default=None,
help="A format pattern defining paths and keys of the raster files to serve.",
)
@click.option(
"--rgb-key",
default=None,
help="Key to use for RGB compositing [default: last key in pattern]. "
"Has no effect if -r/--raster-pattern is not given.",
)
@click.option("--debug", is_flag=True, default=False, help="Enable Flask debugging.")
@click.option("--profile", is_flag=True, default=False, help="Enable Flask profiling.")
@click.option(
"--database-provider",
default=None,
help="Specify the driver to use to read database [default: auto detect].",
)
@click.option(
"--allow-all-ips",
is_flag=True,
default=False,
help="Allow connections from outside IP addresses. Use with care!",
)
@click.option(
"--port",
type=click.INT,
default=None,
help="Port to use [default: first free port between 5000 and 5099].",
)
def serve(
database: Optional[str] = None,
raster_pattern: Optional[RasterPatternType] = None,
debug: bool = False,
profile: bool = False,
database_provider: Optional[str] = None,
allow_all_ips: bool = False,
port: Optional[int] = None,
rgb_key: Optional[str] = None,
) -> None:
"""Serve rasters through a local Flask development server.
Either -d/--database or -r/--raster-pattern must be given.
Example:
$ terracotta serve -r /path/to/rasters/{name}/{date}_{band}_{}.tif
The empty group {} is replaced by a wildcard matching anything (similar to * in glob patterns).
This command is a data exploration tool and not meant for production use. Deploy Terracotta as
a WSGI or serverless app instead.
"""
from terracotta import get_driver, update_settings
from terracotta.server import create_app
if (database is None) == (raster_pattern is None):
raise click.UsageError("Either --database or --raster-pattern must be given")
if raster_pattern is not None:
dbfile = tempfile.NamedTemporaryFile(suffix=".sqlite", delete=False)
dbfile.close()
keys, raster_files = raster_pattern
if rgb_key is not None:
if rgb_key not in keys:
raise click.BadParameter("RGB key not found in raster pattern")
# re-order keys
rgb_idx = keys.index(rgb_key)
def push_to_last(seq: Sequence[Any], index: int) -> Tuple[Any, ...]:
return (*seq[:index], *seq[index + 1 :], seq[index])
keys = list(push_to_last(keys, rgb_idx))
raster_files = {
push_to_last(k, rgb_idx): v for k, v in raster_files.items()
}
driver = get_driver(dbfile.name, provider="sqlite")
driver.create(keys)
with driver.connect():
click.echo("")
for key, filepath in tqdm.tqdm(
raster_files.items(), desc="Ingesting raster files"
):
driver.insert(key, filepath, skip_metadata=True)
click.echo("")
database = dbfile.name
database = cast(str, database)
update_settings(
DRIVER_PATH=database,
DRIVER_PROVIDER=database_provider,
DEBUG=debug,
FLASK_PROFILE=profile,
)
# ensure database can be connected to
driver = get_driver(database, provider=database_provider)
with driver.connect():
pass
# find suitable port
port_range = [port] if port is not None else range(5000, 5100)
port = find_open_port(port_range)
if port is None:
click.echo(
f"Could not find open port to bind to (ports tried: {port_range})", err=True
)
raise click.Abort()
host = "0.0.0.0" if allow_all_ips else "localhost"
server_app = create_app(debug=debug, profile=profile)
if os.environ.get("TC_TESTING"):
return
server_app.run(port=port, host=host, threaded=False) # pragma: no cover