Xpress missing _license_probe: licensed_solvers includes xpress with an out-of-date licence, breaking local tests
维护者通常 1 天内回复
评估
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 新手友好度
- 58/100
- Issue 类型
- 缺陷
- 描述清晰度
- 基本清楚
- 活跃度
- 活跃
- 技术栈
- python
- 领域
- backend, testing-qa
调研方向
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.
由索引模型根据 Issue 内容生成。
描述
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.
- 主要语言
- Python
- 星标
- 260
- 派生
- 89
- 平均合并
- 1 天 1 小时
- 30 天内合并 PR
- 24
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
PyPSA/linopy 的其他 Issue
-
bug solver interface
难度 2/5 1-3 小时 新手友好度 84/100
维护者通常 1 天内回复
-
CSR constraints: support `update()` instead of raising frozen errors可能已有人在做 关联的 PR 仍在进行中或已合并。 未关闭enhancement sparse
难度 4/5 3-5 天 新手友好度 48/100
维护者通常 1 天内回复
-
refactor sparse
难度 5/5 一周以上 新手友好度 35/100
维护者通常 1 天内回复
-
Meta: separate dense and sparse stores from the expression and constraint frontend可能已有人在做 @Rishabhkanhaiya 于 12 天前认领。 未关闭discussion refactor sparse
难度 5/5 一周以上 新手友好度 25/100
维护者通常 1 天内回复
-
refactor sparse
难度 4/5 3-5 天 新手友好度 38/100
维护者通常 1 天内回复
相似的 Issue
-
难度 1/5 1-3 小时 新手友好度 66/100
AlexsLemonade/scpca-docs#518 ·
-
bug
难度 1/5 1 小时以内 新手友好度 95/100
rocketride-org/rocketride-server#2705 · 1 条评论 ·
维护者通常 1 天内回复
-
documentation
难度 2/5 1-3 小时 新手友好度 85/100
Metabuilder-Labs/tokenjam#786 ·
维护者通常 1 天内回复
-
prototype(verifier_token): map disallowed warning to contraindicated before composite precedence check可能已有人在做 @drk-ram 今天认领。 未关闭
难度 2/5 1-3 小时 新手友好度 88/100
agentrust-io/trace-spec#582 ·
维护者通常 1 天内回复
-
area:cli priority:low refactor size:small
难度 1/5 1 小时以内 新手友好度 88/100
RonaldHensbergen/composable-data-stack#906 ·
维护者通常 1 天内回复