diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 550f410..d41b82f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: timeout-minutes: 30 strategy: matrix: - python-version: ['3.10'] + python-version: ['3.12'] aiida-version: ['stable'] services: postgres: @@ -31,10 +31,10 @@ jobs: - 5672:5672 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} @@ -44,7 +44,6 @@ jobs: pip install -r requirements.txt # Change plugin_name to test we're not specific to "aiida-diff" cookiecutter --no-input . plugin_name=${PLUGIN_NAME} - pip install -e ${PLUGIN_NAME}[testing] env: PLUGIN_NAME: aiida-ck @@ -55,17 +54,17 @@ jobs: PLUGIN_NAME: aiida-ck run: | cd ${PLUGIN_NAME} - pytest -v + hatch test -v docs: runs-on: ubuntu-latest timeout-minutes: 15 steps: - - uses: actions/checkout@v2 - - name: Set up Python 3.10 - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - name: Set up Python 3.12 + uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: "3.12" - name: Install python dependencies env: PLUGIN_NAME: aiida-ck @@ -74,21 +73,22 @@ jobs: pip install -r requirements.txt # Change plugin_name to test we're not specific to "aiida-diff" cookiecutter --no-input . plugin_name=${PLUGIN_NAME} - pip install -e ${PLUGIN_NAME}[docs] - name: Build docs env: PLUGIN_NAME: aiida-ck - run: cd ${PLUGIN_NAME}/docs && make + run: | + cd ${PLUGIN_NAME} + hatch run docs:build - pre-commit: + static-analysis: runs-on: ubuntu-latest timeout-minutes: 15 steps: - - uses: actions/checkout@v2 - - name: Set up Python 3.10 - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - name: Set up Python 3.12 + uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: "3.12" - name: Install python dependencies env: PLUGIN_NAME: aiida-ck @@ -97,13 +97,9 @@ jobs: pip install -r requirements.txt # Change plugin_name to test we're not specific to "aiida-diff" cookiecutter --no-input . plugin_name=${PLUGIN_NAME} - pip install -e ${PLUGIN_NAME}[pre-commit,docs,testing] - - name: Run pre-commit + - name: Run formatter and linter env: PLUGIN_NAME: aiida-ck run: | cd ${PLUGIN_NAME} - git init - git add -A - pre-commit install - pre-commit run --all-files || ( git status --short ; git diff ; exit 1 ) + hatch fmt diff --git a/hooks/post_gen_project.sh b/hooks/post_gen_project.sh index c12ffdc..71bd95f 100644 --- a/hooks/post_gen_project.sh +++ b/hooks/post_gen_project.sh @@ -1,9 +1,11 @@ #!/bin/bash -if [ -x "$(command -v black)" ]; then - echo "Running black on {{ cookiecutter.plugin_name }}" - black '../{{ cookiecutter.plugin_name }}/' +if [ -x "$(command -v hatch)" ]; then + echo "Running hatch on {{ cookiecutter.plugin_name }}" + # For some reason we need to invoke the formatting twice to be effective + hatch fmt || true + hatch fmt || true else - echo "black not found. 'pip install black' to automatically \ + echo "hatch not found. 'pip install hatch' to automatically \ run formatter on {{ cookiecutter.plugin_name }}" fi diff --git a/requirements.txt b/requirements.txt index 92e49d5..18a2a75 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ cookiecutter -black==22.12.0 +hatch~=1.12.0 diff --git a/tox.ini b/tox.ini index 8435db0..68886d1 100644 --- a/tox.ini +++ b/tox.ini @@ -19,4 +19,4 @@ commands = cookiecutter --no-input . plugin_name={env:PLUGIN_NAME} git init -b main {env:PLUGIN_NAME} cd {env:PLUGIN_NAME} && git add -A - pip install -e {env:PLUGIN_NAME}[testing,docs,pre-commit] + pip install -e {env:PLUGIN_NAME} diff --git a/update-aiida-diff.sh b/update-aiida-diff.sh index d561494..7f57ac1 100755 --- a/update-aiida-diff.sh +++ b/update-aiida-diff.sh @@ -11,8 +11,3 @@ else fi cookiecutter --no-input -f . version=1.2.0 -pip install -e aiida-diff[docs,pre-commit,testing] -cd aiida-diff -git init && git add -A -pre-commit install -pre-commit run diff --git a/update-template-formatting.sh b/update-template-formatting.sh new file mode 100755 index 0000000..3ea2149 --- /dev/null +++ b/update-template-formatting.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Creates plugin cutter with unique labels so we can format and then reverses +# the ids to the cookiecutter identifiers and git apply the diff +set -e + +# clean directory contents if exist (but don't delete .git directory) +if [ -d aiida-diff ]; then + rm -rf cookiecutter_plugin_name +fi +cookiecutter --accept-hooks no --no-input -f . plugin_name=cookiecutter_plugin_name module_name=cookiecutter_module_name short_description=cookiecutter_short_description entry_point_prefix=cookiecutter_entry_point_prefix version=0.0.0-dev author=cookiecutter_author year=cookiecutter_year + +cd cookiecutter_plugin_name +git init && git add -A && git commit -am 'init' +hatch fmt || true +# takes the diff and applies it on the cookie cutter template +git diff > patch +cp patch .. +cd .. +# git diff replacements of specified labels with cookiecutter identifiers +sed -i 's/ a\// a\/\{\{cookiecutter.plugin_name\}\}\//g' patch +sed -i 's/ b\// b\/\{\{cookiecutter.plugin_name\}\}\//g' patch +sed -i 's/cookiecutter_plugin_name/\{\{cookiecutter.plugin_name\}\}/g' patch +sed -i 's/cookiecutter_module_name/\{\{cookiecutter.module_name\}\}/g' patch +sed -i 's/cookiecutter_entry_point_prefix/\{\{cookiecutter.entry_point_prefix\}\}/g' patch +sed -i 's/cookiecutter_short_description/\{\{cookiecutter.short_description\}\}/g' patch +sed -i 's/0\.0\.0-dev/\{\{cookiecutter.version\}\}/g' patch +sed -i 's/cookiecutter_author/\{\{cookiecutter.author\}\}/g' patch +sed -i 's/cookiecutter_year/\{\{cookiecutter.year\}\}/g' patch +git apply --reject --whitespace=fix patch diff --git a/{{cookiecutter.plugin_name}}/.github/install_aiida_github.sh b/{{cookiecutter.plugin_name}}/.github/install_aiida_github.sh deleted file mode 100755 index dc56b09..0000000 --- a/{{cookiecutter.plugin_name}}/.github/install_aiida_github.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -git clone https://github.com/aiidateam/aiida_core ../aiida_core -cd ../aiida_core -git checkout $AIIDA_DEVELOP_GIT_HASH -pip install -e .[docs,pre-commit,testing] -cd ${TRAVIS_BUILD_DIR} diff --git a/{{cookiecutter.plugin_name}}/.github/workflows/ci.yml b/{{cookiecutter.plugin_name}}/.github/workflows/ci.yml index 7e7278c..eb85c6f 100644 --- a/{{cookiecutter.plugin_name}}/.github/workflows/ci.yml +++ b/{{cookiecutter.plugin_name}}/.github/workflows/ci.yml @@ -1,6 +1,11 @@ name: ci -on: [push, pull_request] +on: + push: + # only pushes to main trigger + branches: [main] + pull_request: + # always triggered jobs: @@ -9,7 +14,7 @@ jobs: timeout-minutes: 30 strategy: matrix: - python-version: ['3.10'] + python-version: ['3.12'] aiida-version: ['stable'] services: @@ -32,54 +37,49 @@ jobs: - 5672:5672 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ '{{ matrix.python-version }}' }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ '{{ matrix.python-version }}' }} - - - name: Install python dependencies + - name: Install project manager run: | - pip install --upgrade pip - pip install -e .[testing] - + pip install hatch - name: Run test suite env: - # show timings of tests PYTEST_ADDOPTS: "--durations=0" - run: pytest --cov {{cookiecutter.module_name}} --cov-append . + run: | + hatch test --cover docs: runs-on: ubuntu-latest timeout-minutes: 15 steps: - - uses: actions/checkout@v2 - - name: Set up Python 3.10 - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - name: Set up Python ${{ '{{ matrix.python-version }}' }} + uses: actions/setup-python@v5 with: - python-version: "3.10" - - name: Install python dependencies + python-version: ${{ '{{ matrix.python-version }}' }} + - name: Install project manager run: | - pip install --upgrade pip - pip install -e .[docs] + pip install hatch - name: Build docs - run: cd docs && make + run: | + hatch run docs:build - pre-commit: + static-analysis: runs-on: ubuntu-latest timeout-minutes: 15 steps: - - uses: actions/checkout@v2 - - name: Set up Python 3.10 - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - name: Set up Python ${{ '{{ matrix.python-version }}' }} + uses: actions/setup-python@v5 with: - python-version: "3.10" - - name: Install python dependencies + python-version: ${{ '{{ matrix.python-version }}' }} + - name: Install project manager run: | - pip install --upgrade pip - pip install -e .[pre-commit,docs,testing] - - name: Run pre-commit + pip install hatch + - name: Run formatter and linter run: | - pre-commit install - pre-commit run --all-files || ( git status --short ; git diff ; exit 1 ) + hatch fmt --check diff --git a/{{cookiecutter.plugin_name}}/.github/workflows/publish-on-pypi.yml b/{{cookiecutter.plugin_name}}/.github/workflows/publish-on-pypi.yml index 75088d5..97268e5 100644 --- a/{{cookiecutter.plugin_name}}/.github/workflows/publish-on-pypi.yml +++ b/{{cookiecutter.plugin_name}}/.github/workflows/publish-on-pypi.yml @@ -15,19 +15,19 @@ jobs: - name: Checkout repository uses: actions/checkout@v2 - - name: Set up Python 3.10 - uses: actions/setup-python@v1 + - name: Set up Python 3.12 + uses: actions/setup-python@v5 with: - python-version: '3.10' + python-version: '3.12' - - name: Install flit + - name: Install hatch run: | python -m pip install --upgrade pip - python -m pip install flit~=3.4 + python -m pip install hatch~=1.12.0 - name: Build and publish run: | - flit publish + hatch publish env: - FLIT_USERNAME: __token__ - FLIT_PASSWORD: ${{ '{{ secrets.pypi_token }}' }} + HATCH_INDEX_USER: __token__ + HATCH_INDEX_AUTH: ${{ '{{ secrets.pypi_token }}' }} diff --git a/{{cookiecutter.plugin_name}}/.pre-commit-config.yaml b/{{cookiecutter.plugin_name}}/.pre-commit-config.yaml index ad3236b..4d839fd 100644 --- a/{{cookiecutter.plugin_name}}/.pre-commit-config.yaml +++ b/{{cookiecutter.plugin_name}}/.pre-commit-config.yaml @@ -1,39 +1,13 @@ -# Install pre-commit hooks via: -# pre-commit install repos: -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 - hooks: - - id: end-of-file-fixer - - id: mixed-line-ending - - id: trailing-whitespace - - id: check-json - -- repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 - hooks: - - id: pyupgrade - args: ["--py37-plus"] - -- repo: https://github.com/PyCQA/isort - rev: 5.12.0 - hooks: - - id: isort - -- repo: https://github.com/psf/black - rev: 22.12.0 - hooks: - - id: black - - repo: local hooks: - - id: pylint + - id: format + name: format + entry: hatch fmt -f + language: system + types: [python] + - id: lint + name: lint + entry: hatch fmt -l language: system - types: [file, python] - name: pylint - description: "This hook runs the pylint static code analyzer" - exclude: &exclude_files > - (?x)^( - docs/.*| - )$ - entry: pylint + types: [python] diff --git a/{{cookiecutter.plugin_name}}/CONTRIBUTING.md b/{{cookiecutter.plugin_name}}/CONTRIBUTING.md new file mode 100644 index 0000000..9292117 --- /dev/null +++ b/{{cookiecutter.plugin_name}}/CONTRIBUTING.md @@ -0,0 +1,52 @@ +### Tests + +Tests can be run in different ways +``` +hatch test # Run tests whith your current python version +hatch test --python 3.9 # Run tests for python version 3.9 +hatch test --show # See all defined test environment +hatch test --all # Run tests for all test environments +hatch test --coverage # Run tests with coverage +``` +You can add arbitrary flags to pytest at the end of the command. For example to run the tests in debug mode use +``` +hatch test --pdb +``` +We use ipdb as debugger backend for autocompletion. + +### Static code analysis + +To check the formatting and linting run +``` +hatch fmt --check +``` +If you want to automatically fix errors that are fixable run +``` +hatch fmt +``` +If you want to run this command before each commit, please install the pre-commit hook +``` +pip install .[pre-commit] +pre-commit install +``` +You can also run the linter and formatter separately +``` +hatch fmt --formatter +hatch fmt --linter +``` + +### Building the docs + +Please run +``` +hatch run docs:build +``` + +### Build and publishing package + +To build and publish a package please use +``` +hatch build +hatch publish -r test # test pypi +hatch publish # pypi +``` diff --git a/{{cookiecutter.plugin_name}}/conftest.py b/{{cookiecutter.plugin_name}}/conftest.py index 74752a2..e696b9d 100644 --- a/{{cookiecutter.plugin_name}}/conftest.py +++ b/{{cookiecutter.plugin_name}}/conftest.py @@ -1,4 +1,5 @@ """pytest fixtures for simplified testing.""" + import pytest pytest_plugins = ["aiida.manage.tests.pytest_fixtures"] diff --git a/{{cookiecutter.plugin_name}}/docs/source/conf.py b/{{cookiecutter.plugin_name}}/docs/source/conf.py index 6a6a582..522cf1c 100755 --- a/{{cookiecutter.plugin_name}}/docs/source/conf.py +++ b/{{cookiecutter.plugin_name}}/docs/source/conf.py @@ -14,15 +14,14 @@ import sys import time +import {{cookiecutter.module_name}} from aiida import load_profile from aiida.storage.sqlite_temp import SqliteTempBackend -import {{cookiecutter.module_name}} - # -- AiiDA-related setup -------------------------------------------------- # Load AiiDA profile -temp_profile = SqliteTempBackend.create_profile('temp-profile') +temp_profile = SqliteTempBackend.create_profile("temp-profile") load_profile(temp_profile, allow_switch=True) # -- General configuration ------------------------------------------------ @@ -68,9 +67,7 @@ current_year = str(time.localtime().tm_year) copyright_year_string = ( - current_year - if current_year == copyright_first_year - else f"{copyright_first_year}-{current_year}" + current_year if current_year == copyright_first_year else f"{copyright_first_year}-{current_year}" ) # pylint: disable=redefined-builtin copyright = f"{copyright_year_string}, {copyright_owners}. All rights reserved" @@ -185,6 +182,7 @@ # We should ignore any python built-in exception, for instance nitpick_ignore = [ ("py:class", "Logger"), + ("py:class", "QbFields"), # Warning started to appear with aiida 2.6 ] @@ -198,7 +196,7 @@ def run_apidoc(_): """ source_dir = os.path.abspath(os.path.dirname(__file__)) apidoc_dir = os.path.join(source_dir, "apidoc") - package_dir = os.path.join(source_dir, os.pardir, os.pardir, "{{cookiecutter.module_name}}") + package_dir = os.path.join(source_dir, os.pardir, os.pardir, "src", "{{cookiecutter.module_name}}") # In #1139, they suggest the route below, but this ended up # calling sphinx-build, not sphinx-apidoc @@ -223,9 +221,7 @@ def run_apidoc(_): # See https://stackoverflow.com/a/30144019 env = os.environ.copy() - env[ - "SPHINX_APIDOC_OPTIONS" - ] = "members,special-members,private-members,undoc-members,show-inheritance" + env["SPHINX_APIDOC_OPTIONS"] = "members,special-members,private-members,undoc-members,show-inheritance" subprocess.check_call([cmd_path] + options, env=env) diff --git a/{{cookiecutter.plugin_name}}/examples/example_01.py b/{{cookiecutter.plugin_name}}/examples/example_01.py index 85e2d4b..a9ec4df 100644 --- a/{{cookiecutter.plugin_name}}/examples/example_01.py +++ b/{{cookiecutter.plugin_name}}/examples/example_01.py @@ -3,13 +3,12 @@ Usage: ./example_01.py """ + from os import path import click - from aiida import cmdline, engine from aiida.plugins import CalculationFactory, DataFactory - from {{cookiecutter.module_name}} import helpers INPUT_DIR = path.join(path.dirname(path.realpath(__file__)), "input_files") @@ -23,15 +22,17 @@ def test_run({{cookiecutter.entry_point_prefix}}_code): if not {{cookiecutter.entry_point_prefix}}_code: # get code computer = helpers.get_computer() - {{cookiecutter.entry_point_prefix}}_code = helpers.get_code(entry_point="{{cookiecutter.entry_point_prefix}}", computer=computer) + {{cookiecutter.entry_point_prefix}}_code = helpers.get_code( + entry_point="{{cookiecutter.entry_point_prefix}}", computer=computer + ) # Prepare input parameters - DiffParameters = DataFactory("{{cookiecutter.entry_point_prefix}}") - parameters = DiffParameters({"ignore-case": True}) + diff_parameters = DataFactory("{{cookiecutter.entry_point_prefix}}") + parameters = diff_parameters({"ignore-case": True}) - SinglefileData = DataFactory("core.singlefile") - file1 = SinglefileData(file=path.join(INPUT_DIR, "file1.txt")) - file2 = SinglefileData(file=path.join(INPUT_DIR, "file2.txt")) + singlefile_data = DataFactory("core.singlefile") + file1 = singlefile_data(file=path.join(INPUT_DIR, "file1.txt")) + file2 = singlefile_data(file=path.join(INPUT_DIR, "file2.txt")) # set up calculation inputs = { diff --git a/{{cookiecutter.plugin_name}}/pyproject.toml b/{{cookiecutter.plugin_name}}/pyproject.toml index 45d78c0..d55889f 100644 --- a/{{cookiecutter.plugin_name}}/pyproject.toml +++ b/{{cookiecutter.plugin_name}}/pyproject.toml @@ -1,12 +1,11 @@ [build-system] -# build the package with [flit](https://flit.readthedocs.io) -requires = ["flit_core >=3.4,<4"] -build-backend = "flit_core.buildapi" +requires = ["hatchling"] +build-backend = "hatchling.build" [project] # See https://www.python.org/dev/peps/pep-0621/ name = "{{cookiecutter.plugin_name}}" -dynamic = ["version"] # read from {{cookiecutter.module_name}}/__init__.py +dynamic = ["version"] # read from {{cookiecutter.module_name}}/src/__init__.py description = "{{cookiecutter.short_description}}" {%- if cookiecutter.contact_email %} authors = [{name = "{{cookiecutter.author}}", email = "{{cookiecutter.contact_email}}"}] @@ -24,26 +23,15 @@ classifiers = [ "Framework :: AiiDA" ] keywords = ["aiida", "plugin"] -requires-python = ">=3.7" +requires-python = ">=3.9" dependencies = [ "aiida-core>={{cookiecutter.aiida_min_version}},<3", "voluptuous" ] -[project.urls] -Source = "https://github.com/{{cookiecutter.github_user}}/{{cookiecutter.repo_name}}" - [project.optional-dependencies] -testing = [ - "pgtest~=1.3.1", - "wheel~=0.31", - "coverage[toml]", - "pytest~=6.0", - "pytest-cov" -] pre-commit = [ - "pre-commit~=2.2", - "pylint~=2.15.10" + 'pre-commit~=3.5', ] docs = [ "sphinx", @@ -53,6 +41,9 @@ docs = [ "markupsafe<2.1" ] +[project.urls] +Source = "https://github.com/{{cookiecutter.github_user}}/{{cookiecutter.repo_name}}" + [project.entry-points."aiida.data"] "{{cookiecutter.entry_point_prefix}}" = "{{cookiecutter.module_name}}.data:DiffParameters" @@ -65,22 +56,10 @@ docs = [ [project.entry-points."aiida.cmdline.data"] "{{cookiecutter.entry_point_prefix}}" = "{{cookiecutter.module_name}}.cli:data_cli" -[tool.flit.module] -name = "{{cookiecutter.module_name}}" - -[tool.pylint.format] -max-line-length = 125 - -[tool.pylint.messages_control] -disable = [ - "too-many-ancestors", - "invalid-name", - "duplicate-code", -] - [tool.pytest.ini_options] # Configuration for [pytest](https://docs.pytest.org) python_files = "test_*.py example_*.py" +addopts = "--pdbcls=IPython.terminal.debugger:TerminalPdb" filterwarnings = [ "ignore::DeprecationWarning:aiida:", "ignore:Creating AiiDA configuration folder:", @@ -88,39 +67,84 @@ filterwarnings = [ "ignore::DeprecationWarning:yaml:", ] + [tool.coverage.run] # Configuration of [coverage.py](https://coverage.readthedocs.io) # reporting which lines of your plugin are covered by tests -source=["{{cookiecutter.module_name}}"] - -[tool.isort] -# Configuration of [isort](https://isort.readthedocs.io) -line_length = 120 -force_sort_within_sections = true -sections = ['FUTURE', 'STDLIB', 'THIRDPARTY', 'AIIDA', 'FIRSTPARTY', 'LOCALFOLDER'] -known_aiida = ['aiida'] - -[tool.tox] -legacy_tox_ini = """ -[tox] -envlist = py38 - -[testenv] -usedevelop=True - -[testenv:py{37,38,39,310}] -description = Run the test suite against a python version -extras = testing -commands = pytest {posargs} - -[testenv:pre-commit] -description = Run the pre-commit checks -extras = pre-commit -commands = pre-commit run {posargs} - -[testenv:docs] -description = Build the documentation -extras = docs -commands = sphinx-build -nW --keep-going -b html {posargs} docs/source docs/build/html -commands_post = echo "open file://{toxinidir}/docs/build/html/index.html" -""" +source = ["src/{{cookiecutter.module_name}}"] + +[tool.ruff] +line-length = 120 + +[tool.ruff.lint] +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 + 'RUF005', # Consider iterable unpacking instead of concatenation + 'RUF012' # Mutable class attributes should be annotated with `typing.ClassVar` +] +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 +] + +## Hatch configurations + +[tool.hatch.version] +path = "src/{{cookiecutter.module_name}}/__init__.py" + +[tool.hatch.envs.hatch-test] +dependencies = [ + 'pgtest~=1.3,>=1.3.1', + 'coverage~=7.0', + 'pytest~=7.0', + "pytest-cov~=4.1", + "ipdb" +] + +[tool.hatch.envs.hatch-test.scripts] +# These are the efault scripts provided by hatch. +# The have been copied to make the execution more transparent + +# This command is run with the command `hatch test` +run = "pytest{env:HATCH_TEST_ARGS:} {args}" +# The three commands below are run with the command `hatch test --coverage` +run-cov = "coverage run -m pytest{env:HATCH_TEST_ARGS:} {args}" +cov-combine = "coverage combine" +cov-report = "coverage report" + +[[tool.hatch.envs.hatch-test.matrix]] +python = ["3.9", "3.10", "3.11", "3.12"] + +[tool.hatch.envs.hatch-static-analysis] +dependencies = ["ruff==0.4.3"] + +[tool.hatch.envs.hatch-static-analysis.scripts] +# Fixes are executed with `hatch fmt`. +# Checks are executed with `hatch fmt --check`. + +format-check = "ruff format --check --config pyproject.toml {args:.}" +format-fix = "ruff format --config pyproject.toml {args:.}" +lint-check = "ruff check --config pyproject.toml {args:.}" +lint-fix = "ruff check --config pyproject.toml --fix --exit-non-zero-on-fix --show-fixes {args:.}" + +[tool.hatch.envs.docs] +features = ["docs"] + +[tool.hatch.envs.docs.scripts] +build = [ + "make -C docs" +] diff --git a/{{cookiecutter.plugin_name}}/{{cookiecutter.module_name}}/__init__.py b/{{cookiecutter.plugin_name}}/src/{{cookiecutter.module_name}}/__init__.py similarity index 100% rename from {{cookiecutter.plugin_name}}/{{cookiecutter.module_name}}/__init__.py rename to {{cookiecutter.plugin_name}}/src/{{cookiecutter.module_name}}/__init__.py diff --git a/{{cookiecutter.plugin_name}}/{{cookiecutter.module_name}}/calculations.py b/{{cookiecutter.plugin_name}}/src/{{cookiecutter.module_name}}/calculations.py similarity index 88% rename from {{cookiecutter.plugin_name}}/{{cookiecutter.module_name}}/calculations.py rename to {{cookiecutter.plugin_name}}/src/{{cookiecutter.module_name}}/calculations.py index 8a4495c..7574183 100644 --- a/{{cookiecutter.plugin_name}}/{{cookiecutter.module_name}}/calculations.py +++ b/{{cookiecutter.plugin_name}}/src/{{cookiecutter.module_name}}/calculations.py @@ -3,6 +3,7 @@ Register calculations via the "aiida.calculations" entry point in setup.json. """ + from aiida.common import datastructures from aiida.engine import CalcJob from aiida.orm import SinglefileData @@ -31,20 +32,14 @@ def define(cls, spec): spec.inputs["metadata"]["options"]["parser_name"].default = "{{cookiecutter.entry_point_prefix}}" # new ports - spec.input( - "metadata.options.output_filename", valid_type=str, default="patch.diff" - ) + spec.input("metadata.options.output_filename", valid_type=str, default="patch.diff") spec.input( "parameters", valid_type=DiffParameters, help="Command line parameters for diff", ) - spec.input( - "file1", valid_type=SinglefileData, help="First file to be compared." - ) - spec.input( - "file2", valid_type=SinglefileData, help="Second file to be compared." - ) + spec.input("file1", valid_type=SinglefileData, help="First file to be compared.") + spec.input("file2", valid_type=SinglefileData, help="Second file to be compared.") spec.output( "{{cookiecutter.entry_point_prefix}}", valid_type=SinglefileData, diff --git a/{{cookiecutter.plugin_name}}/{{cookiecutter.module_name}}/cli.py b/{{cookiecutter.plugin_name}}/src/{{cookiecutter.module_name}}/cli.py similarity index 91% rename from {{cookiecutter.plugin_name}}/{{cookiecutter.module_name}}/cli.py rename to {{cookiecutter.plugin_name}}/src/{{cookiecutter.module_name}}/cli.py index d4ce642..fe56b5e 100644 --- a/{{cookiecutter.plugin_name}}/{{cookiecutter.module_name}}/cli.py +++ b/{{cookiecutter.plugin_name}}/src/{{cookiecutter.module_name}}/cli.py @@ -5,10 +5,10 @@ directly into the 'verdi' command by using AiiDA-specific entry points like "aiida.cmdline.data" (both in the setup.json file). """ + import sys import click - from aiida.cmdline.commands.cmd_data import verdi_data from aiida.cmdline.params.types import DataParamType from aiida.cmdline.utils import decorators @@ -28,16 +28,16 @@ def list_(): # pylint: disable=redefined-builtin """ Display all DiffParameters nodes """ - DiffParameters = DataFactory("{{cookiecutter.entry_point_prefix}}") + diff_parameters = DataFactory("{{cookiecutter.entry_point_prefix}}") qb = QueryBuilder() - qb.append(DiffParameters) + qb.append(diff_parameters) results = qb.all() s = "" for result in results: obj = result[0] - s += f"{str(obj)}, pk: {obj.pk}\n" + s += f"{obj!s}, pk: {obj.pk}\n" sys.stdout.write(s) diff --git a/{{cookiecutter.plugin_name}}/{{cookiecutter.module_name}}/data/__init__.py b/{{cookiecutter.plugin_name}}/src/{{cookiecutter.module_name}}/data/__init__.py similarity index 98% rename from {{cookiecutter.plugin_name}}/{{cookiecutter.module_name}}/data/__init__.py rename to {{cookiecutter.plugin_name}}/src/{{cookiecutter.module_name}}/data/__init__.py index f2aa313..2b39992 100644 --- a/{{cookiecutter.plugin_name}}/{{cookiecutter.module_name}}/data/__init__.py +++ b/{{cookiecutter.plugin_name}}/src/{{cookiecutter.module_name}}/data/__init__.py @@ -1,13 +1,12 @@ -""" -Data types provided by plugin +"""Data types provided by plugin Register data types via the "aiida.data" entry point in setup.json. """ + # You can directly use or subclass aiida.orm.data.Data # or any other data type listed under 'verdi data' -from voluptuous import Optional, Schema - from aiida.orm import Dict +from voluptuous import Optional, Schema # A subset of diff's command line options cmdline_options = { diff --git a/{{cookiecutter.plugin_name}}/{{cookiecutter.module_name}}/helpers.py b/{{cookiecutter.plugin_name}}/src/{{cookiecutter.module_name}}/helpers.py similarity index 97% rename from {{cookiecutter.plugin_name}}/{{cookiecutter.module_name}}/helpers.py rename to {{cookiecutter.plugin_name}}/src/{{cookiecutter.module_name}}/helpers.py index 981a066..6c693cf 100644 --- a/{{cookiecutter.plugin_name}}/{{cookiecutter.module_name}}/helpers.py +++ b/{{cookiecutter.plugin_name}}/src/{{cookiecutter.module_name}}/helpers.py @@ -1,4 +1,4 @@ -""" Helper functions for automatically setting up computer & code. +"""Helper functions for automatically setting up computer & code. Helper functions for setting up 1. An AiiDA localhost computer @@ -7,6 +7,7 @@ Note: Point 2 is made possible by the fact that the ``diff`` executable is available in the PATH on almost any UNIX system. """ + import shutil import tempfile diff --git a/{{cookiecutter.plugin_name}}/{{cookiecutter.module_name}}/parsers.py b/{{cookiecutter.plugin_name}}/src/{{cookiecutter.module_name}}/parsers.py similarity index 93% rename from {{cookiecutter.plugin_name}}/{{cookiecutter.module_name}}/parsers.py rename to {{cookiecutter.plugin_name}}/src/{{cookiecutter.module_name}}/parsers.py index f78ef20..3e040d3 100644 --- a/{{cookiecutter.plugin_name}}/{{cookiecutter.module_name}}/parsers.py +++ b/{{cookiecutter.plugin_name}}/src/{{cookiecutter.module_name}}/parsers.py @@ -3,6 +3,7 @@ Register parsers via the "aiida.parsers" entry point in setup.json. """ + from aiida.common import exceptions from aiida.engine import ExitCode from aiida.orm import SinglefileData @@ -43,9 +44,7 @@ def parse(self, **kwargs): files_expected = [output_filename] # Note: set(A) <= set(B) checks whether A is a subset of B if not set(files_expected) <= set(files_retrieved): - self.logger.error( - f"Found files '{files_retrieved}', expected to find '{files_expected}'" - ) + self.logger.error(f"Found files '{files_retrieved}', expected to find '{files_expected}'") return self.exit_codes.ERROR_MISSING_OUTPUT_FILES # add output file diff --git a/{{cookiecutter.plugin_name}}/tests/__init__.py b/{{cookiecutter.plugin_name}}/tests/__init__.py index 94cbb42..484b467 100644 --- a/{{cookiecutter.plugin_name}}/tests/__init__.py +++ b/{{cookiecutter.plugin_name}}/tests/__init__.py @@ -1,8 +1,9 @@ -""" Tests for the plugin. +"""Tests for the plugin. Includes both tests written in unittest style (test_cli.py) and tests written in pytest style (test_calculations.py). """ + import os TEST_DIR = os.path.dirname(os.path.realpath(__file__)) diff --git a/{{cookiecutter.plugin_name}}/tests/test_calculations.py b/{{cookiecutter.plugin_name}}/tests/test_calculations.py index 1d5f3cc..0bfdd90 100644 --- a/{{cookiecutter.plugin_name}}/tests/test_calculations.py +++ b/{{cookiecutter.plugin_name}}/tests/test_calculations.py @@ -1,4 +1,5 @@ -""" Tests for calculations.""" +"""Tests for calculations.""" + import os from aiida.engine import run @@ -13,8 +14,8 @@ def test_process({{cookiecutter.entry_point_prefix}}_code): note this does not test that the expected outputs are created of output parsing""" # Prepare input parameters - DiffParameters = DataFactory("{{cookiecutter.entry_point_prefix}}") - parameters = DiffParameters({"ignore-case": True}) + diff_parameters = DataFactory("{{cookiecutter.entry_point_prefix}}") + parameters = diff_parameters({"ignore-case": True}) file1 = SinglefileData(file=os.path.join(TEST_DIR, "input_files", "file1.txt")) file2 = SinglefileData(file=os.path.join(TEST_DIR, "input_files", "file2.txt")) diff --git a/{{cookiecutter.plugin_name}}/tests/test_cli.py b/{{cookiecutter.plugin_name}}/tests/test_cli.py index a5099fa..afbd24b 100644 --- a/{{cookiecutter.plugin_name}}/tests/test_cli.py +++ b/{{cookiecutter.plugin_name}}/tests/test_cli.py @@ -1,8 +1,7 @@ -""" Tests for command line interface.""" -from click.testing import CliRunner +"""Tests for command line interface.""" from aiida.plugins import DataFactory - +from click.testing import CliRunner from {{cookiecutter.module_name}}.cli import export, list_ @@ -12,8 +11,8 @@ class TestDataCli: def setup_method(self): """Prepare nodes for cli tests.""" - DiffParameters = DataFactory("{{cookiecutter.entry_point_prefix}}") - self.parameters = DiffParameters({"ignore-case": True}) + diff_parameters = DataFactory("{{cookiecutter.entry_point_prefix}}") + self.parameters = diff_parameters({"ignore-case": True}) self.parameters.store() self.runner = CliRunner() @@ -31,7 +30,5 @@ def test_data_diff_export(self): Tests that it can be reached and that it shows the contents of the node we have set up. """ - result = self.runner.invoke( - export, [str(self.parameters.pk)], catch_exceptions=False - ) + result = self.runner.invoke(export, [str(self.parameters.pk)], catch_exceptions=False) assert "ignore-case" in result.output