diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8e5b1995..401048c5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,49 +12,24 @@ repos: - id: trailing-whitespace exclude: miscellaneous/structures + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.2.2 + hooks: + - id: ruff-format + exclude: ^docs/.* + - id: ruff + args: [--fix, --exit-non-zero-on-fix, --show-fixes] + - repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt rev: 0.2.3 hooks: - id: yamlfmt - - repo: https://github.com/psf/black - rev: 23.12.1 - hooks: - - id: black - language_version: python3 # Should be a command that runs python3.6+ - - - repo: https://github.com/PyCQA/flake8 - rev: 6.1.0 - hooks: - - id: flake8 - args: [--count, --show-source, --statistics] - additional_dependencies: - - flake8-bugbear==23.2.13 - - flake8-builtins==2.1.0 - - flake8-comprehensions==3.10.1 - - flake8-debugger==4.1.2 - - flake8-logging-format==0.9.0 - - pep8-naming==0.13.3 - - pyflakes==3.1.0 - - tryceratops==1.1.0 - - - repo: https://github.com/pycqa/isort - rev: 5.13.2 - hooks: - - id: isort - args: [--profile, black, --filter-files] - - repo: https://github.com/sirosen/check-jsonschema rev: 0.27.3 hooks: - id: check-github-workflows - - repo: https://github.com/asottile/pyupgrade - rev: v3.15.0 - hooks: - - id: pyupgrade - args: [--py39-plus] - - repo: https://github.com/kynan/nbstripout rev: 0.6.1 hooks: diff --git a/aiidalab_widgets_base/bug_report.py b/aiidalab_widgets_base/bug_report.py index 5a494481..9ffe4a67 100644 --- a/aiidalab_widgets_base/bug_report.py +++ b/aiidalab_widgets_base/bug_report.py @@ -28,6 +28,7 @@ def find_installed_packages(python_bin: str | None = None) -> dict[str, str]: [python_bin, "-m", "pip", "list", "--format=json"], encoding="utf-8", capture_output=True, + check=True, ).stdout return {package["name"]: package["version"] for package in json.loads(output)} @@ -160,7 +161,7 @@ def install_create_github_issue_exception_handler(output, url, labels=None): display(welcome_message, app_with_work_chain_selector, footer) """ - global _ORIGINAL_EXCEPTION_HANDLER + global _ORIGINAL_EXCEPTION_HANDLER # noqa if labels is None: labels = [] diff --git a/aiidalab_widgets_base/computational_resources.py b/aiidalab_widgets_base/computational_resources.py index 61db1274..937607f4 100644 --- a/aiidalab_widgets_base/computational_resources.py +++ b/aiidalab_widgets_base/computational_resources.py @@ -376,7 +376,7 @@ def _ssh_keygen(self): "", ] if not fpath.exists(): - subprocess.run(keygen_cmd, capture_output=True) + subprocess.run(keygen_cmd, capture_output=True, check=True) def _can_login(self): """Check if it is possible to login into the remote host.""" @@ -1028,6 +1028,7 @@ def test(self, _=None): process_result = subprocess.run( ["verdi", "computer", "test", "--print-traceback", self.label.value], capture_output=True, + check=False, ) if process_result.returncode == 0: @@ -1602,7 +1603,7 @@ def __init__( enable_detailed_setup=True, ): if not any((enable_detailed_setup, enable_quick_setup)): - raise ValueError( # noqa + raise ValueError( "At least one of `enable_quick_setup` and `enable_detailed_setup` should be True." ) diff --git a/aiidalab_widgets_base/databases.py b/aiidalab_widgets_base/databases.py index c3b57a5b..48d39ccd 100644 --- a/aiidalab_widgets_base/databases.py +++ b/aiidalab_widgets_base/databases.py @@ -152,8 +152,8 @@ class :class:`aiidalab_widgets_base.structures.StructureManagerWidget`. def __init__( self, - embedded: bool = True, - title: str = None, + embedded=True, + title=None, **kwargs, ) -> None: try: diff --git a/aiidalab_widgets_base/elns.py b/aiidalab_widgets_base/elns.py index 64e27f23..b14977a9 100644 --- a/aiidalab_widgets_base/elns.py +++ b/aiidalab_widgets_base/elns.py @@ -249,9 +249,7 @@ def update_list_of_elns(self): self.write_to_config(config) default_eln = None - self.eln_instance.options = [("Setup new ELN", {})] + [ - (k, v) for k, v in config.items() - ] + self.eln_instance.options = [("Setup new ELN", {}), *list(config.items())] if default_eln: self.eln_instance.label = default_eln diff --git a/aiidalab_widgets_base/export.py b/aiidalab_widgets_base/export.py index 9b34304b..bdd4a106 100644 --- a/aiidalab_widgets_base/export.py +++ b/aiidalab_widgets_base/export.py @@ -40,8 +40,6 @@ def export_aiida_subgraph(self, change=None): # pylint: disable=unused-argument document.body.appendChild(link); link.click(); document.body.removeChild(link); - """.format( - payload=payload, filename=f"export_{self.process.id}.aiida" - ) + """.format(payload=payload, filename=f"export_{self.process.id}.aiida") ) display(javas) diff --git a/aiidalab_widgets_base/misc.py b/aiidalab_widgets_base/misc.py index 6afbf00a..fb7e79b7 100644 --- a/aiidalab_widgets_base/misc.py +++ b/aiidalab_widgets_base/misc.py @@ -20,7 +20,7 @@ def copy_to_clipboard(self, change=None): # pylint:disable=unused-argument from IPython.display import Javascript, display javas = Javascript( - """ + f""" function copyStringToClipboard (str) {{ // Create new element var el = document.createElement('textarea'); @@ -37,10 +37,8 @@ def copy_to_clipboard(self, change=None): # pylint:disable=unused-argument // Remove temporary element document.body.removeChild(el); }} - copyStringToClipboard("{selection}"); - """.format( - selection=self.value - ) + copyStringToClipboard("{self.value}"); + """ ) # For the moment works for Chrome, but doesn't work for Firefox. if self.value: # If no value provided - do nothing. display(javas) diff --git a/aiidalab_widgets_base/process.py b/aiidalab_widgets_base/process.py index 8839ddba..bf75cc67 100644 --- a/aiidalab_widgets_base/process.py +++ b/aiidalab_widgets_base/process.py @@ -156,10 +156,10 @@ def __init__(self, process=None, **kwargs): self.info = ipw.HTML() self.flat_mapping = self.generate_flat_mapping(process=process) or {} - inputs_list = [(key, value) for key, value in self.flat_mapping.items()] + inputs_list = list(self.flat_mapping.items()) self._inputs = ipw.Dropdown( - options=[("Select input", "")] + inputs_list, + options=[("Select input", ""), *inputs_list], description="Select input:", style={"description_width": "initial"}, disabled=False, @@ -230,7 +230,7 @@ def __init__(self, process=None, **kwargs): else [] ) outputs = ipw.Dropdown( - options=[("Select output", "")] + outputs_list, + options=[("Select output", ""), *outputs_list], label="Select output", description="Select outputs:", style={"description_width": "initial"}, @@ -285,7 +285,7 @@ def __init__( ) ) self.output = ipw.HTML() - super().__init__(children=[self.output] + self.followers, **kwargs) + super().__init__(children=[self.output, *self.followers], **kwargs) self.update() def update(self): diff --git a/aiidalab_widgets_base/structures.py b/aiidalab_widgets_base/structures.py index c3f4ecea..fc2d80f9 100644 --- a/aiidalab_widgets_base/structures.py +++ b/aiidalab_widgets_base/structures.py @@ -118,8 +118,11 @@ def __init__( self._structure_importers(importers), self.viewer, ipw.HBox( - store_and_description - + [self.structure_label, self.structure_description] + [ + *store_and_description, + self.structure_label, + self.structure_description, + ] ), ] @@ -131,7 +134,7 @@ def __init__( accordion.set_title(0, "Edit Structure") children += [accordion] - super().__init__(children=children + [self.output], **kwargs) + super().__init__(children=[*children, self.output], **kwargs) def _structure_importers(self, importers): """Preparing structure importers.""" @@ -217,7 +220,7 @@ def store_structure(self, _=None): ): # Make a link between self.input_structure and self.structure_node @engine.calcfunction - def user_modifications(source_structure): + def user_modifications(_source_structure): return self.structure_node structure_node = user_modifications(self.input_structure) @@ -469,7 +472,7 @@ class StructureExamplesWidget(ipw.VBox): def __init__(self, examples, title="", **kwargs): self.title = title - self.on_structure_selection = lambda structure_ase, name: None + self.on_structure_selection = lambda _structure_ase, _name: None self._select_structure = ipw.Dropdown( options=self.get_example_structures(examples) ) @@ -483,7 +486,7 @@ def get_example_structures(examples): raise TypeError( f"parameter examples should be of type list, {type(examples)} given" ) - return [("Select structure", False)] + examples + return [("Select structure", False), *examples] def _on_select_structure(self, change=None): """When structure is selected.""" @@ -687,7 +690,7 @@ class SmilesWidget(ipw.VBox): def __init__(self, title=""): self.title = title - try: # noqa: TC101 + try: from rdkit import Chem # noqa: F401 from rdkit.Chem import AllChem # noqa: F401 except ImportError: @@ -1065,13 +1068,11 @@ def _apply_cell_transformation(self, _=None, atoms=None): try: atoms = make_supercell(atoms, mat) except Exception as e: - self._status_message.message = """ + self._status_message.message = f"""
- The transformation matrix is wrong! {} + The transformation matrix is wrong! {e}
- """.format( - e - ) + """ return # translate atoms.translate(-atoms.cell.array.dot(translate)) @@ -1546,8 +1547,9 @@ def copy_sel(self, _=None, atoms=None, selection=None): add_atoms.translate([1.0, 0, 0]) atoms += add_atoms - self.structure, self.input_selection = atoms, list( - range(last_atom, last_atom + len(selection)) + self.structure, self.input_selection = ( + atoms, + list(range(last_atom, last_atom + len(selection))), ) @_register_structure diff --git a/aiidalab_widgets_base/utils/__init__.py b/aiidalab_widgets_base/utils/__init__.py index c6ecc110..e08d9709 100644 --- a/aiidalab_widgets_base/utils/__init__.py +++ b/aiidalab_widgets_base/utils/__init__.py @@ -52,8 +52,8 @@ def get_ase_from_file(fname, file_format=None): # pylint: disable=redefined-bui def find_ranges(iterable): """Yield range of consecutive numbers.""" - for group in mit.consecutive_groups(iterable): - group = list(group) + for grp in mit.consecutive_groups(iterable): + group = list(grp) if len(group) == 1: yield group[0] else: diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 89192e44..3140d41f 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -1,7 +1,6 @@ from __future__ import annotations """Jupyter viewers for AiiDA data objects.""" -# pylint: disable=no-self-use import base64 import copy @@ -111,7 +110,7 @@ def __init__(self, parameter, downloadable=True, **kwargs): pd.set_option("max_colwidth", 40) dataf = pd.DataFrame( - [(key, value) for key, value in sorted(parameter.get_dict().items())], + sorted(parameter.get_dict().items()), columns=["Key", "Value"], ) self.value += dataf.to_html( @@ -415,9 +414,9 @@ def change_supercell(_=None): [ ipw.HTML( description="Super cell:", style={"description_width": "initial"} - ) + ), + *_supercell, ] - + _supercell ) # 2. Choose background color. @@ -445,7 +444,7 @@ def change_camera(change): # 4. Center button. center_button = ipw.Button(description="Center molecule") - center_button.on_click(lambda c: self._viewer.center()) + center_button.on_click(lambda _: self._viewer.center()) # 5. representations buttons self.representations_header = ipw.HBox( @@ -524,11 +523,12 @@ def change_camera(change): def _add_representation(self, _=None, style_id=None, indices=None): """Add a representation to the list of representations.""" - self._all_representations = self._all_representations + [ + self._all_representations = [ + *self._all_representations, NglViewerRepresentation( style_id=style_id or f"{self.REPRESENTATION_PREFIX}{shortuuid.uuid()}", indices=indices, - ) + ), ] self._apply_representations() @@ -914,13 +914,13 @@ def _render_structure(self, change=None): for i in bb ] - objects = ( - [light] - + spheres - + edges - + bonds - + [vapory.Background("color", np.array(to_rgb(self._viewer.background)))] - ) + objects = [ + light, + *spheres, + *edges, + *bonds, + vapory.Background("color", np.array(to_rgb(self._viewer.background))), + ] scene = vapory.Scene(camera, objects=objects) fname = bb.get_chemical_formula() + ".png" @@ -1049,7 +1049,7 @@ def apply_displayed_selection(self, _=None): else: self.wrong_syntax.layout.visibility = "visible" - def download(self, change=None): # pylint: disable=unused-argument + def download(self, _=None): """Prepare a structure for downloading.""" payload = self._prepare_payload(self.file_format.value["format"]) if payload is None: @@ -1086,7 +1086,7 @@ def _prepare_payload(self, file_format=None): file_format = file_format if file_format else self.file_format.value["format"] tmp = NamedTemporaryFile() - self.structure.write(tmp.name, format=file_format) # pylint: disable=no-member + self.structure.write(tmp.name, format=file_format) with open(tmp.name, "rb") as raw: return base64.b64encode(raw.read()).decode() @@ -1520,11 +1520,11 @@ def __init__(self, folder, downloadable=True, **kwargs): children.append(self.download_btn) super().__init__(children, **kwargs) - def change_file_view(self, change=None): # pylint: disable=unused-argument + def change_file_view(self, _=None): with self._folder.base.repository.open(self.files.value) as fobj: self.text.value = fobj.read() - def download(self, change=None): # pylint: disable=unused-argument + def download(self, _=None): """Prepare for downloading.""" from IPython.display import Javascript @@ -1559,18 +1559,14 @@ def __init__(self, bands, **kwargs): output_notebook(hide_banner=True) out = ipw.Output() with out: - plot_info = bands._get_bandplot_data( - cartesian=True, join_symbol="|" - ) # pylint: disable=protected-access + plot_info = bands._get_bandplot_data(cartesian=True, join_symbol="|") # Extract relevant data y_data = plot_info["y"].transpose().tolist() x_data = [plot_info["x"] for i in range(len(y_data))] labels = plot_info["labels"] # Create the figure plot = figure(y_axis_label=f"Dispersion ({bands.units})") - plot.multi_line( - x_data, y_data, line_width=2, line_color="red" - ) # pylint: disable=too-many-function-args + plot.multi_line(x_data, y_data, line_width=2, line_color="red") plot.xaxis.ticker = [label[0] for label in labels] # This trick was suggested here: https://github.com/bokeh/bokeh/issues/8166#issuecomment-426124290 plot.xaxis.major_label_overrides = { diff --git a/docs/source/conf.py b/docs/source/conf.py index e0df0c73..8369cec8 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -44,9 +44,7 @@ if current_year == copyright_first_year else f"{copyright_first_year}-{current_year}" ) -copyright = "{}, {}. All rights reserved".format( - copyright_year_string, copyright_owners -) # pylint: disable=redefined-builtin +copyright = f"{copyright_year_string}, {copyright_owners}. All rights reserved" # noqa # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -144,7 +142,7 @@ def run_apidoc(_): env[ "SPHINX_APIDOC_OPTIONS" ] = "members,special-members,private-members,undoc-members,show-inheritance" - subprocess.check_call([cmd_path] + options, env=env) + subprocess.check_call([cmd_path, *options], env=env) def setup(app): diff --git a/pyproject.toml b/pyproject.toml index a8dcabc8..f3f3e92c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,3 +30,32 @@ filterwarnings = [ 'ignore::DeprecationWarning:selenium', 'ignore::DeprecationWarning:pytest_selenium', ] + +[tool.ruff] +line-length = 88 +show-fixes = true +target-version = "py39" + + +[tool.ruff.lint] +ignore = ["E501", "E402", "ARG002", "TRY003", "RUF001", "RUF012"] +select = [ + "A", # flake8-builtins + "ARG", # flake8-unused-arguments + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "E", # pycodestyle + "F", # pyflakes + "I", # isort + "N", # pep8-naming + "PLE", # pylint error rules + "PLW", # pylint warning rules + "PLC", # pylint convention rules + "RUF", # ruff-specific rules + "TRY", # Tryceratops + "UP" # pyupgrade +] + +[tool.ruff.lint.per-file-ignores] +"tests/*" = ["ARG001"] +"tests_notebooks/*" = ["ARG001"] diff --git a/setup.cfg b/setup.cfg index ce7b0736..c7b3f590 100644 --- a/setup.cfg +++ b/setup.cfg @@ -66,18 +66,6 @@ docs = pydata-sphinx-theme myst-nb - -[flake8] -ignore = - E501 - W503 - E203 -per-file-ignores = - aiidalab_widgets_base/__init__.py: E402 -exclude = - docs/, - docs/source/conf.py - [bumpver] current_version = "v2.1.0" version_pattern = "vMAJOR.MINOR.PATCH[PYTAGNUM]"