diff --git a/.editorconfig b/.editorconfig index 0eafee5..d3229ea 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,30 +1,28 @@ # http://editorconfig.org - root = true [*] charset = utf-8 end_of_line = lf +indent_style = space insert_final_newline = true trim_trailing_whitespace = true -[*.{py,rst,ini}] +[{,.}{j,J}ustfile] +indent_size = 4 + +[*.{py,rst,ini,md}] indent_size = 4 -indent_style = space [*.py] line_length = 120 multi_line_output = 3 -[*.{css,html,js,json,sass,scss,yml,yaml}] +[*.{css,html,js,json,jsx,sass,scss,svelte,ts,tsx,yml,yaml}] indent_size = 2 -indent_style = space [*.md] -indent_size = 4 -indent_style = space trim_trailing_whitespace = false - [{Makefile,*.bat}] indent_style = tab diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e69de29 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..1e1d012 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + timezone: America/Chicago + labels: + - "dependabot" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..21f15bd --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,70 @@ +name: release + +on: + release: + types: [created] + workflow_dispatch: + inputs: + pypi: + description: "Publish to PyPI" + required: false + default: true + type: boolean + +jobs: + check: + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v4 + + - name: Check most recent test run on `main` + id: latest-test-result + run: | + echo "result=$(gh run list \ + --branch=main \ + --workflow=test.yml \ + --json headBranch,workflowName,conclusion \ + --jq '.[] | select(.headBranch=="main" and .conclusion=="success") | .conclusion' \ + | head -n 1)" >> $GITHUB_OUTPUT + + - name: OK + if: ${{ (contains(steps.latest-test-result.outputs.result, 'success')) }} + run: | + exit 0 + + - name: Fail + if: ${{ !contains(steps.latest-test-result.outputs.result, 'success') }} + run: | + exit 1 + + pypi: + if: ${{ github.event_name == 'release' || inputs.pypi }} + runs-on: ubuntu-latest + needs: check + environment: release + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: 3.11 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install hatch + + - name: Build package + run: | + hatch build + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..77c3c6a --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,90 @@ +name: test + +on: + push: + branches: main + pull_request: + +concurrency: + group: test-${{ github.head_ref }} + cancel-in-progress: true + +env: + PYTHONUNBUFFERED: "1" + FORCE_COLOR: "1" + +jobs: + test: + name: Python ${{ matrix.python-version }}, Django ${{ matrix.django-version }} + runs-on: "ubuntu-latest" + strategy: + fail-fast: false + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + django-version: ["3.2", "4.2", "5.0", "main"] + env: + NOX_SESSION: "tests(python='${{ matrix.python-version }}', django='${{ matrix.django-version }}')" + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: "pip" + allow-prereleases: true + + - name: Install dependencies + run: | + python -m pip install --upgrade pip nox + + - name: Check if test session exists + run: | + if nox -l | grep "${{ env.NOX_SESSION }}"; then + echo "Session exists, proceeding with tests" + echo "RUN_TESTS=true" >> $GITHUB_ENV + else + echo "Session does not exist, skipping tests" + echo "RUN_TESTS=false" >> $GITHUB_ENV + fi + + - name: Run tests + if: env.RUN_TESTS == 'true' + run: | + python -m nox --session "${{ env.NOX_SESSION }}" + + tests: + runs-on: ubuntu-latest + needs: test + if: always() + steps: + - name: OK + if: ${{ !(contains(needs.*.result, 'failure')) }} + run: exit 0 + - name: Fail + if: ${{ contains(needs.*.result, 'failure') }} + run: exit 1 + + types: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: wntrblm/nox@main + with: + python-versions: "3.8" + + - name: Run mypy + run: nox --session mypy + + coverage: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: wntrblm/nox@main + with: + python-versions: "3.8" + + - name: Run coverage + run: nox --session coverage diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 215fc7c..ea72d81 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,19 +17,20 @@ repos: language_version: python3.8 args: [--target-version, "3.2"] - - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.262 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.3 hooks: - id: ruff - alias: autoformat - args: ["--fix"] + args: [--fix] + - id: ruff-format - - repo: https://github.com/psf/black - rev: 23.3.0 + - repo: https://github.com/adamchainz/blacken-docs + rev: "1.16.0" hooks: - - id: black - language_version: python3.8 - args: [--target-version, "py38"] + - id: blacken-docs + alias: autoformat + additional_dependencies: + - black==22.12.0 - repo: https://github.com/pre-commit/mirrors-prettier rev: v3.0.0-alpha.9-for-vscode @@ -37,9 +38,39 @@ repos: - id: prettier # lint the following with prettier: # - javascript + # - svelte # - typescript # - JSX/TSX # - CSS # - yaml # ignore any minified code - files: '^(?!.*\.min\..*)(?P[\w-]+(\.[\w-]+)*\.(js|jsx|ts|tsx|yml|yaml|css))$' + files: '^(?!.*\.min\..*)(?P[\w-]+(\.[\w-]+)*\.(js|jsx|svelte|ts|tsx|yml|yaml|css))$' + + - repo: https://github.com/rtts/djhtml + rev: "3.0.6" + hooks: + - id: djhtml + entry: djhtml --tabwidth 2 + alias: autoformat + + - repo: local + hooks: + - id: rustywind + name: rustywind Tailwind CSS class linter + language: node + additional_dependencies: + - rustywind@0.16.0 + entry: rustywind + args: [--write] + types_or: [html, jsx, tsx] + + - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks + rev: v2.11.0 + hooks: + - id: pretty-format-toml + args: [--autofix] + + - repo: https://github.com/abravalheri/validate-pyproject + rev: v0.15 + hooks: + - id: validate-pyproject diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..ab3493c --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,24 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.12" + +sphinx: + configuration: docs/conf.py + +formats: + - pdf + - epub + +python: + install: + - method: pip + path: . + extra_requirements: + - docs diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 0000000..cc5c1d3 --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,3 @@ +# Authors + +Josh Thomas diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29..f577a74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,34 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project attempts to adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + +## [Unreleased] + +## [0.1.0] + +Initial release! + +### Added + +- Initial documentation. +- Initial tests. +- Initial CI/CD (GitHub Actions). + +### New Contributors + +- Josh Thomas (maintainer) + +[unreleased]: https://github.com/westerveltco/django-q-registry/compare/v0.1.0...HEAD diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..70c4680 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,53 @@ +# Contributing + +All contributions are welcome! Besides code contributions, this includes things like documentation improvements, bug reports, and feature requests. + +You should first check if there is a [GitHub issue](https://github.com/westerveltco/django-q-registry/issues) already open or related to what you would like to contribute. If there is, please comment on that issue to let others know you are working on it. If there is not, please open a new issue to discuss your contribution. + +Not all contributions need to start with an issue, such as typo fixes in documentation or version bumps to Python or Django that require no internal code changes, but generally, it is a good idea to open an issue first. + +We adhere to Django's Code of Conduct in all interactions and expect all contributors to do the same. Please read the [Code of Conduct](https://www.djangoproject.com/conduct/) before contributing. + +## Setup + +The following setup steps assume you are using a Unix-like operating system, such as Linux or macOS, and that you have a [supported](#requirements) version of Python installed. If you are using Windows, you will need to adjust the commands accordingly. If you do not have Python installed, you can visit [python.org](https://www.python.org/) for instructions on how to install it for your operating system. + +1. Fork the repository and clone it locally. +2. Create a virtual environment and activate it. You can use whatever tool you prefer for this. Below is an example using the Python standard library's `venv` module: + +```shell +python -m venv venv +source venv/bin/activate +``` + +3. Install `django-q-registry` and the `dev` dependencies in editable mode: + +```shell +python -m pip install -e '.[dev]' +``` + +## Testing + +We use [`pytest`](https://docs.pytest.org/) for testing and [`nox`](https://nox.thea.codes/) to run the tests in multiple environments. + +To run the test suite against the current environment, run: + +```shell +python -m pytest +``` + +To run the test suite against all supported versions of Python and Django, run: + +```shell +python -m nox +``` + +## `just` + +[`just`](https://github.com/casey/just) is a command runner that is used to run common commands, similar to `make` or `invoke`. A `Justfile` is provided at the base of the repository, which contains commands for common development tasks, such as running the test suite or linting. + +To see a list of all available commands, ensure `just` is installed and run the following command at the base of the repository: + +```shell +just +``` diff --git a/Justfile b/Justfile index ef675c6..0553713 100644 --- a/Justfile +++ b/Justfile @@ -1,11 +1,93 @@ set dotenv-load := true @_default: - just --list + just --list -dev: - python -m pip install -U pip - python -m pip install '.[dev]' +# ---------------------------------------------------------------------- +# DEPENDENCIES +# ---------------------------------------------------------------------- -test: - python -m nox --reuse-existing-virtualenvs +alias install := bootstrap + +bootstrap: + python -m pip install --editable '.[dev]' + +pup: + python -m pip install --upgrade pip + +update: + @just pup + @just bootstrap + +# ---------------------------------------------------------------------- +# TESTING/TYPES +# ---------------------------------------------------------------------- + +test *ARGS: + python -m nox --reuse-existing-virtualenvs --session "test" -- "{{ ARGS }}" + +test-all *ARGS: + python -m nox --reuse-existing-virtualenvs --session "tests" -- "{{ ARGS }}" + +coverage: + python -m nox --reuse-existing-virtualenvs --session "coverage" + +types: + python -m nox --reuse-existing-virtualenvs --session "mypy" + +# ---------------------------------------------------------------------- +# DJANGO +# ---------------------------------------------------------------------- + +manage *COMMAND: + #!/usr/bin/env python + import sys + + try: + from django.conf import settings + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + + settings.configure(INSTALLED_APPS=["django_q_registry"]) + execute_from_command_line(sys.argv + "{{ COMMAND }}".split(" ")) + +alias mm := makemigrations + +makemigrations *APPS: + @just manage makemigrations {{ APPS }} + +migrate *ARGS: + @just manage migrate {{ ARGS }} + +# ---------------------------------------------------------------------- +# DOCS +# ---------------------------------------------------------------------- + +@docs-install: + python -m pip install '.[docs]' + +@docs-serve: + #!/usr/bin/env sh + if [ -f "/.dockerenv" ]; then + sphinx-autobuild docs docs/_build/html --host "0.0.0.0" + else + sphinx-autobuild docs docs/_build/html --host "localhost" + fi + +@docs-build LOCATION="docs/_build/html": + sphinx-build docs {{ LOCATION }} + +# ---------------------------------------------------------------------- +# UTILS +# ---------------------------------------------------------------------- + +lint: + python -m nox --reuse-existing-virtualenvs --session "lint" + +mypy: + python -m nox --reuse-existing-virtualenvs --session "mypy" diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..1d8380e --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,75 @@ +# Releasing a New Version + +When it comes time to cut a new release, follow these steps: + +1. Create a new git branch off of `main` for the release. + + Prefer the convention `release-`, where `` is the next incremental version number (e.g. `release-v1.0.0` for version 1.0.0). + + ```shell + git checkout -b release-v + ``` + + However, the branch name is not *super* important, as long as it is not `main`. + +2. Update the version number across the project using the `bumpver` tool. See [this section](#choosing-the-next-version-number) for more details about choosing the correct version number. + + The `pyproject.toml` in the base of the repository contains a `[tool.bumpver]` section that configures the `bumpver` tool to update the version number wherever it needs to be updated and to create a commit with the appropriate commit message. + + `bumpver` is included as a development dependency, so you should already have it installed if you have installed the development dependencies for this project. If you do not have the development dependencies installed, you can install them with either of the following commands: + + ```shell + python -m pip install --editable '.[dev]' + # or using the included `Justfile` + just bootstrap + ``` + + Then, run `bumpver` to update the version number, with the appropriate command line arguments. See the [`bumpver` documentation](https://github.com/mbarkhau/bumpver) for more details. + + **Note**: For any of the following commands, you can add the command line flag `--dry` to preview the changes without actually making the changes. + + Here are the most common commands you will need to run: + + ```shell + bumpver update --patch # for a patch release + bumpver update --minor # for a minor release + bumpver update --major # for a major release + ``` + + To release a tagged version, such as a beta or release candidate, you can run: + + ```shell + bumpver update --tag=beta + # or + bumpver update --tag=rc + ``` + + Running these commands on a tagged version will increment the tag appropriately, but will not increment the version number. + + To go from a tagged release to a full release, you can run: + + ```shell + bumpver update --tag=final + ``` + +3. Ensure the [CHANGELOG](https://github.com/westerveltco/django-q-registry/blob/main/CHANGELOG.md) is up to date. If updates are needed, add them now in the release branch. + +4. Create a pull request from the release branch to `main`. + +5. Once CI has passed and all the checks are green ✅, merge the pull request. + +6. Draft a [new release](https://github.com/westerveltco/django-q-registry/releases/new) on GitHub. + + Use the version number with a leading `v` as the tag name (e.g. `v1.0.0`). + + Allow GitHub to generate the release title and release notes, using the 'Generate release notes' button above the text box. If this is a final release coming from a tagged release (or multiple tagged releases), make sure to copy the release notes from the previous tagged release(s) to the new release notes (after the release notes already generated for this final release). + + If this is a tagged release, make sure to check the 'Set as a pre-release' checkbox. + +7. Once you are satisfied with the release, publish the release. As part of the publication process, GitHub Actions will automatically publish the new version of the package to PyPI. + +## Choosing the Next Version Number + +We try our best to adhere to [Semantic Versioning](https://semver.org/), but we do not promise to follow it perfectly (and let's be honest, this is the case with a lot of projects using SemVer). + +In general, use your best judgement when choosing the next version number. If you are unsure, you can always ask for a second opinion from another contributor. diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..cd4d819 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,106 @@ +# ruff: noqa: E501 +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +from __future__ import annotations + +import os +import sys + +import django + +# -- Path setup -------------------------------------------------------------- +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# + +sys.path.insert(0, os.path.abspath("..")) + + +# -- Django setup ----------------------------------------------------------- +# This is required to import Django code in Sphinx using autodoc. + +os.environ["DJANGO_SETTINGS_MODULE"] = "tests.settings" +django.setup() + + +# -- Project information ----------------------------------------------------- + +project = "Django Q Registry" +copyright = "2023, Josh Thomas" # noqa: A001 +author = "Josh Thomas" + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "myst_parser", + "sphinx_copybutton", + "sphinx_inline_tabs", + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + + +# -- MyST configuration ------------------------------------------------------ +myst_heading_anchors = 3 + +copybutton_selector = "div.copy pre" +copybutton_prompt_text = "$ " + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "furo" + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] + +html_title = project + +html_theme_options = { + "footer_icons": [ + { + "name": "GitHub", + "url": "https://github.com/westerveltco/django-q-registry", + "html": """ + + + + """, + "class": "", + }, + ], +} + +html_sidebars = { + "**": [ + "sidebar/brand.html", + "sidebar/repo.html", + "sidebar/search.html", + "sidebar/scroll-start.html", + "sidebar/navigation.html", + "sidebar/scroll-end.html", + "sidebar/variant-selector.html", + ] +} diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..691383a --- /dev/null +++ b/docs/index.md @@ -0,0 +1,3 @@ +```{include} ../README.md + +``` diff --git a/noxfile.py b/noxfile.py index 74e09fa..42b76fe 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,5 +1,8 @@ from __future__ import annotations +import os +from pathlib import Path + import nox PY38 = "3.8" @@ -8,16 +11,18 @@ PY311 = "3.11" PY312 = "3.12" PY_VERSIONS = [PY38, PY39, PY310, PY311, PY312] -PY_DEFAULT = PY38 +PY_DEFAULT = PY_VERSIONS[0] +PY_LATEST = PY_VERSIONS[-1] DJ32 = "3.2" -DJ40 = "4.0" -DJ41 = "4.1" DJ42 = "4.2" +DJ50 = "5.0" DJMAIN = "main" DJMAIN_MIN_PY = PY310 -DJ_VERSIONS = [DJ32, DJ40, DJ41, DJ42, DJMAIN] -DJ_DEFAULT = DJ32 +DJ_VERSIONS = [DJ32, DJ42, DJ50, DJMAIN] +DJ_LTS = [DJ32, DJ42] +DJ_DEFAULT = DJ_LTS[-1] +DJ_LATEST = DJ_VERSIONS[-2] def version(ver: str) -> tuple[int, ...]: @@ -33,16 +38,28 @@ def should_skip(python: str, django: str) -> tuple[bool, str | None]: if django == DJ32 and version(python) >= version(PY312): return True, f"Django {DJ32} requires Python < {PY312}" + if django == DJ50 and version(python) < version(PY310): + return True, f"Django {DJ50} requires Python {PY310}+" + return False, None -@nox.session(python=PY_VERSIONS) -@nox.parametrize("django", DJ_VERSIONS) -def tests(session, django): - skip = should_skip(session.python, django) - if skip[0]: - session.skip(skip[1]) +@nox.session +def test(session): + default_test = f"tests(python='{PY_DEFAULT}', django='{DJ_DEFAULT}')" + if session.posargs: + session.notify(default_test, posargs=session.posargs) + else: + session.notify(default_test) + session.skip() + +@nox.session +@nox.parametrize( + "python,django", + [(python, django) for python in PY_VERSIONS for django in DJ_VERSIONS if not should_skip(python, django)[0]], +) +def tests(session, django): session.install(".[dev]") if django == DJMAIN: @@ -50,4 +67,46 @@ def tests(session, django): else: session.install(f"django=={django}") - session.run("pytest", "-n", "auto", "--dist", "loadfile") + if session.posargs: + session.run("python", "-m", "pytest", *session.posargs) + else: + session.run("python", "-m", "pytest") + + +@nox.session +def coverage(session): + session.install(".[dev]") + session.run("python", "-m", "pytest", "--cov=django_q_registry") + + try: + summary = os.environ["GITHUB_STEP_SUMMARY"] + with Path(summary).open("a") as output_buffer: + output_buffer.write("") + output_buffer.write("### Coverage\n\n") + output_buffer.flush() + session.run( + "python", + "-m", + "coverage", + "report", + "--skip-covered", + "--skip-empty", + "--format=markdown", + stdout=output_buffer, + ) + except KeyError: + session.run("python", "-m", "coverage", "html", "--skip-covered", "--skip-empty") + + session.run("python", "-m", "coverage", "report") + + +@nox.session +def lint(session): + session.install(".[lint]") + session.run("python", "-m", "pre_commit", "run", "--all-files") + + +@nox.session +def mypy(session): + session.install(".[dev]") + session.run("python", "-m", "mypy", ".") diff --git a/pyproject.toml b/pyproject.toml index a4c7f18..1170274 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,10 +4,16 @@ requires = ["hatchling"] [project] authors = [ - {name = "Josh", email = "josh@joshthomas.dev"}, + {name = "Josh", email = "josh@joshthomas.dev"} ] classifiers = [ "Development Status :: 4 - Beta", + "Framework :: Django", + "Framework :: Django :: 3", + "Framework :: Django :: 3.2", + "Framework :: Django :: 4", + "Framework :: Django :: 4.2", + "Framework :: Django :: 5.0", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", @@ -17,88 +23,156 @@ classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: Implementation :: CPython" ] dependencies = [ "django>=3.2", - "django_q2>=1.4.3", + "django_q2>=1.4.3" ] description = "A Django app to register Django Q tasks." dynamic = ["version"] keywords = [] -license = "MIT" +license = {file = "LICENSE"} name = "django-q-registry" readme = "README.md" requires-python = ">=3.8" [project.optional-dependencies] dev = [ - "black", + "bumpver", "coverage[toml]", + "django-stubs", + "django-stubs-ext", + "faker", "hatch", "mypy", + "model-bakery", "nox", "pytest", + "pytest-cov", "pytest-django", - "pytest-xdist", - "ruff", + "pytest-randomly", + "pytest-reverse", + "pytest-xdist" ] +docs = [ + "cogapp", + "furo", + "myst-parser", + "sphinx", + "sphinx-autobuild", + "sphinx-copybutton", + "sphinx-inline-tabs" +] +lint = ["pre-commit"] [project.urls] Documentation = "https://github.com/joshuadavidthomas/django-q-registry#readme" Issues = "https://github.com/joshuadavidthomas/django-q-registry/issues" Source = "https://github.com/joshuadavidthomas/django-q-registry" -[tool.hatch.version] -path = "src/django_q_registry/__init__.py" +[tool.bumpver] +commit = true +commit_message = ":bookmark: bump version {old_version} -> {new_version}" +current_version = "0.1.0" +push = false # set to false for CI +tag = false +version_pattern = "MAJOR.MINOR.PATCH[PYTAGNUM]" -[tool.black] -line-length = 120 -skip-string-normalization = true -target-version = ["py38"] +[tool.bumpver.file_patterns] +"src/django_q_registry/__init__.py" = [ + '__version__ = "{version}"' +] +"tests/test_version.py" = [ + 'assert __version__ == "{version}"' +] [tool.coverage.report] exclude_lines = [ - "no cov", - "if __name__ == .__main__.:", + "pragma: no cover", + "if DEBUG:", + "if not DEBUG:", + "if settings.DEBUG:", "if TYPE_CHECKING:", + 'def __str__\(self\)\s?\-?\>?\s?\w*\:' ] +fail_under = 75 [tool.coverage.run] -source = ["."] +omit = [ + "tests/*" +] +source = ["django_q_registry"] +[tool.django-stubs] +django_settings_module = "tests.settings" +strict_settings = false + +[tool.hatch.build] +exclude = [ + ".github/*", + ".dockerfiles/*", + ".dockerignore", + ".editorconfig", + ".pre-commit-config.yaml", + "Justfile" +] + +[tool.hatch.build.targets.wheel] +packages = ["src/django_q_registry"] + +[tool.hatch.version] +path = "src/django_q_registry/__init__.py" [tool.mypy] -ignore_missing_imports = true +check_untyped_defs = true +exclude = "docs/.*\\.py$" mypy_path = "src/" namespace_packages = false -show_error_codes = true -strict = true -warn_unreachable = true +no_implicit_optional = true +plugins = [ + "mypy_django_plugin.main" +] +warn_redundant_casts = true +warn_unused_configs = true +warn_unused_ignores = true [[tool.mypy.overrides]] -allow_untyped_defs = true +ignore_errors = true +ignore_missing_imports = true module = "tests.*" +[[tool.mypy.overrides]] +ignore_missing_imports = true +module = [ + "django_q.*" +] + +[tool.mypy_django_plugin] +ignore_missing_model_attributes = true + [tool.pytest.ini_options] -DJANGO_SETTINGS_MODULE = "tests.settings" +addopts = "--create-db -n auto --dist loadfile --doctest-modules" django_find_project = false -pythonpath = ". src" +norecursedirs = ".* bin build dist *.egg htmlcov logs node_modules templates venv" python_files = "tests.py test_*.py *_tests.py" +pythonpath = "src" +testpaths = ["tests"] [tool.ruff] ignore = [ # Allow non-abstract empty methods in abstract base classes - "B027", # Allow boolean positional values in function calls, like `dict.get(... True)` - "FBT003", # Ignore checks for possible passwords + "B027", # Allow boolean positional values in function calls, like `dict.get(... True)` + "FBT003", # Ignore checks for possible passwords "S105", "S106", - "S107", # Ignore complexity + "S107", # Ignore complexity "C901", "PLR0911", "PLR0912", "PLR0913", - "PLR0915", + "PLR0915" ] line-length = 120 select = [ @@ -126,22 +200,22 @@ select = [ "TID", "UP", "W", - "YTT", + "YTT" ] target-version = "py38" unfixable = [ # Don't touch unused imports - "F401", + "F401" ] +[tool.ruff.flake8-tidy-imports] +ban-relative-imports = "all" + [tool.ruff.isort] force-single-line = true known-first-party = ["django_q_registry"] required-imports = ["from __future__ import annotations"] -[tool.ruff.flake8-tidy-imports] -ban-relative-imports = "all" - [tool.ruff.per-file-ignores] # Tests can use magic values, assertions, and relative imports "tests/**/*" = ["PLR2004", "S101", "TID252"] diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..9ad89ef --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +import logging +import multiprocessing + +from django.conf import settings + +pytest_plugins = [] # type: ignore + + +# Settings fixtures to bootstrap our tests +def pytest_configure(config): # noqa: ARG001 + logging.disable(logging.CRITICAL) + + settings.configure( + ALLOWED_HOSTS=["*"], + CACHES={ + "default": { + "BACKEND": "django.core.cache.backends.dummy.DummyCache", + } + }, + DATABASES={ + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": ":memory:", + }, + }, + Q_CLUSTER={ + "name": "ORM", + "workers": multiprocessing.cpu_count() * 2 + 1, + "timeout": 60, + "retry": 120, + "queue_limit": 50, + "bulk": 10, + "orm": "default", + }, + EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend", + INSTALLED_APPS=[ + "django_q", + "django_q_registry", + ], + LOGGING_CONFIG=None, + PASSWORD_HASHERS=["django.contrib.auth.hashers.MD5PasswordHasher"], + SECRET_KEY="NOTASECRET", + USE_TZ=True, + ) diff --git a/tests/settings.py b/tests/settings.py index 6d8421e..e69de29 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -1,7 +0,0 @@ -from __future__ import annotations - -SECRET_KEY = "NOTASECRET" - -ALLOWED_HOSTS = ["*"] - -USE_TZ = True