geovisio 2.5.0__py3-none-any.whl → 2.7.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 (59) hide show
  1. geovisio/__init__.py +38 -8
  2. geovisio/admin_cli/__init__.py +2 -2
  3. geovisio/admin_cli/db.py +8 -0
  4. geovisio/config_app.py +64 -0
  5. geovisio/db_migrations.py +24 -3
  6. geovisio/templates/main.html +14 -14
  7. geovisio/templates/viewer.html +3 -3
  8. geovisio/translations/de/LC_MESSAGES/messages.mo +0 -0
  9. geovisio/translations/de/LC_MESSAGES/messages.po +667 -0
  10. geovisio/translations/en/LC_MESSAGES/messages.mo +0 -0
  11. geovisio/translations/en/LC_MESSAGES/messages.po +730 -0
  12. geovisio/translations/es/LC_MESSAGES/messages.mo +0 -0
  13. geovisio/translations/es/LC_MESSAGES/messages.po +778 -0
  14. geovisio/translations/fi/LC_MESSAGES/messages.mo +0 -0
  15. geovisio/translations/fi/LC_MESSAGES/messages.po +589 -0
  16. geovisio/translations/fr/LC_MESSAGES/messages.mo +0 -0
  17. geovisio/translations/fr/LC_MESSAGES/messages.po +814 -0
  18. geovisio/translations/ko/LC_MESSAGES/messages.mo +0 -0
  19. geovisio/translations/ko/LC_MESSAGES/messages.po +685 -0
  20. geovisio/translations/messages.pot +686 -0
  21. geovisio/translations/nl/LC_MESSAGES/messages.mo +0 -0
  22. geovisio/translations/nl/LC_MESSAGES/messages.po +594 -0
  23. geovisio/utils/__init__.py +1 -1
  24. geovisio/utils/auth.py +50 -11
  25. geovisio/utils/db.py +65 -0
  26. geovisio/utils/excluded_areas.py +83 -0
  27. geovisio/utils/extent.py +30 -0
  28. geovisio/utils/fields.py +1 -1
  29. geovisio/utils/filesystems.py +0 -1
  30. geovisio/utils/link.py +14 -0
  31. geovisio/utils/params.py +20 -0
  32. geovisio/utils/pictures.py +94 -69
  33. geovisio/utils/reports.py +171 -0
  34. geovisio/utils/sequences.py +288 -126
  35. geovisio/utils/tokens.py +37 -42
  36. geovisio/utils/upload_set.py +654 -0
  37. geovisio/web/auth.py +50 -37
  38. geovisio/web/collections.py +305 -319
  39. geovisio/web/configuration.py +14 -0
  40. geovisio/web/docs.py +288 -12
  41. geovisio/web/excluded_areas.py +377 -0
  42. geovisio/web/items.py +203 -151
  43. geovisio/web/map.py +322 -106
  44. geovisio/web/params.py +69 -26
  45. geovisio/web/pictures.py +14 -31
  46. geovisio/web/reports.py +399 -0
  47. geovisio/web/rss.py +13 -7
  48. geovisio/web/stac.py +129 -121
  49. geovisio/web/tokens.py +105 -112
  50. geovisio/web/upload_set.py +768 -0
  51. geovisio/web/users.py +100 -73
  52. geovisio/web/utils.py +38 -9
  53. geovisio/workers/runner_pictures.py +278 -183
  54. geovisio-2.7.0.dist-info/METADATA +95 -0
  55. geovisio-2.7.0.dist-info/RECORD +66 -0
  56. geovisio-2.5.0.dist-info/METADATA +0 -115
  57. geovisio-2.5.0.dist-info/RECORD +0 -41
  58. {geovisio-2.5.0.dist-info → geovisio-2.7.0.dist-info}/LICENSE +0 -0
  59. {geovisio-2.5.0.dist-info → geovisio-2.7.0.dist-info}/WHEEL +0 -0
geovisio/web/users.py CHANGED
@@ -1,20 +1,23 @@
1
1
  import flask
2
- from flask import request, current_app
3
- from geovisio.utils import auth
2
+ from flask import request, current_app, url_for
3
+ from flask_babel import gettext as _
4
+ from geovisio.utils import auth, db
4
5
  from geovisio import errors
5
- import psycopg
6
6
  from psycopg.rows import dict_row
7
7
  from psycopg.sql import SQL
8
8
 
9
+ from geovisio.web import stac
10
+ from geovisio.web.utils import get_root_link
11
+
9
12
  bp = flask.Blueprint("user", __name__, url_prefix="/api/users")
10
13
 
11
14
 
12
15
  def _get_user_info(id, name):
13
16
  userMapUrl = (
14
- flask.url_for("map.getUserTile", userId=id, x="111111", y="222222", z="333333", format="mvt", _external=True)
15
- .replace("111111", "{x}")
16
- .replace("222222", "{y}")
17
- .replace("333333", "{z}")
17
+ flask.url_for("map.getUserTile", userId=id, x="11111111", y="22222222", z="33333333", format="mvt", _external=True)
18
+ .replace("11111111", "{x}")
19
+ .replace("22222222", "{y}")
20
+ .replace("33333333", "{z}")
18
21
  )
