c2cwsgiutils 6.1.8.dev68__tar.gz → 6.1.10.dev5__tar.gz

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 c2cwsgiutils might be problematic. Click here for more details.

Files changed (66) hide show
  1. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/LICENSE +1 -1
  2. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/PKG-INFO +2 -1
  3. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/acceptance/package-lock.json +6 -6
  4. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/db.py +7 -4
  5. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/prometheus.py +5 -0
  6. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/stats_pyramid/_db_spy.py +11 -5
  7. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/templates/index.html.mako +2 -2
  8. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/pyproject.toml +7 -6
  9. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/README.md +0 -0
  10. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/__init__.py +0 -0
  11. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/acceptance/__init__.py +0 -0
  12. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/acceptance/connection.py +0 -0
  13. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/acceptance/image.py +0 -0
  14. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/acceptance/package.json +0 -0
  15. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/acceptance/print.py +0 -0
  16. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/acceptance/screenshot.js +0 -0
  17. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/acceptance/utils.py +0 -0
  18. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/auth.py +0 -0
  19. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/broadcast/__init__.py +0 -0
  20. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/broadcast/interface.py +0 -0
  21. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/broadcast/local.py +0 -0
  22. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/broadcast/redis.py +0 -0
  23. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/broadcast/utils.py +0 -0
  24. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/client_info.py +0 -0
  25. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/config_utils.py +0 -0
  26. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/coverage_setup.py +0 -0
  27. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/db_maintenance_view.py +0 -0
  28. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/debug/__init__.py +0 -0
  29. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/debug/_listeners.py +0 -0
  30. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/debug/_views.py +0 -0
  31. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/debug/utils.py +0 -0
  32. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/errors.py +0 -0
  33. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/health_check.py +0 -0
  34. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/index.py +0 -0
  35. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/loader.py +0 -0
  36. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/logging_view.py +0 -0
  37. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/models_graph.py +0 -0
  38. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/pretty_json.py +0 -0
  39. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/profiler.py +0 -0
  40. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/py.typed +0 -0
  41. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/pyramid.py +0 -0
  42. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/pyramid_logging.py +0 -0
  43. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/redis_stats.py +0 -0
  44. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/redis_utils.py +0 -0
  45. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/request_tracking/__init__.py +0 -0
  46. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/request_tracking/_sql.py +0 -0
  47. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/scripts/__init__.py +0 -0
  48. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/scripts/genversion.py +0 -0
  49. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/scripts/stats_db.py +0 -0
  50. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/scripts/test_print.py +0 -0
  51. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/sentry.py +0 -0
  52. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/services.py +0 -0
  53. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/setup_process.py +0 -0
  54. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/sql_profiler/__init__.py +0 -0
  55. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/sql_profiler/_impl.py +0 -0
  56. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/sqlalchemylogger/README.md +0 -0
  57. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/sqlalchemylogger/__init__.py +0 -0
  58. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/sqlalchemylogger/_filters.py +0 -0
  59. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/sqlalchemylogger/_models.py +0 -0
  60. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/sqlalchemylogger/examples/example.py +0 -0
  61. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/sqlalchemylogger/handlers.py +0 -0
  62. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/static/favicon-16x16.png +0 -0
  63. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/static/favicon-32x32.png +0 -0
  64. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/stats_pyramid/__init__.py +0 -0
  65. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/stats_pyramid/_pyramid_spy.py +0 -0
  66. {c2cwsgiutils-6.1.8.dev68 → c2cwsgiutils-6.1.10.dev5}/c2cwsgiutils/version.py +0 -0
@@ -1,4 +1,4 @@
1
- Copyright (c) 2015-2025, Camptocamp SA
1
+ Copyright (c) 2015-2026, Camptocamp SA
2
2
  All rights reserved.
3
3
 
4
4
  Redistribution and use in source and binary forms, with or without
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: c2cwsgiutils
3
- Version: 6.1.8.dev68
3
+ Version: 6.1.10.dev5
4
4
  Summary: Common utilities for Camptocamp WSGI applications
5
5
  Home-page: https://github.com/camptocamp/c2cwsgiutils
6
6
  License: BSD-2-Clause
@@ -56,6 +56,7 @@ Requires-Dist: requests-oauthlib ; extra == "standard" or extra == "oauth2" or e
56
56
  Requires-Dist: scikit-image ; extra == "test-images"
57
57
  Requires-Dist: sentry-sdk ; extra == "standard" or extra == "sentry" or extra == "all"
58
58
  Requires-Dist: ujson
59
+ Requires-Dist: urllib3
59
60
  Requires-Dist: waitress ; extra == "dev" or extra == "all"
60
61
  Requires-Dist: zope.interface ; extra == "standard" or extra == "webserver" or extra == "all"
61
62
  Requires-Dist: zope.sqlalchemy ; extra == "standard" or extra == "webserver" or extra == "all"
@@ -735,9 +735,9 @@
735
735
  "license": "MIT"
736
736
  },
