diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..a8539ff --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,4 @@ +- [ ] I have read the [section on commits and pull requests](https://github.com/NaturalHistoryMuseum/ckanext-contact/blob/main/CONTRIBUTING.md#commits-and-pull-requests) in `CONTRIBUTING.md` + + +Describe your changes, tagging relevant issues where possible. diff --git a/.github/workflows/bump.yml b/.github/workflows/bump.yml index 8b17261..1bb1860 100644 --- a/.github/workflows/bump.yml +++ b/.github/workflows/bump.yml @@ -7,12 +7,12 @@ on: jobs: bump-version: - if: "!startsWith(github.event.head_commit.message, 'bump:')" + name: Bump version and create changelog runs-on: ubuntu-latest - name: "Bump version and create changelog" + if: "!startsWith(github.event.head_commit.message, 'bump:')" steps: - - name: Check out - uses: actions/checkout@v3 + - name: Checkout source code + uses: actions/checkout@v4 with: token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} fetch-depth: 0 diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml new file mode 100644 index 0000000..134e108 --- /dev/null +++ b/.github/workflows/pull-requests.yml @@ -0,0 +1,27 @@ +name: Validate pull requests + +on: + pull_request: + types: [opened, edited, reopened, synchronize] + +jobs: + validate-commits: + name: Validate commit messages + runs-on: ubuntu-latest + steps: + - name: Checkout source code + uses: actions/checkout@v4 + - name: Check commit message format + uses: webiny/action-conventional-commits@v1.3.0 + with: + allowed-commit-types: 'feat,fix,refactor,perf,docs,style,test,build,ci,chore,new,patch,revert,ui,merge' + pre-commit: + name: Run pre-commit checks + runs-on: ubuntu-latest + steps: + - name: Checkout source code + uses: actions/checkout@v4 + - name: Setup Python + uses: actions/setup-python@v5 + - name: Run pre-commit + uses: pre-commit/action@v3.0.1 diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml index 6866a49..bf30290 100644 --- a/.github/workflows/pypi-publish.yml +++ b/.github/workflows/pypi-publish.yml @@ -10,17 +10,16 @@ permissions: jobs: deploy: + name: Deploy package to PyPI runs-on: ubuntu-latest steps: - - name: Check out - uses: actions/checkout@v3 + - name: Checkout source code + uses: actions/checkout@v4 with: token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v3 - with: - python-version: '3.x' + uses: actions/setup-python@v5 - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index bc66f38..4fec115 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -7,11 +7,11 @@ on: jobs: sync-branches: + name: Sync dev and patch branches to latest commit runs-on: ubuntu-latest - name: "Sync dev and patch branches to latest commit" steps: - - name: Check out - uses: actions/checkout@v3 + - name: Checkout source code + uses: actions/checkout@v4 with: token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} fetch-depth: 0 diff --git a/.github/workflows/main.yml b/.github/workflows/tests.yml similarity index 77% rename from .github/workflows/main.yml rename to .github/workflows/tests.yml index b0c9cd3..5b3f931 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/tests.yml @@ -3,18 +3,18 @@ name: Tests on: push: workflow_dispatch: + pull_request: + types: [opened, edited, reopened, synchronize] jobs: test: + name: Run tests runs-on: ubuntu-latest - steps: - name: Checkout source code - uses: actions/checkout@v3 - + uses: actions/checkout@v4 - name: Build images run: docker compose build - - name: Run tests env: COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2d3b017..04e9d79 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,33 +1,34 @@ exclude: /(vendor|dist)/ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v5.0.0 hooks: - id: check-merge-conflict - id: detect-private-key - id: end-of-file-fixer - id: name-tests-test - args: ["--pytest-test-first"] + args: ['--pytest-test-first'] exclude: ^tests/helpers/ - id: trailing-whitespace - repo: https://github.com/commitizen-tools/commitizen - rev: v2.37.0 + rev: v3.30.0 hooks: - id: commitizen - additional_dependencies: ["cz-nhm"] - - repo: https://github.com/psf/black - rev: 22.10.0 + additional_dependencies: ['cz-nhm'] + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.7.1 hooks: - - id: black + - id: ruff + args: [ '--fix', '--select', 'I', '--select', 'F401', '--fix-only' ] + - id: ruff-format - repo: https://github.com/PyCQA/docformatter - rev: v1.5.0 + rev: eb1df34 hooks: - id: docformatter - # these can't be pulled directly from the config atm, not sure why - args: ["-i", "--wrap-summaries=88", "--wrap-descriptions=88", - "--pre-summary-newline", "--make-summary-multi-line"] + args: [ '-i', '--config', './pyproject.toml' ] + additional_dependencies: ['tomli'] - repo: https://github.com/pre-commit/mirrors-prettier - rev: v3.0.0-alpha.4 + rev: v4.0.0-alpha.8 hooks: - id: prettier types_or: [ javascript, vue, less, sass, scss, css ] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index befed4c..dc58a24 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,7 +41,7 @@ This extension and [several others](https://github.com/search?q=topic:ckan+org:N The current core team consists of: - Josh ([@jrdh](https://github.com/jrdh)) - Technical Lead -- Ginger ([@alycejenni](https://github.com/alycejenni)) - Software Engineer +- Ginger ([@alycejenni](https://github.com/alycejenni)) - Senior Software Engineer ## Questions @@ -81,7 +81,10 @@ The process is generally as follows: 3. Make your changes, and commit often; each commit should only contain one change. See below for specifics on how to word your commits. 4. Push your changes back to your fork. 5. [Open a pull request](https://docs.github.com/en/get-started/quickstart/contributing-to-projects#making-a-pull-request) in this repository, with the base branch set to **dev** and the compare branch set to your new branch. Provide a summary of your changes in the description. -6. If the automatic tests fail (these may take a while), please go back to your code and try to make them pass. You may have to update the tests themselves. You don't have to close the pull request while you're doing this; it'll update as you add further commits. +6. There are several automated checks that will run when you open the pull request. Try to make all of them pass. If you do not at least _attempt_ to make them pass, we will not merge your pull request. + 1. Tests. Update them so that they pass, if necessary. New tests are always welcome in any pull request, but if you have added a new feature that has decreased the coverage, new tests are required. + 2. Commit format validation. If you have not followed the conventional commits format for one or more of your commits, this will fail. + 3. Code format validation. If you have not formatted your code correctly (using Ruff, docformatter, and/or Prettier), this will fail. 7. Wait for feedback from one of the core maintainers. If it's been a week or so and we haven't responded, we may not have seen it. You can find other places to contact us in [SUPPORT.md](./.github/SUPPORT.md). ### Commits @@ -142,9 +145,9 @@ cz c ##### pre-commit -pre-commit is a tool that runs a variety of checks and modifications before a commit is made. You can check the [.pre-commit-config.yaml](./.pre-commit-config.yaml) file to see eaxtly what it's currently configured to do for this repository, but of particular note: +pre-commit is a tool that runs a variety of checks and modifications before a commit is made. You can check the [.pre-commit-config.yaml](./.pre-commit-config.yaml) file to see exactly what it's currently configured to do for this repository, but of particular note: -- reformats Python code with [Black](https://github.com/psf/black) +- reformats Python code with [Ruff](https://docs.astral.sh/ruff) - reformats JavaScript and stylesheets with [Prettier](https://prettier.io) - reformats docstrings with [docformatter](https://github.com/PyCQA/docformatter) - checks your commit message is correcly formatted @@ -161,15 +164,15 @@ pre-commit run Don't forget to stage any modifications that it makes! Once it runs without failing, then you can make your commit. -Something to remember is that empty docstrings will cause conflicts between Black and docformatter and the checks will fail repeatedly - so don't leave your docstrings empty! +Something to remember is that empty docstrings will cause conflicts between Ruff and docformatter and the checks will fail repeatedly - so don't leave your docstrings empty! ### Code changes and style guide -We generally use external style guides and tools to help us maintain standardised code. Black and Prettier will be run with pre-commit. +We generally use external style guides and tools to help us maintain standardised code. Ruff and Prettier will be run with pre-commit. #### Python -We follow the [Black style](https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html), with the notable exception that we use single quotes. +We use [Ruff](https://docs.astral.sh/ruff) to format our code, using defaults for everything except quote style (we use single quotes). We also _mostly_ use [CKAN's style](http://docs.ckan.org/en/latest/contributing/python.html), with the following exceptions: - prefer `f''` strings over `.format()` @@ -178,7 +181,7 @@ We also _mostly_ use [CKAN's style](http://docs.ckan.org/en/latest/contributing/ #### JavaScript and stylesheets (CSS, LESS, etc) -We use [Prettier](https://prettier.io) to format these files. +We use [Prettier](https://prettier.io) to format these files. As with Ruff, we use defaults for everything except quote style (we use single quotes). #### Accessibility diff --git a/ckanext/contact/plugin.py b/ckanext/contact/plugin.py index d3f8d1a..4068cbc 100644 --- a/ckanext/contact/plugin.py +++ b/ckanext/contact/plugin.py @@ -7,11 +7,11 @@ import functools from logging import getLogger -from ckanext.contact.auth import send_contact -from ckanext.contact import routes - from ckan.plugins import SingletonPlugin, implements, interfaces, toolkit +from ckanext.contact import routes +from ckanext.contact.auth import send_contact + log = getLogger(__name__) diff --git a/ckanext/contact/routes/_helpers.py b/ckanext/contact/routes/_helpers.py index 52715ea..e71fbaa 100644 --- a/ckanext/contact/routes/_helpers.py +++ b/ckanext/contact/routes/_helpers.py @@ -5,15 +5,17 @@ # Created by the Natural History Museum in London, UK import logging import socket +from datetime import datetime, timezone + from ckan import logic from ckan.common import asbool from ckan.lib import mailer from ckan.lib.navl.dictization_functions import unflatten from ckan.plugins import PluginImplementations, toolkit +from pyisemail import is_email + from ckanext.contact import recaptcha from ckanext.contact.interfaces import IContact -from datetime import datetime, timezone -from pyisemail import is_email log = logging.getLogger(__name__) @@ -23,8 +25,8 @@ def validate(data_dict): Validates the given data and recaptcha if necessary. :param data_dict: the request params as a dict - :return: a 3-tuple of errors, error summaries and a recaptcha error, in the event where no - issues occur the return is ({}, {}, None) + :returns: a 3-tuple of errors, error summaries and a recaptcha error, in the event + where no issues occur the return is ({}, {}, None) """ errors = {} error_summary = {} @@ -76,10 +78,10 @@ def build_subject( :param subject: a user defined subject line :param default: the default str to use if the user didn't provide a subject or - ckanext.contact.subject isn't specified + ckanext.contact.subject isn't specified :param timestamp_default: the default bool to use if add_timestamp_to_subject isn't - specified - :return: the subject line + specified + :returns: the subject line """ if not subject: subject = toolkit.config.get('ckanext.contact.subject', toolkit._(default)) @@ -101,7 +103,7 @@ def submit(): Take the data in the request params and send an email using them. If the data is invalid or a recaptcha is setup and it fails, don't send the email. - :return: a dict of details + :returns: a dict of details """ # this variable holds the status of sending the email email_success = True diff --git a/ckanext/contact/routes/contact.py b/ckanext/contact/routes/contact.py index 20fa9c6..cb0a2d8 100644 --- a/ckanext/contact/routes/contact.py +++ b/ckanext/contact/routes/contact.py @@ -42,7 +42,8 @@ def form(): Form based interaction, if called as a POST request the request params are used to send the email, if not then the form template is rendered. - :return: a page, either the form page or the success page if the email was sent successfully + :returns: a page, either the form page or the success page if the email was sent + successfully """ # dict of context values for the template renderer extra_vars = { @@ -80,6 +81,6 @@ def ajax_submit(): """ AJAX form submission. - :return: json dumped data for the response + :returns: json dumped data for the response """ return jsonify(_helpers.submit()) diff --git a/docker-compose.yml b/docker-compose.yml index 7f2b913..9f5ab49 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: "3" - services: ckan: build: diff --git a/docs/_scripts/gen_api_pages.py b/docs/_scripts/gen_api_pages.py index ad1e41a..93a5328 100644 --- a/docs/_scripts/gen_api_pages.py +++ b/docs/_scripts/gen_api_pages.py @@ -1,6 +1,5 @@ # !/usr/bin/env python # encoding: utf-8 - """ Generate the code reference pages and navigation. diff --git a/pyproject.toml b/pyproject.toml index 1e81f79..2135280 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,8 +19,8 @@ classifiers = [ "Programming Language :: Python :: 3.8" ] dependencies = [ - "ckantools>=0.3.0", - "pyisemail==2.0.1" + "pyisemail==2.0.1", + "ckantools>=0.4.0" ] [project.optional-dependencies] @@ -65,9 +65,8 @@ version_files = [ "CITATION.cff:^version" ] -[tool.black] -line-length = 88 -skip_string_normalization = true +[tool.ruff.format] +quote-style = "single" [tool.pylint] max-line-length = 88 diff --git a/tests/unit/test_auth.py b/tests/unit/test_auth.py index ce8efec..8df4f7c 100644 --- a/tests/unit/test_auth.py +++ b/tests/unit/test_auth.py @@ -1,9 +1,10 @@ import pytest from ckan.plugins import toolkit from ckan.tests import factories -from ckanext.contact.auth import send_contact from mock import MagicMock +from ckanext.contact.auth import send_contact + def test_auth_always_succeeds_direct(): assert send_contact(MagicMock(), MagicMock()) == {'success': True} diff --git a/tests/unit/test_helpers.py b/tests/unit/test_helpers.py index fc0109f..d8b77a1 100644 --- a/tests/unit/test_helpers.py +++ b/tests/unit/test_helpers.py @@ -1,9 +1,10 @@ -import pytest from datetime import datetime, timezone -from ckanext.contact.routes._helpers import build_subject +import pytest from freezegun import freeze_time +from ckanext.contact.routes._helpers import build_subject + class TestBuildSubject: def test_no_config_all_defaults(self): @@ -32,7 +33,7 @@ def test_no_config_pass_default_timestamp_false(self): subject = build_subject(timestamp_default=timestamp_default) assert subject == 'Contact/Question from visitor' - @freeze_time("2021-01-01") + @freeze_time('2021-01-01') def test_no_config_pass_default_timestamp_true(self): timestamp_default = True @@ -43,7 +44,7 @@ def test_no_config_pass_default_timestamp_true(self): ) assert subject == f'Contact/Question from visitor [{timestamp}]' - @freeze_time("2021-01-01") + @freeze_time('2021-01-01') def test_no_config_pass_both(self): subject_default = 'TEST SUBJECT' timestamp_default = True @@ -57,7 +58,7 @@ def test_no_config_pass_both(self): ) assert subject == f'{subject_default} [{timestamp}]' - @freeze_time("2021-01-01") + @freeze_time('2021-01-01') @pytest.mark.ckan_config('ckanext.contact.subject', 'TEST SUBJECT') @pytest.mark.ckan_config('ckanext.contact.add_timestamp_to_subject', 'true') def test_config_with_timestamp(self):