c2cgeoportal-commons 2.8.1.146__py3-none-any.whl → 2.9.0.350__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 (87) hide show
  1. c2cgeoportal_commons/__init__.py +1 -1
  2. c2cgeoportal_commons/alembic/env.py +14 -10
  3. c2cgeoportal_commons/alembic/main/028477929d13_add_technical_roles.py +2 -2
  4. c2cgeoportal_commons/alembic/main/04f05bfbb05e_remove_the_old_is_expanded_column.py +3 -1
  5. c2cgeoportal_commons/alembic/main/116b9b79fc4d_internal_and_external_layer_tables_.py +12 -12
  6. c2cgeoportal_commons/alembic/main/1418cb05921b_merge_1_6_and_master_branches.py +3 -1
  7. c2cgeoportal_commons/alembic/main/164ac0819a61_add_image_format_to_wmts_layer.py +2 -2
  8. c2cgeoportal_commons/alembic/main/166ff2dcc48d_create_database.py +3 -3
  9. c2cgeoportal_commons/alembic/main/16e43f8c0330_remove_old_metadata_column.py +2 -2
  10. c2cgeoportal_commons/alembic/main/1d5d4abfebd1_add_restricted_theme.py +2 -2
  11. c2cgeoportal_commons/alembic/main/1de20166b274_remove_v1_artifacts.py +2 -2
  12. c2cgeoportal_commons/alembic/main/20137477bd02_update_icons_url.py +2 -2
  13. c2cgeoportal_commons/alembic/main/21f11066f8ec_trigger_on_role_updates_user_in_static.py +2 -2
  14. c2cgeoportal_commons/alembic/main/22e6dfb556de_add_description_tree_.py +2 -2
  15. c2cgeoportal_commons/alembic/main/29f2a32859ec_merge_1_6_and_master_branches.py +3 -1
  16. c2cgeoportal_commons/alembic/main/2b8ed8c1df94_set_layergroup_treeitem_is_as_a_primary_.py +2 -2
  17. c2cgeoportal_commons/alembic/main/2e57710fecfe_update_the_ogc_server_for_ogc_api.py +77 -0
  18. c2cgeoportal_commons/alembic/main/32527659d57b_move_exclude_properties_from_layerv1_to_.py +2 -2
  19. c2cgeoportal_commons/alembic/main/32b21aa1d0ed_merge_e004f76e951a_and_e004f76e951a_.py +3 -1
  20. c2cgeoportal_commons/alembic/main/338b57593823_remove_trigger_on_role_name_change.py +2 -2
  21. c2cgeoportal_commons/alembic/main/415746eb9f6_changes_for_v2.py +2 -2
  22. c2cgeoportal_commons/alembic/main/44c91d82d419_add_table_log.py +8 -6
  23. c2cgeoportal_commons/alembic/main/5109242131ce_add_column_time_widget.py +4 -3
  24. c2cgeoportal_commons/alembic/main/52916d8fde8b_add_sql_fields_to_vector_tiles.py +5 -3
  25. c2cgeoportal_commons/alembic/main/53ba1a68d5fe_add_theme_to_fulltextsearch.py +2 -2
  26. c2cgeoportal_commons/alembic/main/54645a535ad6_add_ordering_in_relation.py +2 -2
  27. c2cgeoportal_commons/alembic/main/56dc90838d90_fix_removing_layerv1.py +2 -2
  28. c2cgeoportal_commons/alembic/main/596ba21e3833_separate_local_internal.py +2 -2
  29. c2cgeoportal_commons/alembic/main/678f88c7ad5e_add_vector_tiles_layers_table.py +2 -2
  30. c2cgeoportal_commons/alembic/main/6a412d9437b1_rename_serverogc_to_ogcserver.py +2 -2
  31. c2cgeoportal_commons/alembic/main/6d87fdad275a_convert_metadata_to_the_right_case.py +2 -2
  32. c2cgeoportal_commons/alembic/main/7530011a66a7_trigger_on_role_updates_user_in_static.py +2 -2
  33. c2cgeoportal_commons/alembic/main/78fd093c8393_add_api_s_intrfaces.py +15 -10
  34. c2cgeoportal_commons/alembic/main/7d271f4527cd_add_layer_column_in_layerv1_table.py +2 -2
  35. c2cgeoportal_commons/alembic/main/809650bd04c3_add_dimension_field.py +2 -2
  36. c2cgeoportal_commons/alembic/main/8117bb9bba16_use_dimension_on_all_the_layers.py +2 -2
  37. c2cgeoportal_commons/alembic/main/87f8330ed64e_add_missing_delete_cascades.py +2 -2
  38. c2cgeoportal_commons/alembic/main/9268a1dffac0_add_trigger_to_be_able_to_correctly_.py +2 -2
  39. c2cgeoportal_commons/alembic/main/94db7e7e5b21_merge_2_2_and_master_branches.py +3 -1
  40. c2cgeoportal_commons/alembic/main/951ff84bd8ec_be_able_to_delete_a_wms_layer_in_sql.py +2 -2
  41. c2cgeoportal_commons/alembic/main/a00109812f89_add_field_layer_wms_valid.py +4 -4
  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 +3 -1
  44. c2cgeoportal_commons/alembic/main/b60f2a505f42_remame_uimetadata_to_metadata.py +2 -2
  45. c2cgeoportal_commons/alembic/main/b6b09f414fe8_sync_model_database.py +174 -0
  46. c2cgeoportal_commons/alembic/main/c75124553bf3_remove_deprecated_columns.py +2 -2
  47. c2cgeoportal_commons/alembic/main/d48a63b348f1_change_mapserver_url_for_docker.py +2 -2
  48. c2cgeoportal_commons/alembic/main/d8ef99bc227e_be_able_to_delete_a_linked_functionality.py +2 -2
  49. c2cgeoportal_commons/alembic/main/daf738d5bae4_merge_2_0_and_master_branches.py +3 -1
  50. c2cgeoportal_commons/alembic/main/dba87f2647f9_merge_2_2_on_2_3.py +3 -1
  51. c2cgeoportal_commons/alembic/main/e004f76e951a_add_missing_not_null.py +2 -2
  52. c2cgeoportal_commons/alembic/main/e7e03dedade3_put_the_default_wms_server_in_the_.py +2 -2
  53. c2cgeoportal_commons/alembic/main/e85afd327ab3_cascade_deletes_to_tsearch.py +2 -2
  54. c2cgeoportal_commons/alembic/main/ec82a8906649_add_missing_on_delete_cascade_on_layer_.py +2 -2
  55. c2cgeoportal_commons/alembic/main/ee25d267bf46_main_interface_desktop.py +2 -2
  56. c2cgeoportal_commons/alembic/main/eeb345672454_merge_2_4_and_master_branches.py +3 -1
  57. c2cgeoportal_commons/alembic/static/0c640a58a09a_add_opt_key_column.py +2 -2
  58. c2cgeoportal_commons/alembic/static/107b81f5b9fe_add_missing_delete_cascades.py +2 -2
  59. c2cgeoportal_commons/alembic/static/1857owc78a07_add_last_login_and_expiration_date.py +2 -2
  60. c2cgeoportal_commons/alembic/static/1da396a88908_move_user_table_to_static_schema.py +3 -3
  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 +2 -2
  63. c2cgeoportal_commons/alembic/static/44c91d82d419_add_table_log.py +6 -4
  64. c2cgeoportal_commons/alembic/static/53d671b17b20_add_timezone_on_datetime_fields.py +2 -2
  65. c2cgeoportal_commons/alembic/static/5472fbc19f39_add_temp_password_column.py +2 -2
  66. c2cgeoportal_commons/alembic/static/76d72fb3fcb9_add_oauth2_pkce.py +3 -1
  67. c2cgeoportal_commons/alembic/static/7ef947f30f20_add_oauth2_tables.py +3 -1
  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 +2 -2
  71. c2cgeoportal_commons/alembic/static/bd029dbfc11a_fill_tech_data_column.py +2 -2
  72. c2cgeoportal_commons/lib/email_.py +15 -19
  73. c2cgeoportal_commons/lib/literal.py +3 -3
  74. c2cgeoportal_commons/lib/url.py +15 -16
  75. c2cgeoportal_commons/lib/validators.py +1 -1
  76. c2cgeoportal_commons/models/__init__.py +15 -8
  77. c2cgeoportal_commons/models/main.py +323 -228
  78. c2cgeoportal_commons/models/sqlalchemy.py +11 -10
  79. c2cgeoportal_commons/models/static.py +125 -76
  80. c2cgeoportal_commons/testing/__init__.py +10 -6
  81. c2cgeoportal_commons/testing/initializedb.py +7 -6
  82. {c2cgeoportal_commons-2.8.1.146.dist-info → c2cgeoportal_commons-2.9.0.350.dist-info}/METADATA +4 -9
  83. c2cgeoportal_commons-2.9.0.350.dist-info/RECORD +89 -0
  84. {c2cgeoportal_commons-2.8.1.146.dist-info → c2cgeoportal_commons-2.9.0.350.dist-info}/WHEEL +1 -1
  85. tests/conftest.py +1 -1
  86. c2cgeoportal_commons-2.8.1.146.dist-info/RECORD +0 -83
  87. {c2cgeoportal_commons-2.8.1.146.dist-info → c2cgeoportal_commons-2.9.0.350.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2012-2023, Camptocamp SA
1
+ # Copyright (c) 2012-2025, Camptocamp SA
2
2
  # All rights reserved.
3
3
 
4
4
  # Redistribution and use in source and binary forms, with or without
@@ -25,7 +25,7 @@
25
25
  # of the authors and should not be interpreted as representing official policies,
26
26
  # either expressed or implied, of the FreeBSD Project.
27
27
  import json
28
- from typing import Any, Dict, Optional, Type
28
+ from typing import Any
29
29
 
30
30
  from sqlalchemy.engine import Dialect
31
31
  from sqlalchemy.types import VARCHAR, TypeDecorator, UserDefinedType
@@ -33,34 +33,35 @@ from sqlalchemy.types import VARCHAR, TypeDecorator, UserDefinedType
33
33
 
34
34
  # get from https://docs.sqlalchemy.org/en/latest/orm/extensions/
35
35
  # mutable.html#establishing-mutability-on-scalar-column-values
36
- class JSONEncodedDict(TypeDecorator): # type: ignore
36
+ class JSONEncodedDict(TypeDecorator[Any]):
37
37
  """Represent an immutable structure as a json-encoded string."""
38
38
 
39
39
  impl = VARCHAR
40
40
 
41
- def process_bind_param(self, value: Optional[Dict[str, Any]], _: Dialect) -> Optional[str]:
41
+ def process_bind_param(self, value: dict[str, Any] | None, _: Dialect) -> str | None:
42
42
  return json.dumps(value) if value is not None else None
43
43
 
44
- def process_result_value(self, value: Optional[str], _: Dialect) -> Optional[Dict[str, Any]]:
44
+ def process_result_value(self, value: str | None, _: Dialect) -> dict[str, Any] | None:
45
45
  return json.loads(value) if value is not None else None
46
46
 
47
47
  @property
48
- def python_type(self) -> Type[Any]:
48
+ def python_type(self) -> type[Any]:
49
49
  return dict
50
50
 
51
- def process_literal_param(self, value: str, dialect: Any) -> str:
51
+ def process_literal_param(self, value: Any | None, dialect: Any) -> str:
52
+ assert isinstance(value, str)
52
53
  del dialect
53
54
  return json.dumps(value)
54
55
 
55
56
 
56
- class TsVector(UserDefinedType): # type: ignore
57
+ class TsVector(UserDefinedType[dict[str, str]]): # pylint: disable=abstract-method
57
58
  """A custom type for PostgreSQL's tsvector type."""
58
59
 
59
60
  cache_ok = True
60
61
 
61
- def get_col_spec(self) -> str:
62
+ def get_col_spec(self) -> str: # type: ignore[override] # pylint: disable=arguments-differ
62
63
  return "TSVECTOR"
63
64
 
64
65
  @property
65
- def python_type(self) -> Type[Any]:
66
+ def python_type(self) -> type[Any]:
66
67
  return dict
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2011-2023, Camptocamp SA
1
+ # Copyright (c) 2011-2025, Camptocamp SA
2
2
  # All rights reserved.
3
3
 
4
4
  # Redistribution and use in source and binary forms, with or without
@@ -28,18 +28,18 @@
28
28
 
29
29
  import crypt
30
30
  import logging
31
- from datetime import datetime
31
+ import os
32
+ from datetime import datetime, timezone
32
33
  from hashlib import sha1
33
34
  from hmac import compare_digest as compare_hash
34
- from typing import Any, List, Optional
35
+ from typing import Any
35
36
 
36
- import pytz
37
37
  import sqlalchemy.schema
38
38
  from c2c.template.config import config
39
39
  from sqlalchemy import Column, ForeignKey, Table
40
40
  from sqlalchemy.dialects.postgresql import HSTORE
41
41
  from sqlalchemy.ext.mutable import MutableDict
42
- from sqlalchemy.orm import backref, relationship
42
+ from sqlalchemy.orm import Mapped, backref, mapped_column, relationship
43
43
  from sqlalchemy.types import Boolean, DateTime, Integer, String, Unicode
44
44
 
45
45
  from c2cgeoportal_commons.lib.literal import Literal
@@ -51,7 +51,7 @@ try:
51
51
  from colander import Email, drop
52
52
  from deform.widget import DateTimeInputWidget, HiddenWidget
53
53
  except ModuleNotFoundError:
54
- drop = None
54
+ drop = None # pylint: disable=invalid-name
55
55
 
56
56
  class GenericClass:
57
57
  """Generic class."""
@@ -63,10 +63,11 @@ except ModuleNotFoundError:
63
63
  HiddenWidget = GenericClass
64
64
  DateTimeInputWidget = GenericClass
65
65
  CollenderGeometry = GenericClass
66
- RelationSelect2Widget = GenericClass
66
+ RelationSelect2Widget = GenericClass # type: ignore[misc,assignment]
67
67
 
68
68
 
69
- LOG = logging.getLogger(__name__)
69
+ _LOG = logging.getLogger(__name__)
70
+ _OPENID_CONNECT_ENABLED = os.environ.get("OPENID_CONNECT_ENABLED", "false").lower() in ("true", "yes", "1")
70
71
 
71
72
  _schema: str = config["schema_static"] or "static"
72
73
 
@@ -123,31 +124,49 @@ class User(Base): # type: ignore
123
124
  ),
