c2cwsgiutils 6.2.0.dev35__py3-none-any.whl → 6.2.0.dev39__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.
- c2cwsgiutils/acceptance/__init__.py +1 -0
- c2cwsgiutils/acceptance/connection.py +3 -2
- c2cwsgiutils/acceptance/image.py +5 -3
- c2cwsgiutils/acceptance/package-lock.json +13 -13
- c2cwsgiutils/acceptance/package.json +1 -1
- c2cwsgiutils/acceptance/print.py +1 -0
- c2cwsgiutils/acceptance/utils.py +2 -1
- c2cwsgiutils/auth.py +9 -8
- c2cwsgiutils/broadcast/__init__.py +1 -1
- c2cwsgiutils/broadcast/local.py +4 -0
- c2cwsgiutils/broadcast/redis.py +8 -2
- c2cwsgiutils/client_info.py +2 -0
- c2cwsgiutils/coverage_setup.py +2 -2
- c2cwsgiutils/db.py +11 -2
- c2cwsgiutils/db_maintenance_view.py +1 -1
- c2cwsgiutils/debug/__init__.py +4 -2
- c2cwsgiutils/debug/_views.py +2 -3
- c2cwsgiutils/errors.py +7 -2
- c2cwsgiutils/health_check.py +39 -29
- c2cwsgiutils/index.py +1 -1
- c2cwsgiutils/loader.py +20 -2
- c2cwsgiutils/logging_view.py +1 -1
- c2cwsgiutils/models_graph.py +1 -1
- c2cwsgiutils/pretty_json.py +1 -1
- c2cwsgiutils/profiler.py +1 -0
- c2cwsgiutils/prometheus.py +58 -0
- c2cwsgiutils/pyramid.py +1 -0
- c2cwsgiutils/pyramid_logging.py +4 -0
- c2cwsgiutils/redis_stats.py +1 -1
- c2cwsgiutils/redis_utils.py +2 -0
- c2cwsgiutils/request_tracking/__init__.py +1 -1
- c2cwsgiutils/scripts/genversion.py +4 -2
- c2cwsgiutils/scripts/stats_db.py +1 -0
- c2cwsgiutils/scripts/test_print.py +4 -1
- c2cwsgiutils/sentry.py +1 -1
- c2cwsgiutils/setup_process.py +5 -1
- c2cwsgiutils/sql_profiler/__init__.py +1 -1
- c2cwsgiutils/sql_profiler/_impl.py +1 -1
- c2cwsgiutils/sqlalchemylogger/handlers.py +18 -12
- c2cwsgiutils/stats_pyramid/__init__.py +2 -1
- c2cwsgiutils/stats_pyramid/_pyramid_spy.py +1 -0
- c2cwsgiutils/version.py +1 -1
- {c2cwsgiutils-6.2.0.dev35.dist-info → c2cwsgiutils-6.2.0.dev39.dist-info}/METADATA +73 -12
- c2cwsgiutils-6.2.0.dev39.dist-info/RECORD +67 -0
- c2cwsgiutils-6.2.0.dev35.dist-info/RECORD +0 -67
- {c2cwsgiutils-6.2.0.dev35.dist-info → c2cwsgiutils-6.2.0.dev39.dist-info}/LICENSE +0 -0
- {c2cwsgiutils-6.2.0.dev35.dist-info → c2cwsgiutils-6.2.0.dev39.dist-info}/WHEEL +0 -0
- {c2cwsgiutils-6.2.0.dev35.dist-info → c2cwsgiutils-6.2.0.dev39.dist-info}/entry_points.txt +0 -0
@@ -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) #
|
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)) #
|
100
|
+
xml_schema = etree.XMLSchema(etree.parse(schema_file)) # noqa: S320
|
100
101
|
xml_schema.assertValid(doc)
|
101
102
|
return doc
|
102
103
|
|
c2cwsgiutils/acceptance/image.py
CHANGED
@@ -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
|
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 ((
|
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
|
-
|
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.
|
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.
|
383
|
-
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.
|
384
|
-
"integrity": "sha512-
|
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.
|
872
|
-
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-23.
|
873
|
-
"integrity": "sha512-
|
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.
|
881
|
-
"puppeteer-core": "23.
|
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.
|
893
|
-
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.
|
894
|
-
"integrity": "sha512-
|
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.
|
900
|
+
"devtools-protocol": "0.0.1367902",
|
901
901
|
"typed-query-selector": "^2.12.0",
|
902
902
|
"ws": "^8.18.0"
|
903
903
|
},
|
c2cwsgiutils/acceptance/print.py
CHANGED
@@ -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
|
c2cwsgiutils/acceptance/utils.py
CHANGED
@@ -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
|
-
|
50
|
+
raise AssertionError("Timeout: " + error)
|
50
51
|
time.sleep(interval)
|
51
52
|
|
52
53
|
|
c2cwsgiutils/auth.py
CHANGED
@@ -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" #
|
16
|
-
SECRET_ENV = "C2C_SECRET" #
|
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" #
|
24
|
-
GITHUB_TOKEN_URL_ENV = "C2C_AUTH_GITHUB_TOKEN_URL" #
|
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" #
|
32
|
-
GITHUB_CLIENT_SECRET_ENV = "C2C_AUTH_GITHUB_CLIENT_SECRET" #
|
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" #
|
40
|
-
GITHUB_AUTH_SECRET_ENV = "C2C_AUTH_GITHUB_SECRET" #
|
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
|
|
c2cwsgiutils/broadcast/local.py
CHANGED
@@ -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
|
c2cwsgiutils/broadcast/redis.py
CHANGED
@@ -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)
|
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:
|
107
|
+
if to_wait <= 0.0:
|
102
108
|
_LOG.warning(
|
103
109
|
"timeout waiting for %d/%d answers on %s",
|
104
110
|
len(answers),
|
c2cwsgiutils/client_info.py
CHANGED
@@ -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)
|
c2cwsgiutils/coverage_setup.py
CHANGED
@@ -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" #
|
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"),
|
c2cwsgiutils/db.py
CHANGED
@@ -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(
|
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(
|
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
|
|
c2cwsgiutils/debug/__init__.py
CHANGED
@@ -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
|
40
|
+
from c2cwsgiutils.debug import ( # pylint: disable=import-outside-toplevel
|
41
|
+
_listeners,
|
42
|
+
)
|
41
43
|
|
42
44
|
_listeners.init()
|
c2cwsgiutils/debug/_views.py
CHANGED
@@ -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: #
|
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
|
-
|
155
|
-
return result
|
154
|
+
return result
|
156
155
|
|
157
156
|
|
158
157
|
def _error(request: pyramid.request.Request) -> Any:
|
c2cwsgiutils/errors.py
CHANGED
@@ -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
|
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
|
|
c2cwsgiutils/health_check.py
CHANGED
@@ -67,6 +67,7 @@ class JsonCheckException(Exception):
|
|
67
67
|
"""Checker exception used to add some structured content to a failure."""
|
68
68
|
|
69
69
|
def __init__(self, message: str, json: Any):
|
70
|
+
"""Initialize the exception."""
|
70
71
|
super().__init__()
|
71
72
|
self.message = message
|
72
73
|
self.json = json
|
@@ -199,6 +200,7 @@ class HealthCheck:
|
|
199
200
|
"""
|
200
201
|
|
201
202
|
def __init__(self, config: pyramid.config.Configurator) -> None:
|
203
|
+
"""Initialize the health check view."""
|
202
204
|
config.add_route(
|
203
205
|
"c2c_health_check", config_utils.get_base_path(config) + r"/health_check", request_method="GET"
|
204
206
|
)
|
@@ -235,6 +237,7 @@ class HealthCheck:
|
|
235
237
|
engine_type: whether to check only the RW, RO or both engines
|
236
238
|
rw_engin: the RW engine to use (if None, use the session one)
|
237
239
|
ro_engin: the RO engine to use (if None, use the session one)
|
240
|
+
|
238
241
|
"""
|
239
242
|
if query_cb is None:
|
240
243
|
query_cb = self._at_least_one(at_least_one_model)
|
@@ -265,6 +268,7 @@ class HealthCheck:
|
|
265
268
|
version_table: override the table name for the version
|
266
269
|
rw_engin: the RW engine to use (if None, use the session one)
|
267
270
|
ro_engin: the RO engine to use (if None, use the session one)
|
271
|
+
|
268
272
|
"""
|
269
273
|
version_ = _get_alembic_version(alembic_ini_path, name)
|
270
274
|
|
@@ -287,26 +291,28 @@ class HealthCheck:
|
|
287
291
|
assert version_schema
|
288
292
|
assert version_table
|
289
293
|
for binding in _get_bindings(self.session, EngineType.READ_AND_WRITE):
|
290
|
-
with
|
291
|
-
|
294
|
+
with (
|
295
|
+
binding as binded_session,
|
296
|
+
_PROMETHEUS_DB_SUMMARY.labels(
|
292
297
|
configuration=alembic_ini_path, connection=binding.name(), check="alembic"
|
293
|
-
).time()
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
)
|
300
|
-
)
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
)
|
298
|
+
).time(),
|
299
|
+
):
|
300
|
+
result = binded_session.execute(
|
301
|
+
sqlalchemy.text(
|
302
|
+
"SELECT version_num FROM " # noqa: S608
|
303
|
+
f"{sqlalchemy.sql.quoted_name(version_schema, True)}."
|
304
|
+
f"{sqlalchemy.sql.quoted_name(version_table, True)}"
|
305
|
+
)
|
306
|
+
).fetchone()
|
307
|
+
assert result is not None
|
308
|
+
(actual_version,) = result
|
309
|
+
_PROMETHEUS_ALEMBIC_VERSION.labels(
|
310
|
+
version=actual_version, name=name, configuration=alembic_ini_path
|
311
|
+
).set(1)
|
312
|
+
if actual_version != version_:
|
313
|
+
raise Exception( # pylint: disable=broad-exception-raised
|
314
|
+
f"Invalid alembic version (db: {actual_version}, code: {version_})"
|
315
|
+
)
|
310
316
|
return version_
|
311
317
|
|
312
318
|
self._checks.append(
|
@@ -321,9 +327,8 @@ class HealthCheck:
|
|
321
327
|
Mapping[str, str], Callable[[pyramid.request.Request], Mapping[str, str]], None
|
322
328
|
] = None,
|
323
329
|
name: Optional[str] = None,
|
324
|
-
check_cb: Callable[
|
325
|
-
|
326
|
-
] = lambda request, response: None,
|
330
|
+
check_cb: Callable[[pyramid.request.Request, requests.Response], Any] = lambda request,
|
331
|
+
response: None,
|
327
332
|
timeout: float = 3,
|
328
333
|
level: int = 1,
|
329
334
|
) -> None:
|
@@ -339,6 +344,7 @@ class HealthCheck:
|
|
339
344
|
response as parameters)
|
340
345
|
timeout: the timeout
|
341
346
|
level: the level of the health check
|
347
|
+
|
342
348
|
"""
|
343
349
|
|
344
350
|
def check(request: pyramid.request.Request) -> Any:
|
@@ -365,6 +371,7 @@ class HealthCheck:
|
|
365
371
|
Arguments:
|
366
372
|
name: the name of the check (defaults to url)
|
367
373
|
level: the level of the health check
|
374
|
+
|
368
375
|
"""
|
369
376
|
|
370
377
|
def check(request: pyramid.request.Request) -> Any:
|
@@ -413,6 +420,7 @@ class HealthCheck:
|
|
413
420
|
Arguments:
|
414
421
|
name: the name of the check (defaults to "version")
|
415
422
|
level: the level of the health check
|
423
|
+
|
416
424
|
"""
|
417
425
|
|
418
426
|
def check(request: pyramid.request.Request) -> dict[str, Any]:
|
@@ -441,6 +449,7 @@ class HealthCheck:
|
|
441
449
|
name: the name of the check
|
442
450
|
check_cb: the callback to call (takes the request as parameter)
|
443
451
|
level: the level of the health check
|
452
|
+
|
444
453
|
"""
|
445
454
|
assert name
|
446
455
|
self._checks.append((name, check_cb, level))
|
@@ -453,9 +462,8 @@ class HealthCheck:
|
|
453
462
|
"successes": {},
|
454
463
|
}
|
455
464
|
checks = None
|
456
|
-
if "checks" in request.params:
|
457
|
-
|
458
|
-
checks = request.params["checks"].split(",")
|
465
|
+
if "checks" in request.params and request.params["checks"] != "":
|
466
|
+
checks = request.params["checks"].split(",")
|
459
467
|
for name, check, level in self._checks:
|
460
468
|
if level <= max_level and (checks is None or name in checks):
|
461
469
|
self._run_one(check, is_auth, level, name, request, results)
|
@@ -498,11 +506,13 @@ class HealthCheck:
|
|
498
506
|
) -> tuple[str, Callable[[pyramid.request.Request], None]]:
|
499
507
|
def check(request: pyramid.request.Request) -> None:
|
500
508
|
del request # unused
|
501
|
-
with
|
502
|
-
|
509
|
+
with (
|
510
|
+
binding as session,
|
511
|
+
_PROMETHEUS_DB_SUMMARY.labels(
|
503
512
|
connection=binding.name(), check="database", configuration="<default>"
|
504
|
-
).time()
|
505
|
-
|
513
|
+
).time(),
|
514
|
+
):
|
515
|
+
return query_cb(session)
|
506
516
|
|
507
517
|
return "db_engine_" + binding.name(), check
|
508
518
|
|
c2cwsgiutils/index.py
CHANGED
@@ -501,7 +501,7 @@ def _github_logout(request: pyramid.request.Request) -> dict[str, Any]:
|
|
501
501
|
|
502
502
|
def init(config: pyramid.config.Configurator) -> None:
|
503
503
|
"""Initialize the index page, for backward compatibility."""
|
504
|
-
warnings.warn("init function is deprecated; use includeme instead")
|
504
|
+
warnings.warn("init function is deprecated; use includeme instead", stacklevel=2)
|
505
505
|
includeme(config)
|
506
506
|
|
507
507
|
|