Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

Xpress missing _license_probe: licensed_solvers includes xpress with an out-of-date licence, breaking local tests

未关闭
#994 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

@FabianHofmann 已经在做这个了。

开始于 2026年9月28日。

  • #995 来自 @FabianHofmann —— 未关闭
  • #1028 来自 @Blizzeq —— 已关闭,未合并

评估

难度
4/5
预计耗时
3-5 天
新手友好度
58/100
Issue 类型
缺陷
描述清晰度
基本清楚
活跃度
活跃
技术栈
python

调研方向

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 内容生成。

描述

bug solver interface

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-solver option: 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

环境准备

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

PyPSA/linopy 的其他 Issue

查看 PyPSA/linopy 的全部 Issue

相似的 Issue

更多 Python Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。