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.
Files changed (70) hide show
  1. geovisio/__init__.py +6 -1
  2. geovisio/config_app.py +16 -5
  3. geovisio/translations/ar/LC_MESSAGES/messages.mo +0 -0
  4. geovisio/translations/ar/LC_MESSAGES/messages.po +818 -0
  5. geovisio/translations/br/LC_MESSAGES/messages.po +1 -1
  6. geovisio/translations/da/LC_MESSAGES/messages.mo +0 -0
  7. geovisio/translations/da/LC_MESSAGES/messages.po +4 -3
  8. geovisio/translations/de/LC_MESSAGES/messages.mo +0 -0
  9. geovisio/translations/de/LC_MESSAGES/messages.po +55 -2
  10. geovisio/translations/el/LC_MESSAGES/messages.po +1 -1
  11. geovisio/translations/en/LC_MESSAGES/messages.mo +0 -0
  12. geovisio/translations/en/LC_MESSAGES/messages.po +193 -139
  13. geovisio/translations/eo/LC_MESSAGES/messages.mo +0 -0
  14. geovisio/translations/eo/LC_MESSAGES/messages.po +53 -4
  15. geovisio/translations/es/LC_MESSAGES/messages.po +1 -1
  16. geovisio/translations/fi/LC_MESSAGES/messages.po +1 -1
  17. geovisio/translations/fr/LC_MESSAGES/messages.mo +0 -0
  18. geovisio/translations/fr/LC_MESSAGES/messages.po +101 -6
  19. geovisio/translations/hu/LC_MESSAGES/messages.po +1 -1
  20. geovisio/translations/it/LC_MESSAGES/messages.mo +0 -0
  21. geovisio/translations/it/LC_MESSAGES/messages.po +63 -3
  22. geovisio/translations/ja/LC_MESSAGES/messages.po +1 -1
  23. geovisio/translations/ko/LC_MESSAGES/messages.po +1 -1
  24. geovisio/translations/messages.pot +185 -129
  25. geovisio/translations/nl/LC_MESSAGES/messages.mo +0 -0
  26. geovisio/translations/nl/LC_MESSAGES/messages.po +421 -86
  27. geovisio/translations/oc/LC_MESSAGES/messages.mo +0 -0
  28. geovisio/translations/oc/LC_MESSAGES/messages.po +818 -0
  29. geovisio/translations/pl/LC_MESSAGES/messages.po +1 -1
  30. geovisio/translations/sv/LC_MESSAGES/messages.mo +0 -0
  31. geovisio/translations/sv/LC_MESSAGES/messages.po +823 -0
  32. geovisio/translations/ti/LC_MESSAGES/messages.mo +0 -0
  33. geovisio/translations/ti/LC_MESSAGES/messages.po +762 -0
  34. geovisio/translations/zh_Hant/LC_MESSAGES/messages.po +1 -1
  35. geovisio/utils/annotations.py +183 -0
  36. geovisio/utils/auth.py +14 -13
  37. geovisio/utils/cql2.py +134 -0
  38. geovisio/utils/db.py +7 -0
  39. geovisio/utils/fields.py +38 -9
  40. geovisio/utils/items.py +44 -0
  41. geovisio/utils/model_query.py +4 -4
  42. geovisio/utils/pic_shape.py +63 -0
  43. geovisio/utils/pictures.py +164 -29
  44. geovisio/utils/reports.py +10 -17
  45. geovisio/utils/semantics.py +196 -57
  46. geovisio/utils/sentry.py +1 -2
  47. geovisio/utils/sequences.py +191 -93
  48. geovisio/utils/tags.py +31 -0
  49. geovisio/utils/upload_set.py +287 -209
  50. geovisio/utils/website.py +1 -1
  51. geovisio/web/annotations.py +346 -9
  52. geovisio/web/auth.py +1 -1
  53. geovisio/web/collections.py +73 -54
  54. geovisio/web/configuration.py +26 -5
  55. geovisio/web/docs.py +143 -11
  56. geovisio/web/items.py +232 -155
  57. geovisio/web/map.py +25 -13
  58. geovisio/web/params.py +55 -52
  59. geovisio/web/pictures.py +34 -0
  60. geovisio/web/stac.py +19 -12
  61. geovisio/web/tokens.py +49 -1
  62. geovisio/web/upload_set.py +148 -37
  63. geovisio/web/users.py +4 -4
  64. geovisio/web/utils.py +2 -2
  65. geovisio/workers/runner_pictures.py +190 -24
  66. {geovisio-2.8.1.dist-info → geovisio-2.10.0.dist-info}/METADATA +27 -26
  67. geovisio-2.10.0.dist-info/RECORD +105 -0
  68. {geovisio-2.8.1.dist-info → geovisio-2.10.0.dist-info}/WHEEL +1 -1
  69. geovisio-2.8.1.dist-info/RECORD +0 -92
  70. {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 processPictureFiles(pic: DbPicture, config):
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
- picture = utils.pictures.createBlurredHDPicture(
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
- log.exception(f"impossible to blur picture {pic.id}")
151
- raise RecoverableProcessException("Blur API failure: " + errors.getMessageFromException(e)) from e
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.check_periodic_tasks()
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("Stoping worker, waiting for last picture processing to finish...")
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.pic, app.config)
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(id=pic_id, metadata=r["metadata"], skip_blurring=(r["args"] or {}).get("skip_blurring", False))
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 comited to be sure to have the state persisted in the database
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 finished_at = CURRENT_TIMESTAMP WHERE id = %(id)s",
452
- {"id": job.job_history_id},
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] > PROCESS_MAX_RETRY:
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.8.1
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 ~= 2.3
11
- Requires-Dist: psycopg[pool] ~= 3.1
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 ~= 9.4
14
- Requires-Dist: Flask-Cors ~= 4.0
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 ~= 8.2
20
- Requires-Dist: psycopg-binary ~= 3.1
21
- Requires-Dist: python-dotenv ~= 0.21
22
- Requires-Dist: authlib ~= 1.2
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] == 1.4.2
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] ~= 1.31
27
- Requires-Dist: pygeofilter[backend-native] ~= 0.2.4
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 ~= 2024.1
30
- Requires-Dist: croniter ~= 2.0.5
31
- Requires-Dist: pydantic ~= 2.7
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 ~= 1.1.0
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 ~= 6.5 ; extra == "dev"
38
- Requires-Dist: protobuf ~= 4.21 ; extra == "dev"
39
- Requires-Dist: mapbox-vector-tile ~= 2.0 ; extra == "dev"
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.3 ; extra == "dev"
42
- Requires-Dist: pytest-datafiles ~= 2.0 ; extra == "dev"
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.1 ; extra == "dev"
45
+ Requires-Dist: testcontainers ~= 4.10 ; extra == "dev"
45
46
  Requires-Dist: requests-mock ~= 1.11 ; extra == "dev"
46
- Requires-Dist: black ~= 24.1 ; extra == "dev"
47
- Requires-Dist: pre-commit ~= 3.3 ; extra == "dev"
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.5.21 ; extra == "docs"
52
- Requires-Dist: mkdocs-swagger-ui-tag ~= 0.6.10 ; extra == "docs"
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: flit 3.11.0
2
+ Generator: flit 3.12.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any