diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000000..1a65ab79f56 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# black +feca12100a2d2251fa2b4e4ad9598d9d6e00ba38 diff --git a/.github/workflows/darkerbot.yaml b/.github/workflows/darkerbot.yaml deleted file mode 100644 index 88d0a085159..00000000000 --- a/.github/workflows/darkerbot.yaml +++ /dev/null @@ -1,62 +0,0 @@ -name: Darker PR Bot - -on: - workflow_run: - workflows: [linters] - types: - - completed - - -concurrency: - # Probably overly cautious group naming. - # Commits to develop will cancel each other, but PRs will only cancel - # commits within the same PR - group: "${{ github.ref }}-${{ github.head_ref }}-${{ github.workflow }}" - cancel-in-progress: true - - -jobs: - darker_bot: - if: "github.repository == 'MDAnalysis/mdanalysis'" - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version: "3.10" - - - name: setup_dependencies - run: | - pip install PyGithub - - - name: 'Download artifact' - uses: actions/github-script@v7 - with: - script: | - let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: context.payload.workflow_run.id, - }); - let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => { - return artifact.name == "darkerlint" - })[0]; - let download = await github.rest.actions.downloadArtifact({ - owner: context.repo.owner, - repo: context.repo.repo, - artifact_id: matchArtifact.id, - archive_format: 'zip', - }); - let fs = require('fs'); - fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/darker_results.zip`, Buffer.from(download.data)); - - - name: 'Unzip artifact' - run: unzip darker_results.zip - - - name: writer-errors - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - python maintainer/ci/darker-outcomes.py --json status.json diff --git a/.github/workflows/linters.yaml b/.github/workflows/linters.yaml index de5eb68fa86..ebc6225036c 100644 --- a/.github/workflows/linters.yaml +++ b/.github/workflows/linters.yaml @@ -16,12 +16,10 @@ defaults: shell: bash -l {0} jobs: - darker_lint: + black: if: "github.repository == 'MDAnalysis/mdanalysis'" runs-on: ubuntu-latest timeout-minutes: 10 - permissions: - pull-requests: write defaults: run: shell: bash @@ -35,66 +33,17 @@ jobs: with: python-version: "3.10" - - name: darker-main-code - id: darker-main-code - uses: akaihola/darker@v2.1.1 - continue-on-error: true + - uses: psf/black@stable with: - version: "~=1.6.1" - options: "--check --diff --color" - src: "./package/MDAnalysis" - revision: "HEAD^" - lint: "flake8" - - - name: darker-test-code - id: darker-test-code - uses: akaihola/darker@v2.1.1 - continue-on-error: true + options: "--check --verbose" + src: "./package" + version: "~= 24.0" + + - uses: psf/black@stable with: - version: "~=1.6.1" - options: "--check --diff --color" - src: "./testsuite/MDAnalysisTests" - revision: "HEAD^" - lint: "flake8" - - - name: get-pr-info - uses: actions/github-script@v7 - with: - script: - const prNumber = context.payload.number; - core.exportVariable('PULL_NUMBER', prNumber); - - - name: save-status - env: - MAIN: ${{ steps.darker-main-code.outcome }} - TEST: ${{ steps.darker-test-code.outcome }} - shell: python - run: | - import os - import json - from pathlib import Path - - Path('./darker_results/').mkdir(exist_ok=True) - - d = { - 'main_stat': os.environ['MAIN'], - 'test_stat': os.environ['TEST'], - 'PR_NUM': os.environ['PULL_NUMBER'], - 'RUN_ID': os.environ['GITHUB_RUN_ID'], - } - - with open('darker_results/status.json', 'w') as f: - json.dump(d, f) - - - name: check-json - run: cat darker_results/status.json - - - name: upload-status - uses: actions/upload-artifact@v4 - with: - name: darkerlint - path: darker_results/ - retention-days: 1 + options: "--check --verbose" + src: "./testsuite" + version: "~= 24.0" pylint_check: diff --git a/maintainer/ci/darker-outcomes.py b/maintainer/ci/darker-outcomes.py deleted file mode 100644 index 4d6bb7fc5ba..00000000000 --- a/maintainer/ci/darker-outcomes.py +++ /dev/null @@ -1,238 +0,0 @@ -#!/usr/bin/env python3 - -# MIT License - -# Copyright (c) 2023 Irfan Alibay - -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. - -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -import argparse -import os -from urllib import request -import json -from github import Github - - -parser = argparse.ArgumentParser( - description="Write PR comment for failed darker linting", -) - - -parser.add_argument( - "--json", - type=str, - help="Input JSON file with status results", -) - - -def get_pull_request(repo, pr_num): - """ - Simple method to get a PyGithub PR object from a PR number - - Parameters - ---------- - repo : PyGithub.Repository - Github Repository API object. - pr_num : int - Pull request number. - - Returns - ------- - PyGithub.PullRequest - Pull Request object corresponding to the input PR number. - """ - # Can get PR directly from PR number - return repo.get_pull(pr_num) - - -def get_action_url(repo, pr, run_id, workflow_name, job_name): - """ - Mix PyGithub & Github V3 REST API method to extract the url path to a - github actions workflow job corresponding to a given GITHUB_RUN_ID. - - Parameters - ---------- - repo : PyGithub.Repository - Github Repository API object. - run_id : str - Github actions RUN ID as defined by the github actions environment - variable `GITHUB_RUN_ID`. - workflow_name : str - Name of the workflow to extract a job url for. - job_name : str - Name of the job within the workflow to extract a url for. - - - Returns - ------- - str - URL to github actions workflow job or 'N/A' if a corresponding job name - could not be found. - """ - # Accessing get_workflow directly currently fails when passing a name - # Instead do the roundabout way by getting list of all workflows - linters = [wf for wf in repo.get_workflows() - if wf.name == workflow_name][0] - - # Extract the gh action run - run = [r for r in linters.get_runs(branch=pr.head.ref) - if r.id == int(run_id)][0] - - # The exact job url can't be recovered via the Python API - # Switch over to using the REST API instead - with request.urlopen(run.jobs_url) as url: - data = json.load(url) - - for job in data['jobs']: - if job['name'] == job_name: - return job['html_url'] - - return 'N/A' - - -def bool_outcome(outcome): - """ - Converts github action job status outcome to bool. - - Parameters - ---------- - outcome : str - Github action job step outcome message. - - Returns - ------- - bool - Whether or not the job step was successful. - """ - return True if (outcome == 'success') else False - - -def gen_message(pr, main_stat, test_stat, action_url): - """ - Generate a user facing message on the status of the darker linting - action. - - Parameters - ---------- - pr : PyGithub.PullRequest - Pull Request object representing the target for this message. - main_stat : str - Outcome of darker linting of main package code. - test_stat : str - Outcome of darker linting of testsuite code. - action_url : str - URL pointing to darker linting job log. - - - Returns - ------- - str - Message to be posted to PR author. - """ - - def _format_outcome(stat): - if bool_outcome(stat): - return "✅ Passed" - else: - return "⚠️ Possible failure" - - msg = ('### Linter Bot Results:\n\n' - f'Hi @{pr.user.login}! Thanks for making this PR. ' - 'We linted your code and found the following: \n\n') - - # If everything is ok - if bool_outcome(main_stat) and bool_outcome(test_stat): - msg += ('There are currently no issues detected! 🎉') - else: - msg += ('Some issues were found with the formatting of your code.\n' - '| Code Location | Outcome |\n' - '| --- | --- |\n' - f'| main package | {_format_outcome(main_stat)}|\n' - f'| testsuite | {_format_outcome(test_stat)}|\n' - '\nPlease have a look at the `darker-main-code` and ' - '`darker-test-code` steps here for more details: ' - f'{action_url}\n\n' - '---\n' - '_**Please note:** The `black` linter is purely ' - 'informational, you can safely ignore these outcomes if ' - 'there are no flake8 failures!_') - return msg - - -def post_comment(pr, message, match_string): - """ - Post a comment in a Pull Request. - - If a comment with text matching `match_string` is found in the - Pull Request, the comment will be edited. - - Parameters - ---------- - pr : PyGithub.PullRequest - Pull Request object representing the target for this message. - message : str - The message to post as a comment. - match_string : str - A matching string to recognise if the comment already exists. - """ - # Extract a list of matching comments from PR - comments = [comm for comm in pr.get_issue_comments() if match_string in comm.body] - - if len(comments) > 0: - # Edit the comment in-place - # By default we assume that the bot can write faster than anyone else - comments[0].edit(body=message) - else: - # If the comment doesn't exist, create one - pr.create_issue_comment(message) - - -if __name__ == "__main__": - args = parser.parse_args() - - git = Github(os.environ['GITHUB_TOKEN']) - repo = git.get_repo("MDAnalysis/mdanalysis") - - with open(args.json, 'r') as f: - status = json.load(f) - - run_id = status['RUN_ID'] - print(f"debug run_id: {run_id}") - - # Get Pull Request - pr_num = int(status['PR_NUM']) - print(f"debug pr_num: {pr_num}") - pr = get_pull_request(repo, pr_num) - - # Get the url to the github action job being pointed to - action_url = get_action_url(repo, pr, run_id, - workflow_name='linters', - job_name='darker_lint') - - # Get the message you want to post to users - with open(args.json, 'r') as f: - results_dict = json.load(f) - - message = gen_message(pr, - status['main_stat'], - status['test_stat'], - action_url) - - # Post your comment - post_comment(pr, message, match_string='Linter Bot Results:') diff --git a/package/MDAnalysis/due.py b/package/MDAnalysis/due.py index 0528bb83e15..7ab1e8b55a9 100644 --- a/package/MDAnalysis/due.py +++ b/package/MDAnalysis/due.py @@ -26,30 +26,33 @@ """ -__version__ = '0.0.5' +__version__ = "0.0.5" class InactiveDueCreditCollector(object): """Just a stub at the Collector which would not do anything""" + def _donothing(self, *args, **kwargs): """Perform no good and no bad""" - pass # pylint: disable=unnecessary-pass + pass # pylint: disable=unnecessary-pass def dcite(self, *args, **kwargs): """If I could cite I would""" + def nondecorating_decorator(func): return func + return nondecorating_decorator cite = load = add = _donothing def __repr__(self): - return self.__class__.__name__ + '()' + return self.__class__.__name__ + "()" def _donothing_func(*args, **kwargs): """Perform no good and no bad""" - pass # pylint: disable=unnecessary-pass + pass # pylint: disable=unnecessary-pass try: @@ -60,17 +63,21 @@ def _donothing_func(*args, **kwargs): import duecredit from duecredit import due, BibTeX, Doi, Url - if 'due' in locals() and not hasattr(due, 'cite'): + + if "due" in locals() and not hasattr(due, "cite"): raise RuntimeError( - "Imported due lacks .cite. DueCredit is now disabled") + "Imported due lacks .cite. DueCredit is now disabled" + ) except Exception as err: if not isinstance(err, ImportError): import logging import warnings + errmsg = "Failed to import duecredit due to {}".format(str(err)) warnings.warn(errmsg) logging.getLogger("duecredit").error( - "Failed to import duecredit due to {}".format(str(err))) + "Failed to import duecredit due to {}".format(str(err)) + ) # else: # Do not issue any warnings if duecredit is not installed; # this is the user's choice (Issue #1872) diff --git a/package/MDAnalysis/topology/tables.py b/package/MDAnalysis/topology/tables.py index 1c0b51b5817..ea46421cc42 100644 --- a/package/MDAnalysis/topology/tables.py +++ b/package/MDAnalysis/topology/tables.py @@ -46,8 +46,10 @@ .. autodata:: TABLE_VDWRADII """ +from typing import Any -def kv2dict(s, convertor=str): + +def kv2dict(s, convertor: Any = str): """Primitive ad-hoc parser of a key-value record list. * The string *s* should contain each key-value pair on a separate @@ -172,10 +174,12 @@ def kv2dict(s, convertor=str): #: with :func:`MDAnalysis.topology.core.guess_atom_type`. atomelements = kv2dict(TABLE_ATOMELEMENTS) +# fmt: off elements = ['H', 'LI', 'BE', 'B', 'C', 'N', 'O', 'F', 'NA', 'MG', 'AL', 'P', 'SI', 'S', 'CL', 'K'] +# fmt: on #: Plain-text table with atomic masses in u. TABLE_MASSES = """ @@ -376,28 +380,31 @@ def kv2dict(s, convertor=str): #: .. SeeAlso:: :func:`MDAnalysis.topology.core.guess_bonds` vdwradii = kv2dict(TABLE_VDWRADII, convertor=float) -Z2SYMB = {1: 'H', 2: 'He', - 3: 'Li', 4: 'Be', 5: 'B', 6: 'C', 7: 'N', 8: 'O', 9: 'F', 10: 'Ne', - 11: 'Na', 12: 'Mg', 13: 'Al', 14: 'Si', 15: 'P', 16: 'S', 17: 'Cl', 18: 'Ar', - 19: 'K', 20: 'Ca', 21: 'Sc', 22: 'Ti', 23: 'V', 24: 'Cr', 25: 'Mn', 26: 'Fe', - 27: 'Co', 28: 'Ni', 29: 'Cu', 30: 'Zn', 31: 'Ga', 32: 'Ge', 33: 'As', 34: 'Se', - 35: 'Br', 36: 'Kr', 37: 'Rb', 38: 'Sr', 39: 'Y', 40: 'Zr', 41: 'Nb', 42: 'Mo', - 43: 'Tc', 44: 'Ru', 45: 'Rh', 46: 'Pd', 47: 'Ag', 48: 'Cd', 49: 'In', 50: 'Sn', - 51: 'Sb', 52: 'Te', 53: 'I', 54: 'Xe', 55: 'Cs', 56: 'Ba', 57: 'La', 58: 'Ce', - 59: 'Pr', 60: 'Nd', 61: 'Pm', 62: 'Sm', 63: 'Eu', 64: 'Gd', 65: 'Tb', 66: 'Dy', - 67: 'Ho', 68: 'Er', 69: 'Tm', 70: 'Yb', 71: 'Lu', 72: 'Hf', 73: 'Ta', 74: 'W', - 75: 'Re', 76: 'Os', 77: 'Ir', 78: 'Pt', 79: 'Au', 80: 'Hg', 81: 'Tl', 82: 'Pb', - 83: 'Bi', 84: 'Po', 85: 'At', 86: 'Rn', 87: 'Fr', 88: 'Ra', 89: 'Ac', 90: 'Th', - 91: 'Pa', 92: 'U', 93: 'Np', 94: 'Pu', 95: 'Am', 96: 'Cm', 97: 'Bk', 98: 'Cf', - 99: 'Es', 100: 'Fm', 101: 'Md', 102: 'No', 103: 'Lr', 104: 'Rf', 105: 'Db', - 106: 'Sg', 107: 'Bh', 108: 'Hs', 109: 'Mt', 110: 'Ds', 111: 'Rg', 112: 'Cn', +# fmt: off +Z2SYMB = {1: 'H', 2: 'He', + 3: 'Li', 4: 'Be', 5: 'B', 6: 'C', 7: 'N', 8: 'O', 9: 'F', 10: 'Ne', + 11: 'Na', 12: 'Mg', 13: 'Al', 14: 'Si', 15: 'P', 16: 'S', 17: 'Cl', 18: 'Ar', + 19: 'K', 20: 'Ca', 21: 'Sc', 22: 'Ti', 23: 'V', 24: 'Cr', 25: 'Mn', 26: 'Fe', + 27: 'Co', 28: 'Ni', 29: 'Cu', 30: 'Zn', 31: 'Ga', 32: 'Ge', 33: 'As', 34: 'Se', + 35: 'Br', 36: 'Kr', 37: 'Rb', 38: 'Sr', 39: 'Y', 40: 'Zr', 41: 'Nb', 42: 'Mo', + 43: 'Tc', 44: 'Ru', 45: 'Rh', 46: 'Pd', 47: 'Ag', 48: 'Cd', 49: 'In', 50: 'Sn', + 51: 'Sb', 52: 'Te', 53: 'I', 54: 'Xe', 55: 'Cs', 56: 'Ba', 57: 'La', 58: 'Ce', + 59: 'Pr', 60: 'Nd', 61: 'Pm', 62: 'Sm', 63: 'Eu', 64: 'Gd', 65: 'Tb', 66: 'Dy', + 67: 'Ho', 68: 'Er', 69: 'Tm', 70: 'Yb', 71: 'Lu', 72: 'Hf', 73: 'Ta', 74: 'W', + 75: 'Re', 76: 'Os', 77: 'Ir', 78: 'Pt', 79: 'Au', 80: 'Hg', 81: 'Tl', 82: 'Pb', + 83: 'Bi', 84: 'Po', 85: 'At', 86: 'Rn', 87: 'Fr', 88: 'Ra', 89: 'Ac', 90: 'Th', + 91: 'Pa', 92: 'U', 93: 'Np', 94: 'Pu', 95: 'Am', 96: 'Cm', 97: 'Bk', 98: 'Cf', + 99: 'Es', 100: 'Fm', 101: 'Md', 102: 'No', 103: 'Lr', 104: 'Rf', 105: 'Db', + 106: 'Sg', 107: 'Bh', 108: 'Hs', 109: 'Mt', 110: 'Ds', 111: 'Rg', 112: 'Cn', 113: 'Nh', 114: 'Fl', 115: 'Mc', 116: 'Lv', 117: 'Ts', 118: 'Og'} +# fmt: on -SYMB2Z = {v:k for k, v in Z2SYMB.items()} +SYMB2Z = {v: k for k, v in Z2SYMB.items()} # Conversion between SYBYL atom types and corresponding elements # Tripos MOL2 file format: # https://web.archive.org/web/*/http://chemyang.ccnu.edu.cn/ccb/server/AIMMS/mol2.pdf +# fmt: off SYBYL2SYMB = { "H": "H", "H.spc": "H", "H.t3p": "H", "C.3": "C", "C.2": "C", "C.1": "C", "C.ar": "C", "C.cat": "C", @@ -428,4 +435,5 @@ def kv2dict(s, convertor=str): "Se": "Se", "Mo": "Mo", "Sn": "Sn", -} \ No newline at end of file +} +# fmt: on diff --git a/package/pyproject.toml b/package/pyproject.toml index 24d726c3ab4..cd7ec3a7806 100644 --- a/package/pyproject.toml +++ b/package/pyproject.toml @@ -126,5 +126,10 @@ MDAnalysis = [ [tool.black] line-length = 79 target-version = ['py310', 'py311', 'py312', 'py313'] -extend-exclude = '.' +include = ''' +( +tables\.py +| due\.py +) +''' required-version = '24'