Skip to content

Commit 57023a9

Browse files
Merge branch 'master' into extract-celery-code
2 parents 86ef5a9 + 3ad6b04 commit 57023a9

File tree

88 files changed

+796
-387
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

88 files changed

+796
-387
lines changed

.vscode/launch.template.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,28 @@
4444
"justMyCode": false
4545
},
4646
{
47+
// This test adds --external-envfile and expects a file named ".secrets" in the workspace root.
48+
"name": "Python: Test w/ repo.config",
49+
"type": "debugpy",
50+
"request": "launch",
51+
"module": "pytest",
52+
"args": [
53+
"--ff",
54+
"--log-cli-level=INFO",
55+
"--external-envfile=${workspaceFolder}/.secrets",
56+
"--pdb",
57+
"--setup-show",
58+
"--durations=5",
59+
"-s",
60+
"-vv",
61+
"${file}"
62+
],
63+
"cwd": "${workspaceFolder}",
64+
"console": "integratedTerminal",
65+
"justMyCode": false
66+
},
67+
{
68+
// This tests enables the httpx spy and dumps captures in a json. Mainly for api-server
4769
"name": "Python: Test-Httpx-Spy",
4870
"type": "debugpy",
4971
"request": "launch",

packages/pytest-simcore/src/pytest_simcore/environment_configs.py

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# pylint: disable=unused-variable
44

55

6+
import logging
67
import re
78
from pathlib import Path
89
from typing import Any
@@ -12,6 +13,8 @@
1213
from .helpers.monkeypatch_envs import load_dotenv, setenvs_from_dict
1314
from .helpers.typing_env import EnvVarsDict
1415

16+
_logger = logging.getLogger(__name__)
17+
1518

1619
def pytest_addoption(parser: pytest.Parser):
1720
simcore_group = parser.getgroup("simcore")
@@ -20,12 +23,17 @@ def pytest_addoption(parser: pytest.Parser):
2023
action="store",
2124
type=Path,
2225
default=None,
23-
help="Path to an env file. Consider passing a link to repo configs, i.e. `ln -s /path/to/osparc-ops-config/repo.config`",
26+
help="Path to an env file. Replaces .env-devel in the tests by an external envfile."
27+
"e.g. consider "
28+
" `ln -s /path/to/osparc-ops-config/repo.config .secrets` and then "
29+
" `pytest --external-envfile=.secrets --pdb tests/unit/test_core_settings.py`",
2430
)
2531

2632

2733
@pytest.fixture(scope="session")
28-
def external_envfile_dict(request: pytest.FixtureRequest) -> EnvVarsDict:
34+
def external_envfile_dict(
35+
request: pytest.FixtureRequest, osparc_simcore_root_dir: Path
36+
) -> EnvVarsDict:
2937
"""
3038
If a file under test folder prefixed with `.env-secret` is present,
3139
then this fixture captures it.
@@ -35,19 +43,43 @@ def external_envfile_dict(request: pytest.FixtureRequest) -> EnvVarsDict:
3543
"""
3644
envs = {}
3745
if envfile := request.config.getoption("--external-envfile"):
38-
print("🚨 EXTERNAL `envfile` option detected. Loading", envfile, "...")
46+
_logger.warning(
47+
"🚨 EXTERNAL `envfile` option detected. Loading '%s' ...", envfile
48+
)
3949

4050
assert isinstance(envfile, Path)
4151
assert envfile.exists()
4252
assert envfile.is_file()
4353

54+
envfile = envfile.resolve()
55+
osparc_simcore_root_dir = osparc_simcore_root_dir.resolve()
56+
57+
if osparc_simcore_root_dir in envfile.parents and not any(
58+
term in envfile.name.lower() for term in ("ignore", "secret")
59+
):
60+
_logger.warning(
61+
"🚨 CAUTION: The external envfile '%s' may contain sensitive data and could be accidentally versioned. "
62+
"To prevent this, include the words 'secret' or 'ignore' in the filename.",
63+
envfile.name,
64+
)
65+
4466
envs = load_dotenv(envfile)
4567

68+
if envs:
69+
response = input(
70+
f"🚨 CAUTION: You are about to run tests using environment variables loaded from '{envfile}'.\n"
71+
"This may cause tests to interact with or modify real external systems (e.g., production or staging environments).\n"
72+
"Proceeding could result in data loss or unintended side effects.\n"
73+
"Are you sure you want to continue? [y/N]: "
74+
)
75+
if response.strip().lower() not in ("y", "yes"):
76+
pytest.exit("Aborted by user due to external envfile usage.")
77+
4678
return envs
4779

4880

4981
@pytest.fixture(scope="session")
50-
def skip_if_external_envfile_dict(external_envfile_dict: EnvVarsDict) -> None:
82+
def skip_if_no_external_envfile(external_envfile_dict: EnvVarsDict) -> None:
5183
if not external_envfile_dict:
5284
pytest.skip(reason="Skipping test since external-envfile is not set")
5385

