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

mypy compliance #1721

Merged
merged 51 commits into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
5370ae8
first pass at mypy compliance
Zeitsperre Apr 18, 2024
1e17209
more mypy fixes
Zeitsperre Apr 18, 2024
314122d
Merge branch 'main' into mypy-pylint
Zeitsperre Apr 18, 2024
de3d11f
fix units typo
Zeitsperre Apr 19, 2024
41ddd8b
Merge branch 'main' into mypy-pylint
Zeitsperre Apr 19, 2024
3ae68da
fix binning error
Zeitsperre Apr 19, 2024
48bdd1d
mypy fixes
Zeitsperre Apr 19, 2024
15b41c4
mypy fixes
Zeitsperre Apr 19, 2024
2fb1f33
fix bins
Zeitsperre Apr 19, 2024
e077988
Merge branch 'main' into mypy-pylint
Zeitsperre Apr 19, 2024
db771ab
add pandas-stubs for typing inference, disable overrides for mypy-com…
Zeitsperre Apr 19, 2024
861d967
more mypy fixes
Zeitsperre Apr 19, 2024
e8d5593
find modifiable dim and return str
Zeitsperre Apr 19, 2024
288b7a8
use casting
Zeitsperre Apr 19, 2024
bfa5e2c
fix regression
Zeitsperre Apr 22, 2024
6d22124
mypy fixes
Zeitsperre Apr 22, 2024
0814182
mypy fixes
Zeitsperre Apr 22, 2024
b262e5a
mypy fixes
Zeitsperre Apr 22, 2024
1c86bfa
fix regressions
Zeitsperre Apr 22, 2024
ae57f32
mypy fixes
Zeitsperre Apr 22, 2024
689d3ae
mypy fixes
Zeitsperre Apr 22, 2024
5d0dac6
mypy fixes
Zeitsperre Apr 22, 2024
9c1a071
remove unneeded overrides
Zeitsperre Apr 22, 2024
11175ae
fix breakages
Zeitsperre Apr 24, 2024
3f462c9
breaking change: remove retro-compatibility of indicator __getitem__
Zeitsperre Apr 24, 2024
f3622cc
adapt test ensemble to new behaviour
Zeitsperre Apr 24, 2024
4b83974
enable testing for PEP8 rule N802
Zeitsperre Apr 24, 2024
408289f
update CHANGES.rst
Zeitsperre Apr 24, 2024
7d842d6
Merge branch 'main' into mypy-pylint
Zeitsperre Apr 24, 2024
fd024d7
Merge branch 'main' into mypy-pylint
Zeitsperre Apr 25, 2024
827f142
Rewrite importlib usage
aulemahal Apr 29, 2024
e4b9866
use recommended importlib pattern in utils too
aulemahal Apr 30, 2024
4cc6578
remove wrapped_partial wrapper, adjust tests, more consistent variabl…
Zeitsperre Apr 30, 2024
af98288
Merge branch 'main' into mypy-pylint
Zeitsperre Apr 30, 2024
292944b
merge upstream
aulemahal Apr 30, 2024
9f0f1da
fix naming errors
Zeitsperre Apr 30, 2024
683e83d
Merge remote-tracking branch 'origin/mypy-pylint' into mypy-pylint
Zeitsperre Apr 30, 2024
5856971
reintroduce Quantified in snowfall_frequency
aulemahal Apr 30, 2024
0793148
Merge branch 'mypy-pylint' of github.com:Ouranosinc/xclim into mypy-p…
aulemahal Apr 30, 2024
fda6516
docstring formatting, consistency
Zeitsperre Apr 30, 2024
494cf9f
remove variable shadowing
Zeitsperre Apr 30, 2024
9424a74
mypy consistency
Zeitsperre Apr 30, 2024
cf2b980
fix regressions
Zeitsperre Apr 30, 2024
dff2ead
make note about typing annotation of decorators, mypy fixes
Zeitsperre Apr 30, 2024
9e3d112
mark N802 violation as excluded for tests
Zeitsperre Apr 30, 2024
285e9ab
remove manual identification of variable types, use `out` less
Zeitsperre Apr 30, 2024
85c131a
update CHANGES.rst
Zeitsperre May 1, 2024
e0f54f2
Merge branch 'main' into mypy-pylint
Zeitsperre May 1, 2024
863bfcd
update CHANGES.rst
Zeitsperre May 1, 2024
1a64b25
fix doctest
Zeitsperre May 1, 2024
ce99186
remove wrapped_partial call
Zeitsperre May 1, 2024
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
5 changes: 4 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ Breaking changes
* The previously deprecated function ``xclim.ensembles.change_significance`` has been removed. (:pull:`1737`).
* Indicators ``snw_season_length`` and ``snd_season_length`` have been modified, see above.
* The `hargeaves85`/`hg85` method for the ``potential_evapotranspiration`` indicator and indice has been modified for precision and consistency with recent academic literature. (:issue:`1710`, :pull:`1723`).