124
125
  }
125
126
  __c2cgeoform_config__ = {"duplicate": True}
126
- item_type = Column(
127
+ item_type: Mapped[str] = mapped_column(
127
128
  "type", String(10), nullable=False, info={"colanderalchemy": {"widget": HiddenWidget()}}
128
129
  )
129
130
  __mapper_args__ = {"polymorphic_on": item_type, "polymorphic_identity": "user"}
130
131
 
131
- id = Column(Integer, primary_key=True, info={"colanderalchemy": {"widget": HiddenWidget()}})
132
- username = Column(
132
+ id: Mapped[int] = mapped_column(
133
+ Integer, primary_key=True, info={"colanderalchemy": {"widget": HiddenWidget()}}
134
+ )
135
+ username: Mapped[str] = mapped_column(
133
136
  Unicode,
134
137
  unique=True,
135
138
  nullable=False,
139
+ info={
140
+ "colanderalchemy": (
141
+ {
142
+ "title": _("Username"),
143
+ "description": _("Name used for authentication (must be unique)."),
144
+ }
145
+ if not _OPENID_CONNECT_ENABLED
146
+ else {"widget": HiddenWidget()}
147
+ )
148
+ },
149
+ )
150
+ display_name: Mapped[str] = mapped_column(
151
+ Unicode,
136
152
  info={
137
153
  "colanderalchemy": {
138
- "title": _("Username"),
139
- "description": _("Name used for authentication (must be unique)."),
154
+ "title": _("Display name"),
155
+ "description": _("Name displayed in the application."),
140
156
  }
141
157
  },
142
158
  )
143
- _password = Column("password", Unicode, nullable=False, info={"colanderalchemy": {"exclude": True}})
144
- temp_password = Column(
159
+ _password: Mapped[str] = mapped_column(
160
+ "password", Unicode, nullable=False, info={"colanderalchemy": {"exclude": True}}
161
+ )
162
+ temp_password: Mapped[str | None] = mapped_column(
145
163
  "temp_password", Unicode, nullable=True, info={"colanderalchemy": {"exclude": True}}
146
164
  )
147
- tech_data = Column(MutableDict.as_mutable(HSTORE), info={"colanderalchemy": {"exclude": True}})
148
- email = Column(
165
+ tech_data = mapped_column(MutableDict.as_mutable(HSTORE), info={"colanderalchemy": {"exclude": True}})
166
+ email: Mapped[str] = mapped_column(
149
167
  Unicode,
150
168
  nullable=False,
169
+ index=True,
151
170
  info={
152
171
  "colanderalchemy": {
153
172
  "title": _("Email"),
@@ -158,19 +177,24 @@ class User(Base): # type: ignore
158
177
  }
159
178
  },
160
179
  )
161
- is_password_changed = Column(
180
+ is_password_changed: Mapped[bool] = mapped_column(
162
181
  Boolean,
163
182
  default=False,
164
183
  info={
165
- "colanderalchemy": {
166
- "title": _("The user changed his password"),
167
- "description": _("Indicates if user has changed his password."),
168
- }
184
+ "colanderalchemy": (
185
+ {
186
+ "title": _("The user changed his password"),
187
+ "description": _("Indicates if user has changed his password."),
188
+ }
189
+ if not _OPENID_CONNECT_ENABLED
190
+ else {"exclude": True}
191
+ )
169
192
  },
170
193
  )
171
194
 
172
- settings_role_id = Column(
173
- Integer,
195
+ settings_role_id: Mapped[int] = mapped_column(
196
+ Integer(),
197
+ nullable=True,
174
198
  info={
175
199
  "colanderalchemy": {
176
200
  "title": _("Settings from role"),
@@ -213,36 +237,49 @@ class User(Base): # type: ignore
213
237
  },
214
238
  )
215
239
 
216
- last_login = Column(
240
+ last_login: Mapped[datetime] = mapped_column(
217
241
  DateTime(timezone=True),
242
+ nullable=True,
218
243
  info={
219
- "colanderalchemy": {
220
- "title": _("Last login"),
221
- "description": _("Date of the user's last login."),
222
- "missing": drop,
223
- "widget": DateTimeInputWidget(readonly=True),
224
- }
244
+ "colanderalchemy": (
245
+ {
246
+ "title": _("Last login"),
247
+ "description": _("Date of the user's last login."),
248
+ "missing": drop,
249
+ "widget": DateTimeInputWidget(readonly=True),
250
+ }
251
+ if not _OPENID_CONNECT_ENABLED
252
+ else {"exclude": True}
253
+ )
225
254
  },
226
255
  )
227
256
 
228
- expire_on = Column(
257
+ expire_on: Mapped[datetime | None] = mapped_column(
229
258
  DateTime(timezone=True),
230
259
  info={
231
- "colanderalchemy": {
232
- "title": _("Expiration date"),
233
- "description": _("After this date the user will not be able to login anymore."),
234
- }
260
+ "colanderalchemy": (
261
+ {
262
+ "title": _("Expiration date"),
263
+ "description": _("After this date the user will not be able to login anymore."),
264
+ }
265
+ if not _OPENID_CONNECT_ENABLED
266
+ else {"exclude": True}
267
+ )
235
268
  },
236
269
  )
237
270
 
238
- deactivated = Column(
271
+ deactivated: Mapped[bool] = mapped_column(
239
272
  Boolean,
240
273
  default=False,
241
274
  info={
242
- "colanderalchemy": {
243
- "title": _("Deactivated"),
244
- "description": _("Deactivate a user without removing it completely."),
245
- }
275
+ "colanderalchemy": (
276
+ {
277
+ "title": _("Deactivated"),
278
+ "description": _("Deactivate a user without removing it completely."),
279
+ }
280
+ if not _OPENID_CONNECT_ENABLED
281
+ else {"exclude": True}
282
+ )
246
283
  },
247
284
  )
248
285
 
@@ -252,12 +289,14 @@ class User(Base): # type: ignore
252
289
  password: str = "",
253
290
  email: str = "",
254
291
  is_password_changed: bool = False,
255
- settings_role: Optional[Role] = None,
256
- roles: Optional[List[Role]] = None,
257
- expire_on: Optional[datetime] = None,
292
+ settings_role: Role | None = None,
293
+ roles: list[Role] | None = None,
294
+ expire_on: datetime | None = None,
258
295
  deactivated: bool = False,
296
+ display_name: str | None = None,
259
297
  ) -> None:
260
298
  self.username = username
299
+ self.display_name = display_name or username
261
300
  self.password = password
262
301
  self.tech_data = {}
263
302
  self.email = email
@@ -271,7 +310,7 @@ class User(Base): # type: ignore
271
310
  @property
272
311
  def password(self) -> str:
273
312
  """Get the password."""
274
- return self._password # type: ignore
313
+ return self._password
275
314
 
276
315
  @password.setter
277
316
  def password(self, password: str) -> None:
@@ -322,10 +361,10 @@ class User(Base): # type: ignore
322
361
  return False
323
362
 
324
363
  def expired(self) -> bool:
325
- return self.expire_on is not None and self.expire_on < datetime.now(pytz.utc)
364
+ return self.expire_on is not None and self.expire_on < datetime.now(timezone.utc)
326
365
 
327
366
  def update_last_login(self) -> None:
328
- self.last_login = datetime.now(pytz.utc)
367
+ self.last_login = datetime.now(timezone.utc)
329
368
 
330
369
  def __str__(self) -> str:
331
370
  return self.username or ""
@@ -336,13 +375,13 @@ class Shorturl(Base): # type: ignore
336
375
 
337
376
  __tablename__ = "shorturl"
338
377
  __table_args__ = {"schema": _schema}
339
- id = Column(Integer, primary_key=True)
340
- url = Column(Unicode)
341
- ref = Column(String(20), index=True, unique=True, nullable=False)
342
- creator_email = Column(Unicode(200))
343
- creation = Column(DateTime)
344
- last_hit = Column(DateTime)
345
- nb_hits = Column(Integer)
378
+ id: Mapped[int] = mapped_column(Integer, primary_key=True)
379
+ url: Mapped[str] = mapped_column(Unicode)
380
+ ref: Mapped[str] = mapped_column(String(20), index=True, unique=True, nullable=False)
381
+ creator_email: Mapped[str | None] = mapped_column(Unicode(200), nullable=True)
382
+ creation: Mapped[datetime] = mapped_column(DateTime)
383
+ last_hit: Mapped[datetime] = mapped_column(DateTime, nullable=True)
384
+ nb_hits: Mapped[int] = mapped_column(Integer)
346
385
 
347
386
 
348
387
  class OAuth2Client(Base): # type: ignore
@@ -352,8 +391,10 @@ class OAuth2Client(Base): # type: ignore
352
391
  __table_args__ = {"schema": _schema}
353
392
  __colanderalchemy_config__ = {"title": _("OAuth2 Client"), "plural": _("OAuth2 Clients")}
354
393
  __c2cgeoform_config__ = {"duplicate": True}
355
- id = Column(Integer, primary_key=True, info={"colanderalchemy": {"widget": HiddenWidget()}})
356
- client_id = Column(
394
+ id: Mapped[int] = mapped_column(
395
+ Integer, primary_key=True, info={"colanderalchemy": {"widget": HiddenWidget()}}
396
+ )
397
+ client_id: Mapped[str] = mapped_column(
357
398
  Unicode,
358
399
  unique=True,
359
400
  info={
@@ -363,7 +404,7 @@ class OAuth2Client(Base): # type: ignore
363
404
  }
364
405
  },
365
406
  )
366
- secret = Column(
407
+ secret: Mapped[str] = mapped_column(
367
408
  Unicode,
368
409
  info={
369
410
  "colanderalchemy": {
@@ -372,7 +413,7 @@ class OAuth2Client(Base): # type: ignore
372
413
  }
373
414
  },
374
415
  )
375
- redirect_uri = Column(
416
+ redirect_uri: Mapped[str] = mapped_column(
376
417
  Unicode,
377
418
  info={
378
419
  "colanderalchemy": {
@@ -386,7 +427,7 @@ class OAuth2Client(Base): # type: ignore
386
427
  }
387
428
  },
388
429
  )
389
- state_required = Column(
430
+ state_required: Mapped[bool] = mapped_column(
390
431
  Boolean,
391
432
  default=False,
392
433
  info={
@@ -399,7 +440,7 @@ class OAuth2Client(Base): # type: ignore
399
440
  }
400
441
  },
401
442
  )
402
- pkce_required = Column(
443
+ pkce_required: Mapped[bool] = mapped_column(
403
444
  Boolean,
404
445
  default=False,
405
446
  info={
@@ -425,15 +466,19 @@ class OAuth2BearerToken(Base): # type: ignore
425
466
  "schema": _schema,
426
467
  },
427
468
  )
428
- id = Column(Integer, primary_key=True)
429
- client_id = Column(Integer, ForeignKey(_schema + ".oauth2_client.id", ondelete="CASCADE"), nullable=False)
469
+ id: Mapped[int] = mapped_column(Integer, primary_key=True)
470
+ client_id: Mapped[int] = mapped_column(
471
+ Integer, ForeignKey(_schema + ".oauth2_client.id", ondelete="CASCADE"), nullable=False
472
+ )
430
473
  client = relationship(OAuth2Client)
431
- user_id = Column(Integer, ForeignKey(_schema + ".user.id", ondelete="CASCADE"), nullable=False)
474
+ user_id: Mapped[int] = mapped_column(
475
+ Integer, ForeignKey(_schema + ".user.id", ondelete="CASCADE"), nullable=False
476
+ )
432
477
  user = relationship(User)
433
- access_token = Column(Unicode(100), unique=True)
434
- refresh_token = Column(Unicode(100), unique=True)
435
- expire_at = Column(DateTime(timezone=True)) # in one hour
436
- state = Column(String)
478
+ access_token: Mapped[str] = mapped_column(Unicode(100), unique=True)
479
+ refresh_token: Mapped[str] = mapped_column(Unicode(100), unique=True)
480
+ expire_at: Mapped[datetime] = mapped_column(DateTime(timezone=True)) # in one hour
481
+ state = mapped_column(String, nullable=True)
437
482
 
438
483
 
439
484
  class OAuth2AuthorizationCode(Base): # type: ignore
@@ -446,17 +491,21 @@ class OAuth2AuthorizationCode(Base): # type: ignore
446
491
  "schema": _schema,
447
492
  },
448
493
  )
449
- id = Column(Integer, primary_key=True)
450
- client_id = Column(Integer, ForeignKey(_schema + ".oauth2_client.id", ondelete="CASCADE"), nullable=False)
494
+ id: Mapped[int] = mapped_column(Integer, primary_key=True)
495
+ client_id: Mapped[int] = mapped_column(
496
+ Integer, ForeignKey(_schema + ".oauth2_client.id", ondelete="CASCADE"), nullable=False
497
+ )
451
498
  client = relationship(OAuth2Client)
452
- user_id = Column(Integer, ForeignKey(_schema + ".user.id", ondelete="CASCADE"), nullable=False)
499
+ user_id: Mapped[int] = mapped_column(
500
+ Integer, ForeignKey(_schema + ".user.id", ondelete="CASCADE"), nullable=False
501
+ )
453
502
  user = relationship(User)
454
- redirect_uri = Column(Unicode)
455
- code = Column(Unicode(100), unique=True)
456
- state = Column(String)
457
- challenge = Column(String(128))
458
- challenge_method = Column(String(6))
459
- expire_at = Column(DateTime(timezone=True)) # in 10 minutes
503
+ redirect_uri: Mapped[str] = mapped_column(Unicode)
504
+ code: Mapped[str] = mapped_column(Unicode(100), unique=True, nullable=True)
505
+ state: Mapped[str | None] = mapped_column(String, nullable=True)
506
+ challenge: Mapped[str] = mapped_column(String(128), nullable=True)
507
+ challenge_method: Mapped[str] = mapped_column(String(6), nullable=True)
508
+ expire_at: Mapped[datetime] = mapped_column(DateTime(timezone=True)) # in 10 minutes
460
509
 
461
510
 
462
511
  class Log(AbstractLog):
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2018-2023, Camptocamp SA
1
+ # Copyright (c) 2018-2025, Camptocamp SA
2
2
  # All rights reserved.
3
3
 
4
4
  # Redistribution and use in source and binary forms, with or without
@@ -26,7 +26,7 @@
26
26
  # either expressed or implied, of the FreeBSD Project.
27
27
 
28
28
 
29
- from typing import Any, Dict
29
+ from typing import Any
30
30
 
31
31
  import zope.sqlalchemy
32
32
  from sqlalchemy import engine_from_config
@@ -35,19 +35,22 @@ from sqlalchemy.orm import Session, configure_mappers, sessionmaker
35
35
  from transaction import TransactionManager
36
36
 
37
37
 
38
- def get_engine(settings: Dict[str, Any], prefix: str = "sqlalchemy.") -> Engine:
38
+ def get_engine(settings: dict[str, Any], prefix: str = "sqlalchemy.") -> Engine:
39
39
  """Get the engine."""
40
40
  return engine_from_config(settings, prefix)
41
41
 
42
42
 
43
- def get_session_factory(engine: Engine) -> sessionmaker:
43
+ def get_session_factory(engine: Engine) -> sessionmaker[Session]: # pylint: disable=unsubscriptable-object
44
44
  """Get the session factory."""
45
45
  factory = sessionmaker()
46
46
  factory.configure(bind=engine)
47
47
  return factory
48
48
 
49
49
 
50
- def get_tm_session(session_factory: sessionmaker, transaction_manager: TransactionManager) -> Session:
50
+ def get_tm_session(
51
+ session_factory: sessionmaker[Session], # pylint: disable=unsubscriptable-object
52
+ transaction_manager: TransactionManager,
53
+ ) -> Session:
51
54
  """
52
55
  Get a ``sqlalchemy.orm.Session`` instance backed by a transaction.
53
56
 
@@ -68,6 +71,7 @@ def get_tm_session(session_factory: sessionmaker, transaction_manager: Transacti
68
71
  dbsession = get_tm_session(session_factory, transaction.manager)
69
72
  """
70
73
  dbsession = session_factory()
74
+ assert isinstance(dbsession, Session)
71
75
  zope.sqlalchemy.register(dbsession, transaction_manager=transaction_manager)
72
76
  return dbsession
73
77
 
@@ -86,7 +90,7 @@ def generate_mappers() -> None:
86
90
 
87
91
 
88
92
  def get_session(
89
- settings: Dict[str, Any], transaction_manager: TransactionManager, prefix: str = "sqlalchemy."
93
+ settings: dict[str, Any], transaction_manager: TransactionManager, prefix: str = "sqlalchemy."
90
94
  ) -> Session:
91
95
  """Get the session."""
92
96
  configure_mappers()
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2017-2021, Camptocamp SA
1
+ # Copyright (c) 2017-2025, Camptocamp SA
2
2
  # All rights reserved.
3
3
 
4
4
  # Redistribution and use in source and binary forms, with or without
@@ -28,15 +28,16 @@
28
28
 
29
29
  import os
30
30
  import sys
31
- from typing import List, cast
31
+ from typing import cast
32
32
 
33
+ import sqlalchemy
33
34
  from sqlalchemy.engine import Connection
34
35
  from sqlalchemy.orm import Session
35
36
 
36
37
  from c2cgeoportal_commons.models import Base
37
38
 
38
39
 
39
- def usage(argv: List[str]) -> None:
40
+ def usage(argv: list[str]) -> None:
40
41
  """Get the usage."""
41
42
  cmd = os.path.basename(argv[0])
42
43
  print(f"usage: {cmd} <config_uri> [var=value]\n" '(example: "{cmd} development.ini")')
@@ -50,15 +51,15 @@ SELECT count(*) AS count
50
51
  FROM information_schema.schemata
51
52
  WHERE schema_name = '{schema_name}';
52
53
  """
53
- result = connection.execute(sql)
54
+ result = connection.execute(sqlalchemy.text(sql))
54
55
  row = result.first()
55
- return cast(bool, row[0] == 1)
56
+ return cast(bool, row[0] == 1) # type: ignore[index]
56
57
 
57
58
 
58
59
  def truncate_tables(connection: Connection) -> None:
59
60
  """Truncate all the tables defined in the model."""
60
61
  for t in Base.metadata.sorted_tables:
61
- connection.execute(f"TRUNCATE TABLE {t.schema}.{t.name} CASCADE;")
62
+ connection.execute(sqlalchemy.text(f"TRUNCATE TABLE {t.schema}.{t.name} CASCADE;"))
62
63
 
63
64
 
64
65
  def setup_test_data(dbsession: Session) -> None:
@@ -1,13 +1,11 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: c2cgeoportal-commons
3
- Version: 2.8.1.146
3
+ Version: 2.9.0.350
4
4
  Summary: c2cgeoportal commons
5
5
  Home-page: https://github.com/camptocamp/c2cgeoportal/
6
6
  Author: Camptocamp
7
7
  Author-email: info@camptocamp.com
8
- License: UNKNOWN
9
8
  Keywords: web gis geoportail c2cgeoportal geocommune pyramid
10
- Platform: UNKNOWN
11
9
  Classifier: Development Status :: 6 - Mature
12
10
  Classifier: Environment :: Web Environment
13
11
  Classifier: Framework :: Pyramid
@@ -16,16 +14,15 @@ Classifier: License :: OSI Approved :: BSD License
16
14
  Classifier: Operating System :: OS Independent
17
15
  Classifier: Programming Language :: Python
18
16
  Classifier: Programming Language :: Python :: 3
19
- Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.10
20
18
  Classifier: Topic :: Scientific/Engineering :: GIS
21
19
  Classifier: Typing :: Typed
20
+ Requires-Python: >=3.10
22
21
  Description-Content-Type: text/markdown
23
22
  Requires-Dist: GeoAlchemy2
24
- Requires-Dist: c2c.template
25
- Requires-Dist: jinja2 (>=3.1.3)
26
23
  Requires-Dist: papyrus
27
24
  Requires-Dist: pyramid
28
- Requires-Dist: setuptools (>=65.5.1)
25
+ Requires-Dist: setuptools >=78.1.1
29
26
  Requires-Dist: sqlalchemy
30
27
  Requires-Dist: transaction
31
28
  Requires-Dist: zope.event
@@ -67,5 +64,3 @@ use `common/testing/initialized.py` to create the database (`development.ini` at
67
64
  ```
68
65
  make test
69
66
  ```
70
-
71
-