packages/service-library/src/servicelib/aiohttp/monitoring.py

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"""Enables monitoring of some quantities needed for diagnostics"""
22

3-
import asyncio
43
import logging
54
from collections.abc import Awaitable, Callable
65
from time import perf_counter
@@ -61,9 +60,8 @@ async def middleware_handler(request: web.Request, handler: Handler):
6160
# See https://prometheus.io/docs/concepts/metric_types
6261

6362
log_exception: BaseException | None = None
64-
resp: web.StreamResponse = web.HTTPInternalServerError(
65-
reason="Unexpected exception"
66-
)
63+
response: web.StreamResponse = web.HTTPInternalServerError()
64+
6765
canonical_endpoint = request.path
6866
if request.match_info.route.resource:
6967
canonical_endpoint = request.match_info.route.resource.canonical
@@ -86,29 +84,25 @@ async def middleware_handler(request: web.Request, handler: Handler):
8684
endpoint=canonical_endpoint,
8785
user_agent=user_agent,
8886
):
89-
resp = await handler(request)
87+
response = await handler(request)
9088

9189
assert isinstance( # nosec
92-
resp, web.StreamResponse
90+
response, web.StreamResponse
9391
), "Forgot envelope middleware?"
9492

9593
except web.HTTPServerError as exc:
96-
resp = exc
94+
response = exc
9795
log_exception = exc
98-
raise resp from exc
96+
raise
97+
9998
except web.HTTPException as exc:
100-
resp = exc
99+
response = exc
101100
log_exception = None
102-
raise resp from exc
103-
except asyncio.CancelledError as exc:
104-
resp = web.HTTPInternalServerError(text=f"{exc}")
105-
log_exception = exc
106-
raise resp from exc
101+
raise
102+
107103
except Exception as exc: # pylint: disable=broad-except
108-
resp = web.HTTPInternalServerError(text=f"{exc}")
109-
resp.__cause__ = exc
110104
log_exception = exc
111-
raise resp from exc
105+
raise
112106

113107
finally:
114108
response_latency_seconds = perf_counter() - start_time
@@ -118,13 +112,13 @@ async def middleware_handler(request: web.Request, handler: Handler):
118112
method=request.method,
119113
endpoint=canonical_endpoint,
120114
user_agent=user_agent,
121-
http_status=resp.status,
115+
http_status=response.status,
122116
response_latency_seconds=response_latency_seconds,
123117
)
124118

125119
if exit_middleware_cb:
126120
with log_catch(logger=log, reraise=False):
127-
await exit_middleware_cb(request, resp)
121+
await exit_middleware_cb(request, response)
128122

129123
if log_exception:
130124
log.error(
@@ -135,12 +129,12 @@ async def middleware_handler(request: web.Request, handler: Handler):
135129
request.method,
136130
request.path,
137131
response_latency_seconds,
138-
resp.status,
132+
response.status,
139133
exc_info=log_exception,
140134
stack_info=True,
141135
)
142136

143-
return resp
137+
return response
144138

145139
setattr( # noqa: B010
146140
middleware_handler, "__middleware_name__", f"{__name__}.monitor_{app_name}"

packages/service-library/src/servicelib/aiohttp/profiler_middleware.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ async def profiling_middleware(request: Request, handler):
1313
try:
1414
if _profiler.is_running or (_profiler.last_session is not None):
1515
raise HTTPInternalServerError(
16-
reason="Profiler is already running. Only a single request can be profiled at any given time.",
16+
text="Profiler is already running. Only a single request can be profiled at any given time.",
1717
headers={},
1818
)
1919
_profiler.reset()
@@ -24,7 +24,7 @@ async def profiling_middleware(request: Request, handler):
2424

2525
if response.content_type != MIMETYPE_APPLICATION_JSON:
2626
raise HTTPInternalServerError(
27-
reason=f"Profiling middleware is not compatible with {response.content_type=}",
27+
text=f"Profiling middleware is not compatible with {response.content_type=}",
2828
headers={},
2929
)
3030

packages/service-library/src/servicelib/aiohttp/requests_validation.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def handle_validation_as_http_error(
5151
}
5252
for e in err.errors()
5353
]
54-
reason_msg = error_msg_template.format(
54+
user_error_message = error_msg_template.format(
5555
failed=", ".join(d["loc"] for d in details)
5656
)
5757

@@ -80,15 +80,14 @@ def handle_validation_as_http_error(
8080
error_str = json_dumps(
8181
{
8282
"error": {
83-
"msg": reason_msg,
83+
"msg": user_error_message,
8484
"resource": resource_name, # optional
8585
"details": details, # optional
8686
}
8787
}
8888
)
8989

9090
raise web.HTTPUnprocessableEntity( # 422
91-
reason=reason_msg,
9291
text=error_str,
9392
content_type=MIMETYPE_APPLICATION_JSON,
9493
) from err

0 commit comments

Comments
 (0)