19
22
  response = {
20
23
  "id": id,
@@ -76,12 +79,11 @@ def getUserInfo(userId):
76
79
  schema:
77
80
  $ref: '#/components/schemas/GeoVisioUser'
78
81
  """
79
- with psycopg.connect(current_app.config["DB_URL"], row_factory=dict_row) as conn, conn.cursor() as cursor:
80
- account = cursor.execute(SQL("SELECT name, id FROM accounts WHERE id = %s"), [userId]).fetchone()
81
- if not account:
82
- raise errors.InvalidAPIUsage("Impossible to find user", status_code=404)
82
+ account = db.fetchone(current_app, SQL("SELECT name, id FROM accounts WHERE id = %s"), [userId], row_factory=dict_row)
83
+ if not account:
84
+ raise errors.InvalidAPIUsage(_("Impossible to find user"), status_code=404)
83
85
 
84
- return _get_user_info(account["id"], account["name"])
86
+ return _get_user_info(account["id"], account["name"])
85
87
 
86
88
 
87
89
  @bp.route("/me/catalog")
@@ -155,14 +157,11 @@ def searchUser():
155
157
  q = request.args.get("q")
156
158
  # for the moment, we can only search by string
157
159
  if not q:
158
- raise errors.InvalidAPIUsage("No search parameter given, you should provide `q=<pattern>` as query parameter", status_code=400)
160
+ raise errors.InvalidAPIUsage(_("No search parameter given, you should provide `q=<pattern>` as query parameter"), status_code=400)
159
161
 
160
162
  limit = request.args.get("limit", default=20, type=int)
161
-
162
- with psycopg.connect(current_app.config["DB_URL"], row_factory=dict_row) as conn, conn.cursor() as cursor:
163
- res = cursor.execute(
164
- SQL(
165
- """
163
+ query = SQL(
164
+ """
166
165
  WITH ranked AS (
167
166
  SELECT name, id, similarity({q}, name) AS similarity from accounts
168
167
  )
@@ -171,29 +170,30 @@ WHERE similarity > 0.1
171
170
  ORDER BY similarity DESC
172
171
  LIMIT {limit};
173
172
  """
174
- ).format(limit=limit, q=q)
175
- ).fetchall()
176
- return {
177
- "features": [
178
- {
179
- "label": r["name"],
180
- "id": r["id"],
181
- "links": [
182
- {
183
- "rel": "user-info",
184
- "type": "application/json",
185
- "href": flask.url_for("user.getUserInfo", userId=r["id"], _external=True),
186
- },
187
- {
188
- "rel": "collection",
189
- "type": "application/json",
190
- "href": flask.url_for("stac_collections.getUserCollection", userId=r["id"], _external=True),
191
- },
192
- ],
193
- }
194
- for r in res
195
- ]
196
- }
173
+ ).format(limit=limit, q=q)
174
+ res = db.fetchall(current_app, query, row_factory=dict_row)
175
+
176
+ return {
177
+ "features": [
178
+ {
179
+ "label": r["name"],
180
+ "id": r["id"],
181
+ "links": [
182
+ {
183
+ "rel": "user-info",
184
+ "type": "application/json",
185
+ "href": flask.url_for("user.getUserInfo", userId=r["id"], _external=True),
186
+ },
187
+ {
188
+ "rel": "collection",
189
+ "type": "application/json",
190
+ "href": flask.url_for("stac_collections.getUserCollection", userId=r["id"], _external=True),
191
+ },
192
+ ],
193
+ }
194
+ for r in res
195
+ ]
196
+ }
197
197
 
198
198
 
199
199
  @bp.route("/")
@@ -213,35 +213,62 @@ def listUsers():
213
213
 
214
214
  # no pagination yet, can be done when needed
215
215
  limit = min(request.args.get("limit", default=1000, type=int), 1000)
