From ff0108378660326c446310f25f12186c1db8c03c Mon Sep 17 00:00:00 2001 From: Sebastiaan Huber Date: Fri, 22 Dec 2023 17:17:10 +0100 Subject: [PATCH] Pre-commit: Switch to `ruff` for linting and formatting --- .github/workflows/validate_release_tag.py | 7 ++- .pre-commit-config.yaml | 44 ++++--------- pyproject.toml | 62 ++++++++----------- src/aiida_pseudo/cli/__init__.py | 8 +-- src/aiida_pseudo/cli/family.py | 18 +++--- src/aiida_pseudo/cli/install.py | 59 +++++++++--------- src/aiida_pseudo/cli/list.py | 5 +- src/aiida_pseudo/cli/params/__init__.py | 3 +- src/aiida_pseudo/cli/params/options.py | 38 ++++++++---- src/aiida_pseudo/cli/params/types.py | 4 +- src/aiida_pseudo/cli/root.py | 2 +- src/aiida_pseudo/cli/utils.py | 3 +- src/aiida_pseudo/data/pseudo/__init__.py | 3 +- src/aiida_pseudo/data/pseudo/jthxml.py | 4 +- src/aiida_pseudo/data/pseudo/pseudo.py | 14 +++-- src/aiida_pseudo/data/pseudo/psf.py | 4 +- src/aiida_pseudo/data/pseudo/psml.py | 4 +- src/aiida_pseudo/data/pseudo/psp8.py | 4 +- src/aiida_pseudo/data/pseudo/upf.py | 6 +- src/aiida_pseudo/data/pseudo/vps.py | 4 +- src/aiida_pseudo/groups/family/__init__.py | 3 +- src/aiida_pseudo/groups/family/pseudo.py | 15 ++--- src/aiida_pseudo/groups/family/pseudo_dojo.py | 13 ++-- src/aiida_pseudo/groups/family/sssp.py | 9 ++- src/aiida_pseudo/groups/mixins/__init__.py | 3 +- src/aiida_pseudo/groups/mixins/cutoffs.py | 17 +++-- tests/cli/params/test_types.py | 1 - tests/cli/test_family.py | 13 ++-- tests/cli/test_install.py | 43 ++++++++++--- tests/cli/test_list.py | 3 +- tests/cli/test_root.py | 9 ++- tests/cli/test_utils.py | 3 - tests/conftest.py | 14 ++--- tests/data/pseudo/test_common.py | 3 +- tests/data/pseudo/test_jthxml.py | 4 +- tests/data/pseudo/test_pseudo.py | 21 ++++--- tests/data/pseudo/test_psf.py | 23 +++---- tests/data/pseudo/test_psml.py | 4 +- tests/data/pseudo/test_psp8.py | 4 +- tests/data/pseudo/test_upf.py | 9 ++- tests/data/pseudo/test_vps.py | 12 ++-- tests/groups/family/test_pseudo.py | 6 +- tests/groups/family/test_pseudo_dojo.py | 4 +- tests/groups/family/test_sssp.py | 4 +- tests/groups/mixins/test_cutoffs.py | 11 ++-- 45 files changed, 269 insertions(+), 278 deletions(-) diff --git a/.github/workflows/validate_release_tag.py b/.github/workflows/validate_release_tag.py index 40b84d2..ccec4e5 100644 --- a/.github/workflows/validate_release_tag.py +++ b/.github/workflows/validate_release_tag.py @@ -17,8 +17,11 @@ def get_version_from_module(content: str) -> str: try: return next( - ast.literal_eval(statement.value) for statement in module.body if isinstance(statement, ast.Assign) - for target in statement.targets if isinstance(target, ast.Name) and target.id == '__version__' + ast.literal_eval(statement.value) + for statement in module.body + if isinstance(statement, ast.Assign) + for target in statement.targets + if isinstance(target, ast.Name) and target.id == '__version__' ) except StopIteration as exception: raise IOError('Unable to find the `__version__` attribute in the module.') from exception diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2520f38..e90fd3b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,36 +13,14 @@ repos: hooks: - id: flynt -- repo: https://github.com/pycqa/isort - rev: '5.12.0' - hooks: - - id: isort - -- repo: https://github.com/pre-commit/mirrors-yapf - rev: 'v0.32.0' - hooks: - - id: yapf - name: yapf - types: [python] - args: ['-i'] - additional_dependencies: ['toml'] - exclude: &exclude_files > - (?x)^( - docs/.*| - )$ - -- repo: https://github.com/PyCQA/pydocstyle - rev: '6.1.1' - hooks: - - id: pydocstyle - additional_dependencies: ['toml'] - exclude: *exclude_files - -- repo: local - hooks: - - id: pylint - name: pylint - entry: pylint - types: [python] - language: system - exclude: *exclude_files +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.9 + hooks: + - id: ruff-format + exclude: &exclude_files > + (?x)^( + docs/.*| + )$ + - id: ruff + args: [--fix, --exit-non-zero-on-fix, --show-fixes] + exclude: *exclude_files diff --git a/pyproject.toml b/pyproject.toml index a940a3e..7a79960 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,8 +44,6 @@ docs = [ ] pre-commit = [ 'pre-commit~=2.2', - 'pylint==2.15.5', - 'pylint-aiida~=0.1', ] tests = [ 'pgtest~=1.3', @@ -84,32 +82,6 @@ exclude = [ line-length = 120 fail-on-change = true -[tool.isort] -force_sort_within_sections = true -include_trailing_comma = true -line_length = 120 -multi_line_output = 3 - -[tool.pydocstyle] -ignore = [ - 'D104', - 'D202', - 'D203', - 'D213' -] - -[tool.pylint.master] -load-plugins = ['pylint_aiida'] - -[tool.pylint.format] -max-line-length = 120 - -[tool.pylint.messages_control] -disable = [ - 'duplicate-code', - 'import-outside-toplevel', - 'too-many-arguments', -] [tool.pytest.ini_options] filterwarnings = [ @@ -120,11 +92,29 @@ testpaths = [ 'tests', ] -[tool.yapf] -align_closing_bracket_with_visual_indent = true -based_on_style = 'google' -coalesce_brackets = true -column_limit = 120 -dedent_closing_brackets = true -indent_dictionary_value = false -split_arguments_when_comma_terminated = true +[tool.ruff] +ignore = [ + 'F403', # Star imports unable to detect undefined names + 'F405', # Import may be undefined or defined from star imports + 'PLR0911', # Too many return statements + 'PLR0912', # Too many branches + 'PLR0913', # Too many arguments in function definition + 'PLR0915', # Too many statements + 'PLR2004' # Magic value used in comparison +] +line-length = 120 +select = [ + 'E', # pydocstyle + 'W', # pydocstyle + 'F', # pyflakes + 'I', # isort + 'N', # pep8-naming + 'PLC', # pylint-convention + 'PLE', # pylint-error + 'PLR', # pylint-refactor + 'PLW', # pylint-warning + 'RUF' # ruff +] + +[tool.ruff.format] +quote-style = 'single' diff --git a/src/aiida_pseudo/cli/__init__.py b/src/aiida_pseudo/cli/__init__.py index 7d41aaf..8da5832 100644 --- a/src/aiida_pseudo/cli/__init__.py +++ b/src/aiida_pseudo/cli/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """Module for the command line interface.""" -from .family import cmd_family -from .install import cmd_install, cmd_install_family, cmd_install_pseudo_dojo, cmd_install_sssp -from .list import cmd_list -from .root import cmd_root +from .family import cmd_family # noqa: F401 +from .install import cmd_install, cmd_install_family, cmd_install_pseudo_dojo, cmd_install_sssp # noqa: F401 +from .list import cmd_list # noqa: F401 +from .root import cmd_root # noqa: F401 diff --git a/src/aiida_pseudo/cli/family.py b/src/aiida_pseudo/cli/family.py index 94f84d1..387bbde 100644 --- a/src/aiida_pseudo/cli/family.py +++ b/src/aiida_pseudo/cli/family.py @@ -2,9 +2,9 @@ """Commands to inspect or modify the contents of pseudo potential families.""" import json +import click from aiida.cmdline.params import options as options_core from aiida.cmdline.utils import decorators, echo -import click from .params import arguments, options, types from .root import cmd_root @@ -28,7 +28,6 @@ def cmd_family_show(family, stringency, unit, raw): from ..groups.mixins import RecommendedCutoffMixin if isinstance(family, RecommendedCutoffMixin): - try: family.validate_stringency(stringency) except ValueError as exception: @@ -37,10 +36,15 @@ def cmd_family_show(family, stringency, unit, raw): unit = unit or family.get_cutoffs_unit(stringency) headers = ['Element', 'Pseudo', 'MD5', f'Wavefunction ({unit})', f'Charge density ({unit})'] - rows = [[ - pseudo.element, pseudo.filename, pseudo.md5, - *family.get_recommended_cutoffs(elements=pseudo.element, stringency=stringency, unit=unit) - ] for pseudo in family.nodes] + rows = [ + [ + pseudo.element, + pseudo.filename, + pseudo.md5, + *family.get_recommended_cutoffs(elements=pseudo.element, stringency=stringency, unit=unit), + ] + for pseudo in family.nodes + ] else: headers = ['Element', 'Pseudo', 'MD5'] rows = [[pseudo.element, pseudo.filename, pseudo.md5] for pseudo in family.nodes] @@ -64,7 +68,7 @@ def cmd_family_cutoffs(): @options.STRINGENCY(required=True) @options.UNIT() @decorators.with_dbenv() -def cmd_family_cutoffs_set(family, cutoffs, stringency, unit): # noqa: D301 +def cmd_family_cutoffs_set(family, cutoffs, stringency, unit): """Set the recommended cutoffs for a pseudo potential family and a specified stringency. The cutoffs should be provided as a JSON file through the argument `CUTOFFS` which should have the structure: diff --git a/src/aiida_pseudo/cli/install.py b/src/aiida_pseudo/cli/install.py index 7c90d9d..1fbf25c 100644 --- a/src/aiida_pseudo/cli/install.py +++ b/src/aiida_pseudo/cli/install.py @@ -7,11 +7,11 @@ import tempfile import typing as t -from aiida.cmdline.params import options as options_core -from aiida.cmdline.utils import decorators, echo import click import requests import yaml +from aiida.cmdline.params import options as options_core +from aiida.cmdline.utils import decorators, echo from .params import options, types from .root import cmd_root @@ -37,7 +37,7 @@ def cmd_install(): @options.PSEUDO_TYPE() @options.TRACEBACK() @decorators.with_dbenv() -def cmd_install_family(archive, label, description, archive_format, family_type, pseudo_type, traceback): # pylint: disable=too-many-arguments +def cmd_install_family(archive, label, description, archive_format, family_type, pseudo_type, traceback): """Install a standard pseudopotential family from an ARCHIVE. The ARCHIVE can be a (compressed) archive of a directory containing the pseudopotentials on the local file system or @@ -96,7 +96,7 @@ def download_sssp( configuration: 'SsspConfiguration', filepath_archive: pathlib.Path, filepath_metadata: pathlib.Path, - traceback: bool = False + traceback: bool = False, ) -> str: """Download the pseudopotential archive and metadata for an SSSP configuration to a path on disk. @@ -154,7 +154,7 @@ def install_sssp( filepath_metadata: pathlib.Path, label: str, description: str = '', - traceback: bool = False + traceback: bool = False, ) -> None: """Install the ``SsspFamily`` and set the recommended cutoffs. @@ -204,7 +204,7 @@ def cmd_install_sssp(version, functional, protocol, download_only, from_download The SSSP configuration will be automatically downloaded from the Materials Cloud Archive entry to create a new `SsspFamily`. """ - # pylint: disable=too-many-locals, too-many-statements + from aiida import load_profile from aiida.common.exceptions import ConfigurationError from aiida.common.files import md5_file @@ -216,7 +216,7 @@ def cmd_install_sssp(version, functional, protocol, download_only, from_download if download_only and from_download is not None: raise click.BadParameter( 'cannot specify both `--download-only` and `--from-download`.', - param_hint="'--download-only' / '--from-download'" + param_hint="'--download-only' / '--from-download'", ) configuration = SsspConfiguration(version, functional, protocol) @@ -225,16 +225,14 @@ def cmd_install_sssp(version, functional, protocol, download_only, from_download if configuration not in SsspFamily.valid_configurations: echo.echo_critical(f'{version} {functional} {protocol} is not a valid SSSP configuration') - with tempfile.TemporaryDirectory() as dirpath: - - dirpath = pathlib.Path(dirpath) + with tempfile.TemporaryDirectory() as tmppath: + dirpath = pathlib.Path(tmppath) filepath_archive = dirpath / 'archive.tar.gz' filepath_metadata = dirpath / 'metadata.json' filepath_configuration = dirpath / 'configuration.json' if download_only: - tarball_path = pathlib.Path.cwd() / f'{label}.aiida_pseudo'.replace('/', '_') if tarball_path.exists(): @@ -253,7 +251,6 @@ def cmd_install_sssp(version, functional, protocol, download_only, from_download return if from_download is not None: - tarball_path = pathlib.Path(from_download).absolute() with tarfile.open(tarball_path, 'r') as handle: handle.extractall(dirpath) @@ -300,7 +297,7 @@ def download_pseudo_dojo( configuration: 'PseudoDojoConfiguration', filepath_archive: pathlib.Path, filepath_metadata: pathlib.Path, - traceback: bool = False + traceback: bool = False, ) -> None: """Download the pseudopotential archive and metadata for a PseudoDojo configuration to a path on disk. @@ -338,7 +335,7 @@ def install_pseudo_dojo( pseudo_type: 'PseudoPotentialData', label: str, description: str = '', - traceback: bool = False + traceback: bool = False, ) -> 'PseudoDojoFamily': """Install the ``PseudoDojoFamily`` and set the recommended cutoffs. @@ -349,7 +346,7 @@ def install_pseudo_dojo( :param description: description of the pseudopotential family group. :param traceback: boolean, if true, print the traceback when an exception occurs. """ - # pylint: disable=too-many-locals + from aiida.orm import Group from aiida_pseudo.groups.family.pseudo_dojo import PseudoDojoConfiguration, PseudoDojoFamily @@ -368,14 +365,12 @@ def install_pseudo_dojo( msg = f'md5 of pseudo for element {element} does not match that of the metadata {md5}' echo.echo_critical(msg) - # yapf: disable paw_configurations = ( PseudoDojoConfiguration('1.0', 'PBE', 'SR', 'standard', 'jthxml'), PseudoDojoConfiguration('1.0', 'PBE', 'SR', 'stringent', 'jthxml'), PseudoDojoConfiguration('1.0', 'LDA', 'SR', 'standard', 'jthxml'), - PseudoDojoConfiguration('1.0', 'LDA', 'SR', 'stringent', 'jthxml') + PseudoDojoConfiguration('1.0', 'LDA', 'SR', 'stringent', 'jthxml'), ) - # yapf: enable # The PAW configurations have missing cutoffs for the Lanthanides, which have ben replaced with a placeholder # value of `-1`. We replace these with the 1.5 * the maximum cutoff from the same stringency so that these @@ -395,9 +390,11 @@ def install_pseudo_dojo( adjusted_cutoffs[stringency].append(element) for stringency, elements in adjusted_cutoffs.items(): - msg = f'stringency `{stringency}` has missing recommended cutoffs for elements ' \ - f'{", ".join(elements)}: as a substitute, 1.5 * the maximum cutoff of the stringency ' \ + msg = ( + f'stringency `{stringency}` has missing recommended cutoffs for elements ' + f'{", ".join(elements)}: as a substitute, 1.5 * the maximum cutoff of the stringency ' 'was set for these elements. USE WITH CAUTION!' + ) echo.echo_warning(msg) family.description = description @@ -420,15 +417,22 @@ def install_pseudo_dojo( @options.FROM_DOWNLOAD() @options.TRACEBACK() def cmd_install_pseudo_dojo( - version, functional, relativistic, protocol, pseudo_format, default_stringency, download_only, from_download, - traceback + version, + functional, + relativistic, + protocol, + pseudo_format, + default_stringency, + download_only, + from_download, + traceback, ): """Install a PseudoDojo configuration. The PseudoDojo configuration will be automatically downloaded from pseudo-dojo.org to create a new `PseudoDojoFamily` subclass instance based on the specified pseudopotential format. """ - # pylint: disable=too-many-locals,too-many-arguments,too-many-branches,too-many-statements + from aiida import load_profile from aiida.common.exceptions import ConfigurationError from aiida.common.files import md5_file @@ -441,7 +445,7 @@ def cmd_install_pseudo_dojo( if download_only and from_download is not None: raise click.BadParameter( 'cannot specify both `--download-only` and `--from-download`.', - param_hint="'--download-only' / '--from-download'" + param_hint="'--download-only' / '--from-download'", ) pseudo_type_mapping = { @@ -458,16 +462,14 @@ def cmd_install_pseudo_dojo( if configuration not in PseudoDojoFamily.valid_configurations: echo.echo_critical(f'{configuration} is not a valid configuration') - with tempfile.TemporaryDirectory() as dirpath: - - dirpath = pathlib.Path(dirpath) + with tempfile.TemporaryDirectory() as tmppath: + dirpath = pathlib.Path(tmppath) filepath_archive = dirpath / 'archive.tgz' filepath_metadata = dirpath / 'metadata.tgz' filepath_configuration = dirpath / 'configuration.json' if download_only: - tarball_path = pathlib.Path.cwd() / f'{label}.aiida_pseudo'.replace('/', '_') if tarball_path.exists(): @@ -486,7 +488,6 @@ def cmd_install_pseudo_dojo( return if from_download is not None: - tarball_path = pathlib.Path(from_download).absolute() with tarfile.open(tarball_path, 'r') as handle: handle.extractall(dirpath) diff --git a/src/aiida_pseudo/cli/list.py b/src/aiida_pseudo/cli/list.py index f0d338d..2edd97f 100644 --- a/src/aiida_pseudo/cli/list.py +++ b/src/aiida_pseudo/cli/list.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- """Commands to list instances of `PseudoPotentialFamily`.""" +import click from aiida.cmdline.params import options as options_core from aiida.cmdline.utils import decorators, echo -import click from .params import options from .root import cmd_root @@ -44,8 +44,7 @@ def cmd_list(project, raw, family_type): rows = [] - for group, in get_families_builder().iterall(): - + for (group,) in get_families_builder().iterall(): if family_type and family_type.entry_point != group.type_string: continue diff --git a/src/aiida_pseudo/cli/params/__init__.py b/src/aiida_pseudo/cli/params/__init__.py index 7e8cca6..03c9367 100644 --- a/src/aiida_pseudo/cli/params/__init__.py +++ b/src/aiida_pseudo/cli/params/__init__.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- -# pylint: disable=undefined-variable """Module for command line interface parameters.""" from .options import * from .types import * -__all__ = (options.__all__ + types.__all__) +__all__ = options.__all__ + types.__all__ diff --git a/src/aiida_pseudo/cli/params/options.py b/src/aiida_pseudo/cli/params/options.py index a86ae56..445b8e1 100644 --- a/src/aiida_pseudo/cli/params/options.py +++ b/src/aiida_pseudo/cli/params/options.py @@ -1,18 +1,30 @@ # -*- coding: utf-8 -*- """Reusable options for CLI commands.""" import functools -from pathlib import Path import shutil +from pathlib import Path +import click from aiida.cmdline.params import options as core_options from aiida.cmdline.params import types as core_types -import click from .types import PseudoPotentialFamilyTypeParam, PseudoPotentialTypeParam, UnitParamType __all__ = ( - 'PROFILE', 'VERBOSITY', 'VERSION', 'FUNCTIONAL', 'RELATIVISTIC', 'PROTOCOL', 'PSEUDO_FORMAT', 'STRINGENCY', - 'DEFAULT_STRINGENCY', 'TRACEBACK', 'FAMILY_TYPE', 'ARCHIVE_FORMAT', 'UNIT', 'DOWNLOAD_ONLY' + 'PROFILE', + 'VERBOSITY', + 'VERSION', + 'FUNCTIONAL', + 'RELATIVISTIC', + 'PROTOCOL', + 'PSEUDO_FORMAT', + 'STRINGENCY', + 'DEFAULT_STRINGENCY', + 'TRACEBACK', + 'FAMILY_TYPE', + 'ARCHIVE_FORMAT', + 'UNIT', + 'DOWNLOAD_ONLY', ) PROFILE = functools.partial( @@ -33,7 +45,7 @@ '--functional', type=click.STRING, required=False, - help='Select the functional of the installed configuration.' + help='Select the functional of the installed configuration.', ) RELATIVISTIC = core_options.OverridableOption( @@ -41,7 +53,7 @@ '--relativistic', type=click.STRING, required=False, - help='Select the type of relativistic effects included in the installed configuration.' + help='Select the type of relativistic effects included in the installed configuration.', ) PROTOCOL = core_options.OverridableOption( @@ -53,7 +65,7 @@ '--pseudo-format', type=click.STRING, required=True, - help='Select the pseudopotential file format of the installed configuration.' + help='Select the pseudopotential file format of the installed configuration.', ) STRINGENCY = core_options.OverridableOption( @@ -65,7 +77,7 @@ '--default-stringency', type=click.STRING, required=False, - help='Select the default stringency level for the installed configuration.' + help='Select the default stringency level for the installed configuration.', ) TRACEBACK = core_options.OverridableOption( @@ -78,7 +90,7 @@ type=PseudoPotentialFamilyTypeParam(), default='pseudo.family', show_default=True, - help='Choose the type of pseudo potential family to create.' + help='Choose the type of pseudo potential family to create.', ) PSEUDO_TYPE = core_options.OverridableOption( @@ -90,7 +102,7 @@ help=( 'Select the pseudopotential type to be used for the family. Should be the entry point name of a ' 'subclass of `PseudoPotentialData`.' - ) + ), ) ARCHIVE_FORMAT = core_options.OverridableOption( @@ -104,7 +116,7 @@ required=False, default='eV', show_default=True, - help='Specify the energy unit of the cutoffs. Must be recognized by the ``UnitRegistry`` of the ``pint`` library.' + help='Specify the energy unit of the cutoffs. Must be recognized by the ``UnitRegistry`` of the ``pint`` library.', ) DOWNLOAD_ONLY = core_options.OverridableOption( @@ -113,12 +125,12 @@ help=( 'Only download the pseudopotential files to the current working directory, without installing the ' 'pseudopotential family.' - ) + ), ) FROM_DOWNLOAD = core_options.OverridableOption( '--from-download', type=click.Path(exists=True, dir_okay=False, path_type=Path), required=False, - help='Install the pseudpotential family from the archive and metadata downloaded with the `--download-only` option.' + help='Install the pseudpotential family from archive and metadata downloaded with the `--download-only` option.', ) diff --git a/src/aiida_pseudo/cli/params/types.py b/src/aiida_pseudo/cli/params/types.py index 97b7dd6..36da262 100644 --- a/src/aiida_pseudo/cli/params/types.py +++ b/src/aiida_pseudo/cli/params/types.py @@ -4,9 +4,9 @@ import pathlib -from aiida.cmdline.params.types import GroupParamType import click import requests +from aiida.cmdline.params.types import GroupParamType from ..utils import attempt @@ -49,6 +49,7 @@ def complete(self, _, incomplete): :returns: list of tuples of valid entry points (matching incomplete) and a description """ from aiida.plugins.entry_point import get_entry_point_names + entry_points = get_entry_point_names('aiida.data') return [(ep, '') for ep in entry_points if (ep.startswith('pseudo') and ep.startswith(incomplete))] @@ -127,6 +128,7 @@ def complete(self, _, incomplete): :returns: list of tuples of valid entry points (matching incomplete) and a description """ from aiida.plugins.entry_point import get_entry_point_names + entry_points = get_entry_point_names('aiida.groups') return [(ep, '') for ep in entry_points if (ep.startswith('pseudo.family') and ep.startswith(incomplete))] diff --git a/src/aiida_pseudo/cli/root.py b/src/aiida_pseudo/cli/root.py index c6ae9f1..7a2868b 100644 --- a/src/aiida_pseudo/cli/root.py +++ b/src/aiida_pseudo/cli/root.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Command line interface `aiida-pseudo`.""" -from aiida.cmdline.groups.verdi import VerdiCommandGroup import click +from aiida.cmdline.groups.verdi import VerdiCommandGroup from .params import options diff --git a/src/aiida_pseudo/cli/utils.py b/src/aiida_pseudo/cli/utils.py index 954394c..0b89e4e 100644 --- a/src/aiida_pseudo/cli/utils.py +++ b/src/aiida_pseudo/cli/utils.py @@ -22,7 +22,7 @@ def attempt(message, exception_types=Exception, include_traceback=False): try: yield - except exception_types as exception: # pylint: disable=broad-except + except exception_types as exception: echo.echo(' [FAILED]', fg='red', bold=True) message = str(exception) if include_traceback: @@ -51,7 +51,6 @@ def create_family_from_archive(cls, label, filepath_archive: Path, fmt=None, pse import tempfile with tempfile.TemporaryDirectory() as dirpath: - try: shutil.unpack_archive(filepath_archive, dirpath, format=fmt) except shutil.ReadError as exception: diff --git a/src/aiida_pseudo/data/pseudo/__init__.py b/src/aiida_pseudo/data/pseudo/__init__.py index 37c726c..ad3b465 100644 --- a/src/aiida_pseudo/data/pseudo/__init__.py +++ b/src/aiida_pseudo/data/pseudo/__init__.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# pylint: disable=undefined-variable """Module with data plugins to represent pseudo potentials.""" from .jthxml import * from .pseudo import * @@ -9,4 +8,4 @@ from .upf import * from .vps import * -__all__ = (jthxml.__all__ + pseudo.__all__ + psf.__all__ + psml.__all__ + psp8.__all__ + upf.__all__ + vps.__all__) +__all__ = jthxml.__all__ + pseudo.__all__ + psf.__all__ + psml.__all__ + psp8.__all__ + upf.__all__ + vps.__all__ diff --git a/src/aiida_pseudo/data/pseudo/jthxml.py b/src/aiida_pseudo/data/pseudo/jthxml.py index ac34c91..db7fd8b 100644 --- a/src/aiida_pseudo/data/pseudo/jthxml.py +++ b/src/aiida_pseudo/data/pseudo/jthxml.py @@ -31,7 +31,9 @@ def parse_element(stream: typing.BinaryIO): class JthXmlData(PseudoPotentialData): """Data plugin to represent a pseudo potential in JTH XML format.""" - def set_file(self, source: typing.Union[str, pathlib.Path, typing.BinaryIO], filename: str = None, **kwargs): # pylint: disable=arguments-differ + def set_file( + self, source: typing.Union[str, pathlib.Path, typing.BinaryIO], filename: typing.Optional[str] = None, **kwargs + ): """Set the file content and parse other optional attributes from the content. .. note:: this method will first analyse the type of the ``source`` and if it is a filepath will convert it diff --git a/src/aiida_pseudo/data/pseudo/pseudo.py b/src/aiida_pseudo/data/pseudo/pseudo.py index 09b9f57..dca20dc 100644 --- a/src/aiida_pseudo/data/pseudo/pseudo.py +++ b/src/aiida_pseudo/data/pseudo/pseudo.py @@ -19,7 +19,9 @@ class PseudoPotentialData(plugins.DataFactory('core.singlefile')): _key_md5 = 'md5' @classmethod - def get_or_create(cls, source: typing.Union[str, pathlib.Path, typing.BinaryIO], filename: str = None): + def get_or_create( + cls, source: typing.Union[str, pathlib.Path, typing.BinaryIO], filename: typing.Optional[str] = None + ): """Get pseudopotenial data node from database with matching md5 checksum or create a new one if not existent. :param source: the source pseudopotential content, either a binary stream, or a ``str`` or ``Path`` to the path @@ -49,6 +51,7 @@ def get_entry_point_name(cls): :return: the entry point name. """ from aiida.plugins.entry_point import get_entry_point_from_class + _, entry_point = get_entry_point_from_class(cls.__module__, cls.__name__) return entry_point.name @@ -59,9 +62,8 @@ def is_readable_byte_stream(stream) -> bool: :param stream: the object to analyse. :returns: True if ``stream`` appears to be a readable filelike object in binary mode, False otherwise. """ - return ( - isinstance(stream, io.BytesIO) or - (hasattr(stream, 'read') and hasattr(stream, 'mode') and 'b' in stream.mode) + return isinstance(stream, io.BytesIO) or ( + hasattr(stream, 'read') and hasattr(stream, 'mode') and 'b' in stream.mode ) @classmethod @@ -107,7 +109,9 @@ def validate_md5(self, md5: str): if md5 != md5_file: raise ValueError(f'md5 does not match that of stored file: {md5} != {md5_file}') - def set_file(self, source: typing.Union[str, pathlib.Path, typing.BinaryIO], filename: str = None, **kwargs): + def set_file( + self, source: typing.Union[str, pathlib.Path, typing.BinaryIO], filename: typing.Optional[str] = None, **kwargs + ): """Set the file content. .. note:: this method will first analyse the type of the ``source`` and if it is a filepath will convert it diff --git a/src/aiida_pseudo/data/pseudo/psf.py b/src/aiida_pseudo/data/pseudo/psf.py index e8f6ae9..19d7b8c 100644 --- a/src/aiida_pseudo/data/pseudo/psf.py +++ b/src/aiida_pseudo/data/pseudo/psf.py @@ -30,7 +30,9 @@ def parse_element(stream: typing.BinaryIO): class PsfData(PseudoPotentialData): """Data plugin to represent a pseudo potential in PSF format.""" - def set_file(self, source: typing.Union[str, pathlib.Path, typing.BinaryIO], filename: str = None, **kwargs): # pylint: disable=arguments-differ + def set_file( + self, source: typing.Union[str, pathlib.Path, typing.BinaryIO], filename: typing.Optional[str] = None, **kwargs + ): """Set the file content and parse other optional attributes from the content. .. note:: this method will first analyse the type of the ``source`` and if it is a filepath will convert it diff --git a/src/aiida_pseudo/data/pseudo/psml.py b/src/aiida_pseudo/data/pseudo/psml.py index 623bccb..9667b48 100644 --- a/src/aiida_pseudo/data/pseudo/psml.py +++ b/src/aiida_pseudo/data/pseudo/psml.py @@ -58,7 +58,9 @@ class PsmlData(PseudoPotentialData): _key_z_valence = 'z_valence' - def set_file(self, source: typing.Union[str, pathlib.Path, typing.BinaryIO], filename: str = None, **kwargs): # pylint: disable=arguments-differ + def set_file( + self, source: typing.Union[str, pathlib.Path, typing.BinaryIO], filename: typing.Optional[str] = None, **kwargs + ): """Set the file content and parse other optional attributes from the content. .. note:: this method will first analyse the type of the ``source`` and if it is a filepath will convert it diff --git a/src/aiida_pseudo/data/pseudo/psp8.py b/src/aiida_pseudo/data/pseudo/psp8.py index 9915741..274e52a 100644 --- a/src/aiida_pseudo/data/pseudo/psp8.py +++ b/src/aiida_pseudo/data/pseudo/psp8.py @@ -40,7 +40,9 @@ def parse_element(stream: typing.BinaryIO): class Psp8Data(PseudoPotentialData): """Data plugin to represent a pseudo potential in Psp8 (Abinit) format.""" - def set_file(self, source: typing.Union[str, pathlib.Path, typing.BinaryIO], filename: str = None, **kwargs): # pylint: disable=arguments-differ + def set_file( + self, source: typing.Union[str, pathlib.Path, typing.BinaryIO], filename: typing.Optional[str] = None, **kwargs + ): """Set the file content and parse other optional attributes from the content. .. note:: this method will first analyse the type of the ``source`` and if it is a filepath will convert it diff --git a/src/aiida_pseudo/data/pseudo/upf.py b/src/aiida_pseudo/data/pseudo/upf.py index c627454..5bc4a32 100644 --- a/src/aiida_pseudo/data/pseudo/upf.py +++ b/src/aiida_pseudo/data/pseudo/upf.py @@ -23,7 +23,6 @@ def parse_element(content: str): :return: the symbol of the element following the IUPAC naming standard. """ for regex in [REGEX_ELEMENT_V2, REGEX_ELEMENT_V1]: - match = regex.search(content) if match: @@ -39,7 +38,6 @@ def parse_z_valence(content: str) -> int: :return: the Z valence. """ for regex in [REGEX_Z_VALENCE_V2, REGEX_Z_VALENCE_V1]: - match = regex.search(content) if match: @@ -63,7 +61,9 @@ class UpfData(PseudoPotentialData): _key_z_valence = 'z_valence' - def set_file(self, source: typing.Union[str, pathlib.Path, typing.BinaryIO], filename: str = None, **kwargs): # pylint: disable=arguments-differ + def set_file( + self, source: typing.Union[str, pathlib.Path, typing.BinaryIO], filename: typing.Optional[str] = None, **kwargs + ): """Set the file content and parse other optional attributes from the content. .. note:: this method will first analyse the type of the ``source`` and if it is a filepath will convert it diff --git a/src/aiida_pseudo/data/pseudo/vps.py b/src/aiida_pseudo/data/pseudo/vps.py index 94119e0..fe733a5 100644 --- a/src/aiida_pseudo/data/pseudo/vps.py +++ b/src/aiida_pseudo/data/pseudo/vps.py @@ -102,7 +102,9 @@ class VpsData(PseudoPotentialData): _key_z_valence = 'z_valence' _key_xc_type = 'xc_type' - def set_file(self, source: typing.Union[str, pathlib.Path, typing.BinaryIO], filename: str = None, **kwargs): # pylint: disable=arguments-differ + def set_file( + self, source: typing.Union[str, pathlib.Path, typing.BinaryIO], filename: typing.Optional[str] = None, **kwargs + ): """Set the file content and parse other optional attributes from the content. .. note:: this method will first analyse the type of the ``source`` and if it is a filepath will convert it diff --git a/src/aiida_pseudo/groups/family/__init__.py b/src/aiida_pseudo/groups/family/__init__.py index 7860ad5..eb89279 100644 --- a/src/aiida_pseudo/groups/family/__init__.py +++ b/src/aiida_pseudo/groups/family/__init__.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- -# pylint: disable=undefined-variable """Module with group plugins to represent pseudo potential families.""" from .cutoffs import * from .pseudo import * from .pseudo_dojo import * from .sssp import * -__all__ = (cutoffs.__all__ + pseudo.__all__ + pseudo_dojo.__all__ + sssp.__all__) +__all__ = cutoffs.__all__ + pseudo.__all__ + pseudo_dojo.__all__ + sssp.__all__ diff --git a/src/aiida_pseudo/groups/family/pseudo.py b/src/aiida_pseudo/groups/family/pseudo.py index f03e1bd..e5187e2 100644 --- a/src/aiida_pseudo/groups/family/pseudo.py +++ b/src/aiida_pseudo/groups/family/pseudo.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Subclass of ``Group`` that serves as a base class for representing pseudo potential families.""" import re -from typing import List, Mapping, Tuple, Union +from typing import List, Mapping, Optional, Tuple, Union from aiida.common import exceptions from aiida.common.lang import classproperty, type_check @@ -38,15 +38,17 @@ def __str__(self): def __init__(self, *args, **kwargs): """Validate that the ``_pseudo_types`` class attribute is a tuple of ``PseudoPotentialData`` subclasses.""" - if not self._pseudo_types or not isinstance(self._pseudo_types, tuple) or any( - not issubclass(pseudo_type, PseudoPotentialData) for pseudo_type in self._pseudo_types + if ( + not self._pseudo_types + or not isinstance(self._pseudo_types, tuple) + or any(not issubclass(pseudo_type, PseudoPotentialData) for pseudo_type in self._pseudo_types) ): raise RuntimeError('`_pseudo_types` should be a tuple of `PseudoPotentialData` subclasses.') super().__init__(*args, **kwargs) @classproperty - def pseudo_types(cls): # pylint: disable=no-self-argument + def pseudo_types(cls): # noqa: N805 """Return the pseudo potential types that this family accepts. :return: the tuple of subclasses of ``PseudoPotentialData`` that this family can host nodes of. If it returns @@ -115,7 +117,6 @@ def parse_pseudos_from_directory(cls, dirpath, pseudo_type=None, deduplicate=Tru pseudo_type = cls._validate_pseudo_type(pseudo_type) for filepath in dirpath.iterdir(): - filename = filepath.name if not filepath.is_file(): @@ -199,7 +200,7 @@ def update_pseudo_type(self): if pseudo_types: assert len(pseudo_types) == 1, 'Family contains pseudopotential data nodes of various types.' - entry_point_name = tuple(pseudo_types)[0].get_entry_point_name() + entry_point_name = next(iter(pseudo_types)).get_entry_point_name() else: entry_point_name = None @@ -307,7 +308,7 @@ def get_pseudo(self, element): def get_pseudos( self, *, - elements: Union[List[str], Tuple[str]] = None, + elements: Optional[Union[List[str], Tuple[str]]] = None, structure: StructureData = None, ) -> Mapping[str, StructureData]: """Return the mapping of kind names on pseudo potential data nodes for the given list of elements or structure. diff --git a/src/aiida_pseudo/groups/family/pseudo_dojo.py b/src/aiida_pseudo/groups/family/pseudo_dojo.py index 074b3c4..af6312f 100644 --- a/src/aiida_pseudo/groups/family/pseudo_dojo.py +++ b/src/aiida_pseudo/groups/family/pseudo_dojo.py @@ -1,10 +1,12 @@ # -*- coding: utf-8 -*- """Subclass of `PseudoPotentialFamily` designed to represent a PseudoDojo configuration.""" +from __future__ import annotations + import json import pathlib import re -from typing import NamedTuple, Sequence import warnings +from typing import ClassVar, NamedTuple, Sequence from aiida.common.exceptions import ParsingError @@ -42,7 +44,6 @@ class PseudoDojoFamily(RecommendedCutoffMixin, PseudoPotentialFamily): label_template = 'PseudoDojo/{version}/{functional}/{relativistic}/{protocol}/{pseudo_format}' default_configuration = PseudoDojoConfiguration('0.4', 'PBE', 'SR', 'standard', 'psp8') - # yapf: disable valid_configurations = ( PseudoDojoConfiguration('0.4', 'PBE', 'SR', 'standard', 'psp8'), PseudoDojoConfiguration('0.4', 'PBE', 'SR', 'stringent', 'psp8'), @@ -86,12 +87,11 @@ class PseudoDojoFamily(RecommendedCutoffMixin, PseudoPotentialFamily): PseudoDojoConfiguration('1.0', 'PBE', 'SR', 'standard', 'jthxml'), PseudoDojoConfiguration('1.0', 'PBE', 'SR', 'stringent', 'jthxml'), PseudoDojoConfiguration('1.0', 'LDA', 'SR', 'standard', 'jthxml'), - PseudoDojoConfiguration('1.0', 'LDA', 'SR', 'stringent', 'jthxml') + PseudoDojoConfiguration('1.0', 'LDA', 'SR', 'stringent', 'jthxml'), ) - # yapf: enable url_base = 'http://www.pseudo-dojo.org/pseudos/' - urls = { + urls: ClassVar[dict[str, str]] = { 'PseudoDojo/0.4/PBE/SR/standard/psp8': 'nc-sr-04_pbe_standard_psp8', 'PseudoDojo/0.4/PBE/SR/stringent/psp8': 'nc-sr-04_pbe_stringent_psp8', 'PseudoDojo/0.4/PBEsol/SR/standard/psp8': 'nc-sr-04_pbesol_standard_psp8', @@ -247,7 +247,6 @@ def get_cutoffs_from_djrepo(cls, djrepo, pseudo_type): raise ParsingError('neither `hints` or `ppgen_hints` are defined in the djrepo.') from exception for stringency in ['low', 'normal', 'high']: - try: ecutwfc = hints.get(stringency, {})['ecut'] except KeyError as exception: @@ -259,7 +258,6 @@ def get_cutoffs_from_djrepo(cls, djrepo, pseudo_type): @classmethod def parse_djrepos_from_folder(cls, dirpath: pathlib.Path, pseudo_type): - # pylint: disable=too-many-locals,too-many-branches """Parse the djrepo files in the given directory into a list of data nodes. .. note:: The directory pointed to by `dirpath` should only contain djrepo files. Optionally, it can contain @@ -279,7 +277,6 @@ def parse_djrepos_from_folder(cls, dirpath: pathlib.Path, pseudo_type): dirpath = cls._validate_dirpath(dirpath) for filepath in dirpath.iterdir(): - filename = filepath.name if not filepath.is_file(): diff --git a/src/aiida_pseudo/groups/family/sssp.py b/src/aiida_pseudo/groups/family/sssp.py index 3b02044..45beb9b 100644 --- a/src/aiida_pseudo/groups/family/sssp.py +++ b/src/aiida_pseudo/groups/family/sssp.py @@ -83,9 +83,12 @@ def format_configuration_filename( """ version = configuration.version if patch_version is None else patch_version - return cls.filename_template.format( - version=version, functional=configuration.functional, protocol=configuration.protocol - ) + f'.{extension}' + return ( + cls.filename_template.format( + version=version, functional=configuration.functional, protocol=configuration.protocol + ) + + f'.{extension}' + ) def __init__(self, label=None, **kwargs): """Construct a new instance, validating that the label matches the required format.""" diff --git a/src/aiida_pseudo/groups/mixins/__init__.py b/src/aiida_pseudo/groups/mixins/__init__.py index 81aa93b..9d1b576 100644 --- a/src/aiida_pseudo/groups/mixins/__init__.py +++ b/src/aiida_pseudo/groups/mixins/__init__.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -# pylint: disable=undefined-variable """Module containing various mixins for ``Group`` subclasses.""" from .cutoffs import * -__all__ = (cutoffs.__all__) +__all__ = cutoffs.__all__ diff --git a/src/aiida_pseudo/groups/mixins/cutoffs.py b/src/aiida_pseudo/groups/mixins/cutoffs.py index 7cc439a..e47bf4f 100644 --- a/src/aiida_pseudo/groups/mixins/cutoffs.py +++ b/src/aiida_pseudo/groups/mixins/cutoffs.py @@ -1,14 +1,14 @@ # -*- coding: utf-8 -*- """Mixin that adds support of recommended cutoffs to a ``Group`` subclass, using its extras.""" -from typing import Optional import warnings +from typing import Optional from aiida.common.lang import type_check from aiida.plugins import DataFactory from aiida_pseudo.common.units import U -StructureData = DataFactory('core.structure') # pylint: disable=invalid-name +StructureData = DataFactory('core.structure') __all__ = ('RecommendedCutoffMixin',) @@ -146,7 +146,7 @@ def get_cutoff_stringencies(self) -> tuple: """ return tuple(self._get_cutoffs_dict().keys()) - def set_cutoffs(self, cutoffs: dict, stringency: str, unit: str = None) -> None: + def set_cutoffs(self, cutoffs: dict, stringency: str, unit: Optional[str] = None) -> None: """Set the recommended cutoffs for the pseudos in this family and a specified stringency. .. note: If, after the cutoffs have been set, there is only one stringency defined for the pseudo family, this @@ -188,7 +188,7 @@ def set_cutoffs(self, cutoffs: dict, stringency: str, unit: str = None) -> None: if len(cutoffs_dict) == 1: self.set_default_stringency(stringency) - def get_cutoffs(self, stringency: str = None) -> dict: + def get_cutoffs(self, stringency: Optional[str] = None) -> dict: """Return a set of cutoffs for the given stringency. :param stringency: optional stringency for which to retrieve the cutoffs. If not specified, the default @@ -234,11 +234,9 @@ def delete_cutoffs(self, stringency: str) -> None: if assign_new_default: if len(cutoffs_dict) == 0: - warning += ( - ' Since no other stringencies are defined for this family, no new default can be specified.' - ) + warning += ' Since no other stringencies are defined for this family, no new default can be specified.' elif len(cutoffs_dict) == 1: - final_stringency = list(cutoffs_dict.keys())[0] + final_stringency = next(iter(cutoffs_dict.keys())) self.set_default_stringency(final_stringency) warning += f' Setting `{final_stringency}` as the default since it is now the only defined stringency.' else: @@ -250,7 +248,7 @@ def delete_cutoffs(self, stringency: str) -> None: if len(warning) > 0: warnings.warn(warning) - def get_cutoffs_unit(self, stringency: str = None) -> str: + def get_cutoffs_unit(self, stringency: Optional[str] = None) -> str: """Return the cutoffs unit for the specified or family default stringency. :param stringency: optional stringency for which to retrieve the unit. If not specified, the default stringency @@ -298,7 +296,6 @@ def get_recommended_cutoffs(self, *, elements=None, structure=None, stringency=N cutoffs = self.get_cutoffs(stringency) for element in symbols: - if element not in cutoffs: raise ValueError(f'family does not contain a pseudo for element `{element}`.') diff --git a/tests/cli/params/test_types.py b/tests/cli/params/test_types.py index 3309b6c..78083a0 100644 --- a/tests/cli/params/test_types.py +++ b/tests/cli/params/test_types.py @@ -2,7 +2,6 @@ """Tests for the :mod:`~aiida_pseudo.cli.params.types` module.""" import click import pytest - from aiida_pseudo.cli.params import types from aiida_pseudo.groups.family import PseudoPotentialFamily diff --git a/tests/cli/test_family.py b/tests/cli/test_family.py index 15b382e..71c151a 100644 --- a/tests/cli/test_family.py +++ b/tests/cli/test_family.py @@ -1,18 +1,16 @@ # -*- coding: utf-8 -*- -# pylint: disable=unused-argument,redefined-outer-name """Tests for the command `aiida-pseudo family`.""" -from copy import deepcopy import json +from copy import deepcopy -from aiida.orm import Group -from numpy.testing import assert_almost_equal import pytest - +from aiida.orm import Group from aiida_pseudo.cli.family import cmd_family_cutoffs_set, cmd_family_show from aiida_pseudo.data.pseudo.upf import UpfData from aiida_pseudo.groups.family import CutoffsPseudoPotentialFamily, PseudoPotentialFamily from aiida_pseudo.groups.family.pseudo_dojo import PseudoDojoFamily from aiida_pseudo.groups.family.sssp import SsspFamily +from numpy.testing import assert_almost_equal @pytest.mark.usefixtures('clear_db') @@ -97,8 +95,8 @@ def test_family_cutoffs_set_unit(run_cli_command, get_pseudo_family, generate_cu @pytest.mark.parametrize( - 'family_cls,label', [(SsspFamily, 'SSSP/1.1/PBE/efficiency'), - (PseudoDojoFamily, 'PseudoDojo/0.4/PBE/SR/standard/psp8')] + 'family_cls,label', + [(SsspFamily, 'SSSP/1.1/PBE/efficiency'), (PseudoDojoFamily, 'PseudoDojo/0.4/PBE/SR/standard/psp8')], ) @pytest.mark.usefixtures('clear_db') def test_family_cutoffs_set_established( @@ -145,7 +143,6 @@ def test_family_show_recommended_cutoffs(clear_db, run_cli_command, get_pseudo_f # Test the command for default and explicit stringency for stringency in [None, 'high']: - if stringency is not None: result = run_cli_command(cmd_family_show, [family.label, '--stringency', stringency, '--raw']) else: diff --git a/tests/cli/test_install.py b/tests/cli/test_install.py index ae1e725..7c11784 100644 --- a/tests/cli/test_install.py +++ b/tests/cli/test_install.py @@ -1,14 +1,12 @@ # -*- coding: utf-8 -*- -# pylint: disable=redefined-outer-name """Tests for `aiida-pseudo install`.""" import contextlib import json import pathlib +import pytest from aiida.manage.configuration import Config from aiida.orm import QueryBuilder -import pytest - from aiida_pseudo.cli import cmd_install_family, cmd_install_pseudo_dojo, cmd_install_sssp, install from aiida_pseudo.data.pseudo.upf import UpfData from aiida_pseudo.groups.family import PseudoPotentialFamily @@ -70,9 +68,8 @@ def download_sssp( configuration: SsspConfiguration, filepath_archive: pathlib.Path, filepath_metadata: pathlib.Path, - traceback: bool = False + traceback: bool = False, ) -> None: - # pylint: disable=unused-argument """Download the pseudopotential archive and metadata for an SSSP configuration to a path on disk. :param configuration: the SSSP configuration to download. @@ -112,9 +109,8 @@ def download_pseudo_dojo( configuration: PseudoDojoConfiguration, filepath_archive: pathlib.Path, filepath_metadata: pathlib.Path, - traceback: bool = False + traceback: bool = False, ) -> None: - # pylint: disable=unused-argument """Download the pseudopotential archive and metadata for a PseudoDojo configuration to a path on disk. :param configuration: the PseudoDojo configuration to download. @@ -209,6 +205,7 @@ def test_install_family_url(run_cli_command, get_pseudo_archive, monkeypatch): def convert(*_, **__): from collections import namedtuple + Archive = namedtuple('Archive', ['content']) archive = Archive(get_pseudo_archive(fmt=fmt).read_bytes()) return archive @@ -358,7 +355,13 @@ def test_install_sssp_download_only_exists(run_monkeypatched_install_sssp, get_p def test_install_sssp_from_download(run_monkeypatched_install_sssp, configuration): """Test the ``aiida-pseudo install sssp`` command with the ``--from-download`` option.""" options = [ - '--download-only', '-v', configuration.version, '-x', configuration.functional, '-p', configuration.protocol + '--download-only', + '-v', + configuration.version, + '-x', + configuration.functional, + '-p', + configuration.protocol, ] result = run_monkeypatched_install_sssp(options=options) @@ -409,7 +412,17 @@ def test_install_pseudo_dojo_download_only_exists(run_monkeypatched_install_pseu get_pseudo_family(cls=PseudoDojoFamily, pseudo_type=UpfData, label=label) options = [ - '--download-only', '-v', version, '-x', functional, '-r', relativistic, '-p', protocol, '-f', pseudo_format + '--download-only', + '-v', + version, + '-x', + functional, + '-r', + relativistic, + '-p', + protocol, + '-f', + pseudo_format, ] result = run_monkeypatched_install_pseudo_dojo(options=options) @@ -427,7 +440,17 @@ def test_install_pseudo_dojo_from_download(run_monkeypatched_install_pseudo_dojo pseudo_format = 'jthxml' configuration = PseudoDojoConfiguration(version, functional, relativistic, protocol, pseudo_format) options = [ - '--download-only', '-v', version, '-x', functional, '-r', relativistic, '-p', protocol, '-f', pseudo_format + '--download-only', + '-v', + version, + '-x', + functional, + '-r', + relativistic, + '-p', + protocol, + '-f', + pseudo_format, ] result = run_monkeypatched_install_pseudo_dojo(options=options) diff --git a/tests/cli/test_list.py b/tests/cli/test_list.py index 4c85dd0..8e3d250 100644 --- a/tests/cli/test_list.py +++ b/tests/cli/test_list.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# pylint: disable=unused-argument """Tests for the command `aiida-pseudo list`.""" from aiida_pseudo.cli import cmd_list from aiida_pseudo.cli.list import PROJECTIONS_VALID @@ -34,7 +33,7 @@ def test_list_project(clear_db, run_cli_command, get_pseudo_family): # Test that all `PROJECTIONS_VALID` can actually be projected and don't except for option in ['-P', '--project']: - run_cli_command(cmd_list, [option] + list(PROJECTIONS_VALID)) + run_cli_command(cmd_list, [option, *list(PROJECTIONS_VALID)]) result = run_cli_command(cmd_list, ['--raw', '-P', 'label']) assert len(result.output_lines) == 1 diff --git a/tests/cli/test_root.py b/tests/cli/test_root.py index 173dc8e..3adb41c 100644 --- a/tests/cli/test_root.py +++ b/tests/cli/test_root.py @@ -6,11 +6,10 @@ import click import pytest - from aiida_pseudo.cli import cmd_root -def recurse_commands(command: click.Command, parents: list[str] = None): +def recurse_commands(command: click.Command, parents: list[str] | None = None): """Recursively return all subcommands that are part of ``command``. :param command: The click command to start with. @@ -21,13 +20,13 @@ def recurse_commands(command: click.Command, parents: list[str] = None): for command_name in command.commands: subcommand = command.get_command(None, command_name) if parents is not None: - subparents = parents + [command.name] + subparents = [*parents, command.name] else: subparents = [command.name] yield from recurse_commands(subcommand, subparents) if parents is not None: - yield parents + [command.name] + yield [*parents, command.name] else: yield [command.name] @@ -42,6 +41,6 @@ def test_commands_help_option(command, help_option): compared to a direct invocation on the command line. The invocation through ``invoke`` does not go through all the parent commands and so might not get all the necessary initializations. """ - result = subprocess.run(command + [help_option], check=False, capture_output=True, text=True) + result = subprocess.run([*command, help_option], check=False, capture_output=True, text=True) assert result.returncode == 0, result.stderr assert 'Usage:' in result.stdout diff --git a/tests/cli/test_utils.py b/tests/cli/test_utils.py index 7d90047..75f3874 100644 --- a/tests/cli/test_utils.py +++ b/tests/cli/test_utils.py @@ -1,12 +1,10 @@ # -*- coding: utf-8 -*- -# pylint: disable=unused-argument """Test the command line interface utilities.""" import shutil import tarfile import tempfile import pytest - from aiida_pseudo.cli.utils import attempt, create_family_from_archive from aiida_pseudo.groups.family import PseudoPotentialFamily @@ -35,7 +33,6 @@ def test_create_family_from_archive_incorrect_filetype(tmp_path): def test_create_family_from_archive_incorrect_format(tmp_path): """Test the `create_family_from_archive` utility function for invalid archive content.""" with tempfile.NamedTemporaryFile(suffix='.tar.gz') as filepath_archive: - with tarfile.open(filepath_archive.name, 'w:gz') as tar: tar.add(tmp_path, arcname='.') diff --git a/tests/conftest.py b/tests/conftest.py index a618a3f..0468071 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# pylint: disable=redefined-outer-name,unused-argument """Configuration and fixtures for unit test suite.""" import io import os @@ -7,14 +6,13 @@ import re import shutil -from aiida.plugins import DataFactory import click import pytest - +from aiida.plugins import DataFactory from aiida_pseudo.data.pseudo import PseudoPotentialData from aiida_pseudo.groups.family import CutoffsPseudoPotentialFamily, PseudoPotentialFamily -pytest_plugins = ['aiida.manage.tests.pytest_fixtures'] # pylint: disable=invalid-name +pytest_plugins = ['aiida.manage.tests.pytest_fixtures'] @pytest.fixture @@ -166,7 +164,7 @@ def _get_pseudo_family( elements=None, cutoffs_dict=None, unit=None, - default_stringency=None + default_stringency=None, ) -> PseudoPotentialFamily: """Return an instance of `PseudoPotentialFamily` or subclass containing the given elements. @@ -188,7 +186,7 @@ def _get_pseudo_family( # There is no actual pseudopotential file fixtures for the base class, so default back to `.upf` files extension = 'upf' else: - extension = pseudo_type.get_entry_point_name()[len('pseudo.'):] + extension = pseudo_type.get_entry_point_name()[len('pseudo.') :] dirpath = filepath_pseudos(extension) @@ -199,7 +197,7 @@ def _get_pseudo_family( family = cls.create_from_folder(tmp_path, label, pseudo_type=pseudo_type) if cutoffs_dict is not None and isinstance(family, CutoffsPseudoPotentialFamily): - default_stringency = default_stringency or list(cutoffs_dict.keys())[0] + default_stringency = default_stringency or next(iter(cutoffs_dict.keys())) for stringency, cutoff_values in cutoffs_dict.items(): family.set_cutoffs(cutoff_values, stringency, unit) family.set_default_stringency(default_stringency) @@ -216,7 +214,7 @@ def get_pseudo_archive(tmp_path, filepath_pseudos): def _get_pseudo_archive(fmt='gztar'): shutil.make_archive(tmp_path / 'archive', fmt, filepath_pseudos('upf')) # The created archive should be the only file in ``tmp_path`` so just get first entry from the iterator. - return list(tmp_path.iterdir())[0] + return next(iter(tmp_path.iterdir())) return _get_pseudo_archive diff --git a/tests/data/pseudo/test_common.py b/tests/data/pseudo/test_common.py index 1478f61..7c6fb04 100644 --- a/tests/data/pseudo/test_common.py +++ b/tests/data/pseudo/test_common.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- -# pylint: disable=redefined-outer-name """Tests that are common to all data plugins in the :py:mod:`~aiida_pseudo.data.pseudo` module.""" -from aiida import plugins import pytest +from aiida import plugins def get_entry_point_names(): diff --git a/tests/data/pseudo/test_jthxml.py b/tests/data/pseudo/test_jthxml.py index be9f170..8a6f763 100644 --- a/tests/data/pseudo/test_jthxml.py +++ b/tests/data/pseudo/test_jthxml.py @@ -1,12 +1,10 @@ # -*- coding: utf-8 -*- -# pylint: disable=redefined-outer-name """Tests for the :py:`~aiida_pseudo.data.pseudo.jthxml` module.""" import io import pathlib -from aiida.common.exceptions import ModificationNotAllowed import pytest - +from aiida.common.exceptions import ModificationNotAllowed from aiida_pseudo.data.pseudo import JthXmlData diff --git a/tests/data/pseudo/test_pseudo.py b/tests/data/pseudo/test_pseudo.py index 07d58ae..0bd9a03 100644 --- a/tests/data/pseudo/test_pseudo.py +++ b/tests/data/pseudo/test_pseudo.py @@ -1,15 +1,13 @@ # -*- coding: utf-8 -*- -# pylint: disable=redefined-outer-name """Tests for the :py:mod:`~aiida_pseudo.data.pseudo.pseudo` module.""" import io import pathlib +import pytest from aiida.common.exceptions import ModificationNotAllowed, StoringNotAllowed from aiida.common.files import md5_from_filelike from aiida.common.links import LinkType from aiida.orm import CalcJobNode -import pytest - from aiida_pseudo.data.pseudo import PseudoPotentialData, UpfData @@ -38,7 +36,7 @@ def test_constructor_source_types(source): def test_constructor_invalid(): """Test the constructor for invalid arguments.""" with pytest.raises(TypeError, match='missing 1 required positional argument'): - PseudoPotentialData() # pylint: disable=no-value-for-parameter + PseudoPotentialData() @pytest.mark.usefixtures('chdir_tmp_path') @@ -77,11 +75,14 @@ def test_constructor_filename(get_pseudo_potential_data, implicit, source_type): assert node.filename == explicit_filename -@pytest.mark.parametrize(('value, exception, pattern'), ( - (io.StringIO('content'), TypeError, r'`source` should be a `str` or `pathlib.Path` filepath .*'), - ('non-existing/path', FileNotFoundError, r'No such file or directory: .*'), - (pathlib.Path('non-existing/path'), FileNotFoundError, r'No such file or directory: .*'), -)) +@pytest.mark.parametrize( + ('value, exception, pattern'), + ( + (io.StringIO('content'), TypeError, r'`source` should be a `str` or `pathlib.Path` filepath .*'), + ('non-existing/path', FileNotFoundError, r'No such file or directory: .*'), + (pathlib.Path('non-existing/path'), FileNotFoundError, r'No such file or directory: .*'), + ), +) def test_prepare_source_excepts(value, exception, pattern): """Test the ``PseudoPotentialData.prepare_source`` method when it is supposed to except.""" with pytest.raises(exception, match=pattern): @@ -112,7 +113,7 @@ def test_store(): pseudo.store() pseudo.element = 'Ar' - pseudo.base.attributes.set(PseudoPotentialData._key_md5, md5_incorrect) # pylint: disable=protected-access + pseudo.base.attributes.set(PseudoPotentialData._key_md5, md5_incorrect) with pytest.raises(StoringNotAllowed, match=r'md5 does not match that of stored file:'): pseudo.store() diff --git a/tests/data/pseudo/test_psf.py b/tests/data/pseudo/test_psf.py index dcc46ba..309a33c 100644 --- a/tests/data/pseudo/test_psf.py +++ b/tests/data/pseudo/test_psf.py @@ -1,24 +1,25 @@ # -*- coding: utf-8 -*- -# pylint: disable=redefined-outer-name """Tests for the :py:`~aiida_pseudo.data.pseudo.psf` module.""" import io import pathlib -from aiida.common.exceptions import ModificationNotAllowed import pytest - +from aiida.common.exceptions import ModificationNotAllowed from aiida_pseudo.data.pseudo import PsfData from aiida_pseudo.data.pseudo.psf import parse_element -@pytest.mark.parametrize(('string', 'element'), [ - (b'c other\n', 'C'), - (b'C other\n', 'C'), - (b' C other\n', 'C'), - (b'ca other\n', 'Ca'), - (b'Ca other\n', 'Ca'), - (b' Ca other\n', 'Ca'), -]) +@pytest.mark.parametrize( + ('string', 'element'), + [ + (b'c other\n', 'C'), + (b'C other\n', 'C'), + (b' C other\n', 'C'), + (b'ca other\n', 'Ca'), + (b'Ca other\n', 'Ca'), + (b' Ca other\n', 'Ca'), + ], +) def test_parse_element(string, element): """Test the `parse_element` method.""" assert parse_element(io.BytesIO(string)) == element diff --git a/tests/data/pseudo/test_psml.py b/tests/data/pseudo/test_psml.py index 9e2a113..11ba011 100644 --- a/tests/data/pseudo/test_psml.py +++ b/tests/data/pseudo/test_psml.py @@ -1,12 +1,10 @@ # -*- coding: utf-8 -*- -# pylint: disable=redefined-outer-name """Tests for the :py:`~aiida_pseudo.data.pseudo.psml` module.""" import io import pathlib -from aiida.common.exceptions import ModificationNotAllowed import pytest - +from aiida.common.exceptions import ModificationNotAllowed from aiida_pseudo.data.pseudo import PsmlData diff --git a/tests/data/pseudo/test_psp8.py b/tests/data/pseudo/test_psp8.py index a1571cb..7522441 100644 --- a/tests/data/pseudo/test_psp8.py +++ b/tests/data/pseudo/test_psp8.py @@ -1,12 +1,10 @@ # -*- coding: utf-8 -*- -# pylint: disable=redefined-outer-name """Tests for the :py:`~aiida_pseudo.data.pseudo.psp8` module.""" import io import pathlib -from aiida.common.exceptions import ModificationNotAllowed import pytest - +from aiida.common.exceptions import ModificationNotAllowed from aiida_pseudo.data.pseudo import Psp8Data diff --git a/tests/data/pseudo/test_upf.py b/tests/data/pseudo/test_upf.py index 6830d0d..0d233d6 100644 --- a/tests/data/pseudo/test_upf.py +++ b/tests/data/pseudo/test_upf.py @@ -1,12 +1,10 @@ # -*- coding: utf-8 -*- -# pylint: disable=redefined-outer-name """Tests for the :py:`~aiida_pseudo.data.pseudo.upf` module.""" import io import pathlib -from aiida.common.exceptions import ModificationNotAllowed import pytest - +from aiida.common.exceptions import ModificationNotAllowed from aiida_pseudo.data.pseudo import UpfData from aiida_pseudo.data.pseudo.upf import parse_z_valence @@ -65,7 +63,8 @@ def test_set_file(filepath_pseudos, get_pseudo_potential_data): @pytest.mark.parametrize( - 'content', ( + 'content', + ( 'z_valence="1"', 'z_valence="1.0"', 'z_valence="1.000"', @@ -75,7 +74,7 @@ def test_set_file(filepath_pseudos, get_pseudo_potential_data): 'z_valence=" 1"', 'z_valence="1 "', '1.0 Z valence', - ) + ), ) def test_parse_z_valence(content): """Test the ``parse_z_valence`` method.""" diff --git a/tests/data/pseudo/test_vps.py b/tests/data/pseudo/test_vps.py index 3a3a6f0..55c2b56 100644 --- a/tests/data/pseudo/test_vps.py +++ b/tests/data/pseudo/test_vps.py @@ -1,12 +1,10 @@ # -*- coding: utf-8 -*- -# pylint: disable=redefined-outer-name """Tests for the :py:`~aiida_pseudo.data.pseudo.vps` module.""" import io import pathlib -from aiida.common.exceptions import ModificationNotAllowed import pytest - +from aiida.common.exceptions import ModificationNotAllowed from aiida_pseudo.data.pseudo import VpsData from aiida_pseudo.data.pseudo.vps import parse_xc_type, parse_z_valence @@ -65,13 +63,13 @@ def test_set_file(filepath_pseudos, get_pseudo_potential_data): @pytest.mark.parametrize( - 'content', ( + 'content', + ( 'valence.electron 1.0', 'valence.electron 1', 'VALENCE.ELECTRON 1.0', - 'Valence.Electron 1.0' - 'valence.electron 1.0 # Z valence', - ) + 'Valence.Electron 1.0' 'valence.electron 1.0 # Z valence', + ), ) def test_parse_z_valence(content): """Test the ``parse_z_valence`` method.""" diff --git a/tests/groups/family/test_pseudo.py b/tests/groups/family/test_pseudo.py index f0e68df..b9e1f9b 100644 --- a/tests/groups/family/test_pseudo.py +++ b/tests/groups/family/test_pseudo.py @@ -1,19 +1,17 @@ # -*- coding: utf-8 -*- -# pylint: disable=redefined-outer-name """Tests for the `PseudoPotentialFamily` class.""" import shutil +import pytest from aiida.common import exceptions from aiida.orm import QueryBuilder -import pytest - from aiida_pseudo.data.pseudo import PseudoPotentialData from aiida_pseudo.groups.family.pseudo import PseudoPotentialFamily def test_type_string(): """Verify the `_type_string` class attribute is correctly set to the corresponding entry point name.""" - assert PseudoPotentialFamily._type_string == 'pseudo.family' # pylint: disable=protected-access + assert PseudoPotentialFamily._type_string == 'pseudo.family' def test_pseudo_types(): diff --git a/tests/groups/family/test_pseudo_dojo.py b/tests/groups/family/test_pseudo_dojo.py index ae21f03..3692e97 100644 --- a/tests/groups/family/test_pseudo_dojo.py +++ b/tests/groups/family/test_pseudo_dojo.py @@ -1,15 +1,13 @@ # -*- coding: utf-8 -*- -# pylint: disable=unused-argument,pointless-statement """Tests for the `PseudoDojoFamily` class.""" import pytest - from aiida_pseudo.data.pseudo import JthXmlData, PsmlData, Psp8Data, UpfData from aiida_pseudo.groups.family import PseudoDojoConfiguration, PseudoDojoFamily def test_type_string(clear_db): """Verify the `_type_string` class attribute is correctly set to the corresponding entry point name.""" - assert PseudoDojoFamily._type_string == 'pseudo.family.pseudo_dojo' # pylint: disable=protected-access + assert PseudoDojoFamily._type_string == 'pseudo.family.pseudo_dojo' def test_pseudo_types(): diff --git a/tests/groups/family/test_sssp.py b/tests/groups/family/test_sssp.py index ff0e06c..c1d3efe 100644 --- a/tests/groups/family/test_sssp.py +++ b/tests/groups/family/test_sssp.py @@ -1,15 +1,13 @@ # -*- coding: utf-8 -*- -# pylint: disable=unused-argument,pointless-statement """Tests for the `SsspFamily` class.""" import pytest - from aiida_pseudo.data.pseudo.upf import UpfData from aiida_pseudo.groups.family import SsspConfiguration, SsspFamily def test_type_string(clear_db): """Verify the `_type_string` class attribute is correctly set to the corresponding entry point name.""" - assert SsspFamily._type_string == 'pseudo.family.sssp' # pylint: disable=protected-access + assert SsspFamily._type_string == 'pseudo.family.sssp' def test_pseudo_types(): diff --git a/tests/groups/mixins/test_cutoffs.py b/tests/groups/mixins/test_cutoffs.py index 3e31668..82b7a0d 100644 --- a/tests/groups/mixins/test_cutoffs.py +++ b/tests/groups/mixins/test_cutoffs.py @@ -1,10 +1,8 @@ # -*- coding: utf-8 -*- -# pylint: disable=redefined-outer-name """Tests for the :mod:`aiida_pseudo.groups.mixins.cutoffs` module.""" import copy import pytest - from aiida_pseudo.groups.family import CutoffsPseudoPotentialFamily @@ -12,25 +10,25 @@ def test_get_cutoffs_dict(get_pseudo_family, generate_cutoffs_dict): """Test the ``CutoffsPseudoPotentialFamily._get_cutoffs_dict`` method.""" family = get_pseudo_family(cls=CutoffsPseudoPotentialFamily) - assert family._get_cutoffs_dict() == {} # pylint: disable=protected-access + assert family._get_cutoffs_dict() == {} for stringency, cutoffs in generate_cutoffs_dict(family).items(): family.set_cutoffs(cutoffs, stringency) - assert family._get_cutoffs_dict() == generate_cutoffs_dict(family) # pylint: disable=protected-access + assert family._get_cutoffs_dict() == generate_cutoffs_dict(family) @pytest.mark.usefixtures('clear_db') def test_get_cutoffs_unit_dict(get_pseudo_family, generate_cutoffs_dict): """Test the ``CutoffsPseudoPotentialFamily._get_cutoffs_unit_dict`` method.""" family = get_pseudo_family(cls=CutoffsPseudoPotentialFamily) - assert family._get_cutoffs_unit_dict() == {} # pylint: disable=protected-access + assert family._get_cutoffs_unit_dict() == {} default_units_dict = {} for stringency, cutoffs in generate_cutoffs_dict(family).items(): family.set_cutoffs(cutoffs, stringency) default_units_dict[stringency] = CutoffsPseudoPotentialFamily.DEFAULT_UNIT - assert family._get_cutoffs_unit_dict() == default_units_dict # pylint: disable=protected-access + assert family._get_cutoffs_unit_dict() == default_units_dict @pytest.mark.usefixtures('clear_db') @@ -185,7 +183,6 @@ def test_set_cutoffs_multiple_units(get_pseudo_family, generate_cutoffs): cutoffs_unit_dict[stringency] = unit assert family.get_cutoffs_unit(stringency) == unit - # pylint: disable=protected-access assert family._get_cutoffs_unit_dict() == {'default': 'hartree', 'rydberg': 'Ry'}