Skip to content

Commit

Permalink
ci: move mypy to gh raction and add more rules (#344)
Browse files Browse the repository at this point in the history
* read mypy config correctly

* move mypy to gh action and add more rules

* fix
  • Loading branch information
lkstrp authored Aug 28, 2024
1 parent a800ac0 commit 16f8bf1
Show file tree
Hide file tree
Showing 13 changed files with 82 additions and 46 deletions.
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 @@ def constraints_to_file(
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 @@ def bounds_to_file(
"""
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 @@ def binaries_to_file(
"""
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 @@ def integers_to_file(
"""
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 @@ def to_mosek(m: Model, task: Any | None = None) -> Any:
task : MOSEK Task object
"""

import mosek # type: ignore
import mosek

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 @@ def run_mosek(
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 @@ def run_mindopt(
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 @@ def run_mindopt(
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:
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 @@ def get_solver_solution() -> Solution:
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 @@ def get_solver_solution() -> Solution:
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

0 comments on commit 16f8bf1

Please sign in to comment.