-
Notifications
You must be signed in to change notification settings - Fork 5
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
Conversation
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
Used unsafe fixes to fix
Adhering to FBT rules require lots of refactoring and changes to public api
That we are compliant with
Comment out rules that need lot of refactoring
There was a problem hiding this 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 ( |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed
src/ewatercycle/base/__init__.py
Outdated
There was a problem hiding this comment.
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...
@@ -60,6 +66,8 @@ class ContainerImage(str): | |||
eWatercycle containers typically don't have these issues. | |||
""" | |||
|
|||
__slots__ = () |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because https://docs.astral.sh/ruff/rules/no-slots-in-str-subclass/ told me to.
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())) |
There was a problem hiding this comment.
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
)
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Attributes: | |
Attributes: |
This is the output of xarray, but is misidentified by the formatter? It should stay indented.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatives like EWaterCycleModel or BaseModel are worse.
Not a section in the docstring
There was a problem hiding this 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 ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed
@@ -60,6 +66,8 @@ class ContainerImage(str): | |||
eWatercycle containers typically don't have these issues. | |||
""" | |||
|
|||
__slots__ = () |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because https://docs.astral.sh/ruff/rules/no-slots-in-str-subclass/ told me to.
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())) |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quality Gate failedFailed conditions |
Refs #441
TODO