Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ci: move mypy to gh raction and add more rules #344

Merged
merged 3 commits into from
Aug 28, 2024
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
30 changes: 30 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,33 @@ jobs:
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}

check-types:
name: Check types
needs: [build]
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Needed for setuptools_scm

- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: 3.12

- name: Download package
uses: actions/download-artifact@v4
with:
name: Packages
path: dist

- name: Install package and dependencies
run: |
python -m pip install uv
uv pip install --system "$(ls dist/*.whl)[dev]"

- name: Run type checker (mypy)
run: |
mypy .
6 changes: 0 additions & 6 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,3 @@ repos:
hooks:
- id: jupyter-notebook-cleanup
exclude: examples/solve-on-remote.ipynb
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.11.1
hooks:
- id: mypy
files: ^(linopy|test)/
additional_dependencies: [numpy, pandas, xarray, types-paramiko]
4 changes: 2 additions & 2 deletions linopy/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ def as_dataarray(
)

arr = fill_missing_coords(arr)
return arr # type: ignore
return arr


# TODO: rename to to_pandas_dataframe
Expand Down Expand Up @@ -496,7 +496,7 @@ def best_int(max_value: int) -> type:
Get the minimal int dtype for storing values <= max_value.
"""
for t in (np.int8, np.int16, np.int32, np.int64):
if max_value <= np.iinfo(t).max: # type: ignore
if max_value <= np.iinfo(t).max:
return t
raise ValueError(f"Value {max_value} is too large for int64.")

Expand Down
4 changes: 2 additions & 2 deletions linopy/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ def values(self) -> Union[DataArray, None]:
"The `.values` attribute is deprecated. Use `.labels.values` instead.",
DeprecationWarning,
)
return self.labels.values if self.is_assigned else None # type: ignore
return self.labels.values if self.is_assigned else None

@property
def nterm(self):
Expand Down Expand Up @@ -767,7 +767,7 @@ def labels(self) -> Dataset:
"""
return save_join(
*[v.labels.rename(k) for k, v in self.items()],
integer_dtype=True, # type: ignore
integer_dtype=True,
)

@property
Expand Down
10 changes: 5 additions & 5 deletions linopy/expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def groupby(self) -> xarray.core.groupby.DatasetGroupBy:
"Grouping by pandas objects is only supported in sum function."
)

return self.data.groupby(group=self.group, **self.kwargs) # type: ignore
return self.data.groupby(group=self.group, **self.kwargs)

