Skip to content

[FIX] Replace statsmodels.robust.scale.mad with scipy.stats.median_abs_deviation to avoid enabling the GIL - #126

Open
pvlov wants to merge 1 commit into
nbara:masterfrom
pvlov:master
Open

[FIX] Replace statsmodels.robust.scale.mad with scipy.stats.median_abs_deviation to avoid enabling the GIL#126
pvlov wants to merge 1 commit into
nbara:masterfrom
pvlov:master

Conversation

@pvlov

@pvlov pvlov commented Aug 12, 2026

Copy link
Copy Markdown

Hi! Since this is a small diff and I couldn't find a CONTRIBUTING.md, I went ahead and opened a PR. I hope you don't mind!

I wanted to use meegkit in a project that uses a free-threaded Python interpreter, but I ran into the following warning:

<frozen importlib._bootstrap>:491: RuntimeWarning: 
The global interpreter lock (GIL) has been enabled to load module 'statsmodels.robust._qn', 
which has not declared that it can run safely without the GIL. 
To override this behavior and keep the GIL disabled (at your own risk), run with PYTHON_GIL=0 or -Xgil=0.

You can replicate this warning (using uv) with this command:

uv run --no-project --python 3.14t --with statsmodels python -c "import statsmodels.robust.scale"

I checked where/how statsmodels is used and found only one occurrence: statsmodels.robust.scale.mad. This PR replaces it with the equivalent scipy.stats.median_abs_deviation. This makes the package usable in free-threaded contexts while also dropping the statsmodels dependency entirely.

Additionally, I added a test using the built-in sys._is_gil_enabled check to confirm the GIL stays disabled, and added 3.14 and 3.14t to the CI python matrix. Let me know if you want to keep this or if I should drop this.

@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.28%. Comparing base (6b6c1d7) to head (444bcb3).

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #126      +/-   ##
==========================================
- Coverage   83.29%   83.28%   -0.01%     
==========================================
  Files          25       25              
  Lines        2861     2860       -1     
==========================================
- Hits         2383     2382       -1     
  Misses        478      478              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@nbara
nbara requested a lite review from Copilot August 18, 2026 15:52
@nbara

nbara commented Aug 18, 2026

Copy link
Copy Markdown
Owner

Hi @pvlov thanks for this! Perfectly fine to open PRs here :)

@nbara nbara added the maintenance Code maintenance label Aug 18, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR removes the statsmodels dependency (which can re-enable the GIL under free-threaded CPython) by replacing the one usage of statsmodels.robust.scale.mad with scipy.stats.median_abs_deviation, and adds CI/test coverage to ensure importing meegkit does not re-enable the GIL on free-threaded builds.

Changes:

  • Replace statsmodels.robust.scale.mad with scipy.stats.median_abs_deviation in ASR window cleaning logic.
  • Drop statsmodels from requirements.txt.
  • Add a free-threaded-only test plus extend CI to run on 3.14 and 3.14t.

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 2 comments.

File Description
meegkit/asr.py Replaces MAD implementation to avoid importing statsmodels (prevents triggering GIL enablement).
requirements.txt Removes the statsmodels runtime dependency.
tests/test_freethreading.py Adds a subprocess-based import test to verify GIL remains disabled on free-threaded CPython.
.github/workflows/testing.yml Expands the Python matrix to include 3.14 and 3.14t.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +7 to +10
pytestmark = pytest.mark.skipif(
not sysconfig.get_config_var("Py_GIL_DISABLED"),
reason="requires a free-threaded CPython build",
)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know LLMs like their defensive coding but I wanted to make sure I'm not making any false assumptions, so I dug into cpython a bit.

First, if we have a look at sysconfig's documentation we can see that get_config_var will return genuine python ints for boolean values:

>>> import sysconfig
>>> sysconfig.get_config_var('Py_ENABLE_SHARED')
0
...

Next, if we check the implementation of the sysconfig module in cpython, we can see that it does genuinely parse "int-like" values into python ints, unless it is forced to stay a str as per the set _ALWAYS_STR, which does not apply to Py_GIL_DISABLED. Further, undefs for config values set the config_var to 0 and for cypthons builds the build value for true is 1 so this is fine as well. So imo this is unnecessarily defensive.

Comment thread tests/test_freethreading.py Outdated
…s_deviation to avoid enabling the GIL and remove statsmodels from dependencies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

maintenance Code maintenance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants