Xpress missing _license_probe: licensed_solvers includes xpress with an out-of-date licence, breaking local tests
Los mantenedores suelen responder en 1 día
Evaluación
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Aptitud para principiantes
- 58/100
- Tipo de issue
- Error
- Claridad
- Bastante claro
- Estado de actividad
- Activo
- Stack tecnológico
- python
- Área
- backend, testing-qa
Línea de trabajo
Start in linopy/solvers.py with Solver.license_status(), licensed_solvers, and the Xpress solver implementation; reproduce the stale-license case with XPAUTH_PATH and check_solver_licenses(). Review test_optimization.py alongside solving modules such as test_io.py, test_sos_*.py, and test_infeasibility.py. Done means the probe exposes the invalid licence and solving tests use licensed_solvers without breaking available_solvers semantics.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Local testing should be stable despite the xpress licence problem.
[!NOTE]
The following content was generated by AI.
Issue Description
With a local Xpress licence that predates the pip wheel (here: XPAUTH_PATH pointing to a licence valid up to release 9.4, wheel resolves xpress==9.9.1 via the solvers extra), pytest produces ~683 failures, all Xpress. xpress still counts as licensed, so every solver-parametrized test runs it.
Workaround -k "not xpress" is broken as well: it also deselects every test with expression in its name.
Reproducible Example
uv sync --extra dev --extra solvers # xpress 9.9.1, Python 3.13, Linux
export XPAUTH_PATH=/opt/xpress/xpauth.xpr # licence valid up to 9.4
uv run python -c "import linopy; print(list(linopy.licensed_solvers)); print(linopy.solvers.check_solver_licenses('xpress'))"
# ['gurobi', 'highs', 'scip', 'cplex', 'xpress']
# {'xpress': LicenseStatus(solver='xpress', ok=True, message=None)}
uv run python -c "import xpress; xpress.init()"
# xpress.InterfaceError: Xpress licensing error 21: Your license only authorizes up to release 9.4. Please contact [email protected] to upgrade it.
With env -u XPAUTH_PATH the wheel's bundled community licence is used and everything passes, so CI (no XPAUTH_PATH) never hits this.
Root cause
Solver.license_status() / licensed_solvers rely on the per-solver _license_probe hook. Gurobi, Knitro, Mosek, COPT, MindOpt, cuPDLPx and cuOpt implement it; Xpress does not, so the no-op default reports it as licensed. On top of that, most test modules (15 files, e.g. test_io.py, test_sos_*.py, test_infeasibility.py) parametrize over available_solvers (importable only) rather than licensed_solvers like test_optimization.py does.
Expected Behavior / proposed solution
A solver that cannot acquire a licence is not in licensed_solvers, and tests that solve only parametrize over licensed_solvers:
class Xpress(Solver[None]):
@classmethod
def _license_probe(cls) -> None:
xpress.init()
# test modules that actually solve
from linopy import licensed_solvers
@pytest.mark.parametrize("solver", licensed_solvers)
Stays fail-fast and visible: available_solvers keeps its importable-only semantics (no licence at import), the probe error is exposed via check_solver_licenses(), and test_optimization.py already prints the tested solvers in its header.
Alternatives considered:
- Probe the licence in
available_solvers: breaks the documented "no licence at import/membership" contract. - Pin
xpress<9.5: pins everyone to an old release to fit one local licence; the community licence covers newer releases. - Pytest
--exclude-solveroption: treats the symptom; still reports an unlicensed solver as licensed to users.
Related: mypy
uv run mypy linopy reports 28 errors in linopy/solvers.py when xpress 9.9.1 is installed. The wheel ships py.typed/__init__.pyi, and the flagged lines are mostly the pre-9.8 fallback branches (chgobjsense, addnames, postsolve, getDual, readbasis, xpress.maximize, ...) plus Optional arrays passed to loadMIQP. CI does not see it because the mypy job installs [dev,spec] without solvers. Related to #533.
mypy output (xpress 9.9.1, mypy 2.3.1)
linopy/solvers.py:2730: error: Module has no attribute "maximize" [attr-defined]
linopy/solvers.py:2755: error: Argument 1 to "enter_context" of "_BaseExitStack" has incompatible type "problem"; expected "AbstractContextManager[Never, bool | None]" [arg-type]
linopy/solvers.py:2854: error: Argument "objqcol1" to "loadMIQP" of "problem" has incompatible type "ndarray[...] | None"; expected "Iterable[var | int | str]" [arg-type]
linopy/solvers.py:2876: error: "problem" has no attribute "loadproblem" [attr-defined]
linopy/solvers.py:2909: error: "problem" has no attribute "chgobjsense"; maybe "chgObjSense"? [attr-defined]
linopy/solvers.py:2922: error: "problem" has no attribute "addnames"; maybe "addNames"? [attr-defined]
linopy/solvers.py:3015: error: Argument 1 to "setControl" of "problem" has incompatible type "dict[str, Any]"; expected "dict[str | int, str | float]" [arg-type]
linopy/solvers.py:3021: error: "problem" has no attribute "setlogfile"; maybe "setLogFile"? [attr-defined]
linopy/solvers.py:3027: error: "problem" has no attribute "readbasis"; maybe "readBasis"? [attr-defined]
linopy/solvers.py:3035: error: "problem" has no attribute "postsolve"; maybe "postSolve"? [attr-defined]
linopy/solvers.py:3042: error: "problem" has no attribute "writebasis"; maybe "writeBasis"? [attr-defined]
linopy/solvers.py:3051: error: "problem" has no attribute "writebinsol"; maybe "writeBinSol"? [attr-defined]
linopy/solvers.py:3080: error: "problem" has no attribute "getDual"; maybe "getDuals"? [attr-defined]
... (28 errors total)
Installed Versions
linopy master / spec-whole-model (0.9.1.post1.dev89), xpress 9.9.1, mypy 2.3.1, Python 3.13.11, Ubuntu 24.04.
- Lenguaje dominante
- Python
- Estrellas
- 259
- Forks
- 89
- Merge medio
- 1 d 18 h
- PR fusionados (30 d)
- 26
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de PyPSA/linopy
-
bug solver interface
Dificultad 2/5 1-3 horas Aptitud para principiantes 84/100
Los mantenedores suelen responder en 1 día
-
refactor sparse
Dificultad 5/5 Más de una semana Aptitud para principiantes 35/100
Los mantenedores suelen responder en 1 día
-
Meta: separate dense and sparse stores from the expression and constraint frontendPosiblemente ocupada @Rishabhkanhaiya la tomó hace 2 días. Abiertodiscussion refactor sparse
Dificultad 5/5 Más de una semana Aptitud para principiantes 25/100
PyPSA/linopy#1019 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
refactor sparse
Dificultad 4/5 3-5 días Aptitud para principiantes 38/100
Los mantenedores suelen responder en 1 día
-
discussion math-spec
Dificultad 5/5 Más de una semana Aptitud para principiantes 30/100
PyPSA/linopy#993 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
Todos los issues de PyPSA/linopy
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 62/100
Los mantenedores suelen responder en 1 día
-
enhancement good first issue Stellar Wave trivial
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
StellarCanary/ProtocolCanary-Fixtures#258 ·
Los mantenedores suelen responder en 1 día
-
enhancement
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
IBM/ai-atlas-nexus#295 ·
Los mantenedores suelen responder en 6 días
-
github_actions
Dificultad 2/5 1-3 horas Aptitud para principiantes 65/100
Hochfrequenz/aibap.mcp#578 ·
Los mantenedores suelen responder en 1 día
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
mishraprafful/multihull#150 ·
Los mantenedores suelen responder en 1 día