geovisio 2.8.1__py3-none-any.whl → 2.10.0__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.
- geovisio/__init__.py +6 -1
- geovisio/config_app.py +16 -5
- geovisio/translations/ar/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/ar/LC_MESSAGES/messages.po +818 -0
- geovisio/translations/br/LC_MESSAGES/messages.po +1 -1
- geovisio/translations/da/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/da/LC_MESSAGES/messages.po +4 -3
- geovisio/translations/de/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/de/LC_MESSAGES/messages.po +55 -2
- geovisio/translations/el/LC_MESSAGES/messages.po +1 -1
- geovisio/translations/en/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/en/LC_MESSAGES/messages.po +193 -139
- geovisio/translations/eo/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/eo/LC_MESSAGES/messages.po +53 -4
- geovisio/translations/es/LC_MESSAGES/messages.po +1 -1
- geovisio/translations/fi/LC_MESSAGES/messages.po +1 -1
- geovisio/translations/fr/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/fr/LC_MESSAGES/messages.po +101 -6
- geovisio/translations/hu/LC_MESSAGES/messages.po +1 -1
- geovisio/translations/it/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/it/LC_MESSAGES/messages.po +63 -3
- geovisio/translations/ja/LC_MESSAGES/messages.po +1 -1
- geovisio/translations/ko/LC_MESSAGES/messages.po +1 -1
- geovisio/translations/messages.pot +185 -129
- geovisio/translations/nl/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/nl/LC_MESSAGES/messages.po +421 -86
- geovisio/translations/oc/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/oc/LC_MESSAGES/messages.po +818 -0
- geovisio/translations/pl/LC_MESSAGES/messages.po +1 -1
- geovisio/translations/sv/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/sv/LC_MESSAGES/messages.po +823 -0
- geovisio/translations/ti/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/ti/LC_MESSAGES/messages.po +762 -0
- geovisio/translations/zh_Hant/LC_MESSAGES/messages.po +1 -1
- geovisio/utils/annotations.py +183 -0
- geovisio/utils/auth.py +14 -13
- geovisio/utils/cql2.py +134 -0
- geovisio/utils/db.py +7 -0
- geovisio/utils/fields.py +38 -9
- geovisio/utils/items.py +44 -0
- geovisio/utils/model_query.py +4 -4
- geovisio/utils/pic_shape.py +63 -0
- geovisio/utils/pictures.py +164 -29
- geovisio/utils/reports.py +10 -17
- geovisio/utils/semantics.py +196 -57
- geovisio/utils/sentry.py +1 -2
- geovisio/utils/sequences.py +191 -93
- geovisio/utils/tags.py +31 -0
- geovisio/utils/upload_set.py +287 -209
- geovisio/utils/website.py +1 -1
- geovisio/web/annotations.py +346 -9
- geovisio/web/auth.py +1 -1
- geovisio/web/collections.py +73 -54
- geovisio/web/configuration.py +26 -5
- geovisio/web/docs.py +143 -11
- geovisio/web/items.py +232 -155
- geovisio/web/map.py +25 -13
- geovisio/web/params.py +55 -52
- geovisio/web/pictures.py +34 -0
- geovisio/web/stac.py +19 -12
- geovisio/web/tokens.py +49 -1
- geovisio/web/upload_set.py +148 -37
- geovisio/web/users.py +4 -4
- geovisio/web/utils.py +2 -2
- geovisio/workers/runner_pictures.py +190 -24
- {geovisio-2.8.1.dist-info → geovisio-2.10.0.dist-info}/METADATA +27 -26
- geovisio-2.10.0.dist-info/RECORD +105 -0
- {geovisio-2.8.1.dist-info → geovisio-2.10.0.dist-info}/WHEEL +1 -1
- geovisio-2.8.1.dist-info/RECORD +0 -92
- {geovisio-2.8.1.dist-info → geovisio-2.10.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,7 +2,7 @@ from fs.path import dirname
|
|
|
2
2
|
from PIL import Image, ImageOps
|
|
3
3
|
from flask import current_app
|
|
4
4
|
from geovisio import utils
|
|
5
|
-
from geovisio.utils import db, sequences, upload_set
|
|
5
|
+
from geovisio.utils import db, semantics, sequences, upload_set
|
|
6
6
|
import psycopg
|
|
7
7
|
from psycopg.rows import dict_row
|
|
8
8
|
from psycopg.sql import SQL
|
|
@@ -17,13 +17,11 @@ from typing import Any, Dict, Optional
|
|
|
17
17
|
import threading
|
|
18
18
|
from uuid import UUID
|
|
19
19
|
from croniter import croniter
|
|
20
|
-
from datetime import datetime, timezone
|
|
20
|
+
from datetime import datetime, timezone, timedelta
|
|
21
21
|
import geovisio.utils.filesystems
|
|
22
22
|
|
|
23
23
|
log = logging.getLogger("geovisio.runner_pictures")
|
|
24
24
|
|
|
25
|
-
PROCESS_MAX_RETRY = 5 # Number of times a job will be retryed if there is a `RecoverableProcessException` during process (like if the blurring api is not reachable).
|
|
26
|
-
|
|
27
25
|
|
|
28
26
|
class PictureBackgroundProcessor(object):
|
|
29
27
|
def __init__(self, app):
|
|
@@ -50,12 +48,17 @@ class PictureBackgroundProcessor(object):
|
|
|
50
48
|
worker = PictureProcessor(app=current_app)
|
|
51
49
|
return self.executor.submit(worker.process_jobs)
|
|
52
50
|
|
|
51
|
+
def stop(self):
|
|
52
|
+
if self.enabled:
|
|
53
|
+
self.executor.shutdown(cancel_futures=True, wait=True)
|
|
54
|
+
|
|
53
55
|
|
|
54
56
|
class ProcessTask(str, Enum):
|
|
55
57
|
prepare = "prepare"
|
|
56
58
|
delete = "delete"
|
|
57
59
|
dispatch = "dispatch"
|
|
58
60
|
finalize = "finalize"
|
|
61
|
+
read_metadata = "read_metadata"
|
|
59
62
|
|
|
60
63
|
|
|
61
64
|
@dataclass
|
|
@@ -63,6 +66,7 @@ class DbPicture:
|
|
|
63
66
|
id: UUID
|
|
64
67
|
metadata: dict
|
|
65
68
|
skip_blurring: bool
|
|
69
|
+
orientation: str
|
|
66
70
|
|
|
67
71
|
def blurred_by_author(self):
|
|
68
72
|
return self.metadata.get("blurredByAuthor", False)
|
|
@@ -88,6 +92,8 @@ class DbJob:
|
|
|
88
92
|
seq: Optional[DbSequence]
|
|
89
93
|
|
|
90
94
|
task: ProcessTask
|
|
95
|
+
args: Optional[Dict[Any, Any]] = None
|
|
96
|
+
warning: Optional[str] = None
|
|
91
97
|
|
|
92
98
|
def label(self):
|
|
93
99
|
impacted_object = ""
|
|
@@ -103,7 +109,83 @@ class DbJob:
|
|
|
103
109
|
return f"{self.task} for {impacted_object}"
|
|
104
110
|
|
|
105
111
|
|
|
106
|
-
def
|
|
112
|
+
def store_detection_semantics(job: DbJob, metadata: Dict[str, Any], store_id: bool):
|
|
113
|
+
"""store the detection returned by the blurring API in the database.
|
|
114
|
+
|
|
115
|
+
The semantics part is stored as annotations, linked to the default account.
|
|
116
|
+
|
|
117
|
+
The blurring id, which could be used to unblur the picture later, is stored in a separate column?
|
|
118
|
+
|
|
119
|
+
Note that all old semantics tags are removed, and to know this, we check the `service_name` field returned by the blurring API, and the special qualifier tag
|
|
120
|
+
`detection_model` that is formated like a user-agent.
|
|
121
|
+
So we delete all old tags (and related qualifiers) o
|
|
122
|
+
"""
|
|
123
|
+
from geovisio.utils import annotations
|
|
124
|
+
|
|
125
|
+
tags = metadata.pop("annotations", [])
|
|
126
|
+
|
|
127
|
+
with job.reporting_conn.cursor() as cursor:
|
|
128
|
+
blurring_id = metadata.get("blurring_id")
|
|
129
|
+
if blurring_id and store_id:
|
|
130
|
+
# we store the blurring id to be able to unblur the picture later
|
|
131
|
+
cursor.execute(
|
|
132
|
+
"UPDATE pictures SET blurring_id = %(blurring_id)s WHERE id = %(id)s",
|
|
133
|
+
{"blurring_id": blurring_id, "id": job.pic.id},
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
if not tags:
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
default_account_id = cursor.execute("SELECT id from accounts where is_default = true").fetchone()
|
|
140
|
+
if not default_account_id:
|
|
141
|
+
log.error("Impossible to find a default account, cannot add semantics from blurring api")
|
|
142
|
+
default_account_id = default_account_id[0]
|
|
143
|
+
|
|
144
|
+
# we want to remove all the tags added by the same bluring api previously
|
|
145
|
+
# it's especially usefull when a picture is blurred multiple times
|
|
146
|
+
# and if the detection model has been updated between the blurrings
|
|
147
|
+
semantics.delete_annotation_tags_from_service(job.reporting_conn, job.pic.id, service_name="SGBlur", account=default_account_id)
|
|
148
|
+
try:
|
|
149
|
+
annotations_to_create = [
|
|
150
|
+
annotations.AnnotationCreationParameter(**t, account_id=default_account_id, picture_id=job.pic.id) for t in tags
|
|
151
|
+
]
|
|
152
|
+
for a in annotations_to_create:
|
|
153
|
+
annotations.creation_annotation(a, job.reporting_conn)
|
|
154
|
+
except Exception as e:
|
|
155
|
+
# if the detections are not in the correct format, we skip them
|
|
156
|
+
msg = errors.getMessageFromException(e)
|
|
157
|
+
if hasattr(e, "payload"):
|
|
158
|
+
msg += f": {e.payload}"
|
|
159
|
+
log.error(f"impossible to save blurring detections, skipping it for picture {job.pic.id}: {msg}")
|
|
160
|
+
job.warning = msg
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def update_picture_orientation(conn: psycopg.Connection, db_pic: DbPicture, picturePillow: Image):
|
|
164
|
+
"""if the picture is side oriented, we need to check if the blurring API has rotated the picture, and update its size"""
|
|
165
|
+
if db_pic.orientation not in ("6", "8"):
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
new_size = utils.pictures.getPictureSizing(picturePillow)
|
|
169
|
+
if new_size["width"] != db_pic.metadata["width"] or new_size["height"] != db_pic.metadata["height"]:
|
|
170
|
+
with conn.cursor() as cursor:
|
|
171
|
+
# update the new X/Y dimensions and reset the orientation, to tell that it's no longer side oriented
|
|
172
|
+
cursor.execute(
|
|
173
|
+
"""UPDATE pictures SET
|
|
174
|
+
exif = exif - 'Exif.Image.Orientation' || jsonb_build_object('Exif.Photo.PixelXDimension', %(width)s, 'Exif.Photo.PixelYDimension', %(height)s),
|
|
175
|
+
metadata = metadata || jsonb_build_object('width', %(width)s, 'height', %(height)s, 'cols', %(cols)s, 'rows', %(rows)s)
|
|
176
|
+
WHERE id = %(id)s
|
|
177
|
+
""",
|
|
178
|
+
{
|
|
179
|
+
"width": new_size["width"],
|
|
180
|
+
"height": new_size["height"],
|
|
181
|
+
"id": db_pic.id,
|
|
182
|
+
"cols": new_size["cols"],
|
|
183
|
+
"rows": new_size["rows"],
|
|
184
|
+
},
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def processPictureFiles(job: DbJob, config):
|
|
107
189
|
"""Generates the files associated with a sequence picture.
|
|
108
190
|
|
|
109
191
|
If needed the image is blurred before the tiles and thumbnail are generated.
|
|
@@ -117,6 +199,7 @@ def processPictureFiles(pic: DbPicture, config):
|
|
|
117
199
|
config : dict
|
|
118
200
|
Flask app.config (passed as param to allow using ThreadPoolExecutor)
|
|
119
201
|
"""
|
|
202
|
+
pic = job.pic
|
|
120
203
|
skipBlur = pic.skip_blurring or config.get("API_BLUR_URL") is None
|
|
121
204
|
fses = config["FILESYSTEMS"]
|
|
122
205
|
fs = fses.permanent if skipBlur else fses.tmp
|
|
@@ -140,15 +223,27 @@ def processPictureFiles(pic: DbPicture, config):
|
|
|
140
223
|
if not skipBlur:
|
|
141
224
|
with sentry_sdk.start_span(description="Blurring picture"):
|
|
142
225
|
try:
|
|
143
|
-
|
|
226
|
+
res = utils.pictures.createBlurredHDPicture(
|
|
144
227
|
fses.permanent,
|
|
145
228
|
config.get("API_BLUR_URL"),
|
|
146
229
|
pictureBytes,
|
|
147
230
|
picHdPath,
|
|
231
|
+
keep_unblured_parts=config["PICTURE_PROCESS_KEEP_UNBLURRED_PARTS"],
|
|
148
232
|
)
|
|
149
233
|
except Exception as e:
|
|
150
|
-
|
|
151
|
-
|
|
234
|
+
msg = errors.getMessageFromException(e)
|
|
235
|
+
log.error(f"impossible to blur picture {pic.id}: {msg}")
|
|
236
|
+
raise RecoverableProcessException("Blur API failure: " + msg) from e
|
|
237
|
+
if res is None:
|
|
238
|
+
picture = None
|
|
239
|
+
else:
|
|
240
|
+
picture = res.image
|
|
241
|
+
|
|
242
|
+
if pic.orientation in ("6", "8"):
|
|
243
|
+
update_picture_orientation(job.reporting_conn, pic, picture)
|
|
244
|
+
|
|
245
|
+
if res.metadata:
|
|
246
|
+
store_detection_semantics(job, res.metadata, store_id=config["PICTURE_PROCESS_KEEP_UNBLURRED_PARTS"])
|
|
152
247
|
|
|
153
248
|
# Delete original unblurred file
|
|
154
249
|
geovisio.utils.filesystems.removeFsEvenNotFound(fses.tmp, picHdPath)
|
|
@@ -228,7 +323,9 @@ class PictureProcessor:
|
|
|
228
323
|
if self.app.pool.closed and self.stop:
|
|
229
324
|
# in some tests, the pool is closed before the worker is stopped, we check this here
|
|
230
325
|
return
|
|
231
|
-
self.
|
|
326
|
+
if not self.stop:
|
|
327
|
+
# periodic tasks are only checked by permanent workers
|
|
328
|
+
self.check_periodic_tasks()
|
|
232
329
|
r = process_next_job(self.app)
|
|
233
330
|
if not r:
|
|
234
331
|
if self.stop:
|
|
@@ -249,7 +346,7 @@ class PictureProcessor:
|
|
|
249
346
|
signal.signal(signal.SIGTERM, self._graceful_shutdown)
|
|
250
347
|
|
|
251
348
|
def _graceful_shutdown(self, *args):
|
|
252
|
-
log.info("
|
|
349
|
+
log.info("Stopping worker, waiting for last picture processing to finish...")
|
|
253
350
|
self.stop = True
|
|
254
351
|
|
|
255
352
|
def check_periodic_tasks(self):
|
|
@@ -305,16 +402,19 @@ def process_next_job(app):
|
|
|
305
402
|
span.set_data("pic_id", job.pic.id)
|
|
306
403
|
with utils.time.log_elapsed(f"Processing picture {job.pic.id}"):
|
|
307
404
|
# open another connection for reporting and queries
|
|
308
|
-
processPictureFiles(job
|
|
405
|
+
processPictureFiles(job, app.config)
|
|
309
406
|
elif job.task == ProcessTask.delete and job.pic:
|
|
310
407
|
with sentry_sdk.start_span(description="Deleting picture") as span:
|
|
311
408
|
span.set_data("pic_id", job.pic.id)
|
|
312
409
|
with utils.time.log_elapsed(f"Deleting picture {job.pic.id}"):
|
|
313
|
-
_delete_picture(job.pic)
|
|
410
|
+
_delete_picture(job.reporting_conn, job.pic)
|
|
411
|
+
elif job.task == ProcessTask.read_metadata and job.pic:
|
|
412
|
+
with utils.time.log_elapsed(f"Reading metadata of picture {job.pic.id}"):
|
|
413
|
+
_read_picture_metadata(job.pic, **(job.args or {}))
|
|
314
414
|
elif job.task == ProcessTask.dispatch and job.upload_set:
|
|
315
415
|
with utils.time.log_elapsed(f"Dispatching upload set {job.upload_set.id}"):
|
|
316
416
|
try:
|
|
317
|
-
upload_set.dispatch(job.upload_set.id)
|
|
417
|
+
upload_set.dispatch(job.reporting_conn, job.upload_set.id)
|
|
318
418
|
except Exception as e:
|
|
319
419
|
log.exception(f"impossible to dispatch upload set {job.upload_set.id}")
|
|
320
420
|
raise RecoverableProcessException("Upload set dispatch error: " + errors.getMessageFromException(e)) from e
|
|
@@ -340,7 +440,7 @@ def _get_next_job(app):
|
|
|
340
440
|
with app.pool.connection() as locking_transaction:
|
|
341
441
|
with locking_transaction.transaction(), locking_transaction.cursor(row_factory=dict_row) as cursor:
|
|
342
442
|
r = cursor.execute(
|
|
343
|
-
"""SELECT j.id, j.picture_id, j.upload_set_id, j.sequence_id, j.task, j.picture_to_delete_id, p.metadata, j.args
|
|
443
|
+
"""SELECT j.id, j.picture_id, j.upload_set_id, j.sequence_id, j.task, j.picture_to_delete_id, p.metadata, j.args, p.exif->'Exif.Image.Orientation' as orientation
|
|
344
444
|
FROM job_queue j
|
|
345
445
|
LEFT JOIN pictures p ON p.id = j.picture_id
|
|
346
446
|
ORDER by
|
|
@@ -359,7 +459,12 @@ def _get_next_job(app):
|
|
|
359
459
|
# (and it will not a foreign key since the picture's row will already have been deleted from the db)
|
|
360
460
|
pic_id = r["picture_id"] or r["picture_to_delete_id"]
|
|
361
461
|
db_pic = (
|
|
362
|
-
DbPicture(
|
|
462
|
+
DbPicture(
|
|
463
|
+
id=pic_id,
|
|
464
|
+
metadata=r["metadata"],
|
|
465
|
+
skip_blurring=(r["args"] or {}).get("skip_blurring", False),
|
|
466
|
+
orientation=r["orientation"],
|
|
467
|
+
)
|
|
363
468
|
if pic_id is not None
|
|
364
469
|
else None
|
|
365
470
|
)
|
|
@@ -383,12 +488,13 @@ def _get_next_job(app):
|
|
|
383
488
|
_finalize_job(locking_transaction, job)
|
|
384
489
|
log.debug(f"Job {job.label()} processed")
|
|
385
490
|
except RecoverableProcessException as e:
|
|
386
|
-
_mark_process_as_error(locking_transaction, job, e, recoverable=True)
|
|
491
|
+
_mark_process_as_error(locking_transaction, job, e, config=app.config, recoverable=True)
|
|
387
492
|
except RetryLaterProcessException as e:
|
|
388
493
|
_mark_process_as_error(
|
|
389
494
|
locking_transaction,
|
|
390
495
|
job,
|
|
391
496
|
e,
|
|
497
|
+
config=app.config,
|
|
392
498
|
recoverable=True,
|
|
393
499
|
mark_as_error=False,
|
|
394
500
|
)
|
|
@@ -396,17 +502,17 @@ def _get_next_job(app):
|
|
|
396
502
|
log.error(f"Interruption received, stoping job {job.label()}")
|
|
397
503
|
# starts a new connection, since the current one can be corrupted by the exception
|
|
398
504
|
with app.pool.connection() as t:
|
|
399
|
-
_mark_process_as_error(t, job, interruption, recoverable=True)
|
|
505
|
+
_mark_process_as_error(t, job, interruption, config=app.config, recoverable=True)
|
|
400
506
|
error = interruption
|
|
401
507
|
except Exception as e:
|
|
402
508
|
log.exception(f"Impossible to finish job {job.label()}")
|
|
403
|
-
_mark_process_as_error(locking_transaction, job, e, recoverable=False)
|
|
509
|
+
_mark_process_as_error(locking_transaction, job, e, config=app.config, recoverable=False)
|
|
404
510
|
|
|
405
511
|
# try to finalize the sequence anyway
|
|
406
512
|
_finalize_sequence(job)
|
|
407
513
|
error = e
|
|
408
514
|
|
|
409
|
-
# we raise an error after the transaction has been
|
|
515
|
+
# we raise an error after the transaction has been committed to be sure to have the state persisted in the database
|
|
410
516
|
if error:
|
|
411
517
|
raise error
|
|
412
518
|
|
|
@@ -447,9 +553,16 @@ def _finalize_job(conn, job: DbJob):
|
|
|
447
553
|
f"The job {job.job_history_id} ({job.label()}) has likely been deleted during the process (it can happen if the picture/upload_set/sequence has been deleted by another process), we don't need to finalize it"
|
|
448
554
|
)
|
|
449
555
|
return
|
|
556
|
+
|
|
557
|
+
params = {"id": job.job_history_id}
|
|
558
|
+
fields = [SQL("finished_at = CURRENT_TIMESTAMP")]
|
|
559
|
+
if job.warning:
|
|
560
|
+
fields.append(SQL("warning = %(warn)s"))
|
|
561
|
+
params["warn"] = job.warning
|
|
562
|
+
|
|
450
563
|
job.reporting_conn.execute(
|
|
451
|
-
"UPDATE job_history SET
|
|
452
|
-
|
|
564
|
+
SQL("UPDATE job_history SET {fields} WHERE id = %(id)s").format(fields=SQL(", ").join(fields)),
|
|
565
|
+
params,
|
|
453
566
|
)
|
|
454
567
|
if job.task == ProcessTask.prepare and job.pic:
|
|
455
568
|
# Note: the status is slowly been deprecated by replacing it with more precise status, and in the end it will be removed
|
|
@@ -484,7 +597,7 @@ def _initialize_job(
|
|
|
484
597
|
"pic_to_delete": db_pic.id if db_pic and task == ProcessTask.delete else None,
|
|
485
598
|
"us_id": db_upload_set.id if db_upload_set else None,
|
|
486
599
|
"task": task.value,
|
|
487
|
-
"args": Jsonb(args),
|
|
600
|
+
"args": Jsonb(args) if args else None,
|
|
488
601
|
},
|
|
489
602
|
).fetchone()
|
|
490
603
|
|
|
@@ -499,6 +612,7 @@ def _initialize_job(
|
|
|
499
612
|
upload_set=db_upload_set,
|
|
500
613
|
task=task,
|
|
501
614
|
job_history_id=r[0],
|
|
615
|
+
args=args,
|
|
502
616
|
)
|
|
503
617
|
|
|
504
618
|
|
|
@@ -506,6 +620,7 @@ def _mark_process_as_error(
|
|
|
506
620
|
conn,
|
|
507
621
|
job: DbJob,
|
|
508
622
|
e: Exception,
|
|
623
|
+
config: Dict,
|
|
509
624
|
recoverable: bool = False,
|
|
510
625
|
mark_as_error: bool = True,
|
|
511
626
|
):
|
|
@@ -524,7 +639,7 @@ def _mark_process_as_error(
|
|
|
524
639
|
RETURNING nb_errors""",
|
|
525
640
|
{"err": str(e), "id": job.job_queue_id},
|
|
526
641
|
).fetchone()
|
|
527
|
-
if nb_error and nb_error[0] >
|
|
642
|
+
if nb_error and nb_error[0] > config["PICTURE_PROCESS_NB_RETRIES"]:
|
|
528
643
|
logging.info(f"Job {job.label()} has failed {nb_error} times, we stop trying to process it.")
|
|
529
644
|
recoverable = False
|
|
530
645
|
else:
|
|
@@ -547,7 +662,58 @@ def _mark_process_as_error(
|
|
|
547
662
|
conn.execute("DELETE FROM job_queue WHERE id = %(id)s", {"id": job.job_queue_id})
|
|
548
663
|
|
|
549
664
|
|
|
550
|
-
def _delete_picture(pic: DbPicture):
|
|
665
|
+
def _delete_picture(conn: psycopg.Connection, pic: DbPicture):
|
|
551
666
|
"""Delete a picture from the filesystem"""
|
|
552
667
|
log.debug(f"Deleting picture files {pic.id}")
|
|
668
|
+
|
|
669
|
+
def check_if_no_workers_preparing():
|
|
670
|
+
try:
|
|
671
|
+
# We try to check if there at some workers preparing this picture, if it's the case, we wait a bit and retry.
|
|
672
|
+
# after some time, if the lock is still not released, we raise a RetryLaterProcessException, to reschedule the whole job later
|
|
673
|
+
conn.execute(
|
|
674
|
+
"SELECT id FROM job_queue WHERE picture_id = %(id)s and task = 'prepare' FOR UPDATE NOWAIT",
|
|
675
|
+
{"id": pic.id},
|
|
676
|
+
)
|
|
677
|
+
return True
|
|
678
|
+
except psycopg.errors.LockNotAvailable:
|
|
679
|
+
logging.debug(f"The picture {pic.id} is being processed, we'll retry later")
|
|
680
|
+
return False
|
|
681
|
+
|
|
682
|
+
_retry_for(check_if_no_workers_preparing, error=f"Picture {pic.id} is being processed")
|
|
683
|
+
|
|
684
|
+
# Delete the row if needed (note that it can have already been deleted (for example if a whole upload_set has been deleted, the `ON DELETE CASCADE` deletes all the pictures's row (but the files still need to be deleted)))
|
|
685
|
+
conn.execute("DELETE FROM pictures WHERE id = %(id)s", {"id": pic.id})
|
|
686
|
+
|
|
553
687
|
utils.pictures.removeAllFiles(pic.id)
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
def _retry_for(func, error, timeout=timedelta(minutes=1), sleep=timedelta(seconds=5)):
|
|
691
|
+
import time
|
|
692
|
+
|
|
693
|
+
cur_duration = timedelta(seconds=0)
|
|
694
|
+
while cur_duration < timeout:
|
|
695
|
+
r = func()
|
|
696
|
+
if r:
|
|
697
|
+
return
|
|
698
|
+
cur_duration += sleep
|
|
699
|
+
time.sleep(sleep.total_seconds())
|
|
700
|
+
|
|
701
|
+
raise RetryLaterProcessException(error)
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
def _read_picture_metadata(picture: DbPicture, read_file=False):
|
|
705
|
+
"""Reread the picture metadata.
|
|
706
|
+
|
|
707
|
+
Normally the picture's metadata are read during upload, but sometimes (mainly when the geopic-tag-reader library has been updated),
|
|
708
|
+
we need to read the metadata again.
|
|
709
|
+
|
|
710
|
+
Parameters
|
|
711
|
+
----------
|
|
712
|
+
picture_id : UUID
|
|
713
|
+
The ID of the picture to read the metadata from
|
|
714
|
+
read_file : bool
|
|
715
|
+
If True, the picture's raw metadata will be read again, else the Exif tools stored in the database will be used (way faster).
|
|
716
|
+
"""
|
|
717
|
+
|
|
718
|
+
with db.conn(current_app) as conn:
|
|
719
|
+
utils.pictures.update_picture_metadata(conn, picture.id, read_file)
|
|
@@ -1,55 +1,56 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: geovisio
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.10.0
|
|
4
4
|
Summary: GeoVisio API - Main
|
|
5
5
|
Author-email: Adrien PAVIE <panieravide@riseup.net>, Antoine Desbordes <antoine.desbordes@gmail.com>
|
|
6
6
|
Requires-Python: >=3.10
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
Classifier: License :: OSI Approved :: MIT License
|
|
9
9
|
License-File: LICENSE
|
|
10
|
-
Requires-Dist: Flask ~=
|
|
11
|
-
Requires-Dist: psycopg[pool] ~= 3.
|
|
10
|
+
Requires-Dist: Flask ~= 3.1
|
|
11
|
+
Requires-Dist: psycopg[pool] ~= 3.2
|
|
12
12
|
Requires-Dist: flasgger ~= 0.9.7
|
|
13
|
-
Requires-Dist: Pillow ~=
|
|
14
|
-
Requires-Dist: Flask-Cors ~=
|
|
13
|
+
Requires-Dist: Pillow ~= 11.1
|
|
14
|
+
Requires-Dist: Flask-Cors ~= 6.0
|
|
15
15
|
Requires-Dist: fs ~= 2.4
|
|
16
16
|
Requires-Dist: fs-s3fs-forked ~= 1.1.4
|
|
17
17
|
Requires-Dist: flask-compress ~= 1.14
|
|
18
18
|
Requires-Dist: requests ~= 2.31
|
|
19
|
-
Requires-Dist: yoyo-migrations ~=
|
|
20
|
-
Requires-Dist: psycopg-binary ~= 3.
|
|
21
|
-
Requires-Dist: python-dotenv ~=
|
|
22
|
-
Requires-Dist: authlib ~= 1.
|
|
19
|
+
Requires-Dist: yoyo-migrations ~= 9.0
|
|
20
|
+
Requires-Dist: psycopg-binary ~= 3.2
|
|
21
|
+
Requires-Dist: python-dotenv ~= 1.1
|
|
22
|
+
Requires-Dist: authlib ~= 1.6
|
|
23
23
|
Requires-Dist: Flask-Executor ~= 1.0
|
|
24
|
-
Requires-Dist: geopic-tag-reader[write-exif]
|
|
24
|
+
Requires-Dist: geopic-tag-reader[write-exif] ~= 1.8.0
|
|
25
25
|
Requires-Dist: rfeed ~= 1.1.1
|
|
26
|
-
Requires-Dist: sentry-sdk[flask] ~=
|
|
27
|
-
Requires-Dist: pygeofilter[backend-native] ~= 0.
|
|
26
|
+
Requires-Dist: sentry-sdk[flask] ~= 2.24
|
|
27
|
+
Requires-Dist: pygeofilter[backend-native] ~= 0.3.1
|
|
28
28
|
Requires-Dist: python-dateutil ~= 2.9.0
|
|
29
|
-
Requires-Dist: tzdata ~=
|
|
30
|
-
Requires-Dist: croniter ~=
|
|
31
|
-
Requires-Dist: pydantic ~= 2.
|
|
29
|
+
Requires-Dist: tzdata ~= 2025.2
|
|
30
|
+
Requires-Dist: croniter ~= 6.0.0
|
|
31
|
+
Requires-Dist: pydantic ~= 2.11
|
|
32
32
|
Requires-Dist: pydantic-extra-types ~= 2.7
|
|
33
33
|
Requires-Dist: flask-babel ~= 4.0.0
|
|
34
|
-
Requires-Dist: geojson-pydantic ~=
|
|
34
|
+
Requires-Dist: geojson-pydantic ~= 2.0.0
|
|
35
35
|
Requires-Dist: email-validator ~= 2.2.0
|
|
36
|
+
Requires-Dist: multipart>=1.2.1
|
|
36
37
|
Requires-Dist: flit ~= 3.9.0 ; extra == "build"
|
|
37
|
-
Requires-Dist: coverage ~=
|
|
38
|
-
Requires-Dist: protobuf ~=
|
|
39
|
-
Requires-Dist: mapbox-vector-tile ~= 2.
|
|
38
|
+
Requires-Dist: coverage ~= 7.9 ; extra == "dev"
|
|
39
|
+
Requires-Dist: protobuf ~= 5.26 ; extra == "dev"
|
|
40
|
+
Requires-Dist: mapbox-vector-tile ~= 2.1 ; extra == "dev"
|
|
40
41
|
Requires-Dist: pystac ~= 1.9 ; extra == "dev"
|
|
41
|
-
Requires-Dist: pytest ~= 8.
|
|
42
|
-
Requires-Dist: pytest-datafiles ~=
|
|
42
|
+
Requires-Dist: pytest ~= 8.4 ; extra == "dev"
|
|
43
|
+
Requires-Dist: pytest-datafiles ~= 3.0 ; extra == "dev"
|
|
43
44
|
Requires-Dist: pyexiv2 ~= 2.15 ; extra == "dev"
|
|
44
|
-
Requires-Dist: testcontainers ~= 4.
|
|
45
|
+
Requires-Dist: testcontainers ~= 4.10 ; extra == "dev"
|
|
45
46
|
Requires-Dist: requests-mock ~= 1.11 ; extra == "dev"
|
|
46
|
-
Requires-Dist: black ~=
|
|
47
|
-
Requires-Dist: pre-commit ~=
|
|
47
|
+
Requires-Dist: black ~= 25.1 ; extra == "dev"
|
|
48
|
+
Requires-Dist: pre-commit ~= 4.2 ; extra == "dev"
|
|
48
49
|
Requires-Dist: pyyaml ~= 6.0 ; extra == "dev"
|
|
49
50
|
Requires-Dist: openapi-spec-validator ~= 0.7 ; extra == "dev"
|
|
50
51
|
Requires-Dist: stac-api-validator ~= 0.6.4 ; extra == "dev"
|
|
51
|
-
Requires-Dist: mkdocs-material ~= 9.
|
|
52
|
-
Requires-Dist: mkdocs-swagger-ui-tag ~= 0.
|
|
52
|
+
Requires-Dist: mkdocs-material ~= 9.6.14 ; extra == "docs"
|
|
53
|
+
Requires-Dist: mkdocs-swagger-ui-tag ~= 0.7.1 ; extra == "docs"
|
|
53
54
|
Project-URL: Home, https://gitlab.com/panoramax/server/api
|
|
54
55
|
Project-URL: Source Code, https://gitlab.com/panoramax/server/api
|
|
55
56
|
Provides-Extra: build
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
geovisio/__init__.py,sha256=LcHU6JLcjjGDdaFgc83jbxUV_Zc8YXUkD_dIBA7u-nQ,7952
|
|
2
|
+
geovisio/config_app.py,sha256=cS4TtW7ESy5jeaHi_9neJYWwA21VBNNrh4IDgf2iMmI,14848
|
|
3
|
+
geovisio/db_migrations.py,sha256=9lHkyG_RiCWzrFkfwhkslScUsbCZScN-KVhkXrtnPDo,4560
|
|
4
|
+
geovisio/errors.py,sha256=uTn-kI7SUl5OPB8Mv3Qqu7Ucp5JvcqWPQFfgLCqsEpI,1376
|
|
5
|
+
geovisio/admin_cli/__init__.py,sha256=1e0hX771-3iG8eBcNmVvUYyg8qXnpng-9YWvi3MI3Kg,3248
|
|
6
|
+
geovisio/admin_cli/cleanup.py,sha256=G85I7rrfPJwaArL6MQAnC04Ye9wWciA-Yqu5iv23uJ0,4862
|
|
7
|
+
geovisio/admin_cli/db.py,sha256=mJ-cGuOAAsg-ovbP9L1kyL4xE0C4bYRuozzqQkaFyw8,897
|
|
8
|
+
geovisio/admin_cli/default_account_tokens.py,sha256=W-v5uPjCBvAujjAUx1HrfgjPj-tEyncb-EUMLpsWc9w,469
|
|
9
|
+
geovisio/admin_cli/reorder_sequences.py,sha256=LKKzdO2w4N-cQmi6rqKHKYG5YGzPxYRTbnfcPKakuYM,2826
|
|
10
|
+
geovisio/admin_cli/sequence_heading.py,sha256=BEPuRfCDXXpqSSzK2ysrxHf0OD4THzrMI_YK2uXQlGk,633
|
|
11
|
+
geovisio/admin_cli/user.py,sha256=4ml2E_aphz3I3NcuUPB2dwe_jXhcE7AGa0R5VTm3_ik,2753
|
|
12
|
+
geovisio/templates/main.html,sha256=VDVQwCZ1mNjH7sH4VOIdn8gM09R9LJZX49SPtA2VEzM,2963
|
|
13
|
+
geovisio/templates/viewer.html,sha256=JErXdU2ujj4LdMHgQbYNCTfKuYGEXbJTQwBE-K_MNXQ,892
|
|
14
|
+
geovisio/translations/messages.pot,sha256=zSzcuOQtONiGQrZE2hRkrB7thd5UGyC41dox867kV88,20827
|
|
15
|
+
geovisio/translations/ar/LC_MESSAGES/messages.mo,sha256=2OvIx-hvvhBbjXvVhc5MaQ-0QnzTWISmNeb4vPthffM,516
|
|
16
|
+
geovisio/translations/ar/LC_MESSAGES/messages.po,sha256=WcdpU3LH1Qf39WiIXQ8UMy1SyL7DHiWCbv0BVG_crW4,20816
|
|
17
|
+
geovisio/translations/br/LC_MESSAGES/messages.mo,sha256=FjvUouzaLCBxvVH51A4PVwGGFk6mAepYasnI4v8LcnQ,719
|
|
18
|
+
geovisio/translations/br/LC_MESSAGES/messages.po,sha256=tGJK1DoeKM-fRHuGa716xHkYtprVoKSNcB4oIqL0JtY,19433
|
|
19
|
+
geovisio/translations/da/LC_MESSAGES/messages.mo,sha256=ketqWtB3ymB0tUaHEp2_nnqFrRX25pwuE2AcCno9W-Q,21197
|
|
20
|
+
geovisio/translations/da/LC_MESSAGES/messages.po,sha256=af7uax57NEw_JFNO11lFKGPGYJ05bOQCGryw15wP8Ms,29397
|
|
21
|
+
geovisio/translations/de/LC_MESSAGES/messages.mo,sha256=4YJijXlC1Dix39oX6CxNYd0J6k-I4AFx5jXyZq7Jib4,24359
|
|
22
|
+
geovisio/translations/de/LC_MESSAGES/messages.po,sha256=6S7rJpu7qfq78_YwZ9M5ueF4-TpvlZmF14b13g_aF8Y,33121
|
|
23
|
+
geovisio/translations/el/LC_MESSAGES/messages.mo,sha256=vy1jtEG6mLS5sYWPfQIr5U4XsZ21ZzSbsHAJHGQXZSY,433
|
|
24
|
+
geovisio/translations/el/LC_MESSAGES/messages.po,sha256=kIz9QJr_GPk_Eh7bJ2vxjsFbIGyvt_-hwra0zVlupyM,17404
|
|
25
|
+
geovisio/translations/en/LC_MESSAGES/messages.mo,sha256=i7HmGj2_mAgGDV9b5dZwSNawx7WNlVBqV5T5uXVl8ok,21397
|
|
26
|
+
geovisio/translations/en/LC_MESSAGES/messages.po,sha256=KD-Fq-gCywKcQnBw7B-Rhfk0s2-xAraXiBqUEPmNjNo,29920
|
|
27
|
+
geovisio/translations/eo/LC_MESSAGES/messages.mo,sha256=AdSbZMS6Fj10_w5dQVpgLgjfs32EHw5m87eTp0IaFmo,22387
|
|
28
|
+
geovisio/translations/eo/LC_MESSAGES/messages.po,sha256=0vgsW6aWR0xUZptAym4HyO8pQh1yEShbojurjZvp0nU,30954
|
|
29
|
+
geovisio/translations/es/LC_MESSAGES/messages.mo,sha256=R5JmcfauTrQxIynQNT7asjdLEJC9-VEMXYrcugfBbsY,18950
|
|
30
|
+
geovisio/translations/es/LC_MESSAGES/messages.po,sha256=sn3UQXBk6pg7ny4ndaPxbvZzTBzNFQBsmJY2lJxTXFQ,26779
|
|
31
|
+
geovisio/translations/fi/LC_MESSAGES/messages.mo,sha256=6-WCesFiV00MkNM_Wpi7-D51DOZRNg_QOM2sL7-UPhA,626
|
|
32
|
+
geovisio/translations/fi/LC_MESSAGES/messages.po,sha256=AS-VBD2kM0lXQXCvMu_CqaPxGtZj1uzkJJV8ex7coVU,14684
|
|
33
|
+
geovisio/translations/fr/LC_MESSAGES/messages.mo,sha256=i1RnE28Jom-bzN6lzf6qO8AuXpJ5bTNLjerwU2P1y34,25063
|
|
34
|
+
geovisio/translations/fr/LC_MESSAGES/messages.po,sha256=Q_VNX6EdCjwilbptnB4PaWJToPMZ0NkxtXXYfCESpzY,33759
|
|
35
|
+
geovisio/translations/hu/LC_MESSAGES/messages.mo,sha256=0Hb7mv7p1BVM8QqZIYUtF3LRym8Sl9HFWfZAa00TobU,20013
|
|
36
|
+
geovisio/translations/hu/LC_MESSAGES/messages.po,sha256=HudkPHKwEdCoY-vwCtSilSqPe88jcDIn8_hF6U6CThg,27493
|
|
37
|
+
geovisio/translations/it/LC_MESSAGES/messages.mo,sha256=8vLUUFP157568oUM25eft0UJtA6AcTTUykRNZ-WiQpc,24087
|
|
38
|
+
geovisio/translations/it/LC_MESSAGES/messages.po,sha256=uMme7kN5KJobxs2J611WG-jEuxVCt9G6IjCRzmRyJZo,32828
|
|
39
|
+
geovisio/translations/ja/LC_MESSAGES/messages.mo,sha256=ZPHJrNdf4bgiNFjxP8W41fkZ2OTJ7Swrqt-Hkh5LfO8,24194
|
|
40
|
+
geovisio/translations/ja/LC_MESSAGES/messages.po,sha256=JWjqZnlmM_decqpVrFAc79sV3yIxAF9nkGSiB5ocZtU,32626
|
|
41
|
+
geovisio/translations/ko/LC_MESSAGES/messages.mo,sha256=eKuQS9zLcJ9s-DzbfR-QK2INBJL10jTIQ1kuSTdJ9Rg,426
|
|
42
|
+
geovisio/translations/ko/LC_MESSAGES/messages.po,sha256=Bdj_LmU69MWZCmajgCm_QNxlKETmN8jzaJnmemuftHg,17404
|
|
43
|
+
geovisio/translations/nl/LC_MESSAGES/messages.mo,sha256=ZSjPgkgVdFD1EU39I9MvbS0WmCz_GiZb-gS-GbbiEWg,22878
|
|
44
|
+
geovisio/translations/nl/LC_MESSAGES/messages.po,sha256=dUg-xm7cr6yL1eKITjNJQBLPxgkQYE_34H1EgNrOQ6c,32005
|
|
45
|
+
geovisio/translations/oc/LC_MESSAGES/messages.mo,sha256=zaKy9mleHCTWd7rLFV2Ced4KvdD-rZhxVafQEIBuQnY,433
|
|
46
|
+
geovisio/translations/oc/LC_MESSAGES/messages.po,sha256=-sLU4ULuSarTqt26XCq5xU6pwTTr_xGpcogmYnb7xqI,20816
|
|
47
|
+
geovisio/translations/pl/LC_MESSAGES/messages.mo,sha256=0iFTAhma7jjyl13DCLr2Xr0hgDSN-_fOqcKoYcdDwGE,9912
|
|
48
|
+
geovisio/translations/pl/LC_MESSAGES/messages.po,sha256=p2SV3x0LvOJFT41eaJ0_p1COHt78N2AiZoQhbwJS98k,22186
|
|
49
|
+
geovisio/translations/sv/LC_MESSAGES/messages.mo,sha256=k7Ssr8tax43wmVy_wVsL5oM03qykV0WeG8-ClxkSZoE,20659
|
|
50
|
+
geovisio/translations/sv/LC_MESSAGES/messages.po,sha256=yglWtKCyLiivuMfKI-ret2GLs98aRYaAOvk6YTCBkO4,28760
|
|
51
|
+
geovisio/translations/ti/LC_MESSAGES/messages.mo,sha256=YeCBuScoL_qi-ZSA4v2O-h0PxKWjRWDgh_de3sJaUas,433
|
|
52
|
+
geovisio/translations/ti/LC_MESSAGES/messages.po,sha256=4Oju5eWJkD3DxjKoOseSr4geWPnZ8iOUIZR8ApvYyPc,19433
|
|
53
|
+
geovisio/translations/zh_Hant/LC_MESSAGES/messages.mo,sha256=TmRUyfTGoBpU-2BE-nKjhwdr9r0ikDioVQU-JQ_ih90,431
|
|
54
|
+
geovisio/translations/zh_Hant/LC_MESSAGES/messages.po,sha256=HcorbHk7OpRWL6v3oEiWxeifaentdF5xgcY_oFAOGEI,18356
|
|
55
|
+
geovisio/utils/__init__.py,sha256=g4SWVoV73cSXjf5-5D9-HmyB5xKmHSuxxOGWnx7W3V0,71
|
|
56
|
+
geovisio/utils/annotations.py,sha256=vLP6YRJgqFUfGHvX9qQabBduMaHGLWiivEk8YH9fdEA,6884
|
|
57
|
+
geovisio/utils/auth.py,sha256=WDwllPHp7WI38SilT2mf29IinRsPR5nnbBVxGjYKkQQ,13934
|
|
58
|
+
geovisio/utils/cql2.py,sha256=42igfsnpTAW8Ee70uvq7wAeTrDZ9JwDyIOWJuV_hkDI,4720
|
|
59
|
+
geovisio/utils/db.py,sha256=cxetPL-URdeOxmDMPwxKovB93yQYLNlOL8UHvP4DHg0,2856
|
|
60
|
+
geovisio/utils/excluded_areas.py,sha256=6f3wwsgNpJKxAXnHH8RKlktgHpsG-0QVNTWDDTFqPZ8,2585
|
|
61
|
+
geovisio/utils/extent.py,sha256=vzOHvbG6lpSNt7KrsaonBOx7Tz46S1J603gLbZvs36g,557
|
|
62
|
+
geovisio/utils/fields.py,sha256=Q1o3_eSo66UAyFdmhK2kz-drVWnHA2cbvWCZ5eP0fPk,3475
|
|
63
|
+
geovisio/utils/filesystems.py,sha256=W_wH7TlvdEux_q4FP0XInxruxlbepFSEpJbPLO9Cnr4,4133
|
|
64
|
+
geovisio/utils/items.py,sha256=8-F9zYmcFzrXCtQyWqZhXvojV34RC-aw1d9d9MbxXkc,1557
|
|
65
|
+
geovisio/utils/link.py,sha256=u9x4xJa57L1448neD7uPJuAA76_sFXVE0-9_zPW-bJM,490
|
|
66
|
+
geovisio/utils/loggers.py,sha256=_OrGXME4o5qQz8VBaLxYopHVK0DY0QgzXu6O2W0WBjo,477
|
|
67
|
+
geovisio/utils/model_query.py,sha256=Rdd0_mWJp5nmABVNzzVapaai413PxjgMzmo9NjjUrmI,2074
|
|
68
|
+
geovisio/utils/params.py,sha256=Yj9-PwC8jxb9LjQZ5K8TERimSsWKwJBHPhUxlzDVMhg,714
|
|
69
|
+
geovisio/utils/pic_shape.py,sha256=1HVlhgxNDtGpSFumL_GUc7JOsYWOi1wjtAtkhCijxGo,2271
|
|
70
|
+
geovisio/utils/pictures.py,sha256=Xv3eOIPp4fycGRNAs3VJRt3kArOajQ4t3QtjF8A83M8,28889
|
|
71
|
+
geovisio/utils/reports.py,sha256=3simWHf6t_OuLw2hh2HPYj8vsji00Pz9iRnJOqbTnPs,5227
|
|
72
|
+
geovisio/utils/semantics.py,sha256=MCJtxFSIsuDez8807hGHdlkOuVey6ThCFvUfJvva9f0,9832
|
|
73
|
+
geovisio/utils/sentry.py,sha256=lGDhm0ROW2Z-Ugtzq_M9R8yTKalYN4Etq19Uz63c6Tk,3737
|
|
74
|
+
geovisio/utils/sequences.py,sha256=1rPkUjOrat6jl1Ev3As_idhRMAmPQ6fTJpjfl5ql9-s,30735
|
|
75
|
+
geovisio/utils/tags.py,sha256=GyihW-TgQk1ZCpqYPMoN64qOZ6PCFCvCLK9HQnZhtY8,868
|
|
76
|
+
geovisio/utils/time.py,sha256=-hOs7FSx-8eiGmMQtTOrCoF8d_yMlj2306vfxJftEr8,642
|
|
77
|
+
geovisio/utils/tokens.py,sha256=tkihnnXqgQeIME_d12tC8PVrPN90A0i9k6UPEbgZ9TQ,3047
|
|
78
|
+
geovisio/utils/upload_set.py,sha256=Aym0K93PlNe8JzHPpdTEvKOPOdQ3mDOAGemc0fDTFQI,31038
|
|
79
|
+
geovisio/utils/website.py,sha256=3U9-WL0WGLKPDQHU8N08QCmUL6rKp54yRatpQ64kQhM,1958
|
|
80
|
+
geovisio/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
81
|
+
geovisio/web/annotations.py,sha256=Sz-n6eYyiWtwFUo0wjQE-CDepsJtWkmtzAXGXE0P6sw,12178
|
|
82
|
+
geovisio/web/auth.py,sha256=coY35MzOXJQ8QocmAopWztecBUTVT4YBPTovXQhxEpw,7142
|
|
83
|
+
geovisio/web/collections.py,sha256=ABFOhrUqqIERBTi-hr1dexWFw1LFEf42SijmVgsC9lg,48054
|
|
84
|
+
geovisio/web/configuration.py,sha256=uRSfRQIBZqAfH09QFZP_8ofu-bajJUvrhToev1DgzJE,3143
|
|
85
|
+
geovisio/web/docs.py,sha256=b-tBrKRrtF7UsblOhKaL247c9pmM9MDavzdg1YILJLc,62741
|
|
86
|
+
geovisio/web/excluded_areas.py,sha256=5BNSZ0UqgFMtgvgrJ73eYGJXPJRnV-mGEs36WDRRxTk,13024
|
|
87
|
+
geovisio/web/items.py,sha256=cFEJevQWP0a-9rrXBPK5ogWSfIGpy564HkPPylvT9pk,65090
|
|
88
|
+
geovisio/web/map.py,sha256=hzVC0vS-71mFEvRmfCxAprums2Zz5baP6EFTL0uW02M,26136
|
|
89
|
+
geovisio/web/pages.py,sha256=Hkc3KJFE6D38vGnkCK5WUBJ8KQemI1f1wGXpxeiOiNo,6632
|
|
90
|
+
geovisio/web/params.py,sha256=W1wLGFjSztAx9Xl-zzqQSNPtD28trsxqr_qc5fzL6Dw,21523
|
|
91
|
+
geovisio/web/pictures.py,sha256=dr-oSl2_k-sl1ZmfrwyW2HZPxPsSzxOHtY5a0-9u_Gk,7471
|
|
92
|
+
geovisio/web/prepare.py,sha256=R10_xf6O9dmAAwOMC-vsaxgNTdc9BkDJLATqH6MKtCw,5620
|
|
93
|
+
geovisio/web/reports.py,sha256=8v9a4PMM9RsvSGadZEN2o5PTKG_TohjyMMEBfFeY13E,14123
|
|
94
|
+
geovisio/web/rss.py,sha256=NLUd2Or92tcKRaGUHAze6QMLWczHyzag9ybOzrA8djE,2962
|
|
95
|
+
geovisio/web/stac.py,sha256=PQqbUHBDi0xqVmYjH1oC16qtuaZbU8P0SufM0gdiG9w,14853
|
|
96
|
+
geovisio/web/tokens.py,sha256=natYQT1kdmlT2h0t3R1y0B6W4qhGfFmQR3UER9_KvkQ,11215
|
|
97
|
+
geovisio/web/upload_set.py,sha256=64Hr5Kv3Ixp57CMZavOCf1XL11kO7KiQ6L89j2cXSMM,41231
|
|
98
|
+
geovisio/web/users.py,sha256=DNbg7fKqoP__TKTD-xSIb250rYEBftyi17kclGlBXeg,14056
|
|
99
|
+
geovisio/web/utils.py,sha256=43zf8qAnRn6L_tEvf4h14mLQXKQHmuAW4EN3yYGKP7o,3638
|
|
100
|
+
geovisio/workers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
101
|
+
geovisio/workers/runner_pictures.py,sha256=0EMTR1N_3VhpLA66nrQcRkKONl7Ojf8SyT8BXlF5pOw,30359
|
|
102
|
+
geovisio-2.10.0.dist-info/licenses/LICENSE,sha256=iRFSz7MJ7_j4hh3hvIgzNbS2buy5NMva8lulaixd3IE,1069
|
|
103
|
+
geovisio-2.10.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
104
|
+
geovisio-2.10.0.dist-info/METADATA,sha256=yUEAR-PkyLi9ZhTnZJmzJ5bR5cdTiqo_Bq9U7TARFUs,4355
|
|
105
|
+
geovisio-2.10.0.dist-info/RECORD,,
|