From 8aa5e4a12e542267d014e9041f3fac57b8d43abc Mon Sep 17 00:00:00 2001 From: Michael Tauraso Date: Tue, 1 Oct 2024 10:46:35 -0700 Subject: [PATCH] Adding "better" python version propagation, and removing hardcoded python versions. (#484) - There are now three defined python versions you can use in templates, they are derived from python_versions and available through jinja macros. - py.pref(), py.max(), and py.min() are the macros, using the preferred import {%- import 'python-versions.jinja' as py -%}. Notably this is the only type of import that copier will accept in files who's pathnames contain templates. which eliminates several more syntactically ergonomic methods of achieving this same interface. - py.pref(python_versions), the preferred version, is the "middle-est" in the python_versions list, see python-versions.jinja for the math. For the default case where python_versions = [3.9,3.10,3,11], 3.10 is the preferred python version. - Hardcoded mentions of python 3.9 and python 3.10 have been set to py.min(python_versions) or py.pref(python_versions) respectively so that this change does not affect projects using the default configuration, and any python upgrade compatibility issues can be deferred until either the project template or an individual project changes python version support. - References to python_versions[0] have been replaced with py.min(python_versions) for consistency. --- .../.github/workflows/pre-commit-ci.yml.jinja | 3 +- ...-to-pypi.yml => publish-to-pypi.yml.jinja} | 3 +- ...benchmarks %}asv-main.yml{% endif %}.jinja | 5 ++- ...chmarks %}asv-nightly.yml{% endif %}.jinja | 5 ++- ...e_benchmarks %}asv-pr.yml{% endif %}.jinja | 5 ++- ...%}build-documentation.yml{% endif %}.jinja | 5 +-- .../.pre-commit-config.yaml.jinja | 3 +- python-project-template/README.md.jinja | 3 +- python-project-template/pyproject.toml.jinja | 7 ++-- ...nforce_style %}.pylintrc{% endif %}.jinja} | 3 +- ...nforce_style %}.pylintrc{% endif %}.jinja} | 3 +- .../asv.conf.json.jinja | 3 +- ..._docs %}.readthedocs.yml{% endif %}.jinja} | 3 +- .../index.rst.jinja | 3 +- python-versions.jinja | 35 +++++++++++++++++++ 15 files changed, 72 insertions(+), 17 deletions(-) rename python-project-template/.github/workflows/{publish-to-pypi.yml => publish-to-pypi.yml.jinja} (90%) rename python-project-template/src/{{% if 'pylint' in enforce_style %}.pylintrc{% endif %} => {% if 'pylint' in enforce_style %}.pylintrc{% endif %}.jinja} (99%) rename python-project-template/tests/{{% if 'pylint' in enforce_style %}.pylintrc{% endif %} => {% if 'pylint' in enforce_style %}.pylintrc{% endif %}.jinja} (99%) rename python-project-template/{{% if include_docs %}.readthedocs.yml{% endif %} => {% if include_docs %}.readthedocs.yml{% endif %}.jinja} (83%) create mode 100644 python-versions.jinja diff --git a/python-project-template/.github/workflows/pre-commit-ci.yml.jinja b/python-project-template/.github/workflows/pre-commit-ci.yml.jinja index a57e2219..752b5126 100644 --- a/python-project-template/.github/workflows/pre-commit-ci.yml.jinja +++ b/python-project-template/.github/workflows/pre-commit-ci.yml.jinja @@ -1,3 +1,4 @@ +{%- import 'python-versions.jinja' as py %} # This workflow runs pre-commit hooks on pushes and pull requests to main # to enforce coding style. To ensure correct configuration, please refer to: # https://lincc-ppt.readthedocs.io/en/latest/practices/ci_precommit.html @@ -19,7 +20,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.10' + python-version: '{{ py.pref(python_versions) }}' - name: Install dependencies run: | sudo apt-get update diff --git a/python-project-template/.github/workflows/publish-to-pypi.yml b/python-project-template/.github/workflows/publish-to-pypi.yml.jinja similarity index 90% rename from python-project-template/.github/workflows/publish-to-pypi.yml rename to python-project-template/.github/workflows/publish-to-pypi.yml.jinja index f7cecc2e..fec3125d 100644 --- a/python-project-template/.github/workflows/publish-to-pypi.yml +++ b/python-project-template/.github/workflows/publish-to-pypi.yml.jinja @@ -1,3 +1,4 @@ +{%- import 'python-versions.jinja' as py %} # This workflow will upload a Python Package using Twine when a release is created # For more information see: https://github.com/pypa/gh-action-pypi-publish#trusted-publishing @@ -26,7 +27,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.10' + python-version: '{{ py.pref(python_versions) }}' - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/python-project-template/.github/workflows/{% if include_benchmarks %}asv-main.yml{% endif %}.jinja b/python-project-template/.github/workflows/{% if include_benchmarks %}asv-main.yml{% endif %}.jinja index ff836b9d..2c537ffd 100644 --- a/python-project-template/.github/workflows/{% if include_benchmarks %}asv-main.yml{% endif %}.jinja +++ b/python-project-template/.github/workflows/{% if include_benchmarks %}asv-main.yml{% endif %}.jinja @@ -9,7 +9,10 @@ on: branches: [ main ] env: - PYTHON_VERSION: "3.10" +{%- endraw %} +{%- import 'python-versions.jinja' as py%} + PYTHON_VERSION: "{{ py.pref(python_versions) }}" +{%- raw %} ASV_VERSION: "0.6.4" WORKING_DIR: ${{github.workspace}}/benchmarks diff --git a/python-project-template/.github/workflows/{% if include_benchmarks %}asv-nightly.yml{% endif %}.jinja b/python-project-template/.github/workflows/{% if include_benchmarks %}asv-nightly.yml{% endif %}.jinja index 583d988d..a3358e52 100644 --- a/python-project-template/.github/workflows/{% if include_benchmarks %}asv-nightly.yml{% endif %}.jinja +++ b/python-project-template/.github/workflows/{% if include_benchmarks %}asv-nightly.yml{% endif %}.jinja @@ -10,7 +10,10 @@ on: workflow_dispatch: env: - PYTHON_VERSION: "3.10" +{%- endraw %} +{%- import 'python-versions.jinja' as py%} + PYTHON_VERSION: "{{ py.pref(python_versions) }}" +{%- raw %} ASV_VERSION: "0.6.4" WORKING_DIR: ${{github.workspace}}/benchmarks NIGHTLY_HASH_FILE: nightly-hash diff --git a/python-project-template/.github/workflows/{% if include_benchmarks %}asv-pr.yml{% endif %}.jinja b/python-project-template/.github/workflows/{% if include_benchmarks %}asv-pr.yml{% endif %}.jinja index be577f26..328ea864 100644 --- a/python-project-template/.github/workflows/{% if include_benchmarks %}asv-pr.yml{% endif %}.jinja +++ b/python-project-template/.github/workflows/{% if include_benchmarks %}asv-pr.yml{% endif %}.jinja @@ -16,7 +16,10 @@ concurrency: cancel-in-progress: true env: - PYTHON_VERSION: "3.10" +{%- endraw %} +{%- import 'python-versions.jinja' as py%} + PYTHON_VERSION: "{{ py.pref(python_versions) }}" +{%- raw %} ASV_VERSION: "0.6.4" WORKING_DIR: ${{github.workspace}}/benchmarks ARTIFACTS_DIR: ${{github.workspace}}/artifacts diff --git a/python-project-template/.github/workflows/{% if include_docs %}build-documentation.yml{% endif %}.jinja b/python-project-template/.github/workflows/{% if include_docs %}build-documentation.yml{% endif %}.jinja index 6df816d9..17102b48 100644 --- a/python-project-template/.github/workflows/{% if include_docs %}build-documentation.yml{% endif %}.jinja +++ b/python-project-template/.github/workflows/{% if include_docs %}build-documentation.yml{% endif %}.jinja @@ -1,3 +1,4 @@ +{%- import 'python-versions.jinja' as py %} # This workflow will install Python dependencies, build the package and then build the documentation. name: Build documentation @@ -20,10 +21,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up Python 3.10 + - name: Set up Python {{ py.pref(python_versions) }} uses: actions/setup-python@v5 with: - python-version: '3.10' + python-version: '{{ py.pref(python_versions) }}' - name: Install dependencies run: | sudo apt-get update diff --git a/python-project-template/.pre-commit-config.yaml.jinja b/python-project-template/.pre-commit-config.yaml.jinja index b16574ca..1fd949b0 100644 --- a/python-project-template/.pre-commit-config.yaml.jinja +++ b/python-project-template/.pre-commit-config.yaml.jinja @@ -1,3 +1,4 @@ +{%- import 'python-versions.jinja' as py %} repos: # Compare the local template version to the latest remote template version # This hook should always pass. It will print a message if the local version @@ -98,7 +99,7 @@ repos: # supported by your project here, or alternatively use # pre-commit's default_language_version, see # https://pre-commit.com/#top_level-default_language_version - language_version: python3.10 + language_version: python{{ py.pref(python_versions) }} {%- endif %} {%- if 'ruff_lint' in enforce_style %} - repo: https://github.com/astral-sh/ruff-pre-commit diff --git a/python-project-template/README.md.jinja b/python-project-template/README.md.jinja index b39d9f63..5702f658 100644 --- a/python-project-template/README.md.jinja +++ b/python-project-template/README.md.jinja @@ -1,3 +1,4 @@ +{%- import 'python-versions.jinja' as py %} # {{project_name}} [![Template](https://img.shields.io/badge/Template-LINCC%20Frameworks%20Python%20Project%20Template-brightgreen)](https://lincc-ppt.readthedocs.io/en/latest/) @@ -29,7 +30,7 @@ environments. If you have conda installed locally, you can run the following to create and activate a new environment. ``` ->> conda create -n python=3.10 +>> conda create -n python={{ py.pref(python_versions) }} >> conda activate ``` diff --git a/python-project-template/pyproject.toml.jinja b/python-project-template/pyproject.toml.jinja index 5d6a9b32..8c4b23ba 100644 --- a/python-project-template/pyproject.toml.jinja +++ b/python-project-template/pyproject.toml.jinja @@ -1,3 +1,4 @@ +{%- import 'python-versions.jinja' as py %} [project] name = "{{project_name}}" license = {file = "LICENSE"} @@ -18,7 +19,7 @@ classifiers = [ "Programming Language :: Python", ] dynamic = ["version"] -requires-python = ">={{ python_versions[0] }}" +requires-python = ">={{ py.min(python_versions) }}" dependencies = [ ] @@ -66,7 +67,7 @@ testpaths = [ [tool.black] line-length = 110 -target-version = ["py{{ python_versions[0] | replace(".", "") }}"] +target-version = ["py{{ py.min(python_versions) | replace(".", "") }}"] [tool.isort] profile = "black" @@ -74,7 +75,7 @@ line_length = 110 [tool.ruff] line-length = 110 -target-version = "py{{ python_versions[0] | replace(".", "") }}" +target-version = "py{{ py.min(python_versions) | replace(".", "") }}" [tool.ruff.lint] select = [ diff --git a/python-project-template/src/{% if 'pylint' in enforce_style %}.pylintrc{% endif %} b/python-project-template/src/{% if 'pylint' in enforce_style %}.pylintrc{% endif %}.jinja similarity index 99% rename from python-project-template/src/{% if 'pylint' in enforce_style %}.pylintrc{% endif %} rename to python-project-template/src/{% if 'pylint' in enforce_style %}.pylintrc{% endif %}.jinja index 7bd3e031..772ee400 100644 --- a/python-project-template/src/{% if 'pylint' in enforce_style %}.pylintrc{% endif %} +++ b/python-project-template/src/{% if 'pylint' in enforce_style %}.pylintrc{% endif %}.jinja @@ -1,3 +1,4 @@ +{%- import 'python-versions.jinja' as py %} [MAIN] # Analyse import fallback blocks. This can be used to support both Python 2 and @@ -87,7 +88,7 @@ persistent=yes # Minimum Python version to use for version dependent checks. Will default to # the version used to run pylint. -py-version=3.9 +py-version={{ py.min(python_versions) }} # Discover python modules and packages in the file system subtree. recursive=no diff --git a/python-project-template/tests/{% if 'pylint' in enforce_style %}.pylintrc{% endif %} b/python-project-template/tests/{% if 'pylint' in enforce_style %}.pylintrc{% endif %}.jinja similarity index 99% rename from python-project-template/tests/{% if 'pylint' in enforce_style %}.pylintrc{% endif %} rename to python-project-template/tests/{% if 'pylint' in enforce_style %}.pylintrc{% endif %}.jinja index b5afc249..dad61ad6 100644 --- a/python-project-template/tests/{% if 'pylint' in enforce_style %}.pylintrc{% endif %} +++ b/python-project-template/tests/{% if 'pylint' in enforce_style %}.pylintrc{% endif %}.jinja @@ -1,3 +1,4 @@ +{%- import 'python-versions.jinja' as py %} [MAIN] # Analyse import fallback blocks. This can be used to support both Python 2 and @@ -87,7 +88,7 @@ persistent=yes # Minimum Python version to use for version dependent checks. Will default to # the version used to run pylint. -py-version=3.9 +py-version={{ py.min(python_versions) }} # Discover python modules and packages in the file system subtree. recursive=no diff --git a/python-project-template/{% if include_benchmarks %}benchmarks{% endif %}/asv.conf.json.jinja b/python-project-template/{% if include_benchmarks %}benchmarks{% endif %}/asv.conf.json.jinja index bc65f500..590c0768 100644 --- a/python-project-template/{% if include_benchmarks %}benchmarks{% endif %}/asv.conf.json.jinja +++ b/python-project-template/{% if include_benchmarks %}benchmarks{% endif %}/asv.conf.json.jinja @@ -1,3 +1,4 @@ +{%- import 'python-versions.jinja' as py %} { // The version of the config file format. Do not change, unless // you know what you are doing. @@ -36,7 +37,7 @@ // The Pythons you'd like to test against. If not provided, defaults // to the current version of Python used to run `asv`. "pythons": [ - "3.10" + "{{ py.pref(python_versions) }}" ], // The matrix of dependencies to test. Each key is the name of a // package (in PyPI) and the values are version numbers. An empty diff --git a/python-project-template/{% if include_docs %}.readthedocs.yml{% endif %} b/python-project-template/{% if include_docs %}.readthedocs.yml{% endif %}.jinja similarity index 83% rename from python-project-template/{% if include_docs %}.readthedocs.yml{% endif %} rename to python-project-template/{% if include_docs %}.readthedocs.yml{% endif %}.jinja index 79bfc272..63d9fdaa 100644 --- a/python-project-template/{% if include_docs %}.readthedocs.yml{% endif %} +++ b/python-project-template/{% if include_docs %}.readthedocs.yml{% endif %}.jinja @@ -1,3 +1,4 @@ +{%- import 'python-versions.jinja' as py %} # .readthedocs.yml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details @@ -8,7 +9,7 @@ version: 2 build: os: ubuntu-22.04 tools: - python: "3.10" + python: "{{ py.pref(python_versions) }}" # Build documentation in the docs/ directory with Sphinx sphinx: diff --git a/python-project-template/{% if include_docs %}docs{% endif %}/index.rst.jinja b/python-project-template/{% if include_docs %}docs{% endif %}/index.rst.jinja index 4c2dc087..52f8390e 100644 --- a/python-project-template/{% if include_docs %}docs{% endif %}/index.rst.jinja +++ b/python-project-template/{% if include_docs %}docs{% endif %}/index.rst.jinja @@ -1,3 +1,4 @@ +{%- import 'python-versions.jinja' as py %} .. {{package_name}} documentation main file. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. @@ -15,7 +16,7 @@ create and activate a new environment. .. code-block:: console - >> conda create env -n python=3.10 + >> conda create env -n python={{ py.pref(python_versions) }} >> conda activate diff --git a/python-versions.jinja b/python-versions.jinja new file mode 100644 index 00000000..0845287f --- /dev/null +++ b/python-versions.jinja @@ -0,0 +1,35 @@ +{# You use this file by including it via jinja like: + + {%- import 'python-versions.jinja' as py -%} + +Then pass the python_versions list into the various macros like so: + + {{ py.min(python_versions) }} + +This template creates no output, but does allow you to transform python_versions answer into a +number of helpful values for when you need a single python version. + +Note that the import syntax and access MUST BE in the form above. Copier has problems processing import lines +that are any more complex when they occur in files who's paths are also jinja templated. #} + + +{# This gives a middle python version from a potentially long list of versions +For even-length lists we prefer the older of the two middle values. + ["3.9","3.10","3.11","3.12"] -> "3.10" +For odd-length lists we prefer the middle value + ["3.9","3.10","3.11"] -> "3.10" #} +{% macro pref(python_versions) -%} +{%- set n = python_versions | length -%} +{{ python_versions[((n+1)//2 - 1)] }} +{%- endmacro -%} + +{# These give the minimum and maximum python versions supported #} +{% macro min(python_versions) -%} +{%- set n = python_versions | length -%} +{{ python_versions[0] }} +{%- endmacro -%} + +{% macro max(python_versions) -%} +{%- set n = python_versions | length -%} +{{ python_versions[n-1] }} +{%- endmacro -%} \ No newline at end of file