737
737
  "node_modules/js-yaml": {
738
- "version": "4.1.0",
739
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
740
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
738
+ "version": "4.1.1",
739
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
740
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
741
741
  "license": "MIT",
742
742
  "dependencies": {
743
743
  "argparse": "^2.0.1"
@@ -1107,9 +1107,9 @@
1107
1107
  }
1108
1108
  },
1109
1109
  "node_modules/tar-fs": {
1110
- "version": "3.0.9",
1111
- "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.9.tgz",
1112
- "integrity": "sha512-XF4w9Xp+ZQgifKakjZYmFdkLoSWd34VGKcsTCwlNWM7QG3ZbaxnTsaBwnjFZqHRf/rROxaR8rXnbtwdvaDI+lA==",
1110
+ "version": "3.1.1",
1111
+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz",
1112
+ "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==",
1113
1113
  "license": "MIT",
1114
1114
  "dependencies": {
1115
1115
  "pump": "^3.0.0",
@@ -51,7 +51,7 @@ def setup_session(
51
51
  With an accompanying tween that switches between the master and the slave DB
52
52
  connection. Uses prefixed entries in the application's settings.
53
53
 
54
- The slave DB will be used for anything that is GET and OPTIONS queries. The master DB will be used for
54
+ The slave DB will be used for anything that is GET, HEAD and OPTIONS queries. The master DB will be used for
55
55
  all the other queries. You can tweak this behavior with the force_master and force_slave parameters.
56
56
  Those parameters are lists of regex that are going to be matched against "{VERB} {PATH}". Warning, the
57
57
  path includes the route_prefix.
@@ -107,7 +107,7 @@ def create_session(
107
107
  With an accompanying tween that switches between the master and the slave DB
108
108
  connection.
109
109
 
110
- The slave DB will be used for anything that is GET and OPTIONS queries. The master DB will be used for
110
+ The slave DB will be used for anything that is GET, HEAD and OPTIONS queries. The master DB will be used for
111
111
  all the other queries. You can tweak this behavior with the force_master and force_slave parameters.
112
112
  Those parameters are lists of regex that are going to be matched against "{VERB} {PATH}". Warning, the
113
113
  path includes the route_prefix.
@@ -178,7 +178,10 @@ def _add_tween(
178
178
  has_force_master = any(r.match(method_path) for r in master_paths)
179
179
  if FORCE_READONLY or (
180
180
  not has_force_master
181
- and (request.method in ("GET", "OPTIONS") or any(r.match(method_path) for r in slave_paths))
181
+ and (
182
+ request.method in ("GET", "HEAD", "OPTIONS")
183
+ or any(r.match(method_path) for r in slave_paths)
184
+ )
182
185
  ):
183
186
  _LOG.debug(
184
187
  "Using %s database for: %s",
@@ -246,7 +249,7 @@ class SessionFactory(_sessionmaker):
246
249
  if FORCE_READONLY or (
247
250
  not has_force_master
248
251
  and (
249
- request.method in ("GET", "OPTIONS")
252
+ request.method in ("GET", "OPTIONS", "HEAD")
250
253
  or any(r.match(method_path) for r in self.slave_paths)
251
254
  )
252
255
  ):
@@ -132,6 +132,7 @@ class MultiProcessCustomCollector(prometheus_client.registry.Collector):
132
132
  def _deserialize_collected_data(
133
133
  results: list[list[SerializedMetric]],
134
134
  ) -> Generator[prometheus_client.core.Metric, None, None]:
135
+ imported = set()
135
136
  for serialized_collection in results:
136
137
  if serialized_collection is None:
137
138
  continue
@@ -149,6 +150,10 @@ def _deserialize_collected_data(
149
150
  else:
150
151
  raise NotImplementedError()
151
152
  for sample in serialized_metric["samples"]:
153
+ canonical = (sample["name"], frozenset(sample.get("labels", {}).items()))
154
+ if canonical in imported:
155
+ continue
156
+ imported.add(canonical)
152
157
  metric.samples.append(
153
158
  prometheus_client.metrics_core.Sample(**sample), # type: ignore[attr-defined]
154
159
  )
@@ -15,7 +15,7 @@ _LOG = logging.getLogger(__name__)
15
15
  _PROMETHEUS_DB_SUMMARY = prometheus_client.Summary(
16
16
  prometheus.build_metric_name("database"),
17
17
  "Database requests",
18
- ["what"],
18
+ ["what", "engine"],
19
19
  unit="seconds",
20
20
  )
21
21
 
@@ -66,12 +66,14 @@ def _simplify_sql(sql: str) -> str:
66
66
  return re.sub(r"%\(\w+\)\w", "?", sql)
67
67
 
68
68
 
69
- def _create_sqlalchemy_timer_cb(what: str) -> Callable[..., Any]:
69
+ def _create_sqlalchemy_timer_cb(what: str, engine_name: str | None = None) -> Callable[..., Any]:
70
70
  start = time.perf_counter()
71
71
 
72
72
  def after(*_args: Any, **_kwargs: Any) -> None:
73
- _PROMETHEUS_DB_SUMMARY.labels({"query": what}).observe(time.perf_counter() - start)
74
- _LOG.debug("Execute statement '%s' in %d.", what, time.perf_counter() - start)
73
+ elapsed_time = time.perf_counter() - start
74
+ _PROMETHEUS_DB_SUMMARY.labels(what=what, engine=engine_name).observe(elapsed_time)
75
+ engine_suffix = f", on {engine_name}" if engine_name else ""
76
+ _LOG.debug("Execute statement '%s' in %.3f%ss.", what, elapsed_time, engine_suffix)
75
77
 
76
78
  return after
77
79
 
@@ -79,8 +81,12 @@ def _create_sqlalchemy_timer_cb(what: str) -> Callable[..., Any]:
79
81
  def _before_cursor_execute(
80
82
  conn: Connection, _cursor: Any, statement: str, _parameters: Any, _context: Any, _executemany: Any
81
83
  ) -> None:
84
+ engine_name = getattr(conn.engine, "c2c_name", None)
82
85
  sqlalchemy.event.listen(
83
- conn, "after_cursor_execute", _create_sqlalchemy_timer_cb(_simplify_sql(statement)), once=True
86
+ conn,
87
+ "after_cursor_execute",
88
+ _create_sqlalchemy_timer_cb(_simplify_sql(statement), engine_name),
89
+ once=True,
84
90
  )
85
91
 
86
92
 
@@ -19,8 +19,8 @@
19
19
  />
20
20
  <link
21
21
  rel="stylesheet"
22
- href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css"
23
- integrity="sha512-jnSuA4Ss2PkkikSOLtYs8BlYIeeIK1h99ty4YfvRPAlzr377vr3CXDb7sb7eEEBYjDtcYj+AjBH3FLv5uSJuXg=="
22
+ href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.8/css/bootstrap.min.css"
23
+ integrity="sha512-2bBQCjcnw658Lho4nlXJcc6WkV/UxpE/sAokbXPxQNGqmNdQrWqtw26Ns9kFF/yG792pKR1Sx8/Y1Lf1XN4GKA=="
24
24
  crossorigin="anonymous"
25
25
  referrerpolicy="no-referrer"
26
26
  />
@@ -16,7 +16,7 @@ strict = true
16
16
 
17
17
  [tool.poetry]
18
18
  name = "c2cwsgiutils"
19
- version = "6.1.8.dev68"
19
+ version = "6.1.10.dev5"
20
20
  description = "Common utilities for Camptocamp WSGI applications"
21
21
  readme = "README.md"
22
22
  authors = ["Camptocamp <info@camptocamp.com>"]
@@ -67,8 +67,8 @@ sentry = "c2cwsgiutils.sentry:filter_factory"
67
67
 
68
68
  [tool.poetry.dependencies]
69
69
  python = ">=3.10,<4.0"
70
- requests = "2.32.4"
71
- pyyaml = { version = "6.0.2" }
70
+ requests = "2.32.5"
71
+ pyyaml = { version = "6.0.3" }
72
72
  alembic = { version = "1.13.3", optional = true }
73
73
  boltons = { version = "24.0.0", optional = true }
74
74
  cornice = { version = "6.1.0", optional = true }
@@ -76,13 +76,13 @@ redis = { version = "5.1.1", optional = true }
76
76
  gunicorn = { version = "23.0.0", optional = true }
77
77
  lxml = { version = "5.3.1", optional = true }
78
78
  objgraph = { version = "3.6.2", optional = true }
79
- psycopg2 = { version = "2.9.10", optional = true }
79
+ psycopg2 = { version = "2.9.11", optional = true }
80
80
  pyramid = { version = "2.0.2", optional = true }
81
81
  pyramid-tm = { version = "2.5", optional = true }
82
82
  sentry-sdk = { version = "2.15.0", optional = true }
83
83
  ujson = { version = "5.10.0" }
84
84
  cee_syslog_handler = { version = "0.6.0" }
85
- SQLAlchemy = { version = "2.0.41", optional = true }
85
+ SQLAlchemy = { version = "2.0.45", optional = true }
86
86
  SQLAlchemy-Utils = { version = "0.41.2", optional = true }
87
87
  "zope.interface" = { version = "7.0.3", optional = true }
88
88
  "zope.sqlalchemy" = { version = "3.1", optional = true }
@@ -92,6 +92,7 @@ waitress = "3.0.2"
92
92
  scikit-image = { version = "0.24.0", optional = true }
93
93
  prometheus-client = { version = "0.21.1", optional = true}
94
94
  pyramid_mako = { version = "1.1.0", optional = true}
95
+ urllib3 = "2.6.3"
95
96
 
96
97
  [tool.poetry.extras]
97
98
  standard = [
@@ -174,7 +175,7 @@ prospector = { extras = ["with_bandit", "with_mypy", "with_pyroma"], version = "
174
175
  prospector-profile-duplicated = "1.6.0"
175
176
  prospector-profile-utils = "1.9.1"
176
177
  coverage = "7.6.12"
177
- junit2html = "31.0.2"
178
+ junit2html = "31.0.5"
178
179
  pytest = "8.3.5"
179
180
  pytest-cov = "5.0.0"
180
181
  pytest-html = "4.1.1"