c2cwsgiutils 6.2.0.dev35__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.dev35 → c2cwsgiutils-6.2.0.dev39}/PKG-INFO +73 -12
  2. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/README.md +58 -0
  3. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/acceptance/__init__.py +1 -0
  4. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/acceptance/connection.py +3 -2
  5. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/acceptance/image.py +5 -3
  6. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/acceptance/package-lock.json +13 -13
  7. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/acceptance/package.json +1 -1
  8. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/acceptance/print.py +1 -0
  9. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/acceptance/utils.py +2 -1
  10. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/auth.py +9 -8
  11. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/broadcast/__init__.py +1 -1
  12. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/broadcast/local.py +4 -0
  13. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/broadcast/redis.py +8 -2
  14. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/client_info.py +2 -0
  15. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/coverage_setup.py +2 -2
  16. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/db.py +11 -2
  17. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/db_maintenance_view.py +1 -1
  18. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/debug/__init__.py +4 -2
  19. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/debug/_views.py +2 -3
  20. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/errors.py +7 -2
  21. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/health_check.py +39 -29
  22. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/index.py +1 -1
  23. c2cwsgiutils-6.2.0.dev39/c2cwsgiutils/loader.py +39 -0
  24. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/logging_view.py +1 -1
  25. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/models_graph.py +1 -1
  26. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/pretty_json.py +1 -1
  27. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/profiler.py +1 -0
  28. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/prometheus.py +58 -0
  29. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/pyramid.py +1 -0
  30. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/pyramid_logging.py +4 -0
  31. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/redis_stats.py +1 -1
  32. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/redis_utils.py +2 -0
  33. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/request_tracking/__init__.py +1 -1
  34. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/scripts/genversion.py +4 -2
  35. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/scripts/stats_db.py +1 -0
  36. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/scripts/test_print.py +4 -1
  37. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/sentry.py +1 -1
  38. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/setup_process.py +5 -1
  39. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/sql_profiler/__init__.py +1 -1
  40. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/sql_profiler/_impl.py +1 -1
  41. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/sqlalchemylogger/handlers.py +18 -12
  42. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/stats_pyramid/__init__.py +2 -1
  43. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/stats_pyramid/_pyramid_spy.py +1 -0
  44. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/version.py +1 -1
  45. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/pyproject.toml +31 -12
  46. c2cwsgiutils-6.2.0.dev35/c2cwsgiutils/loader.py +0 -21
  47. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/LICENSE +0 -0
  48. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/__init__.py +0 -0
  49. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/acceptance/screenshot.js +0 -0
  50. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/broadcast/interface.py +0 -0
  51. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/broadcast/utils.py +0 -0
  52. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/config_utils.py +0 -0
  53. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/debug/_listeners.py +0 -0
  54. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/debug/utils.py +0 -0
  55. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/py.typed +0 -0
  56. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/request_tracking/_sql.py +0 -0
  57. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/scripts/__init__.py +0 -0
  58. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/services.py +0 -0
  59. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/sqlalchemylogger/README.md +0 -0
  60. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/sqlalchemylogger/__init__.py +0 -0
  61. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/sqlalchemylogger/_filters.py +0 -0
  62. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/sqlalchemylogger/_models.py +0 -0
  63. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/sqlalchemylogger/examples/example.py +0 -0
  64. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/static/favicon-16x16.png +0 -0
  65. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/static/favicon-32x32.png +0 -0
  66. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/stats_pyramid/_db_spy.py +0 -0
  67. {c2cwsgiutils-6.2.0.dev35 → c2cwsgiutils-6.2.0.dev39}/c2cwsgiutils/templates/index.html.mako +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: c2cwsgiutils
