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
geovisio/utils/website.py CHANGED
@@ -14,7 +14,7 @@ class Website:
14
14
  """Website associated to the API.
15
15
  This wrapper will define the routes we expect from the website.
16
16
 
17
- We should limit the interraction from the api to the website, but for some flow (especially auth flows), it's can be useful to redirect to website's page
17
+ We should limit the interaction from the api to the website, but for some flow (especially auth flows), it's can be useful to redirect to website's page
18
18
 
19
19
  If the url is:
20
20
  * set to `false`, there is no associated website
@@ -1,17 +1,354 @@
1
- from geovisio.utils import auth
2
- from psycopg.rows import dict_row, class_row
3
- from psycopg.sql import SQL
4
- from geovisio.utils.semantics import Entity, EntityType, SemanticTagUpdate, update_tags
1
+ from typing import List, Optional
2
+ from geovisio.utils import auth, db
3
+ from geovisio.utils.annotations import (
4
+ AnnotationCreationParameter,
5
+ creation_annotation,
6
+ get_annotation,
7
+ update_annotation,
8
+ InputAnnotationShape,
9
+ )
10
+ from geovisio.utils.tags import SemanticTagUpdate
5
11
  from geovisio.web.utils import accountIdOrDefault
6
- from psycopg.types.json import Jsonb
7
- from geovisio.utils import db
8
12
  from geovisio.utils.params import validation_error
9
13
  from geovisio import errors
10
- from pydantic import BaseModel, ConfigDict, ValidationError
14
+ from pydantic import BaseModel, ValidationError, Field
11
15
  from uuid import UUID
12
- from typing import List, Optional
13
- from flask import Blueprint, request, current_app
16
+ from flask import Blueprint, current_app, request, url_for
14
17
  from flask_babel import gettext as _
15
18
 
16
19
 
17
20
  bp = Blueprint("annotations", __name__, url_prefix="/api")
21
+
22
+
23
+ class AnnotationPostParameter(BaseModel):
24
+ shape: InputAnnotationShape
25
+ """Shape defining the annotation.
26
+ The annotation shape is either a full geojson geometry or only a bounding box (4 floats).
27
+
28
+ The coordinates should be given in pixel, starting from the bottom left of the picture.
29
+
30
+ Note that the API will always output geometry as geojson geometry (thus will transform the bbox into a polygon).
31
+ """
32
+
33
+ semantics: List[SemanticTagUpdate] = Field(default_factory=list)
34
+ """Semantic tags associated to the annotation"""
35
+
36
+
37
+ @bp.route("/pictures/<uuid:itemId>/annotations", methods=["POST"])
38
+ @auth.login_required()
39
+ def postAnnotationNonStacAlias(itemId, account):
40
+ """Create an annotation on a picture.
41
+
42
+ The geometry can be provided as a bounding box (a list of 4 integers, minx, miny, maxx, maxy) or as a geojson geometry.
43
+ All coordinates must be in pixel, starting from the top left of the picture.
44
+
45
+ If an annotation already exists on the picture with the same shape, it will be used.
46
+
47
+ The is an alias to the `/api/collections/<collectionId>/items/<itemId>/annotations` endpoint (but you don't need to know the collection ID here).
48
+ ---
49
+ tags:
50
+ - Editing
51
+ - Semantics
52
+ parameters:
53
+ - name: itemId
54
+ in: path
55
+ description: ID of item to retrieve
56
+ required: true
57
+ schema:
58
+ type: string
59
+ requestBody:
60
+ content:
61
+ application/json:
62
+ schema:
63
+ $ref: '#/components/schemas/GeoVisioPostAnnotation'
64
+ security:
65
+ - bearerToken: []
66
+ - cookieAuth: []
67
+ responses:
68
+ 200:
69
+ description: the annotation metadata
70
+ content:
71
+ application/json:
72
+ schema:
73
+ $ref: '#/components/schemas/GeoVisioAnnotation'
74
+ """
75
+
76
+ account_id = UUID(accountIdOrDefault(account))
77
+
78
+ pic = db.fetchone(
79
+ current_app,
80
+ "SELECT 1 FROM pictures WHERE id = %(pic)s",
81
+ {"pic": itemId},
82
+ )
83
+ if not pic:
84
+ raise errors.InvalidAPIUsage(_("Picture %(p)s wasn't found in database", p=itemId), status_code=404)
85
+
86
+ if request.is_json and request.json is not None:
87
+ try:
88
+ post_params = AnnotationPostParameter(**request.json, account_id=account_id, picture_id=itemId)
89
+ except ValidationError as ve:
90
+ raise errors.InvalidAPIUsage(_("Impossible to create an annotation"), payload=validation_error(ve))
91
+ else:
92
+ raise errors.InvalidAPIUsage(_("Parameter for creating an annotation should be a valid JSON"), status_code=415)
93
+
94
+ creation_params = AnnotationCreationParameter(
95
+ account_id=account_id, picture_id=itemId, shape=post_params.shape, semantics=post_params.semantics
96
+ )
97
+
98
+ with db.conn(current_app) as conn:
99
+ annotation = creation_annotation(creation_params, conn)
100
+
101
+ return (
102
+ annotation.model_dump_json(exclude_none=True),
103
+ 200,
104
+ {
105
+ "Content-Type": "application/json",
106
+ "Access-Control-Expose-Headers": "Location", # Needed for allowing web browsers access Location header
107
+ "Location": url_for("annotations.getAnnotationById", _external=True, annotationId=annotation.id),
108
+ },
109
+ )
110
+
111
+
112
+ @bp.route("/collections/<uuid:collectionId>/items/<uuid:itemId>/annotations", methods=["POST"])
113
+ @auth.login_required()
114
+ def postAnnotation(collectionId, itemId, account):
115
+ """Create an annotation on a picture.
116
+
117
+ The geometry can be provided as a bounding box (a list of 4 integers, minx, miny, maxx, maxy) or as a geojson geometry.
118
+ All coordinates must be in pixel, starting from the top left of the picture.
119
+
120
+ If an annotation already exists on the picture with the same shape, it will be used.
121
+ ---
122
+ tags:
123
+ - Editing
124
+ - Semantics
125
+ parameters:
126
+ - name: collectionId
127
+ in: path
128
+ description: ID of collection to retrieve
129
+ required: true
130
+ schema:
131
+ type: string
132
+ - name: itemId
133
+ in: path
134
+ description: ID of item to retrieve
135
+ required: true
136
+ schema:
137
+ type: string
138
+ requestBody:
139
+ content:
140
+ application/json:
141
+ schema:
142
+ $ref: '#/components/schemas/GeoVisioPostAnnotation'
143
+ security:
144
+ - bearerToken: []
145
+ - cookieAuth: []
146
+ responses:
147
+ 200:
148
+ description: the annotation metadata
149
+ content:
150
+ application/json:
151
+ schema:
152
+ $ref: '#/components/schemas/GeoVisioAnnotation'
153
+ """
154
+ return postAnnotationNonStacAlias(itemId=itemId, account=account)
155
+
156
+
157
+ @bp.route("/collections/<uuid:collectionId>/items/<uuid:itemId>/annotations/<uuid:annotationId>", methods=["GET"])
158
+ def getAnnotation(collectionId, itemId, annotationId):
159
+ """Get an annotation
160
+
161
+ Note that this is the same route as `/api/annotations/<uuid:annotationId>` but you need to know the picture's and collection's IDs.
162
+ ---
163
+ tags:
164
+ - Semantics
165
+ parameters:
166
+ - name: collectionId
167
+ in: path
168
+ description: ID of collection
169
+ required: true
170
+ schema:
171
+ type: string
172
+ - name: itemId
173
+ in: path
174
+ description: ID of item
175
+ required: true
176
+ schema:
177
+ type: string
178
+ - name: annotationId
179
+ in: path
180
+ description: ID of annotation
181
+ required: true
182
+ schema:
183
+ type: string
184
+ security:
185
+ - bearerToken: []
186
+ - cookieAuth: []
187
+ responses:
188
+ 200:
189
+ description: the annotation metadata
190
+ content:
191
+ application/json:
192
+ schema:
193
+ $ref: '#/components/schemas/GeoVisioAnnotation'
194
+ """
195
+ with db.conn(current_app) as conn:
196
+
197
+ annotation = get_annotation(conn, annotationId)
198
+ if not annotation or annotation.picture_id != itemId:
199
+ raise errors.InvalidAPIUsage(_("Annotation %(p)s not found", p=itemId), status_code=404)
200
+
201
+ return annotation.model_dump_json(exclude_none=True), 200, {"Content-Type": "application/json"}
202
+
203
+
204
+ @bp.route("/annotations/<uuid:annotationId>", methods=["GET"])
205
+ def getAnnotationById(annotationId):
206
+ """Get an annotation.
207
+
208
+ This is the same route as `/api/collections/<uuid:collectionId>/items/<uuid:itemId>/annotations/<uuid:annotationId>` but you don't need to know the picture's and collection's IDs.
209
+
210
+ ---
211
+ tags:
212
+ - Semantics
213
+ parameters:
214
+ - name: annotationId
215
+ in: path
216
+ description: ID of annotation
217
+ required: true
218
+ schema:
219
+ type: string
220
+ security:
221
+ - bearerToken: []
222
+ - cookieAuth: []
223
+ responses:
224
+ 200:
225
+ description: the annotation metadata
226
+ content:
227
+ application/json:
228
+ schema:
229
+ $ref: '#/components/schemas/GeoVisioAnnotation'
230
+ """
231
+ with db.conn(current_app) as conn:
232
+ annotation = get_annotation(conn, annotationId)
233
+ if not annotation:
234
+ raise errors.InvalidAPIUsage(_("Annotation %(p)s not found", p=annotationId), status_code=404)
235
+
236
+ return annotation.model_dump_json(exclude_none=True), 200, {"Content-Type": "application/json"}
237
+
238
+
239
+ class AnnotationPatchParameter(BaseModel):
240
+ """Parameters used to update an annotation"""
241
+
242
+ semantics: Optional[List[SemanticTagUpdate]] = None
243
+ """Tags to update on the annotation. By default each tag will be added to the annotation's tags, but you can change this behavior by setting the `action` parameter to `delete`.
244
+
245
+ If you want to replace a tag, you need to first delete it, then add it again.
246
+
247
+ Like:
248
+ [
249
+ {"key": "some_key", "value": "some_value", "action": "delete"},
250
+ {"key": "some_key", "value": "some_new_value"}
251
+ ]
252
+ """
253
+
254
+
255
+ @bp.route("/annotations/<uuid:annotationId>", methods=["PATCH"])
256
+ @auth.login_required()
257
+ def patchAnnotationNonStacAlias(annotationId, account):
258
+ """Patch an annotation
259
+
260
+ Note that if the annotation has no associated tags anymore, it will be deleted.
261
+
262
+ Note that is an alias to the `/api/collections/<collectionId>/items/<itemId>/annotations/<annotationId>` endpoint (but you don't need to know the collection/item ID here).
263
+ ---
264
+ tags:
265
+ - Semantics
266
+ parameters:
267
+ - name: annotationId
268
+ in: path
269
+ description: ID of annotation
270
+ required: true
271
+ schema:
272
+ type: string
273
+ requestBody:
274
+ content:
275
+ application/json:
276
+ schema:
277
+ $ref: '#/components/schemas/GeoVisioPatchAnnotation'
278
+ security:
279
+ - bearerToken: []
280
+ - cookieAuth: []
281
+ responses:
282
+ 200:
283
+ description: the annotation metadata
284
+ content:
285
+ application/json:
286
+ schema:
287
+ $ref: '#/components/schemas/GeoVisioAnnotation'
288
+ 204:
289
+ description: The annotation was empty, it has been correctly deleted
290
+ """
291
+ if request.is_json and request.json is not None:
292
+ try:
293
+ params = AnnotationPatchParameter(**request.json)
294
+ except ValidationError as ve:
295
+ raise errors.InvalidAPIUsage(_("Impossible to patch annotation, invalid parameters"), payload=validation_error(ve))
296
+ else:
297
+ raise errors.InvalidAPIUsage(_("Parameter for updating an annotation should be a valid JSON"), status_code=415)
298
+
299
+ with db.conn(current_app) as conn:
300
+
301
+ annotation = get_annotation(conn, annotationId)
302
+ if not annotation:
303
+ raise errors.InvalidAPIUsage(_("Annotation %(p)s not found", p=annotationId), status_code=404)
304
+
305
+ a = update_annotation(annotation, params.semantics, account.id)
306
+ if a is None:
307
+ return "", 204
308
+ return a.model_dump_json(exclude_none=True), 200, {"Content-Type": "application/json"}
309
+
310
+
311
+ @bp.route("/collections/<uuid:collectionId>/items/<uuid:itemId>/annotations/<uuid:annotationId>", methods=["PATCH"])
312
+ @auth.login_required()
313
+ def patchAnnotation(collectionId, itemId, annotationId, account):
314
+ """Patch an annotation
315
+
316
+ Note that if the annotation has no associated tags anymore, it will be deleted.
317
+
318
+ Note that is the an alias to the `/api/annotations/<annotationId>` endpoint (but you need to know the collection/item ID here).
319
+ ---
320
+ tags:
321
+ - Semantics
322
+ parameters:
323
+ - name: collectionId
324
+ in: path
325
+ description: ID of collection
326
+ required: true
327
+ schema:
328
+ type: string
329
+ - name: itemId
330
+ in: path
331
+ description: ID of item
332
+ required: true
333
+ schema:
334
+ type: string
335
+ - name: annotationId
336
+ in: path
337
+ description: ID of annotation
338
+ required: true
339
+ schema:
340
+ type: string
341
+ security:
342
+ - bearerToken: []
343
+ - cookieAuth: []
344
+ responses:
345
+ 200:
346
+ description: the annotation metadata
347
+ content:
348
+ application/json:
349
+ schema:
350
+ $ref: '#/components/schemas/GeoVisioAnnotation'
351
+ 204:
352
+ description: The annotation was empty, it has been correctly deleted
353
+ """
354
+ return patchAnnotationNonStacAlias(annotationId=annotationId, account=account)
geovisio/web/auth.py CHANGED
@@ -112,7 +112,7 @@ def auth():
112
112
  def logout():
113
113
  """Log out from geovisio
114
114
  * If the OAuth Provider is keycloak, this will redirect to a keycloak confirmation page,
115
- and uppon confirmation keycloak will call post_logout_redirect that will invalidate the session
115
+ and upon confirmation keycloak will call post_logout_redirect that will invalidate the session
116
116
  * If the OAuth Provider is not keycloak, this will invalidate the session
117
117
  ---
118
118
  tags: