Skip to content

Commit bc694a8

Browse files
authored
Merge branch 'dev' into precommit_autofixing
Signed-off-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com>
2 parents a1fc8ea + 605611b commit bc694a8

29 files changed

Lines changed: 1043 additions & 102 deletions

.github/workflows/weekly-preview.yml

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,18 @@ permissions:
66
on:
77
schedule:
88
- cron: "0 2 * * 0" # 02:00 of every Sunday
9+
pull_request:
10+
branches:
11+
- dev
12+
13+
env:
14+
PYTHON_VER: '3.10'
15+
PYTORCH_VER: '2.8.0'
16+
PIP_EXTRA_INDEX_URL: "https://download.pytorch.org/whl/cpu" # forces CPU PyTorch installation, should be faster
917

1018
jobs:
1119
static-checks:
20+
if: github.event_name == 'schedule' # only check on cron run, these checks are redundant in a PR
1221
runs-on: ubuntu-latest
1322
strategy:
1423
matrix:
@@ -25,10 +34,10 @@ jobs:
2534
- uses: actions/checkout@v7
2635
with:
2736
persist-credentials: false
28-
- name: Set up Python 3.10
37+
- name: Set up Python ${{ env.PYTHON_VER }}
2938
uses: actions/setup-python@v6
3039
with:
31-
python-version: '3.10'
40+
python-version: ${{ env.PYTHON_VER }}
3241
cache: 'pip'
3342
- name: Install dependencies
3443
run: |
@@ -40,22 +49,24 @@ jobs:
4049
$(pwd)/runtests.sh --build --clean
4150
$(pwd)/runtests.sh --build --${{ matrix.opt }}
4251
43-
packaging:
52+
publish:
4453
if: github.repository == 'Project-MONAI/MONAI'
4554
runs-on: ubuntu-latest
4655
steps:
4756
- uses: actions/checkout@v7
4857
with:
49-
ref: dev
58+
# get the ref for the PR branch or dev if this is a cron job
59+
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || 'dev' }}
5060
fetch-depth: 0
5161
persist-credentials: false
52-
- name: Set up Python 3.10
62+
- name: Set up Python ${{ env.PYTHON_VER }}
5363
uses: actions/setup-python@v6
5464
with:
55-
python-version: '3.10'
56-
- name: Install setuptools
65+
python-version: ${{ env.PYTHON_VER }}
66+
cache: 'pip'
67+
- name: Install tools
5768
run: |
58-
python -m pip install --user --upgrade setuptools wheel packaging
69+
python -m pip install -U pip build
5970
- name: Build distribution
6071
run: |
6172
export HEAD_COMMIT_ID=$(git rev-parse HEAD)
@@ -72,9 +83,15 @@ jobs:
7283
git tag "1.7.dev${YEAR_WEEK}"
7384
git log -1
7485
git tag --list
75-
python setup.py sdist bdist_wheel
76-
86+
python -m build
87+
ls -lh dist
88+
- name: Test Installation
89+
run: |
90+
pip install dist/*.whl
91+
pip list
92+
(cd "$(mktemp -d)" && python -c 'import monai; print(monai.__version__)')
7793
- name: Publish to PyPI
94+
if: github.event_name == 'schedule' # only publish on cron run
7895
uses: pypa/gh-action-pypi-publish@release/v1
7996
with:
8097
user: __token__

docs/requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ sphinxcontrib-serializinghtml
2020
sphinx-autodoc-typehints==1.11.1
2121
pandas
2222
einops
23-
transformers>=4.53.0
24-
mlflow>=2.12.2,<3.13
23+
transformers>=5.5.0
24+
mlflow>=3.15.2
2525
clearml>=1.10.0rc0
2626
tensorboardX
2727
imagecodecs; platform_system == "Linux" or platform_system == "Darwin"

docs/source/utils.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,8 @@ Ordering
8080
--------
8181
.. automodule:: monai.utils.ordering
8282
:members:
83+
84+
Safe Evaluation
85+
---------------
86+
.. automodule:: monai.utils.safeeval
87+
:members:

monai/bundle/config_parser.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,14 @@ def __getattr__(self, key: str) -> Any:
162162
try:
163163
return self._chain(key)
164164
except KeyError:
165-
return getattr(self._value, key)
165+
pass
166+
if isinstance(self._value, dict) and key in self._value:
167+
# the chained id is absent from the resolver (for example when this proxy is
168+
# backed by a `$@ref`, whose children have no ids of their own), but the key
169+
# does exist in the container: resolve it like `__getitem__` does, so dot- and
170+
# bracket-notation agree and config keys keep precedence over dict methods.
171+
return self._value[key]
172+
return getattr(self._value, key)
166173

167174
def __getitem__(self, key: str | int) -> Any:
168175
try:

monai/bundle/scripts.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from textwrap import dedent
2626
from typing import Any
2727

28+
import numpy as np
2829
import torch
2930
from torch.cuda import is_available
3031

@@ -51,13 +52,13 @@
5152
min_version,
5253
optional_import,
5354
pprint_edges,
55+
safe_eval,
5456
)
5557

5658
validate, _ = optional_import("jsonschema", name="validate")
5759
ValidationError, _ = optional_import("jsonschema.exceptions", name="ValidationError")
5860
Checkpoint, has_ignite = optional_import("ignite.handlers", IgniteInfo.OPT_IMPORT_VERSION, min_version, "Checkpoint")
5961
requests, has_requests = optional_import("requests")
60-
onnx, _ = optional_import("onnx")
6162
huggingface_hub, _ = optional_import("huggingface_hub")
6263

6364
logger = get_logger(module_name=__name__)
@@ -158,10 +159,12 @@ def _get_fake_spatial_shape(shape: Sequence[str | int], p: int = 1, n: int = 1,
158159
if i == "*":
159160
ret.append(any)
160161
else:
161-
for c in _get_var_names(i):
162-
if c not in ["p", "n"]:
163-
raise ValueError(f"only support variables 'p' and 'n' so far, but got: {c}.")
164-
ret.append(eval(i, {"p": p, "n": n}))
162+
bad_names = set(c for c in _get_var_names(i) if c not in {"p", "n"})
163+
if bad_names:
164+
raise ValueError(f"Only variables `p` and `n` currently supported. Invalid names: {bad_names}")
165+
166+
# evaluate using Numpy types to prevent slow Python DoS attacks
167+
ret.append(int(safe_eval(i, {"p": np.int32(p), "n": np.int32(n)}, rewrite_np=True)))
165168
else:
166169
raise ValueError(f"spatial shape items must be int or string, but got: {type(i)} {i}.")
167170
return tuple(ret)
@@ -994,7 +997,8 @@ def run(
994997
common parameters shown below will be added and can be passed through the `override` parameter of this method.
995998
996999
- ``"output_dir"``: the path to save mlflow tracking outputs locally, default to "<bundle root>/eval".
997-
- ``"tracking_uri"``: uri to save mlflow tracking outputs, default to "/output_dir/mlruns".
1000+
- ``"tracking_uri"``: uri to save mlflow tracking outputs, default to a local SQLite database
1001+
at "<output_dir>/mlruns.db" with run artifacts kept under "<output_dir>/mlruns".
9981002
- ``"experiment_name"``: experiment name for this run, default to "monai_experiment".
9991003
- ``"run_name"``: the name of current run.
10001004
- ``"save_execute_config"``: whether to save the executed config files. It can be `False`, `/path/to/artifacts`
@@ -1433,6 +1437,7 @@ def onnx_export(
14331437
converter_kwargs_.update({"inputs": inputs_, "use_trace": use_trace_})
14341438

14351439
def save_onnx(onnx_obj: Any, filename_prefix_or_stream: str, **kwargs: Any) -> None:
1440+
onnx, _ = optional_import("onnx")
14361441
onnx.save(onnx_obj, filename_prefix_or_stream)
14371442

14381443
_export(

monai/bundle/utils.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,10 @@
116116
"configs": {
117117
# if no "output_dir" in the bundle config, default to "<bundle root>/eval"
118118
"output_dir": "$@bundle_root + '/eval'",
119-
# use URI to support linux, mac and windows os
120-
"tracking_uri": "$monai.utils.path_to_uri(@output_dir) + '/mlruns'",
119+
# MLflow 3.13+ rejects the filesystem (file store) tracking backend, so default tracking
120+
# to a local SQLite database. The handler keeps run artifacts under "<output_dir>/mlruns"
121+
# (next to the db). A URI is used so the path is valid on linux, mac and windows os.
122+
"tracking_uri": "$monai.utils.path_to_sqlite_uri(@output_dir + '/mlruns.db')",
121123
"experiment_name": "monai_experiment",
122124
"run_name": None,
123125
# may fill it at runtime

monai/bundle/workflows.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import os
1616
import sys
1717
import time
18+
import warnings
1819
from abc import ABC, abstractmethod
1920
from collections.abc import Sequence
2021
from copy import copy
@@ -34,6 +35,23 @@
3435
logger = get_logger(module_name=__name__)
3536

3637

38+
def _warn_logging_file_execution(logging_file: str) -> None:
39+
"""
40+
Warn that ``logging_file`` is about to be executed by `logging.config.fileConfig`.
41+
42+
Called immediately before every `fileConfig` invocation in this module, so the warning is only
43+
raised when the file is really executed -- not when it is missing or logging is disabled.
44+
"""
45+
warnings.warn(
46+
f"applying logging config {logging_file}: `logging.config.fileConfig` passes the `class=` and "
47+
"`args=` fields of the INI's handler and formatter sections to Python `eval()`, so this file "
48+
"runs as code. A bundle ships its own `configs/logging.conf` and it is applied by default, "
49+
"before any of the bundle's config is parsed. Only proceed if this file is from a source you "
50+
"trust (see https://github.com/Project-MONAI/MONAI/security/advisories/GHSA-wvpx-5qmp-46g3).",
51+
stacklevel=3,
52+
)
53+
54+
3755
class BundleWorkflow(ABC):
3856
"""
3957
Base class for the workflow specification in bundle, it can be a training, evaluation or inference workflow.
@@ -55,6 +73,10 @@ class BundleWorkflow(ABC):
5573
meta_file: filepath of the metadata file, if this is a list of file paths, their contents will be merged in order.
5674
logging_file: config file for `logging` module in the program. for more details:
5775
https://docs.python.org/3/library/logging.config.html#logging.config.fileConfig.
76+
Security note: `fileConfig` passes the INI's `class=` and `args=` fields to Python
77+
`eval()`, so this file runs as code and applying it raises a warning -- once per call
78+
site, as Python's default warning filter suppresses repeats
79+
(see https://github.com/Project-MONAI/MONAI/security/advisories/GHSA-wvpx-5qmp-46g3).
5880
5981
"""
6082

@@ -72,6 +94,7 @@ def __init__(
7294
if not os.path.isfile(logging_file):
7395
raise FileNotFoundError(f"Cannot find the logging config file: {logging_file}.")
7496
logger.info(f"Setting logging properties based on config: {logging_file}.")
97+
_warn_logging_file_execution(logging_file)
7598
fileConfig(logging_file, disable_existing_loggers=False)
7699

77100
if meta_file is not None:
@@ -273,6 +296,10 @@ class PythonicWorkflow(BundleWorkflow):
273296
meta_file: filepath of the metadata file, if this is a list of file paths, their contents will be merged in order.
274297
logging_file: config file for `logging` module in the program. for more details:
275298
https://docs.python.org/3/library/logging.config.html#logging.config.fileConfig.
299+
Security note: `fileConfig` passes the INI's `class=` and `args=` fields to Python
300+
`eval()`, so this file runs as code and applying it raises a warning -- once per call
301+
site, as Python's default warning filter suppresses repeats
302+
(see https://github.com/Project-MONAI/MONAI/security/advisories/GHSA-wvpx-5qmp-46g3).
276303
277304
"""
278305

@@ -375,6 +402,10 @@ class ConfigWorkflow(BundleWorkflow):
375402
https://docs.python.org/3/library/logging.config.html#logging.config.fileConfig.
376403
If None, default to "configs/logging.conf", which is commonly used for bundles in MONAI model zoo.
377404
If False, the logging logic for the bundle will not be modified.
405+
Security note: `fileConfig` passes the INI's `class=` and `args=` fields to Python
406+
`eval()`, so this file runs as code and applying it raises a warning -- once per call
407+
site, as Python's default warning filter suppresses repeats
408+
(see https://github.com/Project-MONAI/MONAI/security/advisories/GHSA-wvpx-5qmp-46g3).
378409
init_id: ID name of the expected config expression to initialize before running, default to "initialize".
379410
allow a config to have no `initialize` logic and the ID.
380411
run_id: ID name of the expected config expression to run, default to "run".
@@ -444,6 +475,7 @@ def __init__(
444475
else:
445476
raise FileNotFoundError(f"Cannot find the logging config file: {logging_file}.")
446477
else:
478+
_warn_logging_file_execution(str(logging_file))
447479
fileConfig(str(logging_file), disable_existing_loggers=False)
448480
logger.info(f"Setting logging properties based on config: {logging_file}.")
449481

0 commit comments

Comments
 (0)