c2cgeoportal-commons 2.6.0__py3-none-any.whl → 2.9rc45__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.

Potentially problematic release.


This version of c2cgeoportal-commons might be problematic. Click here for more details.

Files changed (88) hide show
  1. c2cgeoportal_commons/__init__.py +2 -5
  2. c2cgeoportal_commons/alembic/env.py +49 -33
  3. c2cgeoportal_commons/alembic/main/028477929d13_add_technical_roles.py +10 -7
  4. c2cgeoportal_commons/alembic/main/04f05bfbb05e_remove_the_old_is_expanded_column.py +62 -0
  5. c2cgeoportal_commons/alembic/main/116b9b79fc4d_internal_and_external_layer_tables_.py +42 -43
  6. c2cgeoportal_commons/alembic/main/1418cb05921b_merge_1_6_and_master_branches.py +9 -8
  7. c2cgeoportal_commons/alembic/main/164ac0819a61_add_image_format_to_wmts_layer.py +9 -6
  8. c2cgeoportal_commons/alembic/main/166ff2dcc48d_create_database.py +21 -24
  9. c2cgeoportal_commons/alembic/main/16e43f8c0330_remove_old_metadata_column.py +9 -6
  10. c2cgeoportal_commons/alembic/main/1d5d4abfebd1_add_restricted_theme.py +14 -8
  11. c2cgeoportal_commons/alembic/main/1de20166b274_remove_v1_artifacts.py +9 -6
  12. c2cgeoportal_commons/alembic/main/20137477bd02_update_icons_url.py +9 -6
  13. c2cgeoportal_commons/alembic/main/21f11066f8ec_trigger_on_role_updates_user_in_static.py +21 -20
  14. c2cgeoportal_commons/alembic/main/22e6dfb556de_add_description_tree_.py +9 -6
  15. c2cgeoportal_commons/alembic/main/29f2a32859ec_merge_1_6_and_master_branches.py +9 -8
  16. c2cgeoportal_commons/alembic/main/2b8ed8c1df94_set_layergroup_treeitem_is_as_a_primary_.py +9 -6
  17. c2cgeoportal_commons/alembic/main/2e57710fecfe_update_the_ogc_server_for_ogc_api.py +71 -0
  18. c2cgeoportal_commons/alembic/main/32527659d57b_move_exclude_properties_from_layerv1_to_.py +15 -12
  19. c2cgeoportal_commons/alembic/main/32b21aa1d0ed_merge_e004f76e951a_and_e004f76e951a_.py +9 -8
  20. c2cgeoportal_commons/alembic/main/338b57593823_remove_trigger_on_role_name_change.py +25 -24
  21. c2cgeoportal_commons/alembic/main/415746eb9f6_changes_for_v2.py +45 -43
  22. c2cgeoportal_commons/alembic/main/44c91d82d419_add_table_log.py +72 -0
  23. c2cgeoportal_commons/alembic/main/5109242131ce_add_column_time_widget.py +11 -9
  24. c2cgeoportal_commons/alembic/main/52916d8fde8b_add_sql_fields_to_vector_tiles.py +60 -0
  25. c2cgeoportal_commons/alembic/main/53ba1a68d5fe_add_theme_to_fulltextsearch.py +9 -6
  26. c2cgeoportal_commons/alembic/main/54645a535ad6_add_ordering_in_relation.py +17 -14
  27. c2cgeoportal_commons/alembic/main/56dc90838d90_fix_removing_layerv1.py +10 -8
  28. c2cgeoportal_commons/alembic/main/596ba21e3833_separate_local_internal.py +13 -14
  29. c2cgeoportal_commons/alembic/main/678f88c7ad5e_add_vector_tiles_layers_table.py +9 -6
  30. c2cgeoportal_commons/alembic/main/6a412d9437b1_rename_serverogc_to_ogcserver.py +9 -6
  31. c2cgeoportal_commons/alembic/main/6d87fdad275a_convert_metadata_to_the_right_case.py +13 -20
  32. c2cgeoportal_commons/alembic/main/7530011a66a7_trigger_on_role_updates_user_in_static.py +21 -20
  33. c2cgeoportal_commons/alembic/main/78fd093c8393_add_api_s_intrfaces.py +36 -52
  34. c2cgeoportal_commons/alembic/main/7d271f4527cd_add_layer_column_in_layerv1_table.py +9 -6
  35. c2cgeoportal_commons/alembic/main/809650bd04c3_add_dimension_field.py +9 -6
  36. c2cgeoportal_commons/alembic/main/8117bb9bba16_use_dimension_on_all_the_layers.py +9 -6
  37. c2cgeoportal_commons/alembic/main/87f8330ed64e_add_missing_delete_cascades.py +9 -6
  38. c2cgeoportal_commons/alembic/main/9268a1dffac0_add_trigger_to_be_able_to_correctly_.py +11 -8
  39. c2cgeoportal_commons/alembic/main/94db7e7e5b21_merge_2_2_and_master_branches.py +9 -8
  40. c2cgeoportal_commons/alembic/main/951ff84bd8ec_be_able_to_delete_a_wms_layer_in_sql.py +9 -6
  41. c2cgeoportal_commons/alembic/main/a00109812f89_add_field_layer_wms_valid.py +11 -8
  42. c2cgeoportal_commons/alembic/main/a4558f032d7d_add_support_of_cog_layers.py +74 -0
  43. c2cgeoportal_commons/alembic/main/a4f1aac9bda_merge_1_6_and_master_branches.py +9 -8
  44. c2cgeoportal_commons/alembic/main/b60f2a505f42_remame_uimetadata_to_metadata.py +9 -6
  45. c2cgeoportal_commons/alembic/main/b6b09f414fe8_sync_model_database.py +165 -0
  46. c2cgeoportal_commons/alembic/main/c75124553bf3_remove_deprecated_columns.py +9 -6
  47. c2cgeoportal_commons/alembic/main/d48a63b348f1_change_mapserver_url_for_docker.py +13 -14
  48. c2cgeoportal_commons/alembic/main/d8ef99bc227e_be_able_to_delete_a_linked_functionality.py +15 -12
  49. c2cgeoportal_commons/alembic/main/daf738d5bae4_merge_2_0_and_master_branches.py +9 -8
  50. c2cgeoportal_commons/alembic/main/dba87f2647f9_merge_2_2_on_2_3.py +9 -8
  51. c2cgeoportal_commons/alembic/main/e004f76e951a_add_missing_not_null.py +15 -32
  52. c2cgeoportal_commons/alembic/main/e7e03dedade3_put_the_default_wms_server_in_the_.py +13 -14
  53. c2cgeoportal_commons/alembic/main/e85afd327ab3_cascade_deletes_to_tsearch.py +9 -6
  54. c2cgeoportal_commons/alembic/main/ec82a8906649_add_missing_on_delete_cascade_on_layer_.py +13 -10
  55. c2cgeoportal_commons/alembic/main/ee25d267bf46_main_interface_desktop.py +11 -14
  56. c2cgeoportal_commons/alembic/main/eeb345672454_merge_2_4_and_master_branches.py +9 -8
  57. c2cgeoportal_commons/alembic/static/0c640a58a09a_add_opt_key_column.py +9 -6
  58. c2cgeoportal_commons/alembic/static/107b81f5b9fe_add_missing_delete_cascades.py +9 -6
  59. c2cgeoportal_commons/alembic/static/1857owc78a07_add_last_login_and_expiration_date.py +7 -6
  60. c2cgeoportal_commons/alembic/static/1da396a88908_move_user_table_to_static_schema.py +19 -20
  61. c2cgeoportal_commons/alembic/static/267b4c1bde2e_add_display_name_in_the_user_for_better_.py +62 -0
  62. c2cgeoportal_commons/alembic/static/3f89a7d71a5e_alter_column_url_to_remove_limitation.py +8 -7
  63. c2cgeoportal_commons/alembic/static/44c91d82d419_add_table_log.py +72 -0
  64. c2cgeoportal_commons/alembic/static/53d671b17b20_add_timezone_on_datetime_fields.py +9 -6
  65. c2cgeoportal_commons/alembic/static/5472fbc19f39_add_temp_password_column.py +9 -6
  66. c2cgeoportal_commons/alembic/static/76d72fb3fcb9_add_oauth2_pkce.py +70 -0
  67. c2cgeoportal_commons/alembic/static/7ef947f30f20_add_oauth2_tables.py +9 -6
  68. c2cgeoportal_commons/alembic/static/910b4ca53b68_sync_model_database.py +186 -0
  69. c2cgeoportal_commons/alembic/static/aa41e9613256_wip_add_openid_connect_support.py +64 -0
  70. c2cgeoportal_commons/alembic/static/ae5e88f35669_add_table_user_role.py +15 -18
  71. c2cgeoportal_commons/alembic/static/bd029dbfc11a_fill_tech_data_column.py +10 -8
  72. c2cgeoportal_commons/lib/email_.py +14 -16
  73. c2cgeoportal_commons/lib/literal.py +44 -0
  74. c2cgeoportal_commons/lib/url.py +164 -58
  75. c2cgeoportal_commons/lib/validators.py +2 -4
  76. c2cgeoportal_commons/models/__init__.py +19 -15
  77. c2cgeoportal_commons/models/main.py +1191 -263
  78. c2cgeoportal_commons/models/sqlalchemy.py +14 -18
  79. c2cgeoportal_commons/models/static.py +305 -78
  80. c2cgeoportal_commons/py.typed +0 -0
  81. c2cgeoportal_commons/testing/__init__.py +17 -12
  82. c2cgeoportal_commons/testing/initializedb.py +15 -14
  83. {c2cgeoportal_commons-2.6.0.dist-info → c2cgeoportal_commons-2.9rc45.dist-info}/METADATA +15 -16
  84. c2cgeoportal_commons-2.9rc45.dist-info/RECORD +89 -0
  85. {c2cgeoportal_commons-2.6.0.dist-info → c2cgeoportal_commons-2.9rc45.dist-info}/WHEEL +1 -1
  86. tests/conftest.py +1 -3
  87. c2cgeoportal_commons-2.6.0.dist-info/RECORD +0 -76
  88. {c2cgeoportal_commons-2.6.0.dist-info → c2cgeoportal_commons-2.9rc45.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2013-2021, Camptocamp SA
1
+ # Copyright (c) 2013-2024, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -27,89 +25,197 @@
27
25
  # of the authors and should not be interpreted as representing official policies,
28
26
  # either expressed or implied, of the FreeBSD Project.
29
27
 
30
-
28
+ import logging
29
+ import re
31
30
  import urllib.parse
32
- from typing import Dict, Optional, Set
33
31
 
34
32
  from pyramid.request import Request
35
33
 
34
+ _LOG = logging.getLogger(__name__)
35
+
36
+
37
+ class Url:
38
+ """Object representation of an URI."""
39
+
40
+ scheme = ""
41
+ _netloc = ""
42
+ _hostname: str | None = None
43
+ _port: int | None = None
44
+ path = ""
45
+ query: dict[str, str] = {}
46
+ fragment = ""
47
+
48
+ def __init__(self, url: str | None = None):
49
+ if url:
50
+ url_split = urllib.parse.urlsplit(url)
51
+ self.scheme = url_split.scheme
52
+ self._netloc = url_split.netloc
53
+ self._hostname = url_split.hostname
54
+ try:
55
+ self._port = url_split.port
56
+ except ValueError as error:
57
+ _LOG.debug(error)
58
+ self.path = url_split.path
59
+ self.query = dict(urllib.parse.parse_qsl(url_split.query))
60
+ self.fragment = url_split.fragment
61
+
62
+ def clone(self) -> "Url":
63
+ result = Url()
64
+ result.scheme = self.scheme
65
+ result._netloc = self._netloc # pylint: disable=protected-access
66
+ result._hostname = self._hostname # pylint: disable=protected-access
67
+ result._port = self._port # pylint: disable=protected-access
68
+ result.path = self.path
69
+ result.query = dict(self.query)
70
+ result.fragment = self.fragment
71
+ return result
72
+
73
+ @staticmethod
74
+ def _is_valid_hostname(hostname: str) -> bool:
75
+ if len(hostname) > 255:
76
+ return False
77
+ if hostname[-1] == ".":
78
+ hostname = hostname[:-1] # strip exactly one dot from the right, if present
79
+ allowed = re.compile(r"(?!-)[a-z\d-]{1,63}(?<!-)$", re.IGNORECASE)
80
+ return all(allowed.match(x) for x in hostname.split("."))
81
+
82
+ @property
83
+ def netloc(self) -> str:
84
+ return self._netloc
85
+
86
+ @netloc.setter
87
+ def netloc(self, netloc: str) -> None:
88
+ netloc_split = netloc.split(":")
89
+ if len(netloc_split) > 2:
90
+ raise RuntimeError(f"The netloc '{netloc}' in invalid")
91
+ if not self._is_valid_hostname(netloc_split[0]):
92
+ raise RuntimeError(f"The netloc '{netloc}' in invalid")
93
+ if len(netloc_split) == 2:
94
+ allowed = re.compile(r"^[0-9]+$")
95
+ if not allowed.match(netloc_split[1]):
96
+ _LOG.debug("The netloc '%s' contains invalid port", netloc)
97
+ self._port = None
98
+ else:
99
+ self._port = int(netloc_split[1])
100
+ else:
101
+ self._port = None
102
+ self._netloc = netloc
103
+ self._hostname = netloc_split[0]
104
+
105
+ @property
106
+ def hostname(self) -> str | None:
107
+ return self._hostname
108
+
109
+ @hostname.setter
110
+ def hostname(self, hostname: str) -> None:
111
+ if not self._is_valid_hostname(hostname):
112
+ raise RuntimeError(f"The hostname '{hostname}' in invalid")
113
+ self._hostname = hostname
114
+ self.netloc = hostname if self._port is None else f"{hostname}:{self._port}"
115
+
116
+ @property
117
+ def port(self) -> int | None:
118
+ return self._port
119
+
120
+ @port.setter
121
+ def port(self, port: int | None) -> None:
122
+ self._port = port
123
+ self.netloc = (self._hostname or "") if port is None else f"{self._hostname}:{port}"
124
+
125
+ def add_query(self, query: dict[str, str], force: bool = False) -> "Url":
126
+ if query:
127
+ for key, value in query.items():
128
+ if force or key not in self.query:
129
+ self.query[key] = value
130
+ return self
131
+
132
+ @property
133
+ def query_lower(self) -> dict[str, str]:
134
+ return {k.lower(): v for k, v in self.query.items()}
135
+
136
+ def url(self) -> str:
137
+ return urllib.parse.urlunsplit(
138
+ (
139
+ self.scheme,
140
+ self._netloc,
141
+ self.path,
142
+ urllib.parse.urlencode(self.query),
143
+ self.fragment,
144
+ )
145
+ )
36
146
 
37
- def add_url_params(url: str, params: Optional[Dict[str, str]]) -> str:
38
- if not params:
39
- return url
40
- return add_spliturl_params(urllib.parse.urlsplit(url), params)
147
+ def __str__(self) -> str:
148
+ return self.url()
41
149
 
150
+ def __repr__(self) -> str:
151
+ return self.url()
42
152
 
43
- def add_spliturl_params(spliturl: urllib.parse.SplitResult, params: Dict[str, str]) -> str:
44
- query = {k: v[-1] for k, v in list(urllib.parse.parse_qs(spliturl.query).items())}
45
- for key, value in list(params.items()):
46
- if key not in query:
47
- query[key] = value
48
153
 
49
- return urllib.parse.urlunsplit(
50
- (spliturl.scheme, spliturl.netloc, spliturl.path, urllib.parse.urlencode(query), spliturl.fragment)
51
- )
154
+ def get_url2(
155
+ name: str, url: str, request: Request, errors: set[str], servers: dict[str, str] | None = None
156
+ ) -> Url | None:
157
+ """
158
+ Get the real URL from the URI of the administration interface.
52
159
 
160
+ Manage the schema: static and config.
161
+ """
162
+ if servers is None:
163
+ servers = request.registry.settings.get("servers", {})
53
164
 
54
- def get_url2(name: str, url: str, request: Request, errors: Set[str]) -> Optional[str]:
165
+ url_obj = Url(url)
55
166
  url_split = urllib.parse.urlsplit(url)
56
- if url_split.scheme == "":
57
- if url_split.netloc == "" and url_split.path not in ("", "/"):
167
+ if url_obj.scheme == "":
168
+ if url_obj.netloc == "" and url_obj.path not in ("", "/"):
58
169
  # Relative URL like: /dummy/static/url or dummy/static/url
59
- return urllib.parse.urlunsplit(url_split)
60
- errors.add("{}='{}' is not an URL.".format(name, url))
170
+ return url_obj
171
+ errors.add(f"{name}='{url}' is not an URL.")
61
172
  return None
62
- if url_split.scheme in ("http", "https"):
63
- if url_split.netloc == "":
64
- errors.add("{}='{}' is not a valid URL.".format(name, url))
173
+ if url_obj.scheme in ("http", "https"):
174
+ if url_obj.netloc == "":
175
+ errors.add(f"{name}='{url}' is not a valid URL.")
65
176
  return None
66
- return urllib.parse.urlunsplit(url_split)
67
- if url_split.scheme == "static":
68
- if url_split.path in ("", "/"):
69
- errors.add("{}='{}' cannot have an empty path.".format(name, url))
177
+ return url_obj
178
+ if url_obj.scheme == "static":
179
+ if request is None:
180
+ errors.add(f"{name}='{url}' The request is required for static URL.")
181
+ if url_obj.path in ("", "/"):
182
+ errors.add(f"{name}='{url}' cannot have an empty path.")
70
183
  return None
71
- proj = url_split.netloc
184
+ proj = url_obj.netloc
72
185
  package = request.registry.settings["package"]
73
186
  if proj in ("", "static"):
74
187
  proj = "/etc/geomapfish/static"
75
188
  elif ":" not in proj:
76
189
  if proj == "static-ngeo":
77
190
  errors.add(
78
- "{}='{}' static-ngeo shouldn't be used out of webpack because it don't has "
79
- "cache bustering.".format(name, url)
191
+ f"{name}='{url}' static-ngeo shouldn't be used out of webpack because it don't has "
192
+ "cache bustering."
80
193
  )
81
- proj = "{}_geoportal:{}".format(package, proj)
82
- return request.static_url("{}{}".format(proj, url_split.path))
83
- if url_split.scheme == "config":
84
- if url_split.netloc == "":
85
- errors.add("{}='{}' cannot have an empty netloc.".format(name, url))
194
+ proj = f"{package}_geoportal:{proj}"
195
+ return Url(request.static_url(f"{proj}{url_split.path}"))
196
+ if url_obj.scheme == "config":
197
+ if url_obj.netloc == "":
198
+ errors.add(f"{name}='{url}' cannot have an empty netloc.")
86
199
  return None
87
- server = request.registry.settings.get("servers", {}).get(url_split.netloc)
200
+ server = servers.get(url_obj.netloc)
88
201
  if server is None:
89
202
  errors.add(
90
- "{}: The server '{}' ({}) is not found in the config: [{}]".format(
91
- name,
92
- url_split.netloc,
93
- url,
94
- ", ".join(request.registry.settings.get("servers", {}).keys()),
95
- )
203
+ f"{name}: The server '{url_obj.netloc}' ({url}) is not found in the config: "
204
+ f"[{', '.join(servers.keys())}]"
96
205
  )
97
206
  return None
98
207
 
99
208
  if isinstance(server, dict):
100
- url = server["url"]
101
- params: Dict[str, str] = server.get("params", {})
209
+ url_obj_server = Url(server["url"])
210
+ url_obj_server.add_query(server.get("params", {}))
102
211
  else:
103
- url_split_server = urllib.parse.urlsplit(server)
104
- params = dict(urllib.parse.parse_qsl(url_split_server.query))
105
- url = urllib.parse.urlunsplit(url_split_server._replace(query=""))
106
- params.update(dict(urllib.parse.parse_qsl(url_split.query)))
107
-
108
- if url_split.path != "":
109
- if url[-1] != "/":
110
- url += "/"
111
- url = urllib.parse.urljoin(url, url_split.path[1:])
112
-
113
- return url if not params else "{}?{}".format(url, urllib.parse.urlencode(params))
114
-
212
+ url_obj_server = Url(server)
213
+
214
+ if url_obj.path != "":
215
+ if url_obj_server.path == "" or url_obj_server.path[-1] != "/":
216
+ url_obj_server.path += "/"
217
+ url_obj_server.path = urllib.parse.urljoin(url_obj_server.path, url_obj.path[1:])
218
+ url_obj_server.add_query(url_obj.query, force=True)
219
+ url_obj_server.fragment = url_obj.fragment
220
+ return url_obj_server
115
221
  return None
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2019-2020, Camptocamp SA
1
+ # Copyright (c) 2019-2023, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -33,5 +31,5 @@ import re
33
31
  import colander
34
32
 
35
33
  # Custom url validator that allow c2cgeoportal static urls.
36
- URL_REGEX = r"(?:{}|^(:?static|config)://\S+$)".format(colander.URL_REGEX)
34
+ URL_REGEX = rf"(?:{colander.URL_REGEX}|^(:?static|config)://\S+$)"
37
35
  url = colander.Regex(URL_REGEX, msg=colander._("Must be a URL"), flags=re.IGNORECASE)
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2011-2020, Camptocamp SA
1
+ # Copyright (c) 2011-2024, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -29,11 +27,11 @@
29
27
 
30
28
 
31
29
  import logging
32
- from typing import Any, Dict, List, Optional, Union # noqa
30
+ from typing import Any
33
31
 
34
32
  import sqlalchemy.ext.declarative
35
- import sqlalchemy.ext.declarative.api
36
- import sqlalchemy.orm # noqa
33
+ import sqlalchemy.orm
34
+ import sqlalchemy.orm.scoping
37
35
  import zope.event
38
36
 
39
37
  try:
@@ -44,20 +42,27 @@ except ModuleNotFoundError:
44
42
  pass
45
43
 
46
44
 
47
- # Should be filed on application initialisation
48
- DBSession: sqlalchemy.orm.Session = None
49
- Base: sqlalchemy.ext.declarative.api.ConcreteBase = sqlalchemy.ext.declarative.declarative_base()
50
- DBSessions: Dict[str, sqlalchemy.orm.Session] = {}
45
+ # Should be filed on application initialization
46
+ DBSession: sqlalchemy.orm.scoping.scoped_session[sqlalchemy.orm.Session] | None = None
47
+
48
+
49
+ class BaseType(sqlalchemy.ext.declarative.DeclarativeMeta, type):
50
+ pass
51
+
52
+
53
+ Base: BaseType = sqlalchemy.orm.declarative_base()
54
+ DBSessions: dict[str, sqlalchemy.orm.scoping.scoped_session[sqlalchemy.orm.Session]] = {}
51
55
 
52
56
 
53
- LOG = logging.getLogger(__name__)
57
+ _LOG = logging.getLogger(__name__)
54
58
 
55
59
 
56
60
  class InvalidateCacheEvent:
57
- pass
61
+ """Event to be broadcast."""
58
62
 
59
63
 
60
- def cache_invalidate_cb(*args: List[Any]) -> None:
64
+ def cache_invalidate_cb(*args: list[Any]) -> None:
65
+ """Invalidate the cache on a broadcast event."""
61
66
  _cache_invalidate_cb()
62
67
 
63
68
 
@@ -68,6 +73,5 @@ try:
68
73
  def _cache_invalidate_cb() -> None:
69
74
  zope.event.notify(InvalidateCacheEvent())
70
75
 
71
-
72
76
  except ModuleNotFoundError:
73
- LOG.error("c2cwsgiutils broadcast not found")
77
+ _LOG.error("c2cwsgiutils broadcast not found")