diff --git a/.github/scripts/report_nightly_build_failure.py b/.github/scripts/report_nightly_build_failure.py new file mode 100644 index 0000000..1af05f3 --- /dev/null +++ b/.github/scripts/report_nightly_build_failure.py @@ -0,0 +1,28 @@ +""" +Called by GH Actions when the nightly build fails. + +This reports an error to the #nightly-build-failures Slack channel. +""" + +import os + +import requests + + +if "SLACK_WEBHOOK_URL" in os.environ: + print("Reporting to #nightly-build-failures slack channel") + response = requests.post( + os.environ["SLACK_WEBHOOK_URL"], + json={ + "text": "A Nightly build failed. See https://github.com/torchbox/django-birdbath/actions/runs/" + + os.environ["GITHUB_RUN_ID"], + }, + timeout=30, + ) + + print("Slack responded with:", response) + +else: + print( + "Unable to report to #nightly-build-failures slack channel because SLACK_WEBHOOK_URL is not set" + ) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 0000000..71a1f5b --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,35 @@ +name: Nightly Wagtail Test + +on: + schedule: + - cron: '0 1 * * *' + # At 01:00, daily + workflow_dispatch: + +jobs: + nightly-wagtail-test: + runs-on: ubuntu-latest + env: + WEBHOOK_EXISTS: ${{ secrets.SLACK_WEBHOOK_URL != '' }} + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - run: git clone https://github.com/wagtail/wagtail.git + + - run: python -m pip install flit + - run: flit install --extras dev + - run: python -m pip install ./wagtail + + - run: pytest + + - name: Report failure + run: | + python -m pip install requests + python ./.github/scripts/report_nightly_build_failure.py + if: ${{ failure() && env.WEBHOOK_EXISTS == 'true' }} + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..178ffd3 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,51 @@ +# See https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/ +# for a detailed guide +name: Publish to PyPI + +on: + release: + types: [published] + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read # to fetch code (actions/checkout) + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install flit + python -m flit install --symlink + + - name: Build + run: python -m flit build + + - uses: actions/upload-artifact@v4 + with: + path: ./dist + + publish: + needs: build + runs-on: ubuntu-latest + permissions: + contents: none + id-token: write # required for trusted publishing + environment: publish + steps: + - uses: actions/download-artifact@v4 + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: artifact/ + print-hash: true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..c68fcd8 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,48 @@ +name: Django Birdbath CI + +on: + push: + branches: + - main + - 'stable/**' + + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: '3.12' + - uses: pre-commit/action@v3.0.1 + + test: + runs-on: ubuntu-latest + needs: lint + strategy: + matrix: + python: ['3.9', '3.10', '3.11', '3.12', '3.13'] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + - name: Install + run: | + python -m pip install --upgrade pip tox tox-gh-actions + - name: Test + run: tox diff --git a/README.md b/README.md index 61aacfe..bf21de6 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Your site will probably have some of your own check/processor needs. Custom checks can be implemented by subclassing `birdbath.checks.BaseCheck` and implementing the `check` method: -``` +```python from birdbath.checks import BaseCheck @@ -54,7 +54,7 @@ The `check` method should either return `True` if the checks should continue, or Custom processors can be implemented by subclassing `birdbath.processors.BaseProcessor` and implementing the `run` method: -``` +```python from birdbath.processors import BaseProcessor @@ -65,7 +65,7 @@ class DeleteAllMyUsersProcessor(BaseProcessor): There are also more specialised base classes in `birdbath.processors` that can help you write cleaner custom processors. For example, the above example could be written using the `BaseModelDeleter` class instead: -``` +```python from birdbath.processors import BaseModelDeleter @@ -75,7 +75,7 @@ class DeleteAllMyUsersProcessor(BaseModelDeleter): If you only need to delete a subset of users, you can override the `get_queryset()` method, like so: -``` +```python from birdbath.processors import BaseModelDeleter @@ -88,8 +88,7 @@ class DeleteNonStaffUsersProcessor(BaseModelDeleter): If you're looking to 'anonymise' rather than delete objects, you will likely find the `BaseModelAnonymiser` class useful. You just need to indicate the fields that should be 'anonymised' or 'cleared', and the class will do the rest. For example: - -``` +```python from birdbath.processors import BaseModelAnonymiser @@ -122,7 +121,7 @@ The class will generate: If you have fields with custom validation requirements, or would simply like to generate more realistic replacement values, you can add 'generate' methods to your subclass to achieve this. `BaseModelAnonymiser` will automatically look for method matching the format `"generate_{field_name}"` when anoymising field values. For example, the following processor will generate random values for "account_holder" and "account_number" fields: -``` +```python from birdbath.processors import BaseModelAnonymiser diff --git a/pyproject.toml b/pyproject.toml index 68574b0..377ddc4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,15 +15,17 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ] keywords = ["django", "anonymization", "data cleaning"] dependencies = [ - "Faker >=8", + "Faker>=8", + "Django>=4.2", ] requires-python = ">=3.9" [project.urls] -Home = "https://git.torchbox.com/internal/django-birdbath" +Home = "https://github.com/torchbox/django-birdbath" [project.optional-dependencies] dev = [ @@ -60,3 +62,8 @@ lint.select = [ "C4", # flake8-comprehensions "UP", # pyupgrade ] + +[tool.pytest.ini_options] +django_find_project = false +pythonpath = "." +DJANGO_SETTINGS_MODULE = "tests.settings" diff --git a/tox.ini b/tox.ini index a786ddf..5b9be82 100644 --- a/tox.ini +++ b/tox.ini @@ -1,18 +1,26 @@ [tox] envlist = - py39-django42-wagtail62 - py{310,311,312}-django{50,51}-wagtail62 - py312-django42-wagtail{52,60,61,62} + # Django versions with their respectively supported Python versions and the most recent Wagtail LTS + py{39,310,311,312,313}-django42-wagtail62 + py{310,311,312,313}-django{50,51}-wagtail62 + # Old Wagtail versions with the oldest Django LTS and Python + py39-django42-wagtail52 isolated_build = True +[gh-actions] +python = + 3.9: py39 + 3.10: py310 + 3.11: py311 + 3.12: py312 + 3.13: py313 + [testenv] deps = django42: Django>=4.2,<5.0 django50: Django>=5.0,<5.1 django51: Django>=5.1,<5.2 wagtail52: wagtail>=5.2,<5.3 - wagtail60: wagtail>=6.0,<6.1 - wagtail61: wagtail>=6.1,<6.2 wagtail62: wagtail>=6.2,<6.3 pytest pytest-django