* The `__getitem__` method of ``xclim.core.indicator.Parameter`` instances has been removed. Accessing members of ``Parameters`` now uniquely uses dot notation. (:pull:`1721`).
* The obsolete function wrapper for generating Indicators ``xclim.core.utils.wrapped_partial`` has been removed. (:pull:`1721`).

Bug fixes
^^^^^^^^^
Expand All @@ -39,6 +40,7 @@ Bug fixes
* Fixed "agreement fraction" in ``robustness_fractions`` to distinguish between negative change and no change. Added "negative" and "changed negative" fractions (:issue:`1690`, :pull:`1711`).
* ``make_criteria`` now skips columns with NaNs across all realizations. (:pull:`1713`).
* Fixed bug QuantileDeltaMapping adjustment not working for seasonal grouping (:issue:`1704`, :pull:`1716`).
* The codebase has been adjusted to address several (~300) `mypy`-related typing errors. (:issue:`1719`, :pull:`1721`).

Internal changes
^^^^^^^^^^^^^^^^
Expand All @@ -48,6 +50,7 @@ Internal changes
* Added the `tox-gh` dependency to the development installation recipe. This will soon be required for running the `tox` test ensemble on GitHub Workflows. (:pull:`1709`).
* Added the `vulture` static code analysis tool for finding dead code to the development dependency list and linters (makefile, tox and pre-commit hooks). (:pull:`1717`).
* Added error message when using `xclim.indices.stats.dist_method` with `nnlf` and included note in docstring. (:issue:`1683`, :pull:`1714`).
* PEP8 rule `N802` is now enabled in the `ruff` formatter. Function names should follow `Snake case <https://en.wikipedia.org/wiki/Snake_case>`_, with rare exceptions. (:pull:`1721`).