def map(
self, func: Callable, shortcut: bool = False, args: tuple[()] = (), **kwargs
Expand Down Expand Up @@ -226,7 +226,7 @@ def sum(self, use_fallback: bool = False, **kwargs) -> LinearExpression:
if group_name == group_dim:
raise ValueError("Group name cannot be the same as group dimension")

arrays = [group, group.groupby(group).cumcount()] # type: ignore
arrays = [group, group.groupby(group).cumcount()]
idx = pd.MultiIndex.from_arrays(
arrays, names=[group_name, GROUPED_TERM_DIM]
)
Expand All @@ -239,7 +239,7 @@ def sum(self, use_fallback: bool = False, **kwargs) -> LinearExpression:
index = ds.indexes["group"].map({v: k for k, v in int_map.items()})
index.names = [str(col) for col in orig_group.columns]
index.name = group_name
coords = Coordinates.from_pandas_multiindex(index, group_name) # type: ignore
coords = Coordinates.from_pandas_multiindex(index, group_name)
ds = xr.Dataset(ds.assign_coords(coords))

return LinearExpression(ds, self.model)
Expand Down Expand Up @@ -549,7 +549,7 @@ def __pow__(self, other: int) -> QuadraticExpression:
raise ValueError("Power must be 2.")
return self * self # type: ignore

def __rmul__( # type: ignore
def __rmul__(
self, other: float | int | DataArray
) -> LinearExpression | QuadraticExpression:
"""
Expand Down Expand Up @@ -590,7 +590,7 @@ def __truediv__(
def __le__(self, rhs: int) -> Constraint:
return self.to_constraint(LESS_EQUAL, rhs)

def __ge__(self, rhs: int | ndarray | DataArray) -> Constraint: # type: ignore
def __ge__(self, rhs: int | ndarray | DataArray) -> Constraint:
return self.to_constraint(GREATER_EQUAL, rhs)

def __eq__(self, rhs: LinearExpression | float | Variable | int) -> Constraint: # type: ignore
Expand Down
11 changes: 6 additions & 5 deletions linopy/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import logging
import shutil
import time
from collections.abc import Iterable
from io import TextIOWrapper
from pathlib import Path
from tempfile import TemporaryDirectory
Expand Down Expand Up @@ -132,7 +133,7 @@
return

f.write("\n\ns.t.\n\n")
names = m.constraints
names: Iterable = m.constraints
if log:
names = tqdm(
list(names),
Expand Down Expand Up @@ -194,7 +195,7 @@
"""
Write out variables of a model to a lp file.
"""
names = list(m.variables.continuous) + list(m.variables.integers)
names: Iterable = list(m.variables.continuous) + list(m.variables.integers)
if not len(list(names)):
return

Expand Down Expand Up @@ -231,7 +232,7 @@
"""
Write out binaries of a model to a lp file.
"""
names = m.variables.binaries
names: Iterable = m.variables.binaries
if not len(list(names)):
return

Expand Down Expand Up @@ -265,7 +266,7 @@
"""
Write out integers of a model to a lp file.
"""
names = m.variables.integers
names: Iterable = m.variables.integers
if not len(list(names)):
return

Expand Down Expand Up @@ -592,7 +593,7 @@
task : MOSEK Task object
"""

import mosek # type: ignore
import mosek

Check warning on line 596 in linopy/io.py

View check run for this annotation

Codecov / codecov/patch

linopy/io.py#L596

Added line #L596 was not covered by tests

if task is None:
task = mosek.Task()
Expand Down
6 changes: 3 additions & 3 deletions linopy/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,7 @@ def add_constraints(
if isinstance(lhs, LinearExpression):
if sign is None or rhs is None:
raise ValueError(msg_sign_rhs_not_none)
data = lhs.to_constraint(sign, rhs).data # type: ignore
data = lhs.to_constraint(sign, rhs).data
elif isinstance(lhs, (list, tuple)):
if sign is None or rhs is None:
raise ValueError(msg_sign_rhs_none)
Expand Down Expand Up @@ -803,7 +803,7 @@ def calculate_block_maps(self) -> None:

dtype = self.blocks.dtype
self.variables.set_blocks(self.blocks)
block_map = self.variables.get_blockmap(dtype) # type: ignore
block_map = self.variables.get_blockmap(dtype)
self.constraints.set_blocks(block_map)

blocks = replace_by_map(self.objective.vars, block_map)
Expand Down Expand Up @@ -1046,7 +1046,7 @@ def solve(
if solver_name is None:
solver_name = available_solvers[0]

logger.info(f" Solve problem using {solver_name.title()} solver") # type: ignore
logger.info(f" Solve problem using {solver_name.title()} solver")
assert solver_name in available_solvers, f"Solver {solver_name} not installed"

# reset result
Expand Down
28 changes: 14 additions & 14 deletions linopy/solvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,15 @@

available_solvers.append("scip")
with contextlib.suppress(ImportError):
import cplex # type: ignore
import cplex

available_solvers.append("cplex")
with contextlib.suppress(ImportError):
import xpress # type: ignore
import xpress

available_solvers.append("xpress")
with contextlib.suppress(ImportError):
import mosek # type: ignore
import mosek

with contextlib.suppress(mosek.Error):
with mosek.Env() as m:
Expand All @@ -100,7 +100,7 @@

available_solvers.append("mindopt")
with contextlib.suppress(ImportError):
import coptpy # type: ignore
import coptpy

with contextlib.suppress(coptpy.CoptError):
coptpy.Envr()
Expand Down Expand Up @@ -962,7 +962,7 @@
if env is None:
env = stack.enter_context(mosek.Env())

with env.Task() as m: # type: ignore
with env.Task() as m:
if io_api == "direct":
model.to_mosek(m)
elif io_api is None or io_api in FILE_IO_APIS:
Expand Down Expand Up @@ -1241,7 +1241,7 @@
warmstart_fn: Path | None = None,
basis_fn: Path | None = None,
keep_files: bool = False,
env: mindoptpy.Env | None = None, # type: ignore
env: mindoptpy.Env | None = None,
**solver_options,
) -> Result:
"""
Expand Down Expand Up @@ -1274,32 +1274,32 @@
problem_fn = model.to_file(problem_fn, io_api)

if env is None:
env = mindoptpy.Env(path_to_string(log_fn) if log_fn else "") # type: ignore
env.start() # type: ignore
env = mindoptpy.Env(path_to_string(log_fn) if log_fn else "")
env.start()

m = mindoptpy.read(path_to_string(problem_fn), env) # type: ignore
m = mindoptpy.read(path_to_string(problem_fn), env)

for k, v in solver_options.items():
m.setParam(k, v)

if warmstart_fn:
try:
m.read(path_to_string(warmstart_fn))
except mindoptpy.MindoptError as err: # type: ignore
except mindoptpy.MindoptError as err:
logger.info("Model basis could not be read. Raised error: %s", err)

m.optimize()

if basis_fn:
try:
m.write(path_to_string(basis_fn))
except mindoptpy.MindoptError as err: # type: ignore
except mindoptpy.MindoptError as err:

Check warning on line 1296 in linopy/solvers.py

View check run for this annotation

Codecov / codecov/patch

linopy/solvers.py#L1296

Added line #L1296 was not covered by tests
logger.info("No model basis stored. Raised error: %s", err)

if solution_fn:
try:
m.write(path_to_string(solution_fn))
except mindoptpy.MindoptError as err: # type: ignore
except mindoptpy.MindoptError as err:
logger.info("No model solution stored. Raised error: %s", err)

condition = m.status
Expand All @@ -1316,7 +1316,7 @@
try:
dual = pd.Series({c.constrname: c.DualSoln for c in m.getConstrs()})
dual = set_int_index(dual)
except (mindoptpy.MindoptError, AttributeError): # type: ignore
except (mindoptpy.MindoptError, AttributeError):
logger.warning("Dual values of MILP couldn't be parsed")
dual = pd.Series(dtype=float)

Expand All @@ -1325,7 +1325,7 @@
solution = safe_get_solution(status, get_solver_solution)
maybe_adjust_objective_sign(solution, model.objective.sense, io_api)

env.dispose() # type: ignore
env.dispose()

return Result(status, solution, m)

Expand Down
4 changes: 1 addition & 3 deletions linopy/variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,9 +396,7 @@ def __pow__(self, other: int) -> QuadraticExpression:
expr = self.to_linexpr()
return expr._multiply_by_linear_expression(expr)

def __rmul__( # type: ignore
self, other: float | DataArray | int | ndarray
) -> LinearExpression:
def __rmul__(self, other: float | DataArray | int | ndarray) -> LinearExpression:
"""
Right-multiply variables with a coefficient.
"""
Expand Down
13 changes: 13 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,23 @@ version_scheme = "no-guess-dev"
branch = true
source = ["linopy"]
omit = ["test/*"]
[tool.coverage.report]
exclude_also = [
"if TYPE_CHECKING:",
]

[tool.mypy]
exclude = ['dev/*', 'examples/*', 'benchmark/*', 'doc/*']
ignore_missing_imports = true
no_implicit_optional = true
warn_unused_ignores = true
show_error_code_links = true
# disallow_any_generics = true
# warn_return_any = true

# [[tool.mypy.overrides]]
# module = "linopy.*"
# disallow_untyped_defs = true

[tool.ruff]
extend-include = ['*.ipynb']
Expand Down
2 changes: 1 addition & 1 deletion test/test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ def test_assign_multiindex_safe():
assert result["humidity"].equals(data)

# Case 2: Assigning a Dataset
result = assign_multiindex_safe(ds, **xr.Dataset({"humidity": data})) # type: ignore
result = assign_multiindex_safe(ds, **xr.Dataset({"humidity": data}))
assert "humidity" in result
assert "value" in result
assert result["humidity"].equals(data)
Expand Down
8 changes: 4 additions & 4 deletions test/test_repr.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@

u = m.add_variables(0, upper, name="u")
v = m.add_variables(lower, upper, name="v")
x = m.add_variables(lower, 10, coords=[lower.index], name="x") # type: ignore
x = m.add_variables(lower, 10, coords=[lower.index], name="x")
y = m.add_variables(0, 10, name="y")
z = m.add_variables(name="z", binary=True)
a = m.add_variables(coords=[lower.index], name="a", binary=True) # type: ignore
b = m.add_variables(coords=[lower.index], name="b", integer=True) # type: ignore
a = m.add_variables(coords=[lower.index], name="a", binary=True)
b = m.add_variables(coords=[lower.index], name="b", integer=True)
c_mask = xr.DataArray(False, coords=upper.axes)
c_mask[:, 5:] = True
c = m.add_variables(lower, upper, name="c", mask=c_mask)
d = m.add_variables(0, 10, coords=[types], name="d") # type: ignore
d = m.add_variables(0, 10, coords=[types], name="d")

# new behavior in v0.2, variable with dimension name and other
# coordinates are added without a warning
Expand Down
2 changes: 1 addition & 1 deletion test/test_variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ def test_variable_where(x):
assert x.labels[9] == x.at[0].label

with pytest.raises(ValueError):
x.where([True] * 4 + [False] * 6, 0) # type: ignore
x.where([True] * 4 + [False] * 6, 0)


def test_variable_shift(x):
Expand Down