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

Replace black, isort, flake8 with ruff #446

Merged
merged 64 commits into from
Jul 31, 2024
Merged

Replace black, isort, flake8 with ruff #446

merged 64 commits into from
Jul 31, 2024

Conversation

sverhoeven
Copy link
Member

@sverhoeven sverhoeven commented Jul 24, 2024

Refs #441

TODO

  • default rules
  • more rules
    • pydocstyle
    • isort
    • etc.
  • Format everything

sverhoeven and others added 30 commits July 23, 2024 13:30
TODO
* [ ] valid pyproject.toml for `pip install -e .[dev]`
Currently pip does not understand pyproject.toml file
Skipped flake8 config as this will be replaced by ruff
Unable to exclude during lock generation as `--no-dev-dependencies` moves dev deps to main cat.
Because it is imported in this package
This reverts commit 0bc3f89.

Was test script not part of repo
Base automatically changed from nobump to main July 29, 2024 08:36
@sverhoeven sverhoeven marked this pull request as ready for review July 29, 2024 12:33
Copy link
Member

@BSchilperoort BSchilperoort left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work, these changes do increase the quality of the code base!

I only encountered some minor issues, after those are fixed feel free to merge 👍

with http.request(
"GET", SHAPEFILE_URL, preload_content=False
) as r, zip_path.open("wb") as out_file:
with (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using parentheses with a multi-line context manager has finally been available since Python 3.10. Makes writing tests with mocks easier to read too. Nice to see that the ruff formatter/linter caught this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed

Copy link
Member

@BSchilperoort BSchilperoort Jul 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wish we could remove these __init__ files (not needed anymore according to the PEPs), but Sphinx still needs it...

src/ewatercycle/base/forcing.py Show resolved Hide resolved
src/ewatercycle/base/model.py Outdated Show resolved Hide resolved
src/ewatercycle/base/model.py Show resolved Hide resolved
src/ewatercycle/container.py Outdated Show resolved Hide resolved
@@ -60,6 +66,8 @@ class ContainerImage(str):
eWatercycle containers typically don't have these issues.
"""

__slots__ = ()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you decide to add __slots__ here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forcing_files = {}
for output_file in output_files:
var_name = output_file.path.stem
if isinstance(output_file, DataFile):
# Datafile means ends with .nc
# Use first variable name from inside file as key
dataset = output_file.load_xarray()
var_name = list(dataset.data_vars.keys())[0]
var_name = next(iter(dataset.data_vars.keys()))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you not noqa this line, but you did on line 157.

(first_diagnostic_output = list(recipe_output.values())[0] # noqa: RUF015)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the ruff fix caused test to fail, so I reverted to orignal
`______________________________________________________________________ test_parse_recipe_output_with_no_diagnostic _______________________________________________________________________

    def test_parse_recipe_output_with_no_diagnostic():
        recipe_output = RecipeOutput({}, info=RecipeInfo({"diagnostics": {}}, "script"))
        with pytest.raises(IndexError):
>           _parse_recipe_output(recipe_output)

tests/src/esmvaltool/test_run.py:53: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

recipe_output = 

    def _parse_recipe_output(recipe_output: RecipeOutput) -> dict[str, str]:
        """Parse ESMValTool recipe output into a dictionary.
    
        This method assumes:
    
        * Recipe had at least one diagnostic
        * Diagnostic produced at least one file
        * All files are in the same directory
        * The first variable name in a NetCDF file is the primary one
    
        Returns:
            Dictionary with forcing data variables as keys and file names as values
            and a key called directory with value the parent directory of the file names.
        """
>       first_diagnostic_output = next(iter(recipe_output.values()))
E       StopIteration

src/ewatercycle/esmvaltool/run.py:157: StopIteration

The above exception was the direct cause of the following exception:

cls = <class '_pytest.runner.CallInfo'>, func = <function call_and_report.<locals>.<lambda> at 0x743f92faab60>, when = 'call'
reraise = (<class '_pytest.outcomes.Exit'>, <class 'KeyboardInterrupt'>)

    @classmethod
    def from_call(
        cls,
        func: Callable[[], TResult],
        when: Literal["collect", "setup", "call", "teardown"],
        reraise: type[BaseException] | tuple[type[BaseException], ...] | None = None,
    ) -> CallInfo[TResult]:
        """Call func, wrapping the result in a CallInfo.
    
        :param func:
            The function to call. Called without arguments.
        :type func: Callable[[], _pytest.runner.TResult]
        :param when:
            The phase in which the function is called.
        :param reraise:
            Exception or exceptions that shall propagate if raised by the
            function, instead of being wrapped in the CallInfo.
        """
        excinfo = None
        start = timing.time()
        precise_start = timing.perf_counter()
        try:
>           result: TResult | None = func()

../../../miniforge3/envs/ewatercycle/lib/python3.12/site-packages/_pytest/runner.py:341: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../miniforge3/envs/ewatercycle/lib/python3.12/site-packages/_pytest/runner.py:242: in <lambda>
    lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
../../../miniforge3/envs/ewatercycle/lib/python3.12/site-packages/pluggy/_hooks.py:513: in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
../../../miniforge3/envs/ewatercycle/lib/python3.12/site-packages/pluggy/_manager.py:120: in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
../../../miniforge3/envs/ewatercycle/lib/python3.12/site-packages/_pytest/threadexception.py:92: in pytest_runtest_call
    yield from thread_exception_runtest_hook()
../../../miniforge3/envs/ewatercycle/lib/python3.12/site-packages/_pytest/threadexception.py:68: in thread_exception_runtest_hook
    yield
../../../miniforge3/envs/ewatercycle/lib/python3.12/site-packages/_pytest/unraisableexception.py:95: in pytest_runtest_call
    yield from unraisable_exception_runtest_hook()
../../../miniforge3/envs/ewatercycle/lib/python3.12/site-packages/_pytest/unraisableexception.py:70: in unraisable_exception_runtest_hook
    yield
../../../miniforge3/envs/ewatercycle/lib/python3.12/site-packages/_pytest/logging.py:848: in pytest_runtest_call
    yield from self._runtest_for(item, "call")
../../../miniforge3/envs/ewatercycle/lib/python3.12/site-packages/_pytest/logging.py:831: in _runtest_for
    yield
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <CaptureManager _method='fd' _global_capturing=<MultiCapture out=<FDCapture 1 oldfd=6 _state='suspended' tmpfile=<_io....xtIOWrapper name='/dev/null' mode='r' encoding='utf-8'>> _state='suspended' _in_suspended=False> _capture_fixture=None>
item = <Function test_parse_recipe_output_with_no_diagnostic>

    @hookimpl(wrapper=True)
    def pytest_runtest_call(self, item: Item) -> Generator[None, None, None]:
        with self.item_capture("call", item):
>           return (yield)
E           RuntimeError: generator raised StopIteration

../../../miniforge3/envs/ewatercycle/lib/python3.12/site-packages/_pytest/capture.py:879: RuntimeError`

@@ -56,7 +55,8 @@ def get_caravan_data(
lon float64 8B ...
area float64 8B ...
streamflow (time) float32 12B ...
Attributes:

Attributes:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Attributes:
Attributes:

This is the output of xarray, but is misidentified by the formatter? It should stay indented.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was probably result of ruff check --fix --unsafe-fixes

To fix I had to noqa E501, D410, D411 and D214

src/ewatercycle/observation/grdc.py Outdated Show resolved Hide resolved
Copy link
Member Author

@sverhoeven sverhoeven left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for reviewing.

After your review I added back the precommit to ci.

with http.request(
"GET", SHAPEFILE_URL, preload_content=False
) as r, zip_path.open("wb") as out_file:
with (
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed

src/ewatercycle/base/forcing.py Show resolved Hide resolved
src/ewatercycle/base/model.py Outdated Show resolved Hide resolved
src/ewatercycle/container.py Outdated Show resolved Hide resolved
@@ -60,6 +66,8 @@ class ContainerImage(str):
eWatercycle containers typically don't have these issues.
"""

__slots__ = ()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forcing_files = {}
for output_file in output_files:
var_name = output_file.path.stem
if isinstance(output_file, DataFile):
# Datafile means ends with .nc
# Use first variable name from inside file as key
dataset = output_file.load_xarray()
var_name = list(dataset.data_vars.keys())[0]
var_name = next(iter(dataset.data_vars.keys()))
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the ruff fix caused test to fail, so I reverted to orignal
`______________________________________________________________________ test_parse_recipe_output_with_no_diagnostic _______________________________________________________________________

    def test_parse_recipe_output_with_no_diagnostic():
        recipe_output = RecipeOutput({}, info=RecipeInfo({"diagnostics": {}}, "script"))
        with pytest.raises(IndexError):
>           _parse_recipe_output(recipe_output)

tests/src/esmvaltool/test_run.py:53: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

recipe_output = 

    def _parse_recipe_output(recipe_output: RecipeOutput) -> dict[str, str]:
        """Parse ESMValTool recipe output into a dictionary.
    
        This method assumes:
    
        * Recipe had at least one diagnostic
        * Diagnostic produced at least one file
        * All files are in the same directory
        * The first variable name in a NetCDF file is the primary one
    
        Returns:
            Dictionary with forcing data variables as keys and file names as values
            and a key called directory with value the parent directory of the file names.
        """
>       first_diagnostic_output = next(iter(recipe_output.values()))
E       StopIteration

src/ewatercycle/esmvaltool/run.py:157: StopIteration

The above exception was the direct cause of the following exception:

cls = <class '_pytest.runner.CallInfo'>, func = <function call_and_report.<locals>.<lambda> at 0x743f92faab60>, when = 'call'
reraise = (<class '_pytest.outcomes.Exit'>, <class 'KeyboardInterrupt'>)

    @classmethod
    def from_call(
        cls,
        func: Callable[[], TResult],
        when: Literal["collect", "setup", "call", "teardown"],
        reraise: type[BaseException] | tuple[type[BaseException], ...] | None = None,
    ) -> CallInfo[TResult]:
        """Call func, wrapping the result in a CallInfo.
    
        :param func:
            The function to call. Called without arguments.
        :type func: Callable[[], _pytest.runner.TResult]
        :param when:
            The phase in which the function is called.
        :param reraise:
            Exception or exceptions that shall propagate if raised by the
            function, instead of being wrapped in the CallInfo.
        """
        excinfo = None
        start = timing.time()
        precise_start = timing.perf_counter()
        try:
>           result: TResult | None = func()

../../../miniforge3/envs/ewatercycle/lib/python3.12/site-packages/_pytest/runner.py:341: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../miniforge3/envs/ewatercycle/lib/python3.12/site-packages/_pytest/runner.py:242: in <lambda>
    lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
../../../miniforge3/envs/ewatercycle/lib/python3.12/site-packages/pluggy/_hooks.py:513: in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
../../../miniforge3/envs/ewatercycle/lib/python3.12/site-packages/pluggy/_manager.py:120: in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
../../../miniforge3/envs/ewatercycle/lib/python3.12/site-packages/_pytest/threadexception.py:92: in pytest_runtest_call
    yield from thread_exception_runtest_hook()
../../../miniforge3/envs/ewatercycle/lib/python3.12/site-packages/_pytest/threadexception.py:68: in thread_exception_runtest_hook
    yield
../../../miniforge3/envs/ewatercycle/lib/python3.12/site-packages/_pytest/unraisableexception.py:95: in pytest_runtest_call
    yield from unraisable_exception_runtest_hook()
../../../miniforge3/envs/ewatercycle/lib/python3.12/site-packages/_pytest/unraisableexception.py:70: in unraisable_exception_runtest_hook
    yield
../../../miniforge3/envs/ewatercycle/lib/python3.12/site-packages/_pytest/logging.py:848: in pytest_runtest_call
    yield from self._runtest_for(item, "call")
../../../miniforge3/envs/ewatercycle/lib/python3.12/site-packages/_pytest/logging.py:831: in _runtest_for
    yield
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <CaptureManager _method='fd' _global_capturing=<MultiCapture out=<FDCapture 1 oldfd=6 _state='suspended' tmpfile=<_io....xtIOWrapper name='/dev/null' mode='r' encoding='utf-8'>> _state='suspended' _in_suspended=False> _capture_fixture=None>
item = <Function test_parse_recipe_output_with_no_diagnostic>

    @hookimpl(wrapper=True)
    def pytest_runtest_call(self, item: Item) -> Generator[None, None, None]:
        with self.item_capture("call", item):
>           return (yield)
E           RuntimeError: generator raised StopIteration

../../../miniforge3/envs/ewatercycle/lib/python3.12/site-packages/_pytest/capture.py:879: RuntimeError`

@@ -56,7 +55,8 @@ def get_caravan_data(
lon float64 8B ...
area float64 8B ...
streamflow (time) float32 12B ...
Attributes:

Attributes:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was probably result of ruff check --fix --unsafe-fixes

To fix I had to noqa E501, D410, D411 and D214

Copy link

sonarcloud bot commented Jul 31, 2024

Quality Gate Failed Quality Gate failed

Failed conditions
69.3% Coverage on New Code (required ≥ 80%)

See analysis details on SonarCloud

@sverhoeven sverhoeven merged commit ed52fbe into main Jul 31, 2024
3 of 4 checks passed
@sverhoeven sverhoeven deleted the ruff branch July 31, 2024 08:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants