mapillary-tools 0.13.3a1__tar.gz → 0.14.0a1__tar.gz

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.
Files changed (108) hide show
  1. {mapillary_tools-0.13.3a1/mapillary_tools.egg-info → mapillary_tools-0.14.0a1}/PKG-INFO +3 -2
  2. mapillary_tools-0.14.0a1/mapillary_tools/__init__.py +1 -0
  3. mapillary_tools-0.14.0a1/mapillary_tools/api_v4.py +372 -0
  4. mapillary_tools-0.14.0a1/mapillary_tools/authenticate.py +363 -0
  5. {mapillary_tools-0.13.3a1/mapillary_tools/geotag → mapillary_tools-0.14.0a1/mapillary_tools}/blackvue_parser.py +74 -54
  6. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/camm/camm_builder.py +55 -97
  7. mapillary_tools-0.14.0a1/mapillary_tools/camm/camm_parser.py +590 -0
  8. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/commands/__main__.py +11 -4
  9. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/commands/authenticate.py +8 -1
  10. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/commands/process.py +27 -51
  11. mapillary_tools-0.14.0a1/mapillary_tools/commands/process_and_upload.py +33 -0
  12. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/commands/sample_video.py +2 -3
  13. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/commands/upload.py +18 -9
  14. mapillary_tools-0.14.0a1/mapillary_tools/commands/video_process_and_upload.py +33 -0
  15. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/config.py +28 -12
  16. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/constants.py +46 -4
  17. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/exceptions.py +34 -35
  18. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/exif_read.py +158 -53
  19. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/exiftool_read.py +19 -5
  20. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/exiftool_read_video.py +12 -1
  21. mapillary_tools-0.14.0a1/mapillary_tools/exiftool_runner.py +77 -0
  22. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/geo.py +148 -107
  23. mapillary_tools-0.14.0a1/mapillary_tools/geotag/factory.py +298 -0
  24. mapillary_tools-0.14.0a1/mapillary_tools/geotag/geotag_from_generic.py +163 -0
  25. mapillary_tools-0.14.0a1/mapillary_tools/geotag/geotag_images_from_exif.py +60 -0
  26. mapillary_tools-0.14.0a1/mapillary_tools/geotag/geotag_images_from_exiftool.py +105 -0
  27. mapillary_tools-0.14.0a1/mapillary_tools/geotag/geotag_images_from_exiftool_both_image_and_video.py +77 -0
  28. mapillary_tools-0.14.0a1/mapillary_tools/geotag/geotag_images_from_gpx.py +150 -0
  29. mapillary_tools-0.14.0a1/mapillary_tools/geotag/geotag_images_from_gpx_file.py +72 -0
  30. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/geotag/geotag_images_from_nmea_file.py +4 -2
  31. mapillary_tools-0.14.0a1/mapillary_tools/geotag/geotag_images_from_video.py +90 -0
  32. mapillary_tools-0.14.0a1/mapillary_tools/geotag/geotag_videos_from_exiftool_video.py +151 -0
  33. mapillary_tools-0.14.0a1/mapillary_tools/geotag/geotag_videos_from_gpx.py +140 -0
  34. mapillary_tools-0.14.0a1/mapillary_tools/geotag/geotag_videos_from_video.py +165 -0
  35. mapillary_tools-0.14.0a1/mapillary_tools/geotag/options.py +159 -0
  36. {mapillary_tools-0.13.3a1/mapillary_tools/geotag → mapillary_tools-0.14.0a1/mapillary_tools/gpmf}/gpmf_parser.py +194 -171
  37. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/history.py +3 -11
  38. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/mp4/io_utils.py +0 -1
  39. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/mp4/mp4_sample_parser.py +11 -3
  40. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/mp4/simple_mp4_parser.py +0 -10
  41. mapillary_tools-0.14.0a1/mapillary_tools/process_geotag_properties.py +417 -0
  42. mapillary_tools-0.14.0a1/mapillary_tools/process_sequence_properties.py +697 -0
  43. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/sample_video.py +8 -15
  44. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/telemetry.py +24 -12
  45. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/types.py +80 -22
  46. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/upload.py +316 -298
  47. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/upload_api_v4.py +55 -122
  48. mapillary_tools-0.14.0a1/mapillary_tools/uploader.py +581 -0
  49. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/utils.py +26 -0
  50. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/video_data_extraction/extract_video_data.py +17 -36
  51. mapillary_tools-0.14.0a1/mapillary_tools/video_data_extraction/extractors/blackvue_parser.py +49 -0
  52. mapillary_tools-0.14.0a1/mapillary_tools/video_data_extraction/extractors/camm_parser.py +62 -0
  53. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/video_data_extraction/extractors/exiftool_runtime_parser.py +4 -1
  54. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/video_data_extraction/extractors/exiftool_xml_parser.py +1 -2
  55. mapillary_tools-0.14.0a1/mapillary_tools/video_data_extraction/extractors/gopro_parser.py +58 -0
  56. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1/mapillary_tools.egg-info}/PKG-INFO +3 -2
  57. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools.egg-info/SOURCES.txt +8 -5
  58. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/setup.py +1 -0
  59. mapillary_tools-0.13.3a1/mapillary_tools/__init__.py +0 -1
  60. mapillary_tools-0.13.3a1/mapillary_tools/api_v4.py +0 -151
  61. mapillary_tools-0.13.3a1/mapillary_tools/authenticate.py +0 -102
  62. mapillary_tools-0.13.3a1/mapillary_tools/camm/camm_parser.py +0 -342
  63. mapillary_tools-0.13.3a1/mapillary_tools/commands/process_and_upload.py +0 -19
  64. mapillary_tools-0.13.3a1/mapillary_tools/commands/video_process_and_upload.py +0 -19
  65. mapillary_tools-0.13.3a1/mapillary_tools/geotag/geotag_from_generic.py +0 -22
  66. mapillary_tools-0.13.3a1/mapillary_tools/geotag/geotag_images_from_exif.py +0 -141
  67. mapillary_tools-0.13.3a1/mapillary_tools/geotag/geotag_images_from_exiftool.py +0 -109
  68. mapillary_tools-0.13.3a1/mapillary_tools/geotag/geotag_images_from_exiftool_both_image_and_video.py +0 -93
  69. mapillary_tools-0.13.3a1/mapillary_tools/geotag/geotag_images_from_gpx.py +0 -225
  70. mapillary_tools-0.13.3a1/mapillary_tools/geotag/geotag_images_from_gpx_file.py +0 -153
  71. mapillary_tools-0.13.3a1/mapillary_tools/geotag/geotag_images_from_video.py +0 -90
  72. mapillary_tools-0.13.3a1/mapillary_tools/geotag/geotag_videos_from_exiftool_video.py +0 -145
  73. mapillary_tools-0.13.3a1/mapillary_tools/geotag/geotag_videos_from_video.py +0 -197
  74. mapillary_tools-0.13.3a1/mapillary_tools/geotag/utils.py +0 -26
  75. mapillary_tools-0.13.3a1/mapillary_tools/process_geotag_properties.py +0 -652
  76. mapillary_tools-0.13.3a1/mapillary_tools/process_sequence_properties.py +0 -345
  77. mapillary_tools-0.13.3a1/mapillary_tools/uploader.py +0 -439
  78. mapillary_tools-0.13.3a1/mapillary_tools/video_data_extraction/extractors/blackvue_parser.py +0 -34
  79. mapillary_tools-0.13.3a1/mapillary_tools/video_data_extraction/extractors/camm_parser.py +0 -38
  80. mapillary_tools-0.13.3a1/mapillary_tools/video_data_extraction/extractors/gopro_parser.py +0 -43
  81. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/LICENSE +0 -0
  82. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/MANIFEST.in +0 -0
  83. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/README.md +0 -0
  84. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/commands/__init__.py +0 -0
  85. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/commands/video_process.py +0 -0
  86. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/commands/zip.py +0 -0
  87. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/exif_write.py +0 -0
  88. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/ffmpeg.py +0 -0
  89. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/geotag/__init__.py +0 -0
  90. {mapillary_tools-0.13.3a1/mapillary_tools/geotag → mapillary_tools-0.14.0a1/mapillary_tools/gpmf}/gpmf_gps_filter.py +0 -0
  91. {mapillary_tools-0.13.3a1/mapillary_tools/geotag → mapillary_tools-0.14.0a1/mapillary_tools/gpmf}/gps_filter.py +0 -0
  92. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/ipc.py +0 -0
  93. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/mp4/__init__.py +0 -0
  94. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/mp4/construct_mp4_parser.py +0 -0
  95. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/mp4/simple_mp4_builder.py +0 -0
  96. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/video_data_extraction/cli_options.py +0 -0
  97. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/video_data_extraction/extractors/base_parser.py +0 -0
  98. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/video_data_extraction/extractors/generic_video_parser.py +0 -0
  99. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/video_data_extraction/extractors/gpx_parser.py +0 -0
  100. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/video_data_extraction/extractors/nmea_parser.py +0 -0
  101. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools/video_data_extraction/video_data_parser_factory.py +0 -0
  102. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools.egg-info/dependency_links.txt +0 -0
  103. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools.egg-info/entry_points.txt +0 -0
  104. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools.egg-info/requires.txt +0 -0
  105. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/mapillary_tools.egg-info/top_level.txt +0 -0
  106. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/requirements.txt +0 -0
  107. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/schema/image_description_schema.json +0 -0
  108. {mapillary_tools-0.13.3a1 → mapillary_tools-0.14.0a1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: mapillary_tools
3
- Version: 0.13.3a1
3
+ Version: 0.14.0a1
4
4
  Summary: Mapillary Image/Video Import Pipeline
5
5
  Home-page: https://github.com/mapillary/mapillary_tools
6
6
  Author: Mapillary
@@ -23,6 +23,7 @@ Dynamic: description
23
23
  Dynamic: description-content-type
24
24
  Dynamic: home-page
25
25
  Dynamic: license
26
+ Dynamic: license-file
26
27
  Dynamic: requires-dist
27
28
  Dynamic: requires-python
28
29
  Dynamic: summary
@@ -0,0 +1 @@
1
+ VERSION = "0.14.0a1"
@@ -0,0 +1,372 @@
1
+ from __future__ import annotations
2
+
3
+ import enum
4
+ import logging
5
+ import os
6
+ import ssl
7
+ import typing as T
8
+ from json import dumps
9
+
10
+ import requests
11
+ from requests.adapters import HTTPAdapter
12
+
13
+ LOG = logging.getLogger(__name__)
14
+ MAPILLARY_CLIENT_TOKEN = os.getenv(
15
+ "MAPILLARY_CLIENT_TOKEN", "MLY|5675152195860640|6b02c72e6e3c801e5603ab0495623282"
16
+ )
17
+ MAPILLARY_GRAPH_API_ENDPOINT = os.getenv(
18
+ "MAPILLARY_GRAPH_API_ENDPOINT", "https://graph.mapillary.com"
19
+ )
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"
28
+
29
+
30
+ class HTTPSystemCertsAdapter(HTTPAdapter):
31
+ """
32
+ This adapter uses the system's certificate store instead of the certifi module.
33
+
34
+ The implementation is based on the project https://pypi.org/project/pip-system-certs/,
35
+ which has a system-wide effect.
36
+ """
37
+
38
+ def init_poolmanager(self, *args, **kwargs):
39
+ ssl_context = ssl.create_default_context()
40
+ ssl_context.load_default_certs()
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: T.Union[str, bytes]
139
+ try:
140
+ data = _truncate(dumps(_sanitize(resp.json())))
141
+ except Exception:
142
+ data = _truncate(resp.content)
143
+
144
+ LOG.debug(f"HTTP {resp.status_code} ({resp.reason}): %s", data)
145
+
146
+
147
+ def readable_http_error(ex: requests.HTTPError) -> str:
148
+ req = ex.request
149
+ resp = ex.response
150
+
151
+ data: T.Union[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
+
199
+
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
+
234
+ return resp
235
+
236
+
237
+ def is_auth_error(resp: requests.Response) -> bool:
238
+ if resp.status_code in [401, 403]:
239
+ return True
240
+
241
+ if resp.status_code in [400]:
242
+ try:
243
+ error_body = resp.json()
244
+ except Exception:
245
+ error_body = {}
246
+
247
+ type = error_body.get("debug_info", {}).get("type")
248
+ if type in ["NotAuthorizedError"]:
249
+ return True
250
+
251
+ return False
252
+
253
+
254
+ def extract_auth_error_message(resp: requests.Response) -> str:
255
+ assert is_auth_error(resp), "has to be an auth error"
256
+
257
+ try:
258
+ error_body = resp.json()
259
+ except Exception:
260
+ error_body = {}
261
+
262
+ # from Graph APIs
263
+ message = error_body.get("error", {}).get("message")
264
+ if message is not None:
265
+ return str(message)
266
+
267
+ # from upload service
268
+ message = error_body.get("debug_info", {}).get("message")
269
+ if message is not None:
270
+ return str(message)
271
+
272
+ return resp.text
273
+
274
+
275
+ def get_upload_token(email: str, password: str) -> requests.Response:
276
+ resp = request_post(
277
+ f"{MAPILLARY_GRAPH_API_ENDPOINT}/login",
278
+ headers={"Authorization": f"OAuth {MAPILLARY_CLIENT_TOKEN}"},
279
+ json={"email": email, "password": password, "locale": "en_US"},
280
+ timeout=REQUESTS_TIMEOUT,
281
+ )
282
+ resp.raise_for_status()
283
+ return resp
284
+
285
+
286
+ def fetch_organization(
287
+ user_access_token: str, organization_id: T.Union[int, str]
288
+ ) -> requests.Response:
289
+ resp = request_get(
290
+ f"{MAPILLARY_GRAPH_API_ENDPOINT}/{organization_id}",
291
+ params={
292
+ "fields": ",".join(["slug", "description", "name"]),
293
+ },
294
+ headers={
295
+ "Authorization": f"OAuth {user_access_token}",
296
+ },
297
+ timeout=REQUESTS_TIMEOUT,
298
+ )
299
+ resp.raise_for_status()
300
+ return resp
301
+
302
+
303
+ def fetch_user_or_me(
304
+ user_access_token: str,
305
+ user_id: int | str | None = None,
306
+ ) -> requests.Response:
307
+ if user_id is None:
308
+ url = f"{MAPILLARY_GRAPH_API_ENDPOINT}/me"
309
+ else:
310
+ url = f"{MAPILLARY_GRAPH_API_ENDPOINT}/{user_id}"
311
+
312
+ resp = request_get(
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
+
323
+ resp.raise_for_status()
324
+ return resp
325
+
326
+
327
+ ActionType = T.Literal[
328
+ "upload_started_upload", "upload_finished_upload", "upload_failed_upload"
329
+ ]
330
+
331
+
332
+ def log_event(action_type: ActionType, properties: T.Dict) -> requests.Response:
333
+ resp = request_post(
334
+ f"{MAPILLARY_GRAPH_API_ENDPOINT}/logging",
335
+ json={
336
+ "action_type": action_type,
337
+ "properties": properties,
338
+ },
339
+ headers={
340
+ "Authorization": f"OAuth {MAPILLARY_CLIENT_TOKEN}",
341
+ },
342
+ timeout=REQUESTS_TIMEOUT,
343
+ )
344
+ resp.raise_for_status()
345
+ return resp
346
+
347
+
348
+ def finish_upload(
349
+ user_access_token: str,
350
+ file_handle: str,
351
+ cluster_filetype: ClusterFileType,
352
+ organization_id: int | str | None = None,
353
+ ) -> requests.Response:
354
+ data: dict[str, str | int] = {
355
+ "file_handle": file_handle,
356
+ "file_type": cluster_filetype.value,
357
+ }
358
+ if organization_id is not None:
359
+ data["organization_id"] = organization_id
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
+ )
369
+
370
+ resp.raise_for_status()
371
+
372
+ return resp