Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

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

Đang mở
#994 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

@FabianHofmann đang làm issue này rồi.

Từ ngày 28/9/2026.

  • #995 của @FabianHofmann — đang mở

Đánh giá

Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức phù hợp với người mới
58/100
Loại issue
Lỗi
Độ rõ ràng
Khá rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
python
Lĩnh vực
backend, testing-qa

Hướng nghiên cứu

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.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

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.

Ngôn ngữ chính
Python
Star
259
Fork
89
Merge trung bình
1 ngày 18 giờ
Pull request đã merge (30 ngày)
26

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của PyPSA/linopy

Tất cả issue của PyPSA/linopy

Issue tương tự

Thêm issue về Python

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.