216
-
217
- with psycopg.connect(current_app.config["DB_URL"], row_factory=dict_row) as conn, conn.cursor() as cursor:
218
- res = cursor.execute(SQL("""SELECT name, id FROM accounts LIMIT {limit};""").format(limit=limit)).fetchall()
219
- return {
220
- "users": [
221
- {
222
- "name": r["name"],
223
- "id": r["id"],
224
- "links": [
225
- {
226
- "rel": "user-info",
227
- "type": "application/json",
228
- "href": flask.url_for("user.getUserInfo", userId=r["id"], _external=True),
229
- },
230
- {
231
- "rel": "collection",
232
- "type": "application/json",
233
- "href": flask.url_for("stac_collections.getUserCollection", userId=r["id"], _external=True),
234
- },
235
- ],
236
- }
237
- for r in res
238
- ],
239
- "links": [
240
- {
241
- "rel": "user-search",
242
- "type": "application/json",
243
- "href": flask.url_for("user.searchUser", _external=True),
244
- "title": "Search users",
245
- },
246
- ],
247
- }
216
+ query = SQL(
217
+ """SELECT
218
+ a.id, a.name, l.has_seq
219
+ FROM accounts a
220
+ LEFT OUTER JOIN LATERAL (
221
+ SELECT 1 as has_seq
222
+ FROM sequences s
223
+ WHERE s.account_id = a.id
224
+ LIMIT 1
225
+ ) l ON true
226
+ ORDER BY created_at
227
+ LIMIT {limit};"""
228
+ ).format(limit=limit)
229
+ res = db.fetchall(current_app, query, row_factory=dict_row)
230
+ return {
231
+ "stac_version": stac.STAC_VERSION,
232
+ "id": "geovisio:users",
233
+ "title": "users catalog",
234
+ "description": "List of users catalog",
235
+ "type": "Catalog",
236
+ "conformsTo": stac.CONFORMANCE_LIST,
237
+ "users": [
238
+ {
239
+ "name": r["name"],
240
+ "id": r["id"],
241
+ "links": [
242
+ {
243
+ "rel": "user-info",
244
+ "type": "application/json",
245
+ "href": flask.url_for("user.getUserInfo", userId=r["id"], _external=True),
246
+ },
247
+ {
248
+ "rel": "collection",
249
+ "type": "application/json",
250
+ "href": flask.url_for("stac_collections.getUserCollection", userId=r["id"], _external=True),
251
+ },
252
+ ],
253
+ }
254
+ for r in res
255
+ ],
256
+ "links": [
257
+ {
258
+ "rel": "user-search",
259
+ "type": "application/json",
260
+ "href": flask.url_for("user.searchUser", _external=True),
261
+ "title": "Search users",
262
+ },
263
+ get_root_link(),
264
+ ]
265
+ + [
266
+ {
267
+ "rel": "child",
268
+ "title": f'User "{r["name"]}" sequences',
269
+ "href": url_for("stac_collections.getUserCollection", userId=r["id"], _external=True),
270
+ }
271
+ for r in res
272
+ if r["has_seq"]
273
+ ],
274
+ }
geovisio/web/utils.py CHANGED
@@ -1,10 +1,14 @@
1
1
  import typing
2
2
  from dateutil import tz
3
- from typing import Optional
4
- from functools import wraps
5
- import psycopg
6
- from flask import current_app, url_for, Response, make_response
3
+ from datetime import timezone
4
+ from dateutil.tz import gettz
5
+ from functools import wraps, cache
7
6
  from geovisio import errors
7
+ from geovisio.utils import db
8
+ from flask import current_app, url_for
9
+ from flask_babel import gettext as _
10
+ from geovisio import __version__
11
+ import subprocess
8
12
 
9
13
  STAC_VERSION = "1.0.0"
10
14
 
@@ -25,6 +29,14 @@ def dbTsToStac(dbts):
25
29
  return dbts.astimezone(tz.gettz("UTC")).isoformat() if dbts is not None else None
26
30
 
27
31
 
32
+ def dbTsToStacTZ(dbts, dbtz):
33
+ """Transforms timestamp returned by PostgreSQL into ISO format with timezone"""
34
+ tzSwitches = {"CEST": "CET"}
35
+ if dbtz in tzSwitches:
36
+ dbtz = tzSwitches[dbtz]
37
+ return dbts.astimezone(gettz(dbtz or "UTC") or timezone.utc).isoformat()
38
+
39
+
28
40
  def cleanNoneInList(val: typing.List) -> typing.List:
29
41
  """Removes empty values from list"""
30
42
  return list(filter(lambda e: e is not None, val))
@@ -34,11 +46,10 @@ def accountIdOrDefault(account):
34
46
  # Get default account ID
35
47
  if account is not None:
36
48
  return account.id
37
- with psycopg.connect(current_app.config["DB_URL"]) as conn:
38
- accountId = conn.execute("SELECT id FROM accounts WHERE is_default").fetchone()
39
- if accountId is None:
40
- raise errors.InternalError("No default account defined, please contact your instance administrator")
41
- return str(accountId[0])
49
+ accountId = db.fetchone(current_app, "SELECT id FROM accounts WHERE is_default")
50
+ if accountId is None:
51
+ raise errors.InternalError(_("No default account defined, please contact your instance administrator"))
52
+ return str(accountId[0])
42
53
 
43
54
 
44
55
  def get_license_link():
@@ -75,6 +86,24 @@ def get_viewerpage_url():
75
86
  return url_for("viewer", _external=True)
76
87
 
77
88
 
89
+ @cache
90
+ def get_api_version():
91
+ """
92
+ Retrieve complete API version.
93
+
94
+ Format can be:
95
+ - 2.6.0-99-abcdefgh (release + amount of commits since last tag + commit short SHA) if Git repo is not on a release tag
96
+ - 2.6.0 (release) if Git repo is on release tag (or no Git repo available)
97
+ """
98
+
99
+ if current_app.config.get("API_GIT_VERSION") is not None:
100
+ return current_app.config["API_GIT_VERSION"]
101
+ try:
102
+ return subprocess.check_output(["git", "describe"]).strip().decode("utf-8")
103
+ except Exception:
104
+ return __version__
105
+
106
+
78
107
  def user_dependant_response(flag):
79
108
  """Set if a response is user dependant.
80
109