Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ jobs:
test:
name: Test (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
env:
MPLBACKEND: Agg
strategy:
fail-fast: false
matrix:
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ shannon.plot(s, window=20) # a matplotlib Figure
Every method accepts a `pd.Series` **or** a `np.ndarray`. Pass a Series and you
get a Series back with its index preserved; pass an array and you get an array.

### Headless environments (Docker, CI)

`entroscope` does not change your matplotlib backend on import, so interactive
plotting in notebooks keeps working. In a headless environment (a Docker
container or CI runner) where you want a guaranteed non-interactive backend, set
the standard environment variable:

```bash
export MPLBACKEND=Agg # or, in a Dockerfile: ENV MPLBACKEND=Agg
```

## The seven measures

| Measure | Import | Captures |
Expand Down
16 changes: 9 additions & 7 deletions entroscope/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@
Every measure module delegates its standard methods here so the
"Series in -> Series out, array in -> array out" contract and the windowing
logic live in exactly one place.
"""

import matplotlib
This module does NOT force a matplotlib backend. Plot helpers build a Figure and
return it (never calling ``plt.show()``), so they work under whatever backend the
environment provides. Headless / Docker / CI users who want a guaranteed
non-interactive backend should set ``MPLBACKEND=Agg`` in their environment.
"""

matplotlib.use("Agg") # headless-safe; never opens a window
import matplotlib.pyplot as plt # noqa: E402
import numpy as np # noqa: E402
import pandas as pd # noqa: E402
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

from .utils.windows import sliding_windows # noqa: E402
from .utils.windows import sliding_windows


def as_array(x):
Expand Down
15 changes: 15 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Test-session setup: force a non-interactive matplotlib backend.

The library deliberately does NOT force a backend (see issue #2). The test
environment is responsible for declaring itself headless, which is what this
does — before matplotlib resolves an interactive backend.
"""

import os

# Set before matplotlib is imported anywhere, so it never picks a GUI backend.
os.environ.setdefault("MPLBACKEND", "Agg")

import matplotlib # noqa: E402

matplotlib.use("Agg", force=True)
30 changes: 30 additions & 0 deletions tests/test_backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Regression test for issue #2: importing entroscope must not force a backend."""

import subprocess
import sys


def test_import_does_not_change_backend():
# Run in a subprocess with a known non-Agg backend selected via MPLBACKEND.
# If importing entroscope calls matplotlib.use("Agg"), the backend will flip
# to "agg" and the assertion in the child fails.
code = (
"import matplotlib\n"
"before = matplotlib.get_backend().lower()\n"
"import entroscope\n"
"from entroscope import shannon, transfer, divergence\n"
"after = matplotlib.get_backend().lower()\n"
"assert before == 'template', f'unexpected start backend {before!r}'\n"
"assert after == 'template', f'import changed backend to {after!r}'\n"
"print('backend-stable')\n"
)
result = subprocess.run(
[sys.executable, "-c", code],
env={"MPLBACKEND": "template", "PATH": __import__("os").environ["PATH"]},
capture_output=True,
text=True,
)
assert result.returncode == 0, (
f"subprocess failed:\nSTDOUT:{result.stdout}\nSTDERR:{result.stderr}"
)
assert "backend-stable" in result.stdout
Loading