diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f1338ed..8366db8 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,7 +1,7 @@ name: Publish on: - release: + tag: types: - created @@ -19,7 +19,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install flit + python -m pip install flit mkdocs-material - name: Publish run: | @@ -27,3 +27,9 @@ jobs: env: FLIT_USERNAME: __token__ FLIT_PASSWORD: ${{ secrets.PYPI_TOKEN }} + + - name: Build documentation + run: mkdocs build + + - name: Publish documentation + run: mkdocs gh-deploy --force diff --git a/.github/workflows/publish_docs.yml b/.github/workflows/publish_docs.yml deleted file mode 100644 index fdbb365..0000000 --- a/.github/workflows/publish_docs.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Publish documentation - -on: - release: - types: - - published - -jobs: - publish_documentation: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up Python 3.12 - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install mkdocs-material - - - name: Build documentation - run: mkdocs build - - - name: Publish documentation - run: mkdocs gh-deploy --force diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c8a9501..fc86a01 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 881e9b4..21a0bfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 2.12.0 + +- Reintroduced support for Python 3.8. Seems as I have to support than I want to. +- Added justfile to ease management of package and dev environment. + ## 2.11.1 - Changed project tooling to uv, nox and flit. diff --git a/justfile b/justfile index 0d1cb08..91de227 100644 --- a/justfile +++ b/justfile @@ -32,17 +32,29 @@ VENV_DIRNAME := ".venv" # upgrade/install all dependencies defined in pyproject.toml @upgrade: create_venv - $VENV_DIRNAME/bin/python3 -m pip install --upgrade pip uv; \ - $VENV_DIRNAME/bin/python3 -m uv pip install --upgrade \ + $VENV_DIRNAME/bin/python -m pip install --upgrade pip uv; \ + $VENV_DIRNAME/bin/python -m uv pip install --upgrade \ --requirement pyproject.toml --all-extras -e .; # run pre-commit rules on all files @lint *ARGS: create_venv - $VENV_DIRNAME/bin/python3 -m pre_commit run {{ ARGS }} --all-files + $VENV_DIRNAME/bin/python -m pre_commit run {{ ARGS }} --all-files # run test suite @test *ARGS: create_venv - $VENV_DIRNAME/bin/python3 -m coverage erase - $VENV_DIRNAME/bin/python3 -m nox --force-venv-backend uv {{ ARGS }} - $VENV_DIRNAME/bin/python3 -m coverage report - $VENV_DIRNAME/bin/python3 -m coverage html + $VENV_DIRNAME/bin/python -m coverage erase + $VENV_DIRNAME/bin/python -m nox --force-venv-backend uv {{ ARGS }} + $VENV_DIRNAME/bin/python -m coverage report + $VENV_DIRNAME/bin/python -m coverage html + +@build-docs: + $VENV_DIRNAME/bin/python -m mkdocs build + +@publish-docs: build-docs + $VENV_DIRNAME/bin/python -m mkdocs gh-deploy --force + +@build: + $VENV_DIRNAME/bin/python -m flit build + +@publish: build + $VENV_DIRNAME/bin/python -m flit publish diff --git a/noxfile.py b/noxfile.py index c713182..cebc692 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,6 +1,28 @@ # type: ignore import nox +import tomllib + +with open("pyproject.toml", "rb") as f: + project_data = tomllib.load(f)["project"] + + # Read supported Python versions from pyproject.toml + python_versions = [] + for classifier in project_data["classifiers"]: + if "Programming Language" in classifier and "Python" in classifier: + split_classifier = classifier.split("::") + if len(split_classifier) == 3: + python_versions.append(split_classifier[2].strip()) + + # Read supported Django versions from pyproject.toml + django_versions = [] + for classifier in project_data["classifiers"]: + if "Framework" in classifier and "Django" in classifier: + split_classifier = classifier.split("::") + if len(split_classifier) == 3: + django_versions.append(split_classifier[2].strip()) + +invalid_combinations = [("3.8", "5.0"), ("3.9", "5.0"), ("3.11", "3.2"), ("3.12", "3.2")] @nox.session() @@ -8,19 +30,15 @@ "python, django", [ (python, tox_version) - for python in ("3.9", "3.10", "3.11", "3.12") - for tox_version in ("3.2", "4.2", "5.0") - if ( - (python, tox_version) != ("3.9", "5.0") - and (python, tox_version) != ("3.11", "3.2") - and (python, tox_version) != ("3.12", "3.2") - ) + for python in python_versions + for tox_version in django_versions + if (python, tox_version) not in invalid_combinations ], ) def run_testsuite(session, django): if session.venv_backend == "uv": - session.install("-r", "pyproject.toml", "--extra", "django-extensions", "--extra", "dev", ".") + session.install("-r", "pyproject.toml", "--extra", "test", ".") else: - session.install(".[django-extensions,dev]") + session.install(".[test]") session.install(f"django~={django}") session.run("pytest", "--cov", "--cov-append", "--cov-report=") diff --git a/pyproject.toml b/pyproject.toml index 96c77e4..4bfa5e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -26,25 +27,30 @@ classifiers = [ "Framework :: Django :: 5.0", ] dynamic = ["version"] -requires-python = ">=3.9" +requires-python = ">=3.8" dependencies = [ "django>=3.2", "certifi>=2023.7.22,<2025.0.0", "django-typer>=1.1.2", ] [project.optional-dependencies] -dev = [ - "django-types", - "coverage[toml]", +django-extensions = ["django-extensions>=3.2", "werkzeug>=3.0"] +test = [ "pytest", "pytest-django", "pytest-cov", "pytest-mock", + "pytest-randomly", + "django_tailwind_cli[django-extensions]", +] +dev = [ + "django-types", "nox", "pre-commit", + "mkdocs-material", + "flit", + "django_tailwind_cli[test]", ] -docs = ["mkdocs-material"] -django-extensions = ["django-extensions>=3.2", "werkzeug>=3.0"] [project.urls] Home = "https://django-tailwind-cli.andrich.me/" @@ -53,12 +59,13 @@ Repository = "https://github.com/oliverandrich/django-tailwind-cli" # Black [tool.black] -target-version = ["py39"] +target-version = ["py38"] line-length = 120 skip-string-normalization = true exclude = ''' /( \.git + | \.nox | \.tox | \.venv | _build @@ -77,7 +84,7 @@ ignore = ["./tests/**/*"] # Ruff [tool.ruff] -target-version = "py39" +target-version = "py38" line-length = 120 select = [ "A", # flake8-builtins @@ -128,11 +135,8 @@ markers = ["mock_network_and_subprocess"] # Coverage [tool.coverage.run] -source = ["django_tailwind_cli", "tests"] +source = ["django_tailwind_cli"] branch = true -[tool.coverage.paths] -source = ["src", ".tox/**/site-packages"] - [tool.coverage.report] exclude_lines = ["no cov", "if __name__ == .__main__.:", "if TYPE_CHECKING:"] diff --git a/src/django_tailwind_cli/__init__.py b/src/django_tailwind_cli/__init__.py index 200c236..95a6d3a 100644 --- a/src/django_tailwind_cli/__init__.py +++ b/src/django_tailwind_cli/__init__.py @@ -1 +1 @@ -__version__ = "2.11.1" +__version__ = "2.12.0" diff --git a/src/django_tailwind_cli/management/commands/tailwind.py b/src/django_tailwind_cli/management/commands/tailwind.py index f9eeafd..4e6bde7 100644 --- a/src/django_tailwind_cli/management/commands/tailwind.py +++ b/src/django_tailwind_cli/management/commands/tailwind.py @@ -9,14 +9,14 @@ import urllib.request from multiprocessing import Process from pathlib import Path -from typing import Annotated, List, Optional, Union +from typing import List, Optional, Union import certifi +import typer from django.conf import settings from django.core.management.base import CommandError from django.template.utils import get_app_template_dirs from django_typer import TyperCommand, command, initialize -from typer import Argument, Option from django_tailwind_cli.utils import Config @@ -104,15 +104,15 @@ def list_templates(self): @command(help="Start the Django development server and the Tailwind CLI in watch mode.") def runserver( self, - addrport: Annotated[Optional[str], Argument(help="Optional port number, or ipaddr:port")] = None, + addrport: Optional[str] = typer.Argument(None, help="Optional port number, or ipaddr:port"), *, - use_ipv6: Annotated[bool, Option("--ipv6", "-6", help="Tells Django to use an IPv6 address.")] = False, - no_threading: Annotated[bool, Option("--nothreading", help="Tells Django to NOT use threading.")] = False, - no_static: Annotated[ - bool, Option("--nostatic", help="Tells Django to NOT automatically serve static files at STATIC_URL.") - ] = False, - no_reloader: Annotated[bool, Option("--noreload", help="Tells Django to NOT use the auto-reloader.")] = False, - skip_checks: Annotated[bool, Option("--skip-checks", help="Skip system checks.")] = False, + use_ipv6: bool = typer.Option(False, "--ipv6", "-6", help="Tells Django to use an IPv6 address."), + no_threading: bool = typer.Option(False, "--nothreading", help="Tells Django to NOT use threading."), + no_static: bool = typer.Option( + False, "--nostatic", help="Tells Django to NOT automatically serve static files at STATIC_URL." + ), + no_reloader: bool = typer.Option(False, "--noreload", help="Tells Django to NOT use the auto-reloader."), + skip_checks: bool = typer.Option(False, "--skip-checks", help="Skip system checks."), ): debug_server_cmd = [sys.executable, "manage.py", "runserver"] @@ -137,40 +137,36 @@ def runserver( ) def runserver_plus( self, - addrport: Annotated[Optional[str], Argument(help="Optional port number, or ipaddr:port")] = None, + addrport: Optional[str] = typer.Argument(None, help="Optional port number, or ipaddr:port"), *, - use_ipv6: Annotated[bool, Option("--ipv6", "-6", help="Tells Django to use an IPv6 address.")] = False, - no_threading: Annotated[bool, Option("--nothreading", help="Tells Django to NOT use threading.")] = False, - no_static: Annotated[ - bool, Option("--nostatic", help="Tells Django to NOT automatically serve static files at STATIC_URL.") - ] = False, - no_reloader: Annotated[bool, Option("--noreload", help="Tells Django to NOT use the auto-reloader.")] = False, - skip_checks: Annotated[bool, Option("--skip-checks", help="Skip system checks.")] = False, - pdb: Annotated[bool, Option("--pdb", help="Drop into pdb shell at the start of any view.")] = False, - ipdb: Annotated[bool, Option("--ipdb", help="Drop into ipdb shell at the start of any view.")] = False, - pm: Annotated[bool, Option("--pm", help="Drop into (i)pdb shell if an exception is raised in a view.")] = False, - print_sql: Annotated[bool, Option("--print-sql", help="Print SQL queries as they're executed.")] = False, - print_sql_location: Annotated[ - bool, Option("--print-sql-location", help="Show location in code where SQL query generated from.") - ] = False, - cert_file: Annotated[ - Optional[str], - Option( - help=( - "SSL .crt file path. If not provided path from --key-file will be selected. Either --cert-file or " - "--key-file must be provided to use SSL." - ) + use_ipv6: bool = typer.Option(False, "--ipv6", "-6", help="Tells Django to use an IPv6 address."), + no_threading: bool = typer.Option(False, "--nothreading", help="Tells Django to NOT use threading."), + no_static: bool = typer.Option( + False, "--nostatic", help="Tells Django to NOT automatically serve static files at STATIC_URL." + ), + no_reloader: bool = typer.Option(False, "--noreload", help="Tells Django to NOT use the auto-reloader."), + skip_checks: bool = typer.Option(False, "--skip-checks", help="Skip system checks."), + pdb: bool = typer.Option(False, "--pdb", help="Drop into pdb shell at the start of any view."), + ipdb: bool = typer.Option(False, "--ipdb", help="Drop into ipdb shell at the start of any view."), + pm: bool = typer.Option(False, "--pm", help="Drop into (i)pdb shell if an exception is raised in a view."), + print_sql: bool = typer.Option(False, "--print-sql", help="Print SQL queries as they're executed."), + print_sql_location: bool = typer.Option( + False, "--print-sql-location", help="Show location in code where SQL query generated from." + ), + cert_file: Optional[str] = typer.Option( + None, + help=( + "SSL .crt file path. If not provided path from --key-file will be selected. Either --cert-file or " + "--key-file must be provided to use SSL." ), - ] = None, - key_file: Annotated[ - Optional[str], - Option( - help=( - "SSL .key file path. If not provided path from --cert-file will be " - "selected. Either --cert-file or --key-file must be provided to use SSL." - ) + ), + key_file: Optional[str] = typer.Option( + None, + help=( + "SSL .key file path. If not provided path from --cert-file will be " + "selected. Either --cert-file or --key-file must be provided to use SSL." ), - ] = None, + ), ): if not importlib.util.find_spec("django_extensions") and not importlib.util.find_spec("werkzeug"): msg = ( @@ -254,11 +250,9 @@ def download_cli(self) -> None: self._write_success(f"Downloading Tailwind CSS CLI from '{download_url}'") dest_file.parent.mkdir(parents=True, exist_ok=True) certifi_context = ssl.create_default_context(cafile=certifi.where()) - with ( - urllib.request.urlopen(download_url, context=certifi_context) as source, - dest_file.open(mode="wb") as dest, # noqa: S310 - ): - shutil.copyfileobj(source, dest) + with urllib.request.urlopen(download_url, context=certifi_context) as source: + with dest_file.open(mode="wb") as dest: + shutil.copyfileobj(source, dest) # make cli executable dest_file.chmod(0o755) self._write_success(f"Downloaded Tailwind CSS CLI to '{dest_file}'")