3
- Version: 6.2.0.dev35
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
@@ -20,6 +20,7 @@ def retry(
20
20
  tries: number of times to try (not retry) before giving up
21
21
  delay: initial delay between retries in seconds
22
22
  backoff: backoff multiplier e.g. value of 2 will double the delay each retry
23
+
23
24
  """
24
25
 
25
26
  def deco_retry(f: typing.Callable[..., typing.Any]) -> typing.Callable[..., typing.Any]:
@@ -20,6 +20,7 @@ class Connection:
20
20
  """The connection."""
21
21
 
22
22
  def __init__(self, base_url: str, origin: str) -> None:
23
+ """Initialize the connection."""
23
24
  self.base_url = base_url
24
25
  if not self.base_url.endswith("/"):
25
26
  self.base_url += "/"
@@ -93,10 +94,10 @@ class Connection:
93
94
  check_response(r, expected_status, cache_expected=cache_expected)
94
95
  self._check_cors(cors, r)
95
96
  r.raw.decode_content = True
96
- doc = etree.parse(r.raw) # nosec
97
+ doc = etree.parse(r.raw) # noqa: S320
97
98
  if schema is not None:
98
99
  with open(schema, encoding="utf-8") as schema_file:
99
- xml_schema = etree.XMLSchema(etree.parse(schema_file)) # nosec
100
+ xml_schema = etree.XMLSchema(etree.parse(schema_file)) # noqa: S320
100
101
  xml_schema.assertValid(doc)
101
102
  return doc
102
103
 
@@ -10,7 +10,7 @@ import skimage.metrics # pylint: disable=import-error
10
10
  import skimage.transform # pylint: disable=import-error
11
11
 
12
12
  if TYPE_CHECKING:
13
- from typing import TypeAlias
13
+ from typing_extensions import TypeAlias
14
14
 
15
15
  NpNdarrayInt: TypeAlias = np.ndarray[np.uint8, Any]
16
16
  else:
@@ -89,6 +89,7 @@ def check_image(
89
89
  level: The minimum similarity level (between 0.0 and 1.0), default to 1.0
90
90
  generate_expected_image: If `True` generate the expected image instead of checking it
91
91
  use_mask: If `False` don't use the mask event if the file exists
92
+
92
93
  """
93
94
  assert image_to_check is not None, "Image required"
94
95
  image_file_basename = os.path.splitext(os.path.basename(expected_filename))[0]
@@ -122,7 +123,7 @@ def check_image(
122
123
  if np.issubdtype(mask.dtype, np.floating):
123
124
  mask = (mask * 255).astype("uint8")
124
125
 
125
- assert ((0 < mask) & (mask < 255)).sum() == 0, "Mask should be only black and white image"
126
+ assert ((mask > 0) & (mask < 255)).sum() == 0, "Mask should be only black and white image"
126
127
 
127
128
  # Convert to boolean
128
129
  mask = mask == 0
@@ -139,7 +140,7 @@ def check_image(
139
140
  return
140
141
  if not os.path.isfile(expected_filename):
141
142
  skimage.io.imsave(expected_filename, image_to_check)
142
- assert False, "Expected image not found: " + expected_filename
143
+ raise AssertionError("Expected image not found: " + expected_filename)
143
144
  expected = skimage.io.imread(expected_filename)
144
145
  assert expected is not None, "Wrong image: " + expected_filename
145
146
  expected = normalize_image(expected)
@@ -201,6 +202,7 @@ def check_screenshot(
201
202
  level: See `check_image`
202
203
  generate_expected_image: See `check_image`
203
204
  use_mask: See `check_image`
205
+
204
206
  """
205
207
  if headers is None:
206
208
  headers = {}
@@ -6,7 +6,7 @@
6
6
  "": {
7
7
  "dependencies": {
8
8
  "commander": "12.1.0",
9
- "puppeteer": "23.7.1"
9
+ "puppeteer": "23.9.0"
10
10
  }
11
11
  },
12
12
  "node_modules/@babel/code-frame": {
@@ -379,9 +379,9 @@
379
379
  }
380
380
  },
381
381
  "node_modules/devtools-protocol": {
382
- "version": "0.0.1354347",
383
- "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1354347.tgz",
384
- "integrity": "sha512-BlmkSqV0V84E2WnEnoPnwyix57rQxAM5SKJjf4TbYOCGLAWtz8CDH8RIaGOjPgPCXo2Mce3kxSY497OySidY3Q==",
382
+ "version": "0.0.1367902",
383
+ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1367902.tgz",
384
+ "integrity": "sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg==",
385
385
  "license": "BSD-3-Clause"
386
386
  },
387
387
  "node_modules/emoji-regex": {
@@ -868,17 +868,17 @@
868
868
  }
869
869
  },
870
870
  "node_modules/puppeteer": {
871
- "version": "23.7.1",
872
- "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-23.7.1.tgz",
873
- "integrity": "sha512-jS6XehagMvxQ12etwY/4EOYZ0Sm8GAsrtGhdQn4AqpJAyHc3RYl7tGd4QYh/MmShDw8sF9FWYQqGidhoXaqokQ==",
871
+ "version": "23.9.0",
872
+ "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-23.9.0.tgz",
873
+ "integrity": "sha512-WfB8jGwFV+qrD9dcJJVvWPFJBU6kxeu2wxJz9WooDGfM3vIiKLgzImEDBxUQnCBK/2cXB3d4dV6gs/LLpgfLDg==",
874
874
  "hasInstallScript": true,
875
875
  "license": "Apache-2.0",
876
876
  "dependencies": {
877
877
  "@puppeteer/browsers": "2.4.1",
878
878
  "chromium-bidi": "0.8.0",
879
879
  "cosmiconfig": "^9.0.0",
880
- "devtools-protocol": "0.0.1354347",
881
- "puppeteer-core": "23.7.1",
880
+ "devtools-protocol": "0.0.1367902",
881
+ "puppeteer-core": "23.9.0",
882
882
  "typed-query-selector": "^2.12.0"
883
883
  },
884
884
  "bin": {
@@ -889,15 +889,15 @@
889
889
  }
890
890
  },
891
891
  "node_modules/puppeteer-core": {
892
- "version": "23.7.1",
893
- "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.7.1.tgz",
894
- "integrity": "sha512-Om/qCZhd+HLoAr7GltrRAZpS3uOXwHu7tXAoDbNcJADHjG2zeAlDArgyIPXYGG4QB/EQUHk13Q6RklNxGM73Pg==",
892
+ "version": "23.9.0",
893
+ "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.9.0.tgz",
894
+ "integrity": "sha512-hLVrav2HYMVdK0YILtfJwtnkBAwNOztUdR4aJ5YKDvgsbtagNr6urUJk9HyjRA9e+PaLI3jzJ0wM7A4jSZ7Qxw==",
895
895
  "license": "Apache-2.0",
896
896
  "dependencies": {
897
897
  "@puppeteer/browsers": "2.4.1",
898
898
  "chromium-bidi": "0.8.0",
899
899
  "debug": "^4.3.7",
900
- "devtools-protocol": "0.0.1354347",
900
+ "devtools-protocol": "0.0.1367902",
901
901
  "typed-query-selector": "^2.12.0",
902
902
  "ws": "^8.18.0"
903
903
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "dependencies": {
3
3
  "commander": "12.1.0",
4
- "puppeteer": "23.7.1"
4
+ "puppeteer": "23.9.0"
5
5
  },
6
6
  "type": "module"
7
7
  }
@@ -21,6 +21,7 @@ class PrintConnection(connection.Connection):
21
21
  base_url: The base URL to the print server (including the /print)
22
22
  app: The name of the application to use
23
23
  origin: The origin and referrer to include in the requests
24
+
24
25
  """
25
26
  super().__init__(base_url=base_url, origin=origin)
26
27
  self.session.headers["Referrer"] = origin
@@ -32,6 +32,7 @@ def retry_timeout(what: Callable[[], Any], timeout: float = _DEFAULT_TIMEOUT, in
32
32
  what: the function to try
33
33
  timeout: the timeout to get a success
34
34
  interval: the interval between try
35
+
35
36
  """
36
37
  timeout = time.perf_counter() + timeout
37
38
  while True:
@@ -46,7 +47,7 @@ def retry_timeout(what: Callable[[], Any], timeout: float = _DEFAULT_TIMEOUT, in
46
47
  error = str(e)
47
48
  _LOG.info(" Failed: %s", e)
48
49
  if time.perf_counter() > timeout:
49
- assert False, "Timeout: " + error
50
+ raise AssertionError("Timeout: " + error)
50
51
  time.sleep(interval)
51
52
 
52
53
 
@@ -12,32 +12,32 @@ from requests_oauthlib import OAuth2Session
12
12
  from c2cwsgiutils.config_utils import config_bool, env_or_config, env_or_settings
13
13
 
14
14
  _COOKIE_AGE = 7 * 24 * 3600
15
- SECRET_PROP = "c2c.secret" # nosec # noqa
16
- SECRET_ENV = "C2C_SECRET" # nosec # noqa
15
+ SECRET_PROP = "c2c.secret" # noqa: S105
16
+ SECRET_ENV = "C2C_SECRET" # noqa: S105
17
17
  _GITHUB_REPOSITORY_PROP = "c2c.auth.github.repository"
18
18
  _GITHUB_REPOSITORY_ENV = "C2C_AUTH_GITHUB_REPOSITORY"
19
19
  _GITHUB_ACCESS_TYPE_PROP = "c2c.auth.github.access_type"
20
20
  _GITHUB_ACCESS_TYPE_ENV = "C2C_AUTH_GITHUB_ACCESS_TYPE"
21
21
  GITHUB_AUTH_URL_PROP = "c2c.auth.github.auth_url"
22
22
  GITHUB_AUTH_URL_ENV = "C2C_AUTH_GITHUB_AUTH_URL"
23
- GITHUB_TOKEN_URL_PROP = "c2c.auth.github.token_url" # nosec
24
- GITHUB_TOKEN_URL_ENV = "C2C_AUTH_GITHUB_TOKEN_URL" # nosec
23
+ GITHUB_TOKEN_URL_PROP = "c2c.auth.github.token_url" # noqa: S105
24
+ GITHUB_TOKEN_URL_ENV = "C2C_AUTH_GITHUB_TOKEN_URL" # noqa: S105
25
25
  GITHUB_USER_URL_PROP = "c2c.auth.github.user_url"
26
26
  GITHUB_USER_URL_ENV = "C2C_AUTH_GITHUB_USER_URL"
27
27
  _GITHUB_REPO_URL_PROP = "c2c.auth.github.repo_url"
28
28
  _GITHUB_REPO_URL_ENV = "C2C_AUTH_GITHUB_REPO_URL"
29
29
  GITHUB_CLIENT_ID_PROP = "c2c.auth.github.client_id"
30
30
  GITHUB_CLIENT_ID_ENV = "C2C_AUTH_GITHUB_CLIENT_ID"
31
- GITHUB_CLIENT_SECRET_PROP = "c2c.auth.github.client_secret" # nosec # noqa
32
- GITHUB_CLIENT_SECRET_ENV = "C2C_AUTH_GITHUB_CLIENT_SECRET" # nosec # noqa
31
+ GITHUB_CLIENT_SECRET_PROP = "c2c.auth.github.client_secret" # noqa: S105
32
+ GITHUB_CLIENT_SECRET_ENV = "C2C_AUTH_GITHUB_CLIENT_SECRET" # noqa: S105
33
33
  GITHUB_SCOPE_PROP = "c2c.auth.github.scope"
34
34
  GITHUB_SCOPE_ENV = "C2C_AUTH_GITHUB_SCOPE"
35
35
  # To be able to use private repository
36
36
  GITHUB_SCOPE_DEFAULT = "repo"
37
37
  GITHUB_AUTH_COOKIE_PROP = "c2c.auth.github.auth.cookie"
38
38
  GITHUB_AUTH_COOKIE_ENV = "C2C_AUTH_GITHUB_COOKIE"
39
- GITHUB_AUTH_SECRET_PROP = "c2c.auth.github.auth.secret" # nosec # noqa
40
- GITHUB_AUTH_SECRET_ENV = "C2C_AUTH_GITHUB_SECRET" # nosec # noqa
39
+ GITHUB_AUTH_SECRET_PROP = "c2c.auth.github.auth.secret" # noqa: S105
40
+ GITHUB_AUTH_SECRET_ENV = "C2C_AUTH_GITHUB_SECRET" # noqa: S105
41
41
  GITHUB_AUTH_PROXY_URL_PROP = "c2c.auth.github.auth.proxy_url"
42
42
  GITHUB_AUTH_PROXY_URL_ENV = "C2C_AUTH_GITHUB_PROXY_URL"
43
43
  USE_SESSION_PROP = "c2c.use_session"
@@ -209,6 +209,7 @@ def check_access(
209
209
  request: is the request object.
210
210
  repo: is the repository to check access to (<organization>/<repository>).
211
211
  access_type: is the type of access to check (admin|push|pull).
212
+
212
213
  """
213
214
  if not is_auth(request):
214
215
  return False
@@ -19,7 +19,7 @@ _broadcaster: Optional[interface.BaseBroadcaster] = None
19
19
 
20
20
  def init(config: Optional[pyramid.config.Configurator] = None) -> None:
21
21
  """Initialize the broadcaster with Redis, if configured, for backward compatibility."""
22
- warnings.warn("init function is deprecated; use includeme instead")
22
+ warnings.warn("init function is deprecated; use includeme instead", stacklevel=2)
23
23
  includeme(config)
24
24
 
25
25
 
@@ -9,17 +9,21 @@ class LocalBroadcaster(interface.BaseBroadcaster):
9
9
  """Fake implementation of broadcasting messages (will just answer locally)."""
10
10
 
11
11
  def __init__(self) -> None:
12
+ """Initialize the broadcaster."""
12
13
  self._subscribers: MutableMapping[str, Callable[..., Any]] = {}
13
14
 
14
15
  def subscribe(self, channel: str, callback: Callable[..., Any]) -> None:
16
+ """Subscribe to a channel."""
15
17
  self._subscribers[channel] = callback
16
18
 
17
19
  def unsubscribe(self, channel: str) -> None:
20
+ """Unsubscribe from a channel."""
18
21
  del self._subscribers[channel]
19
22
 
20
23
  def broadcast(
21
24
  self, channel: str, params: Mapping[str, Any], expect_answers: bool, timeout: float
22
25
  ) -> Optional[list[Any]]:
26
+ """Broadcast a message to all the listeners."""
23
27
  subscriber = self._subscribers.get(channel, None)
24
28
  answers = [utils.add_host_info(subscriber(**params))] if subscriber is not None else []
25
29
  return answers if expect_answers else None
@@ -23,6 +23,7 @@ class RedisBroadcaster(interface.BaseBroadcaster):
23
23
  master: "redis.client.Redis[str]",
24
24
  slave: "redis.client.Redis[str]",
25
25
  ) -> None:
26
+ """Initialize the broadcaster."""
26
27
  from c2cwsgiutils import redis_utils # pylint: disable=import-outside-toplevel
27
28
 
28
29
  self._master = master
@@ -40,6 +41,8 @@ class RedisBroadcaster(interface.BaseBroadcaster):
40
41
  return self._broadcast_prefix + channel
41
42
 
42
43
  def subscribe(self, channel: str, callback: Callable[..., Any]) -> None:
44
+ """Subscribe to a channel."""
45
+
43
46
  def wrapper(message: Mapping[str, Any]) -> None:
44
47
  _LOG.debug("Received a broadcast on %s: %s", message["channel"], repr(message["data"]))
45
48
  data = json.loads(message["data"])
@@ -58,6 +61,7 @@ class RedisBroadcaster(interface.BaseBroadcaster):
58
61
  self._pub_sub.subscribe(**{actual_channel: wrapper})
59
62
 
60
63
  def unsubscribe(self, channel: str) -> None:
64
+ """Unsubscribe from a channel."""
61
65
  _LOG.debug("Unsubscribing from %s")
62
66
  actual_channel = self._get_channel(channel)
63
67
  self._pub_sub.unsubscribe(actual_channel)
@@ -65,6 +69,7 @@ class RedisBroadcaster(interface.BaseBroadcaster):
65
69
  def broadcast(
66
70
  self, channel: str, params: Mapping[str, Any], expect_answers: bool, timeout: float
67
71
  ) -> Optional[list[Any]]:
72
+ """Broadcast a message to all the listeners."""
68
73
  if expect_answers:
69
74
  return self._broadcast_with_answer(channel, params, timeout)
70
75
  else:
@@ -85,7 +90,8 @@ class RedisBroadcaster(interface.BaseBroadcaster):
85
90
  cond.notify()
86
91
 
87
92
  answer_channel = self._get_channel(channel) + "".join(
88
- random.choice(string.ascii_uppercase + string.digits) for _ in range(10) # nosec
93
+ random.choice(string.ascii_uppercase + string.digits) # noqa: S311
94
+ for _ in range(10)
89
95
  )
90
96
  _LOG.debug("Subscribing for broadcast answers on %s", answer_channel)
91
97
  self._pub_sub.subscribe(**{answer_channel: callback})
@@ -98,7 +104,7 @@ class RedisBroadcaster(interface.BaseBroadcaster):
98
104
  with cond:
99
105
  while len(answers) < nb_received:
100
106
  to_wait = timeout_time - time.perf_counter()
101
- if to_wait <= 0.0: # pragma: no cover
107
+ if to_wait <= 0.0:
102
108
  _LOG.warning(
103
109
  "timeout waiting for %d/%d answers on %s",
104
110
  len(answers),
@@ -16,9 +16,11 @@ class Filter:
16
16
  """
17
17
 
18
18
  def __init__(self, application: Callable[[dict[str, str], Any], Any]):
19
+ """Initialize the filter."""
19
20
  self._application = application
20
21
 
21
22
  def __call__(self, environ: dict[str, str], start_response: Any) -> Any:
23
+ """Update the environ with the headers."""
22
24
  # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded
23
25
  if "HTTP_FORWARDED" in environ:
24
26
  _handle_forwarded(environ)
@@ -10,7 +10,7 @@ _LOG = logging.getLogger(__name__)
10
10
 
11
11
  def init() -> None:
12
12
  """Initialize the code coverage, for backward compatibility."""
13
- warnings.warn("init function is deprecated; use includeme instead")
13
+ warnings.warn("init function is deprecated; use includeme instead", stacklevel=2)
14
14
  includeme()
15
15
 
16
16
 
@@ -22,7 +22,7 @@ def includeme(config: Optional[pyramid.config.Configurator] = None) -> None:
22
22
  import coverage # pylint: disable=import-outside-toplevel
23
23
 
24
24
  _LOG.warning("Setting up code coverage")
25
- report_dir = "/tmp/coverage/api" # nosec
25
+ report_dir = "/tmp/coverage/api" # noqa: S108
26
26
  os.makedirs(report_dir, exist_ok=True)
27
27
  cov = coverage.Coverage(
28
28
  data_file=os.path.join(report_dir, "coverage"),
@@ -66,8 +66,11 @@ def setup_session(
66
66
  force_slave: The method/paths that needs to use the slave
67
67
 
68
68
  Returns: The SQLAlchemy session, the R/W engine and the R/O engine
69
+
69
70
  """
70
- warnings.warn("setup_session function is deprecated; use init and request.dbsession instead")
71
+ warnings.warn(
72
+ "setup_session function is deprecated; use init and request.dbsession instead", stacklevel=2
73
+ )
71
74
  if slave_prefix is None:
72
75
  slave_prefix = master_prefix
73
76
  settings = config.registry.settings
@@ -122,8 +125,11 @@ def create_session(
122
125
  engine_config: The rest of the parameters are passed as is to the sqlalchemy.create_engine function
123
126
 
124
127
  Returns: The SQLAlchemy session
128
+
125
129
  """
126
- warnings.warn("create_session function is deprecated; use init and request.dbsession instead")
130
+ warnings.warn(
131
+ "create_session function is deprecated; use init and request.dbsession instead", stacklevel=2
132
+ )
127
133
  if slave_url is None:
128
134
  slave_url = url
129
135
 
@@ -215,6 +221,7 @@ class SessionFactory(_sessionmaker):
215
221
  ro_engine: sqlalchemy.engine.Engine,
216
222
  rw_engine: sqlalchemy.engine.Engine,
217
223
  ):
224
+ """Initialize the session factory."""
218
225
  super().__init__()
219
226
  self.master_paths: Iterable[Pattern[str]] = (
220
227
  list(map(_RE_COMPILE, force_master)) if force_master else []
@@ -232,6 +239,7 @@ class SessionFactory(_sessionmaker):
232
239
  def __call__( # type: ignore
233
240
  self, request: Optional[pyramid.request.Request], readwrite: Optional[bool] = None, **local_kw: Any
234
241
  ) -> _scoped_session:
242
+ """Set the engine based on the request."""
235
243
  if readwrite is not None:
236
244
  if readwrite and not FORCE_READONLY:
237
245
  _LOG.debug("Using %s database", self.rw_engine.c2c_name) # type: ignore
@@ -374,6 +382,7 @@ def init(
374
382
  force_slave: The method/paths that needs to use the slave
375
383
 
376
384
  Returns: The SQLAlchemy session
385
+
377
386
  """
378
387
  settings = config.get_settings()
379
388
  settings["tm.manager_hook"] = "pyramid_tm.explicit_manager"
@@ -15,7 +15,7 @@ _REDIS_PREFIX = "c2c_db_maintenance_"
15
15
 
16
16
  def install_subscriber(config: pyramid.config.Configurator) -> None:
17
17
  """Install the view to configure the loggers, if configured to do so, for backward compatibility."""
18
- warnings.warn("install_subscriber function is deprecated; use includeme instead")
18
+ warnings.warn("install_subscriber function is deprecated; use includeme instead", stacklevel=2)
19
19
  includeme(config)
20
20
 
21
21
 
@@ -16,7 +16,7 @@ dump_memory_maps = utils.dump_memory_maps
16
16
 
17
17
  def init(config: pyramid.config.Configurator) -> None:
18
18
  """Initialize the debug tools, for backward compatibility."""
19
- warnings.warn("init function is deprecated; use includeme instead")
19
+ warnings.warn("init function is deprecated; use includeme instead", stacklevel=2)
20
20
  includeme(config)
21
21
 
22
22
 
@@ -37,6 +37,8 @@ def init_daemon(config: Optional[pyramid.config.Configurator] = None) -> None:
37
37
  those requests.
38
38
  """
39
39
  if config_utils.env_or_config(config, ENV_KEY, CONFIG_KEY, type_=config_utils.config_bool):
40
- from c2cwsgiutils.debug import _listeners # pylint: disable=import-outside-toplevel
40
+ from c2cwsgiutils.debug import ( # pylint: disable=import-outside-toplevel
41
+ _listeners,
42
+ )
41
43
 
42
44
  _listeners.init()
@@ -79,7 +79,7 @@ def _dump_memory_diff(request: pyramid.request.Request) -> list[Any]:
79
79
  try:
80
80
  if request.params.get("no_warmup", "0").lower() in ("1", "true", "on"):
81
81
  request.invoke_subrequest(sub_request)
82
- except Exception: # nosec # pylint: disable=broad-except
82
+ except Exception: # pylint: disable=broad-except
83
83
  pass
84
84
 
85
85
  _LOG.debug("checking memory growth for %s", path)
@@ -151,8 +151,7 @@ def _headers(request: pyramid.request.Request) -> Mapping[str, Any]:
151
151
  }
152
152
  if "status" in request.params:
153
153
  raise exception_response(int(request.params["status"]), detail=result)
154
- else:
155
- return result
154
+ return result
156
155
 
157
156
 
158
157
  def _error(request: pyramid.request.Request) -> Any:
@@ -9,7 +9,12 @@ from typing import Any, Callable
9
9
  import pyramid.request
10
10
  import sqlalchemy.exc
11
11
  from cornice import cors
12
- from pyramid.httpexceptions import HTTPError, HTTPException, HTTPRedirection, HTTPSuccessful
12
+ from pyramid.httpexceptions import (
13
+ HTTPError,
14
+ HTTPException,
15
+ HTTPRedirection,
16
+ HTTPSuccessful,
17
+ )
13
18
  from webob.request import DisconnectionError
14
19
 
15
20
  from c2cwsgiutils import auth, config_utils
@@ -152,7 +157,7 @@ def _passthrough(exception: HTTPException, request: pyramid.request.Request) ->
152
157
 
153
158
  def init(config: pyramid.config.Configurator) -> None:
154
159
  """Initialize the error views, for backward compatibility."""
155
- warnings.warn("init function is deprecated; use includeme instead")
160
+ warnings.warn("init function is deprecated; use includeme instead", stacklevel=2)
156
161
  includeme(config)
157
162
 
158
163