v0.48.2 (2024-02-26)
--------------------
Expand Down
1 change: 1 addition & 0 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ dependencies:
- nc-time-axis
- netCDF4 >=1.4
- notebook
- pandas-stubs
- platformdirs
- pooch
- pre-commit
Expand Down
22 changes: 10 additions & 12 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ dev = [
"nbqa",
"nbval",
"netCDF4 >=1.4",
"pandas-stubs>=2.2",
"platformdirs >=3.2",
"pre-commit >=2.9",
"pybtex",
Expand Down Expand Up @@ -153,7 +154,7 @@ values = [

[tool.codespell]
skip = 'xclim/data/*.json,docs/_build,docs/notebooks/xclim_training/*.ipynb,docs/references.bib,__pycache__,*.nc,*.png,*.gz,*.whl'
ignore-words-list = "absolue,astroid,bloc,bui,callendar,degreee,environnement,hanel,inferrable,lond,nam,nd,ressources,vas"
ignore-words-list = "absolue,astroid,bloc,bui,callendar,degreee,environnement,hanel,inferrable,lond,nam,nd,ressources,sie,vas"

[tool.coverage.run]
relative_files = true
Expand Down Expand Up @@ -207,24 +208,20 @@ python_version = 3.9
show_error_codes = true
warn_return_any = true
warn_unused_configs = true
plugins = ["numpy.typing.mypy_plugin"]

[[tool.mypy.overrides]]
module = [
"boltons.*",
"bottleneck.*",
"cftime.*",
"clisops.core.subset.*",
"dask.*",
"lmoments3.*",
"matplotlib.*",
"jsonpickle.*",
"numba.*",
"numpy.*",
"pandas.*",
"pint.*",
"pytest_socket.*",
"SBCK.*",
"scipy.*",
"sklearn.cluster.*",
"xarray.*",
"sklearn.*",
"statsmodels.*",
"yamale.*",
"yaml.*"
]
ignore_missing_imports = true
Expand Down Expand Up @@ -273,6 +270,7 @@ select = [
"D",
"E",
"F",
"N802",
"W"
]

Expand All @@ -299,7 +297,7 @@ max-complexity = 20

[tool.ruff.lint.per-file-ignores]
"docs/*.py" = ["D100", "D101", "D102", "D103"]
"tests/*.py" = ["D100", "D101", "D102", "D103"]
"tests/*.py" = ["D100", "D101", "D102", "D103", "N802"]
"xclim/**/__init__.py" = ["F401", "F403"]
"xclim/core/indicator.py" = ["D214", "D405", "D406", "D407", "D411"]
"xclim/core/locales.py" = ["E501", "W505"]
Expand Down
36 changes: 19 additions & 17 deletions tests/test_indicators.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ def test_opt_vars(tasmin_series, tasmax_series):
tx = tasmax_series(np.zeros(365))

multiOptVar(tasmin=tn, tasmax=tx)
assert multiOptVar.parameters["tasmin"]["kind"] == InputKind.OPTIONAL_VARIABLE
assert multiOptVar.parameters["tasmin"].kind == InputKind.OPTIONAL_VARIABLE


def test_registering():
Expand Down Expand Up @@ -265,8 +265,10 @@ def test_module():
"""Translations are keyed according to the module where the indicators are defined."""
assert atmos.tg_mean.__module__.split(".")[2] == "atmos"
# Virtual module also are stored under xclim.indicators
assert xclim.indicators.cf.fg.__module__ == "xclim.indicators.cf"
assert xclim.indicators.icclim.GD4.__module__ == "xclim.indicators.icclim"
assert xclim.indicators.cf.fg.__module__ == "xclim.indicators.cf" # noqa: F821
assert (
xclim.indicators.icclim.GD4.__module__ == "xclim.indicators.icclim"
) # noqa: F821


def test_temp_unit_conversion(tas_series):
Expand Down Expand Up @@ -377,7 +379,7 @@ def test_multiindicator(tas_series):
compute=uniindtemp_compute,
)
with pytest.raises(ValueError, match="Indicator minmaxtemp4 was wrongly defined"):
tmin, tmax = ind(tas, freq="YS")
_tmin, _tmax = ind(tas, freq="YS")


def test_missing(tas_series):
Expand Down Expand Up @@ -480,7 +482,7 @@ def test_all_parameters_understood(official_indicators):
for identifier, ind in official_indicators.items():
indinst = ind.get_instance()
for name, param in indinst.parameters.items():
if param["kind"] == InputKind.OTHER_PARAMETER:
if param.kind == InputKind.OTHER_PARAMETER:
problems.add((identifier, name))
# this one we are ok with.
if problems - {
Expand Down Expand Up @@ -587,15 +589,15 @@ def test_parsed_doc():
assert "tas" in xclim.atmos.liquid_precip_accumulation.parameters

params = xclim.atmos.drought_code.parameters
assert params["tas"]["description"] == "Noon temperature."
assert params["tas"]["units"] == "[temperature]"
assert params["tas"]["kind"] is InputKind.VARIABLE
assert params["tas"]["default"] == "tas"
assert params["snd"]["default"] is None
assert params["snd"]["kind"] is InputKind.OPTIONAL_VARIABLE
assert params["snd"]["units"] == "[length]"
assert params["season_method"]["kind"] is InputKind.STRING
assert params["season_method"]["choices"] == {"GFWED", None, "WF93", "LA08"}
assert params["tas"].description == "Noon temperature."
assert params["tas"].units == "[temperature]"
assert params["tas"].kind is InputKind.VARIABLE
assert params["tas"].default == "tas"
assert params["snd"].default is None
assert params["snd"].kind is InputKind.OPTIONAL_VARIABLE
assert params["snd"].units == "[length]"
assert params["season_method"].kind is InputKind.STRING
assert params["season_method"].choices == {"GFWED", None, "WF93", "LA08"}


def test_default_formatter():
Expand Down Expand Up @@ -655,14 +657,14 @@ def test_input_dataset(open_dataset):
ds = open_dataset("ERA5/daily_surface_cancities_1990-1993.nc")

# Use defaults
out = xclim.atmos.daily_temperature_range(freq="YS", ds=ds)
_ = xclim.atmos.daily_temperature_range(freq="YS", ds=ds)

# Use non-defaults (inverted on purpose)
with xclim.set_options(cf_compliance="log"):
out = xclim.atmos.daily_temperature_range("tasmax", "tasmin", freq="YS", ds=ds)
_ = xclim.atmos.daily_temperature_range("tasmax", "tasmin", freq="YS", ds=ds)

# Use a mix
out = xclim.atmos.daily_temperature_range(tasmax=ds.tasmax, freq="YS", ds=ds)
_ = xclim.atmos.daily_temperature_range(tasmax=ds.tasmax, freq="YS", ds=ds)

# Inexistent variable:
dsx = ds.drop_vars("tasmin")
Expand Down
26 changes: 0 additions & 26 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
# Test for utils
from __future__ import annotations

from inspect import signature

import numpy as np
import xarray as xr

Expand All @@ -12,7 +10,6 @@
ensure_chunk_size,
nan_calc_percentiles,
walk_map,
wrapped_partial,
)
from xclim.testing.helpers import test_timeseries as _test_timeseries

Expand All @@ -24,29 +21,6 @@ def test_walk_map():
assert o["b"]["c"] == 0


def test_wrapped_partial():
def func(a, b=1, c=1):
"""Docstring"""
return (a, b, c)

newf = wrapped_partial(func, b=2)
assert list(signature(newf).parameters.keys()) == ["a", "c"]
assert newf(1) == (1, 2, 1)

newf = wrapped_partial(func, suggested=dict(c=2), b=2)
assert list(signature(newf).parameters.keys()) == ["a", "c"]
assert newf(1) == (1, 2, 2)
assert newf.__doc__ == func.__doc__

def func(a, b=1, c=1, **kws): # pylint: disable=function-redefined
"""Docstring"""
return a, b, c

newf = wrapped_partial(func, suggested=dict(c=2), a=2, b=2)
assert list(signature(newf).parameters.keys()) == ["c", "kws"]
assert newf() == (2, 2, 2)


def test_ensure_chunk_size():
da = xr.DataArray(np.zeros((20, 21, 20)), dims=("x", "y", "z"))

Expand Down
25 changes: 10 additions & 15 deletions xclim/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@

from __future__ import annotations

try:
from importlib.resources import files as _files
except ImportError:
from importlib_resources import files as _files
import importlib.resources as _resources

from xclim import indices
from xclim.core import units # noqa
Expand All @@ -19,15 +16,13 @@
__version__ = "0.48.3-dev.15"


_module_data = _files("xclim.data")
with _resources.as_file(_resources.files("xclim.data")) as _module_data:
# Load official locales
for filename in _module_data.glob("??.json"):
# Only select <locale>.json and not <module>.<locale>.json
load_locale(filename, filename.stem)

# Load official locales
for filename in _module_data.glob("??.json"):
# Only select <locale>.json and not <module>.<locale>.json
load_locale(filename, filename.stem)


# Virtual modules creation:
build_indicator_module_from_yaml(_module_data / "icclim", mode="raise")
build_indicator_module_from_yaml(_module_data / "anuclim", mode="raise")
build_indicator_module_from_yaml(_module_data / "cf", mode="raise")
# Virtual modules creation:
build_indicator_module_from_yaml(_module_data / "icclim", mode="raise")
build_indicator_module_from_yaml(_module_data / "anuclim", mode="raise")
build_indicator_module_from_yaml(_module_data / "cf", mode="raise")
14 changes: 7 additions & 7 deletions xclim/analog.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,12 @@ def spatial_analogs(
# Create the target DataArray
# drop any (sub-)index along "dist_dim" that could conflict with target, and rename it.
# The drop is the simplest solution that is compatible with both xarray <=2022.3.0 and >2022.3.1
candidates = candidates.to_array("_indices", "candidates").rename(
candidate_array = candidates.to_array("_indices", "candidates").rename(
{dist_dim: "_dist_dim"}
)
if isinstance(candidates.indexes["_dist_dim"], pd.MultiIndex):
candidates = candidates.drop_vars(
["_dist_dim"] + candidates.indexes["_dist_dim"].names,
if isinstance(candidate_array.indexes["_dist_dim"], pd.MultiIndex):
candidate_array = candidate_array.drop_vars(
["_dist_dim"] + candidate_array.indexes["_dist_dim"].names,
# in xarray <= 2022.3.0 the sub-indexes are not listed as separate coords,
# instead, they are dropped when the multiindex is dropped.
errors="ignore",
Expand All @@ -78,16 +78,16 @@ def spatial_analogs(
f"Method `{method}` is not implemented. Available methods are: {','.join(metrics.keys())}."
) from e

if candidates.chunks is not None:
candidates = candidates.chunk({"_indices": -1})
if candidate_array.chunks is not None:
candidate_array = candidate_array.chunk({"_indices": -1})
if target_array.chunks is not None:
target_array = target_array.chunk({"_indices": -1})

# Compute dissimilarity
diss = xr.apply_ufunc(
metric_func,
target_array,
candidates,
candidate_array,
input_core_dims=[(dist_dim, "_indices"), ("_dist_dim", "_indices")],
output_core_dims=[()],
vectorize=True,
Expand Down
10 changes: 6 additions & 4 deletions xclim/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@
from xclim.testing.helpers import TESTDATA_BRANCH, populate_testing_data
from xclim.testing.utils import _default_cache_dir, publish_release_notes, show_versions

distributed = False
try:
from dask.distributed import Client, progress # pylint: disable=ungrouped-imports
from dask.distributed import Client, progress

distributed = True
except ImportError:
# Distributed is not a dependency of xclim
Client = None
progress = None
pass


def _get_indicator(indicator_name):
Expand Down Expand Up @@ -432,7 +434,7 @@ def cli(ctx, **kwargs):
kwargs["input"] = kwargs["input"][0]

if kwargs["dask_nthreads"] is not None:
if Client is None:
if not distributed:
raise click.BadOptionUsage(
"dask_nthreads",
"Dask's distributed scheduler is not installed, only the "
Expand Down
Loading
Loading