c2cwsgiutils 6.2.0.dev36__tar.gz → 6.2.0.dev39__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.
Files changed (67) hide show
  1. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/PKG-INFO +73 -12
  2. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/README.md +58 -0
  3. c2cwsgiutils-6.2.0.dev39/c2cwsgiutils/loader.py +39 -0
  4. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/prometheus.py +56 -0
  5. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/setup_process.py +2 -0
  6. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/pyproject.toml +24 -5
  7. c2cwsgiutils-6.2.0.dev36/c2cwsgiutils/loader.py +0 -21
  8. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/LICENSE +0 -0
  9. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/__init__.py +0 -0
  10. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/acceptance/__init__.py +0 -0
  11. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/acceptance/connection.py +0 -0
  12. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/acceptance/image.py +0 -0
  13. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/acceptance/package-lock.json +0 -0
  14. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/acceptance/package.json +0 -0
  15. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/acceptance/print.py +0 -0
  16. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/acceptance/screenshot.js +0 -0
  17. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/acceptance/utils.py +0 -0
  18. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/auth.py +0 -0
  19. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/broadcast/__init__.py +0 -0
  20. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/broadcast/interface.py +0 -0
  21. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/broadcast/local.py +0 -0
  22. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/broadcast/redis.py +0 -0
  23. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/broadcast/utils.py +0 -0
  24. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/client_info.py +0 -0
  25. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/config_utils.py +0 -0
  26. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/coverage_setup.py +0 -0
  27. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/db.py +0 -0
  28. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/db_maintenance_view.py +0 -0
  29. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/debug/__init__.py +0 -0
  30. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/debug/_listeners.py +0 -0
  31. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/debug/_views.py +0 -0
  32. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/debug/utils.py +0 -0
  33. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/errors.py +0 -0
  34. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/health_check.py +0 -0
  35. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/index.py +0 -0
  36. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/logging_view.py +0 -0
  37. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/models_graph.py +0 -0
  38. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/pretty_json.py +0 -0
  39. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/profiler.py +0 -0
  40. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/py.typed +0 -0
  41. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/pyramid.py +0 -0
  42. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/pyramid_logging.py +0 -0
  43. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/redis_stats.py +0 -0
  44. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/redis_utils.py +0 -0
  45. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/request_tracking/__init__.py +0 -0
  46. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/request_tracking/_sql.py +0 -0
  47. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/scripts/__init__.py +0 -0
  48. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/scripts/genversion.py +0 -0
  49. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/scripts/stats_db.py +0 -0
  50. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/scripts/test_print.py +0 -0
  51. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/sentry.py +0 -0
  52. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/services.py +0 -0
  53. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/sql_profiler/__init__.py +0 -0
  54. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/sql_profiler/_impl.py +0 -0
  55. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/sqlalchemylogger/README.md +0 -0
  56. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/sqlalchemylogger/__init__.py +0 -0
  57. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/sqlalchemylogger/_filters.py +0 -0
  58. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/sqlalchemylogger/_models.py +0 -0
  59. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/sqlalchemylogger/examples/example.py +0 -0
  60. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/sqlalchemylogger/handlers.py +0 -0
  61. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/static/favicon-16x16.png +0 -0
  62. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/static/favicon-32x32.png +0 -0
  63. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/stats_pyramid/__init__.py +0 -0
  64. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/stats_pyramid/_db_spy.py +0 -0
  65. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/stats_pyramid/_pyramid_spy.py +0 -0
  66. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/templates/index.html.mako +0 -0
  67. {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: c2cwsgiutils
3
- Version: 6.2.0.dev36
3
+ Version: 6.2.0.dev39
4
4
  Summary: Common utilities for Camptocamp WSGI applications
5
5
  Home-page: https://github.com/camptocamp/c2cwsgiutils
6
6
  License: BSD-2-Clause
@@ -33,23 +33,26 @@ Provides-Extra: sentry
33
33
  Provides-Extra: standard
34
34
  Provides-Extra: test-images
35
35
  Provides-Extra: tests
36
+ Provides-Extra: waitress
36
37
  Provides-Extra: webserver
37
- Requires-Dist: SQLAlchemy ; extra == "standard" or extra == "webserver" or extra == "all"
38
- Requires-Dist: SQLAlchemy-Utils ; extra == "standard" or extra == "webserver" or extra == "all"
38
+ Requires-Dist: Paste ; extra == "standard" or extra == "waitress" or extra == "all"
39
+ Requires-Dist: SQLAlchemy ; extra == "standard" or extra == "webserver" or extra == "waitress" or extra == "all"
40
+ Requires-Dist: SQLAlchemy-Utils ; extra == "standard" or extra == "webserver" or extra == "waitress" or extra == "all"
39
41
  Requires-Dist: alembic ; extra == "standard" or extra == "alembic" or extra == "all"
40
42
  Requires-Dist: boltons ; extra == "tests" or extra == "all"
41
43
  Requires-Dist: cee_syslog_handler
42
- Requires-Dist: cornice ; extra == "standard" or extra == "webserver" or extra == "all"
44
+ Requires-Dist: cornice ; extra == "standard" or extra == "webserver" or extra == "waitress" or extra == "all"
45
+ Requires-Dist: coverage ; extra == "debug" or extra == "all"
43
46
  Requires-Dist: gunicorn ; extra == "standard" or extra == "webserver" or extra == "all"
44
47
  Requires-Dist: lxml ; extra == "tests" or extra == "all"
45
48
  Requires-Dist: objgraph ; extra == "debug" or extra == "all"
46
- Requires-Dist: prometheus-client ; extra == "standard" or extra == "webserver" or extra == "all"
49
+ Requires-Dist: prometheus-client ; extra == "standard" or extra == "webserver" or extra == "waitress" or extra == "all"
47
50
  Requires-Dist: psutil ; extra == "debug" or extra == "all"
48
- Requires-Dist: psycopg2 ; extra == "standard" or extra == "webserver" or extra == "all"
51
+ Requires-Dist: psycopg2 ; extra == "standard" or extra == "webserver" or extra == "waitress" or extra == "all"
49
52
  Requires-Dist: pyjwt ; extra == "standard" or extra == "oauth2" or extra == "all"
50
- Requires-Dist: pyramid ; extra == "standard" or extra == "webserver" or extra == "all"
51
- Requires-Dist: pyramid-tm ; extra == "standard" or extra == "webserver" or extra == "all"
52
- Requires-Dist: pyramid_mako ; extra == "standard" or extra == "webserver" or extra == "all"
53
+ Requires-Dist: pyramid ; extra == "standard" or extra == "webserver" or extra == "waitress" or extra == "all"
54
+ Requires-Dist: pyramid-tm ; extra == "standard" or extra == "webserver" or extra == "waitress" or extra == "all"
55
+ Requires-Dist: pyramid_mako ; extra == "standard" or extra == "webserver" or extra == "waitress" or extra == "all"
53
56
  Requires-Dist: pyyaml
54
57
  Requires-Dist: redis ; extra == "standard" or extra == "broadcast" or extra == "all"
55
58
  Requires-Dist: requests
@@ -57,9 +60,9 @@ Requires-Dist: requests-oauthlib ; extra == "standard" or extra == "oauth2" or e
57
60
  Requires-Dist: scikit-image ; extra == "test-images"
58
61
  Requires-Dist: sentry-sdk ; extra == "standard" or extra == "sentry" or extra == "all"
59
62
  Requires-Dist: ujson
60
- Requires-Dist: waitress ; extra == "dev" or extra == "all"
61
- Requires-Dist: zope.interface ; extra == "standard" or extra == "webserver" or extra == "all"
62
- Requires-Dist: zope.sqlalchemy ; extra == "standard" or extra == "webserver" or extra == "all"
63
+ Requires-Dist: waitress ; extra == "standard" or extra == "dev" or extra == "waitress" or extra == "all"
64
+ Requires-Dist: zope.interface ; extra == "standard" or extra == "webserver" or extra == "waitress" or extra == "all"
65
+ Requires-Dist: zope.sqlalchemy ; extra == "standard" or extra == "webserver" or extra == "waitress" or extra == "all"
63
66
  Project-URL: Repository, https://github.com/camptocamp/c2cwsgiutils
64
67
  Description-Content-Type: text/markdown
65
68
 
@@ -673,6 +676,64 @@ def hello_get(request):
673
676
  return {'hello': True}
674
677
  ```
675
678
 
679
+ ## Waitress
680
+
681
+ In production mode we usually use Gunicorn but we can also use Waitress.
682
+
683
+ The advantage to use Waitress is that it creates only one process, that makes it easier to manage especially on Kubernetes:
684
+
685
+ - The memory is more stable.
686
+ - The OOM killer will restart the container.
687
+ - Prometheus didn't request trick to aggregate the metrics.
688
+
689
+ Then to migrate from Gunicorn to Waitress you should do:
690
+
691
+ Add call to `c2cwsgiutils.prometheus.start_single_process()` on your application main function.
692
+
693
+ Changes to do in your docker file:
694
+
695
+ ```diff
696
+
697
+ ENV \
698
+ - GUNICORN_LOG_LEVEL=WARNING \
699
+ + WAITRESS_LOG_LEVEL=WARNING \
700
+ + WAITRESS_THREADS=10 \
701
+
702
+ -RUN mkdir -p /prometheus-metrics \
703
+ - && chmod a+rwx /prometheus-metrics
704
+ -ENV PROMETHEUS_MULTIPROC_DIR=/prometheus-metrics
705
+
706
+
707
+ -CMD ["/venv/bin/gunicorn", "--paste=/app/production.ini"]
708
+ +CMD ["/venv/bin/pserve", "c2c:///app/production.ini"]
709
+ ```
710
+
711
+ Remove the no more needed file `gunicorn.conf.py`.
712
+
713
+ Update the `production.ini` file:
714
+
715
+ ```diff
716
+
717
+ -# this file should be used by gunicorn.
718
+
719
+ [server:main]
720
+ +threads = %(WAITRESS_THREADS)s
721
+ +trusted_proxy = True
722
+ +clear_untrusted_proxy_headers = False
723
+
724
+ [loggers]
725
+ -keys = root, gunicorn, sqlalchemy, c2cwsgiutils, c2cwsgiutils_app
726
+ +keys = root, waitress, sqlalchemy, c2cwsgiutils, c2cwsgiutils_app
727
+
728
+ -[logger_gunicorn]
729
+ -level = %(GUNICORN_LOG_LEVEL)s
730
+ +[logger_waitress]
731
+ +level = %(WAITRESS_LOG_LEVEL)s
732
+ handlers =
733
+ -qualname = gunicorn.error
734
+ +qualname = waitress
735
+ ```
736
+
676
737
  # Exception handling
677
738
 
678
739
  c2cwsgiutils can install exception handling views that will catch any exception raised by the
@@ -608,6 +608,64 @@ def hello_get(request):
608
608
  return {'hello': True}
609
609
  ```
610
610
 
611
+ ## Waitress
612
+
613
+ In production mode we usually use Gunicorn but we can also use Waitress.
614
+
615
+ The advantage to use Waitress is that it creates only one process, that makes it easier to manage especially on Kubernetes:
616
+
617
+ - The memory is more stable.
618
+ - The OOM killer will restart the container.
619
+ - Prometheus didn't request trick to aggregate the metrics.
620
+
621
+ Then to migrate from Gunicorn to Waitress you should do:
622
+
623
+ Add call to `c2cwsgiutils.prometheus.start_single_process()` on your application main function.
624
+
625
+ Changes to do in your docker file:
626
+
627
+ ```diff
628
+
629
+ ENV \
630
+ - GUNICORN_LOG_LEVEL=WARNING \
631
+ + WAITRESS_LOG_LEVEL=WARNING \
632
+ + WAITRESS_THREADS=10 \
633
+
634
+ -RUN mkdir -p /prometheus-metrics \
635
+ - && chmod a+rwx /prometheus-metrics
636
+ -ENV PROMETHEUS_MULTIPROC_DIR=/prometheus-metrics
637
+
638
+
639
+ -CMD ["/venv/bin/gunicorn", "--paste=/app/production.ini"]
640
+ +CMD ["/venv/bin/pserve", "c2c:///app/production.ini"]
641
+ ```
642
+
643
+ Remove the no more needed file `gunicorn.conf.py`.
644
+
645
+ Update the `production.ini` file:
646
+
647
+ ```diff
648
+
649
+ -# this file should be used by gunicorn.
650
+
651
+ [server:main]
652
+ +threads = %(WAITRESS_THREADS)s
653
+ +trusted_proxy = True
654
+ +clear_untrusted_proxy_headers = False
655
+
656
+ [loggers]
657
+ -keys = root, gunicorn, sqlalchemy, c2cwsgiutils, c2cwsgiutils_app
658
+ +keys = root, waitress, sqlalchemy, c2cwsgiutils, c2cwsgiutils_app
659
+
660
+ -[logger_gunicorn]
661
+ -level = %(GUNICORN_LOG_LEVEL)s
662
+ +[logger_waitress]
663
+ +level = %(WAITRESS_LOG_LEVEL)s
664
+ handlers =
665
+ -qualname = gunicorn.error
666
+ +qualname = waitress
667
+ ```
668
+
611
669
  # Exception handling
612
670
 
613
671
  c2cwsgiutils can install exception handling views that will catch any exception raised by the
@@ -0,0 +1,39 @@
1
+ import logging.config
2
+ from typing import Optional, cast
3
+
4
+ from plaster_pastedeploy import Loader as BaseLoader
5
+
6
+ from c2cwsgiutils import get_config_defaults, get_logconfig_dict
7
+
8
+ _LOG = logging.getLogger(__name__)
9
+
10
+
11
+ class Loader(BaseLoader): # type: ignore
12
+ """The application loader."""
13
+
14
+ def _get_defaults(self, defaults: Optional[dict[str, str]] = None) -> dict[str, str]:
15
+ d = get_config_defaults()
16
+ d.update(defaults or {})
17
+ return cast(dict[str, str], super()._get_defaults(d))
18
+
19
+ def __repr__(self) -> str:
20
+ """Get the object representation."""
21
+ return f'c2cwsgiutils.loader.Loader(uri="{self.uri}")'
22
+
23
+ def setup_logging(self, defaults: Optional[dict[str, str]] = None) -> None:
24
+ """
25
+ Set up logging via :func:`logging.config.fileConfig`.
26
+
27
+ Defaults are specified for the special ``__file__`` and ``here``
28
+ variables, similar to PasteDeploy config loading. Extra defaults can
29
+ optionally be specified as a dict in ``defaults``.
30
+
31
+ Arguments:
32
+ defaults: The defaults that will be used when passed to
33
+ :func:`logging.config.fileConfig`.
34
+
35
+ """
36
+ if "loggers" in self.get_sections():
37
+ logging.config.dictConfig(get_logconfig_dict(self.uri.path))
38
+ else:
39
+ logging.basicConfig()
@@ -2,6 +2,7 @@
2
2
 
3
3
  import os
4
4
  import re
5
+ import resource
5
6
  from collections.abc import Generator, Iterable
6
7
  from typing import Any, Optional, TypedDict, cast
7
8
 
@@ -15,6 +16,12 @@ import pyramid.config
15
16
  from c2cwsgiutils import broadcast, redis_utils
16
17
  from c2cwsgiutils.debug.utils import dump_memory_maps
17
18
 
19
+ PSUTILS = True
20
+ try:
21
+ import psutil
22
+ except ImportError:
23
+ PSUTILS = False
24
+
18
25
  _NUMBER_RE = re.compile(r"^[0-9]+$")
19
26
  MULTI_PROCESS_COLLECTOR_BROADCAST_CHANNELS = [
20
27
  "c2cwsgiutils_prometheus_collector_gc",
@@ -22,12 +29,21 @@ MULTI_PROCESS_COLLECTOR_BROADCAST_CHANNELS = [
22
29
  ]
23
30
 
24
31
 
32
+ def start_single_process() -> None:
33
+ """Start separate HTTP server to provide the Prometheus metrics."""
34
+ if os.environ.get("C2C_PROMETHEUS_PORT") is not None:
35
+ prometheus_client.REGISTRY.register(ResourceCollector())
36
+ prometheus_client.REGISTRY.register(MemoryInfoCollector())
37
+ prometheus_client.start_http_server(int(os.environ["C2C_PROMETHEUS_PORT"]))
38
+
39
+
25
40
  def start(registry: Optional[prometheus_client.CollectorRegistry] = None) -> None:
26
41
  """Start separate HTTP server to provide the Prometheus metrics."""
27
42
  if os.environ.get("C2C_PROMETHEUS_PORT") is not None:
28
43
  broadcast.includeme()
29
44
 
30
45
  registry = prometheus_client.CollectorRegistry() if registry is None else registry
46
+ registry.register(ResourceCollector())
31
47
  registry.register(MemoryMapCollector())
32
48
  registry.register(prometheus_client.PLATFORM_COLLECTOR)
33
49
  registry.register(MultiProcessCustomCollector())
@@ -181,3 +197,43 @@ class MemoryMapCollector(prometheus_client.registry.Collector):
181
197
  for e in dump_memory_maps(pid):
182
198
  gauge.add_metric([pid, e["name"]], e[self.memory_type + "_kb"] * 1024)
183
199
  yield gauge
200
+
201
+
202
+ class ResourceCollector(prometheus_client.registry.Collector):
203
+ """Collect the resources used by Python."""
204
+
205
+ def collect(self) -> Generator[prometheus_client.core.GaugeMetricFamily, None, None]:
206
+ """Get the gauge from smap file."""
207
+ gauge = prometheus_client.core.GaugeMetricFamily(
208
+ build_metric_name("python_resource"),
209
+ "Python resources",
210
+ labels=["name"],
211
+ )
212
+ r = resource.getrusage(resource.RUSAGE_SELF)
213
+ for field in dir(r):
214
+ if field.startswith("ru_"):
215
+ gauge.add_metric([field[3:]], getattr(r, field))
216
+ yield gauge
217
+
218
+
219
+ class MemoryInfoCollector(prometheus_client.registry.Collector):
220
+ """Collect the resources used by Python."""
221
+
222
+ process = psutil.Process(os.getpid())
223
+
224
+ def collect(self) -> Generator[prometheus_client.core.GaugeMetricFamily, None, None]:
225
+ """Get the gauge from smap file."""
226
+ gauge = prometheus_client.core.GaugeMetricFamily(
227
+ build_metric_name("python_memory_info"),
228
+ "Python memory info",
229
+ labels=["name"],
230
+ )
231
+ memory_info = self.process.memory_info()
232
+ gauge.add_metric(["rss"], memory_info.rss)
233
+ gauge.add_metric(["vms"], memory_info.vms)
234
+ gauge.add_metric(["shared"], memory_info.shared)
235
+ gauge.add_metric(["text"], memory_info.text)
236
+ gauge.add_metric(["lib"], memory_info.lib)
237
+ gauge.add_metric(["data"], memory_info.data)
238
+ gauge.add_metric(["dirty"], memory_info.dirty)
239
+ yield gauge
@@ -5,6 +5,7 @@ Must be imported at the very beginning of the process's life, before any other m
5
5
  """
6
6
 
7
7
  import argparse
8
+ import logging
8
9
  import warnings
9
10
  from typing import Any, Callable, Optional, TypedDict, cast
10
11
 
@@ -97,4 +98,5 @@ def bootstrap_application(
97
98
  """
98
99
  loader = get_config_loader(config_uri)
99
100
  loader.setup_logging(options)
101
+ logging.getLogger(__name__).info("Loading the application from %s", config_uri)
100
102
  return cast(PyramidEnv, bootstrap(config_uri, options=options))
@@ -16,7 +16,7 @@ strict = true
16
16
 
17
17
  [tool.poetry]
18
18
  name = "c2cwsgiutils"
19
- version = "6.2.0.dev36"
19
+ version = "6.2.0.dev39"
20
20
  description = "Common utilities for Camptocamp WSGI applications"
21
21
  readme = "README.md"
22
22
  authors = ["Camptocamp <info@camptocamp.com>"]
@@ -93,6 +93,8 @@ scikit-image = { version = "0.24.0", optional = true }
93
93
  prometheus-client = { version = "0.21.0", optional = true}
94
94
  pyramid_mako = { version = "1.1.0", optional = true}
95
95
  psutil = { version = "6.1.0", optional = true}
96
+ Paste = { version = "3.10.1", optional = true}
97
+ coverage = { version = "7.6.4", optional = true}
96
98
 
97
99
  [tool.poetry.extras]
98
100
  standard = [
@@ -117,9 +119,12 @@ standard = [
117
119
  "zope.sqlalchemy",
118
120
  "prometheus_client",
119
121
  "pyramid_mako",
122
+ # waitress
123
+ "waitress",
124
+ "Paste",
120
125
  ]
121
126
  alembic = ["alembic"]
122
- debug = ["objgraph", "psutil"]
127
+ debug = ["objgraph", "psutil", "coverage"]
123
128
  oauth2 = ["pyjwt", "requests-oauthlib"]
124
129
  sentry = ["sentry-sdk"]
125
130
  dev = ["waitress"]
@@ -136,6 +141,19 @@ webserver = [
136
141
  "zope.sqlalchemy",
137
142
  "prometheus_client",
138
143
  "pyramid_mako"]
144
+ waitress = [
145
+ "waitress",
146
+ "Paste",
147
+ "cornice",
148
+ "psycopg2",
149
+ "pyramid",
150
+ "pyramid-tm",
151
+ "SQLAlchemy",
152
+ "SQLAlchemy-Utils",
153
+ "zope.interface",
154
+ "zope.sqlalchemy",
155
+ "prometheus_client",
156
+ "pyramid_mako"]
139
157
  tests = ["lxml", "boltons"]
140
158
  all = [
141
159
  # alembic
@@ -143,13 +161,12 @@ all = [
143
161
  # debug
144
162
  "objgraph",
145
163
  "psutil",
164
+ "coverage",
146
165
  # oauth2
147
166
  "pyjwt",
148
167
  "requests-oauthlib",
149
168
  # sentry
150
169
  "sentry-sdk",
151
- # dev
152
- "waitress",
153
170
  # broadcast
154
171
  "redis",
155
172
  # webserver
@@ -167,6 +184,9 @@ all = [
167
184
  # tests
168
185
  "lxml",
169
186
  "boltons",
187
+ # waitress
188
+ "waitress",
189
+ "Paste",
170
190
  ]
171
191
  test_images = ["scikit-image"]
172
192
 
@@ -175,7 +195,6 @@ test_images = ["scikit-image"]
175
195
  prospector = { version = "1.13.3", extras = ["with_bandit", "with_mypy", "with_pyroma", "with_ruff"] }
176
196
  prospector-profile-duplicated = "1.9.0"
177
197
  prospector-profile-utils = "1.14.0"
178
- coverage = "7.6.8"
179
198
  junit2html = "31.0.2"
180
199
  pytest = "8.3.4"
181
200
  pytest-cov = "6.0.0"
@@ -1,21 +0,0 @@
1
- import logging
2
- from typing import Optional, cast
3
-
4
- from plaster_pastedeploy import Loader as BaseLoader
5
-
6
- from c2cwsgiutils import get_config_defaults
7
-
8
- _LOG = logging.getLogger(__name__)
9
-
10
-
11
- class Loader(BaseLoader): # type: ignore
12
- """The application loader."""
13
-
14
- def _get_defaults(self, defaults: Optional[dict[str, str]] = None) -> dict[str, str]:
15
- d = get_config_defaults()
16
- d.update(defaults or {})
17
- return cast(dict[str, str], super()._get_defaults(d))
18
-
19
- def __repr__(self) -> str:
20
- """Get the object representation."""
21
- return f'c2cwsgiutils.loader.Loader(uri="{self.uri}")'