mapillary-tools 0.14.0a2__py3-none-any.whl → 0.14.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mapillary_tools/__init__.py +1 -1
- mapillary_tools/api_v4.py +66 -262
- mapillary_tools/authenticate.py +54 -46
- mapillary_tools/blackvue_parser.py +79 -22
- mapillary_tools/commands/__main__.py +15 -16
- mapillary_tools/commands/upload.py +33 -4
- mapillary_tools/config.py +38 -17
- mapillary_tools/constants.py +127 -43
- mapillary_tools/exceptions.py +4 -0
- mapillary_tools/exif_read.py +2 -1
- mapillary_tools/exif_write.py +3 -1
- mapillary_tools/exiftool_read_video.py +52 -15
- mapillary_tools/exiftool_runner.py +4 -24
- mapillary_tools/ffmpeg.py +406 -232
- mapillary_tools/geo.py +16 -0
- mapillary_tools/geotag/__init__.py +0 -0
- mapillary_tools/geotag/base.py +8 -4
- mapillary_tools/geotag/factory.py +106 -89
- mapillary_tools/geotag/geotag_images_from_exiftool.py +27 -20
- mapillary_tools/geotag/geotag_images_from_gpx.py +7 -6
- mapillary_tools/geotag/geotag_images_from_video.py +35 -0
- mapillary_tools/geotag/geotag_videos_from_exiftool.py +61 -14
- mapillary_tools/geotag/geotag_videos_from_gpx.py +22 -9
- mapillary_tools/geotag/options.py +25 -3
- mapillary_tools/geotag/utils.py +9 -12
- mapillary_tools/geotag/video_extractors/base.py +1 -1
- mapillary_tools/geotag/video_extractors/exiftool.py +1 -1
- mapillary_tools/geotag/video_extractors/gpx.py +61 -70
- mapillary_tools/geotag/video_extractors/native.py +34 -31
- mapillary_tools/history.py +128 -8
- mapillary_tools/http.py +211 -0
- mapillary_tools/mp4/construct_mp4_parser.py +8 -2
- mapillary_tools/process_geotag_properties.py +47 -35
- mapillary_tools/process_sequence_properties.py +340 -325
- mapillary_tools/sample_video.py +8 -8
- mapillary_tools/serializer/description.py +587 -0
- mapillary_tools/serializer/gpx.py +132 -0
- mapillary_tools/types.py +44 -610
- mapillary_tools/upload.py +327 -352
- mapillary_tools/upload_api_v4.py +125 -72
- mapillary_tools/uploader.py +797 -216
- mapillary_tools/utils.py +57 -5
- {mapillary_tools-0.14.0a2.dist-info → mapillary_tools-0.14.1.dist-info}/METADATA +91 -34
- mapillary_tools-0.14.1.dist-info/RECORD +76 -0
- {mapillary_tools-0.14.0a2.dist-info → mapillary_tools-0.14.1.dist-info}/WHEEL +1 -1
- mapillary_tools-0.14.0a2.dist-info/RECORD +0 -72
- {mapillary_tools-0.14.0a2.dist-info → mapillary_tools-0.14.1.dist-info}/entry_points.txt +0 -0
- {mapillary_tools-0.14.0a2.dist-info → mapillary_tools-0.14.1.dist-info}/licenses/LICENSE +0 -0
- {mapillary_tools-0.14.0a2.dist-info → mapillary_tools-0.14.1.dist-info}/top_level.txt +0 -0
mapillary_tools/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
VERSION = "0.14.
|
|
1
|
+
VERSION = "0.14.1"
|
mapillary_tools/api_v4.py
CHANGED
|
@@ -3,12 +3,11 @@ from __future__ import annotations
|
|
|
3
3
|
import enum
|
|
4
4
|
import logging
|
|
5
5
|
import os
|
|
6
|
-
import ssl
|
|
7
6
|
import typing as T
|
|
8
|
-
from json import dumps
|
|
9
7
|
|
|
10
8
|
import requests
|
|
11
|
-
|
|
9
|
+
|
|
10
|
+
from . import http
|
|
12
11
|
|
|
13
12
|
LOG = logging.getLogger(__name__)
|
|
14
13
|
MAPILLARY_CLIENT_TOKEN = os.getenv(
|
|
@@ -17,221 +16,40 @@ MAPILLARY_CLIENT_TOKEN = os.getenv(
|
|
|
17
16
|
MAPILLARY_GRAPH_API_ENDPOINT = os.getenv(
|
|
18
17
|
"MAPILLARY_GRAPH_API_ENDPOINT", "https://graph.mapillary.com"
|
|
19
18
|
)
|
|
20
|
-
REQUESTS_TIMEOUT = 60 # 1 minutes
|
|
21
|
-
USE_SYSTEM_CERTS: bool = False
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class ClusterFileType(enum.Enum):
|
|
25
|
-
ZIP = "zip"
|
|
26
|
-
BLACKVUE = "mly_blackvue_video"
|
|
27
|
-
CAMM = "mly_camm_video"
|
|
19
|
+
REQUESTS_TIMEOUT: float = 60 # 1 minutes
|
|
28
20
|
|
|
29
21
|
|
|
30
|
-
class
|
|
22
|
+
class HTTPContentError(Exception):
|
|
31
23
|
"""
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
The implementation is based on the project https://pypi.org/project/pip-system-certs/,
|
|
35
|
-
which has a system-wide effect.
|
|
24
|
+
Raised when the HTTP response is ok (200) but the content is not as expected
|
|
25
|
+
e.g. not JSON or not a valid response.
|
|
36
26
|
"""
|
|
37
27
|
|
|
38
|
-
def
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
kwargs["ssl_context"] = ssl_context
|
|
42
|
-
|
|
43
|
-
super().init_poolmanager(*args, **kwargs)
|
|
44
|
-
|
|
45
|
-
def cert_verify(self, *args, **kwargs):
|
|
46
|
-
super().cert_verify(*args, **kwargs)
|
|
47
|
-
|
|
48
|
-
# By default Python requests uses the ca_certs from the certifi module
|
|
49
|
-
# But we want to use the certificate store instead.
|
|
50
|
-
# By clearing the ca_certs variable we force it to fall back on that behaviour (handled in urllib3)
|
|
51
|
-
if "conn" in kwargs:
|
|
52
|
-
conn = kwargs["conn"]
|
|
53
|
-
else:
|
|
54
|
-
conn = args[0]
|
|
55
|
-
|
|
56
|
-
conn.ca_certs = None
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
@T.overload
|
|
60
|
-
def _truncate(s: bytes, limit: int = 512) -> bytes: ...
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
@T.overload
|
|
64
|
-
def _truncate(s: str, limit: int = 512) -> str: ...
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def _truncate(s, limit=512):
|
|
68
|
-
if limit < len(s):
|
|
69
|
-
remaining = len(s) - limit
|
|
70
|
-
if isinstance(s, bytes):
|
|
71
|
-
return (
|
|
72
|
-
s[:limit]
|
|
73
|
-
+ b"..."
|
|
74
|
-
+ f"({remaining} more bytes truncated)".encode("utf-8")
|
|
75
|
-
)
|
|
76
|
-
else:
|
|
77
|
-
return str(s[:limit]) + f"...({remaining} more chars truncated)"
|
|
78
|
-
else:
|
|
79
|
-
return s
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def _sanitize(headers: T.Mapping[T.Any, T.Any]) -> T.Mapping[T.Any, T.Any]:
|
|
83
|
-
new_headers = {}
|
|
84
|
-
|
|
85
|
-
for k, v in headers.items():
|
|
86
|
-
if k.lower() in [
|
|
87
|
-
"authorization",
|
|
88
|
-
"cookie",
|
|
89
|
-
"x-fb-access-token",
|
|
90
|
-
"access-token",
|
|
91
|
-
"access_token",
|
|
92
|
-
"password",
|
|
93
|
-
"user_upload_token",
|
|
94
|
-
]:
|
|
95
|
-
new_headers[k] = "[REDACTED]"
|
|
96
|
-
else:
|
|
97
|
-
new_headers[k] = _truncate(v)
|
|
98
|
-
|
|
99
|
-
return new_headers
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def _log_debug_request(
|
|
103
|
-
method: str,
|
|
104
|
-
url: str,
|
|
105
|
-
json: dict | None = None,
|
|
106
|
-
params: dict | None = None,
|
|
107
|
-
headers: dict | None = None,
|
|
108
|
-
timeout: T.Any = None,
|
|
109
|
-
):
|
|
110
|
-
if logging.getLogger().getEffectiveLevel() <= logging.DEBUG:
|
|
111
|
-
return
|
|
112
|
-
|
|
113
|
-
msg = f"HTTP {method} {url}"
|
|
114
|
-
|
|
115
|
-
if USE_SYSTEM_CERTS:
|
|
116
|
-
msg += " (w/sys_certs)"
|
|
117
|
-
|
|
118
|
-
if json:
|
|
119
|
-
t = _truncate(dumps(_sanitize(json)))
|
|
120
|
-
msg += f" JSON={t}"
|
|
121
|
-
|
|
122
|
-
if params:
|
|
123
|
-
msg += f" PARAMS={_sanitize(params)}"
|
|
124
|
-
|
|
125
|
-
if headers:
|
|
126
|
-
msg += f" HEADERS={_sanitize(headers)}"
|
|
127
|
-
|
|
128
|
-
if timeout is not None:
|
|
129
|
-
msg += f" TIMEOUT={timeout}"
|
|
130
|
-
|
|
131
|
-
LOG.debug(msg)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
def _log_debug_response(resp: requests.Response):
|
|
135
|
-
if logging.getLogger().getEffectiveLevel() <= logging.DEBUG:
|
|
136
|
-
return
|
|
137
|
-
|
|
138
|
-
data: str | bytes
|
|
139
|
-
try:
|
|
140
|
-
data = _truncate(dumps(_sanitize(resp.json())))
|
|
141
|
-
except Exception:
|
|
142
|
-
data = _truncate(resp.content)
|
|
28
|
+
def __init__(self, message: str, response: requests.Response):
|
|
29
|
+
self.response = response
|
|
30
|
+
super().__init__(message)
|
|
143
31
|
|
|
144
|
-
LOG.debug(f"HTTP {resp.status_code} ({resp.reason}): %s", data)
|
|
145
32
|
|
|
33
|
+
class ClusterFileType(enum.Enum):
|
|
34
|
+
ZIP = "zip"
|
|
35
|
+
BLACKVUE = "mly_blackvue_video"
|
|
36
|
+
CAMM = "mly_camm_video"
|
|
37
|
+
MLY_BUNDLE_MANIFEST = "mly_bundle_manifest"
|
|
146
38
|
|
|
147
|
-
def readable_http_error(ex: requests.HTTPError) -> str:
|
|
148
|
-
req = ex.request
|
|
149
|
-
resp = ex.response
|
|
150
|
-
|
|
151
|
-
data: str | bytes
|
|
152
|
-
try:
|
|
153
|
-
data = _truncate(dumps(_sanitize(resp.json())))
|
|
154
|
-
except Exception:
|
|
155
|
-
data = _truncate(resp.content)
|
|
156
|
-
|
|
157
|
-
return f"{req.method} {resp.url} => {resp.status_code} ({resp.reason}): {str(data)}"
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def request_post(
|
|
161
|
-
url: str,
|
|
162
|
-
data: T.Any | None = None,
|
|
163
|
-
json: dict | None = None,
|
|
164
|
-
**kwargs,
|
|
165
|
-
) -> requests.Response:
|
|
166
|
-
global USE_SYSTEM_CERTS
|
|
167
|
-
|
|
168
|
-
_log_debug_request(
|
|
169
|
-
"POST",
|
|
170
|
-
url,
|
|
171
|
-
json=json,
|
|
172
|
-
params=kwargs.get("params"),
|
|
173
|
-
headers=kwargs.get("headers"),
|
|
174
|
-
timeout=kwargs.get("timeout"),
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
if USE_SYSTEM_CERTS:
|
|
178
|
-
with requests.Session() as session:
|
|
179
|
-
session.mount("https://", HTTPSystemCertsAdapter())
|
|
180
|
-
resp = session.post(url, data=data, json=json, **kwargs)
|
|
181
|
-
|
|
182
|
-
else:
|
|
183
|
-
try:
|
|
184
|
-
resp = requests.post(url, data=data, json=json, **kwargs)
|
|
185
|
-
except requests.exceptions.SSLError as ex:
|
|
186
|
-
if "SSLCertVerificationError" not in str(ex):
|
|
187
|
-
raise ex
|
|
188
|
-
USE_SYSTEM_CERTS = True
|
|
189
|
-
# HTTPSConnectionPool(host='graph.mapillary.com', port=443): Max retries exceeded with url: /login (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1018)')))
|
|
190
|
-
LOG.warning(
|
|
191
|
-
"SSL error occurred, falling back to system SSL certificates: %s", ex
|
|
192
|
-
)
|
|
193
|
-
return request_post(url, data=data, json=json, **kwargs)
|
|
194
|
-
|
|
195
|
-
_log_debug_response(resp)
|
|
196
|
-
|
|
197
|
-
return resp
|
|
198
39
|
|
|
40
|
+
def create_user_session(user_access_token: str) -> requests.Session:
|
|
41
|
+
session = http.Session()
|
|
42
|
+
session.headers["Authorization"] = f"OAuth {user_access_token}"
|
|
43
|
+
return session
|
|
199
44
|
|
|
200
|
-
def request_get(
|
|
201
|
-
url: str,
|
|
202
|
-
params: dict | None = None,
|
|
203
|
-
**kwargs,
|
|
204
|
-
) -> requests.Response:
|
|
205
|
-
global USE_SYSTEM_CERTS
|
|
206
|
-
|
|
207
|
-
_log_debug_request(
|
|
208
|
-
"GET",
|
|
209
|
-
url,
|
|
210
|
-
params=kwargs.get("params"),
|
|
211
|
-
headers=kwargs.get("headers"),
|
|
212
|
-
timeout=kwargs.get("timeout"),
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
if USE_SYSTEM_CERTS:
|
|
216
|
-
with requests.Session() as session:
|
|
217
|
-
session.mount("https://", HTTPSystemCertsAdapter())
|
|
218
|
-
resp = session.get(url, params=params, **kwargs)
|
|
219
|
-
else:
|
|
220
|
-
try:
|
|
221
|
-
resp = requests.get(url, params=params, **kwargs)
|
|
222
|
-
except requests.exceptions.SSLError as ex:
|
|
223
|
-
if "SSLCertVerificationError" not in str(ex):
|
|
224
|
-
raise ex
|
|
225
|
-
USE_SYSTEM_CERTS = True
|
|
226
|
-
# HTTPSConnectionPool(host='graph.mapillary.com', port=443): Max retries exceeded with url: /login (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1018)')))
|
|
227
|
-
LOG.warning(
|
|
228
|
-
"SSL error occurred, falling back to system SSL certificates: %s", ex
|
|
229
|
-
)
|
|
230
|
-
resp = request_get(url, params=params, **kwargs)
|
|
231
|
-
|
|
232
|
-
_log_debug_response(resp)
|
|
233
45
|
|
|
234
|
-
|
|
46
|
+
def create_client_session(disable_logging: bool = False) -> requests.Session:
|
|
47
|
+
session = http.Session()
|
|
48
|
+
session.headers["Authorization"] = f"OAuth {MAPILLARY_CLIENT_TOKEN}"
|
|
49
|
+
if disable_logging:
|
|
50
|
+
session.disable_logging_request = True
|
|
51
|
+
session.disable_logging_response = True
|
|
52
|
+
return session
|
|
235
53
|
|
|
236
54
|
|
|
237
55
|
def is_auth_error(resp: requests.Response) -> bool:
|
|
@@ -272,55 +90,42 @@ def extract_auth_error_message(resp: requests.Response) -> str:
|
|
|
272
90
|
return resp.text
|
|
273
91
|
|
|
274
92
|
|
|
275
|
-
def get_upload_token(
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
)
|
|
93
|
+
def get_upload_token(
|
|
94
|
+
client_session: requests.Session, email: str, password: str
|
|
95
|
+
) -> requests.Response:
|
|
96
|
+
url = f"{MAPILLARY_GRAPH_API_ENDPOINT}/login"
|
|
97
|
+
json_data = {"email": email, "password": password, "locale": "en_US"}
|
|
98
|
+
|
|
99
|
+
resp = client_session.post(url, json=json_data, timeout=REQUESTS_TIMEOUT)
|
|
282
100
|
resp.raise_for_status()
|
|
101
|
+
|
|
283
102
|
return resp
|
|
284
103
|
|
|
285
104
|
|
|
286
105
|
def fetch_organization(
|
|
287
|
-
|
|
106
|
+
user_session: requests.Session, organization_id: int | str
|
|
288
107
|
) -> requests.Response:
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
},
|
|
294
|
-
headers={
|
|
295
|
-
"Authorization": f"OAuth {user_access_token}",
|
|
296
|
-
},
|
|
297
|
-
timeout=REQUESTS_TIMEOUT,
|
|
298
|
-
)
|
|
108
|
+
url = f"{MAPILLARY_GRAPH_API_ENDPOINT}/{organization_id}"
|
|
109
|
+
params = {"fields": ",".join(["slug", "description", "name"])}
|
|
110
|
+
|
|
111
|
+
resp = user_session.get(url, params=params, timeout=REQUESTS_TIMEOUT)
|
|
299
112
|
resp.raise_for_status()
|
|
113
|
+
|
|
300
114
|
return resp
|
|
301
115
|
|
|
302
116
|
|
|
303
117
|
def fetch_user_or_me(
|
|
304
|
-
|
|
305
|
-
user_id: int | str | None = None,
|
|
118
|
+
user_session: requests.Session, user_id: int | str | None = None
|
|
306
119
|
) -> requests.Response:
|
|
307
120
|
if user_id is None:
|
|
308
121
|
url = f"{MAPILLARY_GRAPH_API_ENDPOINT}/me"
|
|
309
122
|
else:
|
|
310
123
|
url = f"{MAPILLARY_GRAPH_API_ENDPOINT}/{user_id}"
|
|
124
|
+
params = {"fields": ",".join(["id", "username"])}
|
|
311
125
|
|
|
312
|
-
resp =
|
|
313
|
-
url,
|
|
314
|
-
params={
|
|
315
|
-
"fields": ",".join(["id", "username"]),
|
|
316
|
-
},
|
|
317
|
-
headers={
|
|
318
|
-
"Authorization": f"OAuth {user_access_token}",
|
|
319
|
-
},
|
|
320
|
-
timeout=REQUESTS_TIMEOUT,
|
|
321
|
-
)
|
|
322
|
-
|
|
126
|
+
resp = user_session.get(url, params=params, timeout=REQUESTS_TIMEOUT)
|
|
323
127
|
resp.raise_for_status()
|
|
128
|
+
|
|
324
129
|
return resp
|
|
325
130
|
|
|
326
131
|
|
|
@@ -329,44 +134,43 @@ ActionType = T.Literal[
|
|
|
329
134
|
]
|
|
330
135
|
|
|
331
136
|
|
|
332
|
-
def log_event(
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
headers={
|
|
340
|
-
"Authorization": f"OAuth {MAPILLARY_CLIENT_TOKEN}",
|
|
341
|
-
},
|
|
342
|
-
timeout=REQUESTS_TIMEOUT,
|
|
343
|
-
)
|
|
137
|
+
def log_event(
|
|
138
|
+
client_session: requests.Session, action_type: ActionType, properties: dict
|
|
139
|
+
) -> requests.Response:
|
|
140
|
+
url = f"{MAPILLARY_GRAPH_API_ENDPOINT}/logging"
|
|
141
|
+
json_data = {"action_type": action_type, "properties": properties}
|
|
142
|
+
|
|
143
|
+
resp = client_session.post(url, json=json_data, timeout=REQUESTS_TIMEOUT)
|
|
344
144
|
resp.raise_for_status()
|
|
145
|
+
|
|
345
146
|
return resp
|
|
346
147
|
|
|
347
148
|
|
|
348
149
|
def finish_upload(
|
|
349
|
-
|
|
150
|
+
user_session: requests.Session,
|
|
350
151
|
file_handle: str,
|
|
351
152
|
cluster_filetype: ClusterFileType,
|
|
352
153
|
organization_id: int | str | None = None,
|
|
353
154
|
) -> requests.Response:
|
|
354
|
-
|
|
155
|
+
url = f"{MAPILLARY_GRAPH_API_ENDPOINT}/finish_upload"
|
|
156
|
+
json_data: dict[str, str | int] = {
|
|
355
157
|
"file_handle": file_handle,
|
|
356
158
|
"file_type": cluster_filetype.value,
|
|
357
159
|
}
|
|
358
160
|
if organization_id is not None:
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
resp = request_post(
|
|
362
|
-
f"{MAPILLARY_GRAPH_API_ENDPOINT}/finish_upload",
|
|
363
|
-
headers={
|
|
364
|
-
"Authorization": f"OAuth {user_access_token}",
|
|
365
|
-
},
|
|
366
|
-
json=data,
|
|
367
|
-
timeout=REQUESTS_TIMEOUT,
|
|
368
|
-
)
|
|
161
|
+
json_data["organization_id"] = organization_id
|
|
369
162
|
|
|
163
|
+
resp = user_session.post(url, json=json_data, timeout=REQUESTS_TIMEOUT)
|
|
370
164
|
resp.raise_for_status()
|
|
371
165
|
|
|
372
166
|
return resp
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def jsonify_response(resp: requests.Response) -> T.Any:
|
|
170
|
+
"""
|
|
171
|
+
Convert the response to JSON, raising HTTPContentError if the response is not JSON.
|
|
172
|
+
"""
|
|
173
|
+
try:
|
|
174
|
+
return resp.json()
|
|
175
|
+
except requests.JSONDecodeError as ex:
|
|
176
|
+
raise HTTPContentError("Invalid JSON response", resp) from ex
|
mapillary_tools/authenticate.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import getpass
|
|
4
|
-
import json
|
|
5
4
|
import logging
|
|
6
5
|
import re
|
|
7
6
|
import sys
|
|
@@ -11,7 +10,7 @@ import jsonschema
|
|
|
11
10
|
|
|
12
11
|
import requests
|
|
13
12
|
|
|
14
|
-
from . import api_v4, config, constants, exceptions,
|
|
13
|
+
from . import api_v4, config, constants, exceptions, http
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
LOG = logging.getLogger(__name__)
|
|
@@ -64,7 +63,7 @@ def authenticate(
|
|
|
64
63
|
LOG.info('Creating new profile: "%s"', profile_name)
|
|
65
64
|
|
|
66
65
|
if jwt:
|
|
67
|
-
user_items:
|
|
66
|
+
user_items: config.UserItem = {"user_upload_token": jwt}
|
|
68
67
|
user_items = _verify_user_auth(_validate_profile(user_items))
|
|
69
68
|
else:
|
|
70
69
|
user_items = _prompt_login(
|
|
@@ -78,18 +77,17 @@ def authenticate(
|
|
|
78
77
|
# TODO: print more user information
|
|
79
78
|
if profile_name in all_user_items:
|
|
80
79
|
LOG.info(
|
|
81
|
-
'Profile "%s" updated: %s', profile_name,
|
|
80
|
+
'Profile "%s" updated: %s', profile_name, http._sanitize(user_items)
|
|
82
81
|
)
|
|
83
82
|
else:
|
|
84
83
|
LOG.info(
|
|
85
|
-
'Profile "%s" created: %s', profile_name,
|
|
84
|
+
'Profile "%s" created: %s', profile_name, http._sanitize(user_items)
|
|
86
85
|
)
|
|
87
86
|
|
|
88
87
|
|
|
89
88
|
def fetch_user_items(
|
|
90
|
-
user_name: str | None = None,
|
|
91
|
-
|
|
92
|
-
) -> types.UserItem:
|
|
89
|
+
user_name: str | None = None, organization_key: str | None = None
|
|
90
|
+
) -> config.UserItem:
|
|
93
91
|
"""
|
|
94
92
|
Read user information from the config file,
|
|
95
93
|
or prompt the user to authenticate if the specified profile does not exist
|
|
@@ -129,18 +127,28 @@ def fetch_user_items(
|
|
|
129
127
|
|
|
130
128
|
assert profile_name is not None, "profile_name should be set"
|
|
131
129
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
'Uploading to profile "%s": %s', profile_name, api_v4._sanitize(user_items)
|
|
136
|
-
)
|
|
130
|
+
try:
|
|
131
|
+
LOG.info(f'Verifying profile "{profile_name}"...')
|
|
132
|
+
user_items = _verify_user_auth(_validate_profile(user_items))
|
|
137
133
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
user_items["user_upload_token"], organization_key
|
|
134
|
+
LOG.info(
|
|
135
|
+
f'Uploading to profile "{profile_name}": {user_items.get("MAPSettingsUsername")} (ID: {user_items.get("MAPSettingsUserKey")})'
|
|
141
136
|
)
|
|
142
|
-
|
|
143
|
-
|
|
137
|
+
|
|
138
|
+
if organization_key is not None:
|
|
139
|
+
with api_v4.create_user_session(user_items["user_upload_token"]) as session:
|
|
140
|
+
resp = api_v4.fetch_organization(session, organization_key)
|
|
141
|
+
data = api_v4.jsonify_response(resp)
|
|
142
|
+
LOG.info(
|
|
143
|
+
f"Uploading to organization: {data.get('name')} (ID: {data.get('id')})"
|
|
144
|
+
)
|
|
145
|
+
user_items["MAPOrganizationKey"] = data.get("id")
|
|
146
|
+
|
|
147
|
+
except requests.Timeout as ex:
|
|
148
|
+
raise exceptions.MapillaryUploadTimeoutError(str(ex)) from ex
|
|
149
|
+
|
|
150
|
+
except requests.ConnectionError as ex:
|
|
151
|
+
raise exceptions.MapillaryUploadConnectionError(str(ex)) from ex
|
|
144
152
|
|
|
145
153
|
return user_items
|
|
146
154
|
|
|
@@ -155,9 +163,9 @@ def _prompt(message: str) -> str:
|
|
|
155
163
|
return input()
|
|
156
164
|
|
|
157
165
|
|
|
158
|
-
def _validate_profile(user_items:
|
|
166
|
+
def _validate_profile(user_items: config.UserItem) -> config.UserItem:
|
|
159
167
|
try:
|
|
160
|
-
jsonschema.validate(user_items,
|
|
168
|
+
jsonschema.validate(user_items, config.UserItemSchema)
|
|
161
169
|
except jsonschema.ValidationError as ex:
|
|
162
170
|
raise exceptions.MapillaryBadParameterError(
|
|
163
171
|
f"Invalid profile format: {ex.message}"
|
|
@@ -165,30 +173,29 @@ def _validate_profile(user_items: types.UserItem) -> types.UserItem:
|
|
|
165
173
|
return user_items
|
|
166
174
|
|
|
167
175
|
|
|
168
|
-
def _verify_user_auth(user_items:
|
|
176
|
+
def _verify_user_auth(user_items: config.UserItem) -> config.UserItem:
|
|
169
177
|
"""
|
|
170
178
|
Verify that the user access token is valid
|
|
171
179
|
"""
|
|
172
180
|
if constants._AUTH_VERIFICATION_DISABLED:
|
|
173
181
|
return user_items
|
|
174
182
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
raise ex
|
|
183
|
+
with api_v4.create_user_session(user_items["user_upload_token"]) as session:
|
|
184
|
+
try:
|
|
185
|
+
resp = api_v4.fetch_user_or_me(session)
|
|
186
|
+
except requests.HTTPError as ex:
|
|
187
|
+
if api_v4.is_auth_error(ex.response):
|
|
188
|
+
message = api_v4.extract_auth_error_message(ex.response)
|
|
189
|
+
raise exceptions.MapillaryUploadUnauthorizedError(message)
|
|
190
|
+
else:
|
|
191
|
+
raise ex
|
|
185
192
|
|
|
186
|
-
|
|
193
|
+
data = api_v4.jsonify_response(resp)
|
|
187
194
|
|
|
188
195
|
return {
|
|
189
196
|
**user_items,
|
|
190
|
-
"MAPSettingsUsername":
|
|
191
|
-
"MAPSettingsUserKey":
|
|
197
|
+
"MAPSettingsUsername": data.get("username"),
|
|
198
|
+
"MAPSettingsUserKey": data.get("id"),
|
|
192
199
|
}
|
|
193
200
|
|
|
194
201
|
|
|
@@ -205,7 +212,7 @@ def _validate_profile_name(profile_name: str):
|
|
|
205
212
|
)
|
|
206
213
|
|
|
207
214
|
|
|
208
|
-
def _list_all_profiles(profiles: dict[str,
|
|
215
|
+
def _list_all_profiles(profiles: dict[str, config.UserItem]) -> None:
|
|
209
216
|
_echo("Existing Mapillary profiles:")
|
|
210
217
|
|
|
211
218
|
# Header
|
|
@@ -256,7 +263,7 @@ def _is_login_retryable(ex: requests.HTTPError) -> bool:
|
|
|
256
263
|
def _prompt_login(
|
|
257
264
|
user_email: str | None = None,
|
|
258
265
|
user_password: str | None = None,
|
|
259
|
-
) ->
|
|
266
|
+
) -> config.UserItem:
|
|
260
267
|
_enabled = _prompt_enabled()
|
|
261
268
|
|
|
262
269
|
if user_email is None:
|
|
@@ -275,20 +282,21 @@ def _prompt_login(
|
|
|
275
282
|
if user_password:
|
|
276
283
|
break
|
|
277
284
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
285
|
+
with api_v4.create_client_session() as session:
|
|
286
|
+
try:
|
|
287
|
+
resp = api_v4.get_upload_token(session, user_email, user_password)
|
|
288
|
+
except requests.HTTPError as ex:
|
|
289
|
+
if not _enabled:
|
|
290
|
+
raise ex
|
|
283
291
|
|
|
284
|
-
|
|
285
|
-
|
|
292
|
+
if _is_login_retryable(ex):
|
|
293
|
+
return _prompt_login()
|
|
286
294
|
|
|
287
|
-
|
|
295
|
+
raise ex
|
|
288
296
|
|
|
289
|
-
data =
|
|
297
|
+
data = api_v4.jsonify_response(resp)
|
|
290
298
|
|
|
291
|
-
user_items:
|
|
299
|
+
user_items: config.UserItem = {
|
|
292
300
|
"user_upload_token": str(data["access_token"]),
|
|
293
301
|
"MAPSettingsUserKey": str(data["user_id"]),
|
|
294
302
|
}
|