Skip to content

Commit

Permalink
Merge pull request #286 from zopefoundation/dataflake/oldest_python
Browse files Browse the repository at this point in the history
Support for specifying a minimum supported Python version
  • Loading branch information
dataflake authored Oct 3, 2024
2 parents 51aa6a9 + 447235c commit d345584
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 34 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ Change log
1.1 (unreleased)
----------------

- Allow specifying a minimum supported Python version other than the previously
hardcoded default of Python 3.8.


1.0 (2024-10-02)
----------------
Expand Down
5 changes: 5 additions & 0 deletions docs/narr.rst
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,11 @@ The following options are only needed one time as their values are stored in
a final release thus it is not yet generally supported by the zopefoundation
packages.

--oldest-python
The oldest version of Python supported by this package. Specified as version
number, e.g. ``3.8``. This setting is optional and defaults to the lowest
Python version generally supported by zopefoundation packages.

--with-docs
Enable building the documentation using Sphinx. This will also create a
configuration file `.readthedocs.yaml` for integration with
Expand Down
35 changes: 30 additions & 5 deletions src/zope/meta/config_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

import jinja2
import tomlkit
from packaging.version import InvalidVersion
from packaging.version import parse as parse_version

from .set_branch_protection_rules import set_branch_protection
from .shared.call import abort
Expand Down Expand Up @@ -105,6 +107,11 @@ def handle_command_line_arguments():
default=False,
help='Activate support for a future non-final Python version if not'
' already configured in .meta.toml.')
parser.add_argument(
'--oldest-python',
dest='oldest_python',
help='Oldest supported Python version. Defaults to:'
f' {OLDEST_PYTHON_VERSION}.')
parser.add_argument(
'--with-docs',
# people (me) use --with-sphinx and accidentally
Expand Down Expand Up @@ -197,6 +204,22 @@ def config_type(self):
'Please use `--type` to select it.')
return value

@cached_property
def oldest_python(self):
value = (self.args.oldest_python or
self.meta_cfg['python'].get('oldest-python') or
OLDEST_PYTHON_VERSION)
try:
version = parse_version(value)
except InvalidVersion:
raise ValueError(f'Invalid value {value} for oldest Python.')

if version > parse_version(NEWEST_PYTHON_VERSION):
raise ValueError('Oldest Python version cannot be higher than'
' newest supported Python')

return value

@cached_property
def config_type_path(self):
return pathlib.Path(__file__).parent / self.config_type
Expand Down Expand Up @@ -348,7 +371,7 @@ def pre_commit_config_yaml(self):
"pre-commit-config.yaml.j2",
self.path / ".pre-commit-config.yaml",
self.config_type,
oldest_python_version=OLDEST_PYTHON_VERSION.replace(".", ""),
oldest_python_version=self.oldest_python.replace(".", ""),
teyit_exclude=teyit_exclude,
)

Expand Down Expand Up @@ -391,7 +414,7 @@ def manylinux_sh(self):
with_future_python=self.with_future_python,
future_python_shortversion=FUTURE_PYTHON_SHORTVERSION,
supported_python_versions=supported_python_versions(
short_version=True),
self.oldest_python, short_version=True),
stop_at=stop_at,
)
(self.path / '.manylinux-install.sh').chmod(0o755)
Expand Down Expand Up @@ -477,7 +500,7 @@ def tox(self):
setuptools_version_spec=SETUPTOOLS_VERSION_SPEC,
future_python_shortversion=FUTURE_PYTHON_SHORTVERSION,
supported_python_versions=supported_python_versions(
short_version=True),
self.oldest_python, short_version=True),
)

def tests_yml(self):
Expand All @@ -496,8 +519,10 @@ def tests_yml(self):
require_cffi = self.meta_cfg.get(
'c-code', {}).get('require-cffi', False)
py_version_matrix = [
x for x in zip(supported_python_versions(short_version=False),
supported_python_versions(short_version=True))]
x for x in zip(supported_python_versions(self.oldest_python,
short_version=False),
supported_python_versions(self.oldest_python,
short_version=True))]
self.copy_with_meta(
'tests.yml.j2',
workflows / 'tests.yml',
Expand Down
18 changes: 10 additions & 8 deletions src/zope/meta/set_branch_protection_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@


BASE_URL = f'https://raw.githubusercontent.com/{ORG}'
OLDEST_PYTHON = f'py{OLDEST_PYTHON_VERSION.replace(".", "")}'
NEWEST_PYTHON = f'py{NEWEST_PYTHON_VERSION.replace(".", "")}'
DEFAULT_BRANCH = 'master'

Expand Down Expand Up @@ -71,6 +70,9 @@ def set_branch_protection(
template = meta_toml['meta']['template']
with_docs = meta_toml['python'].get('with-docs', False)
with_pypy = meta_toml['python']['with-pypy']
oldest_python_version = meta_toml['python'].get('oldest-python',
OLDEST_PYTHON_VERSION)
oldest_python = oldest_python_version.replace('.', '')
with_windows = meta_toml['python']['with-windows']
with_macos = meta_toml['python']['with-macos']
required = ['linting']
Expand All @@ -79,9 +81,9 @@ def set_branch_protection(
f'manylinux ({MANYLINUX_PYTHON_VERSION}, {MANYLINUX_AARCH64})',
f'manylinux ({MANYLINUX_PYTHON_VERSION}, {MANYLINUX_I686})',
f'manylinux ({MANYLINUX_PYTHON_VERSION}, {MANYLINUX_X86_64})',
f'test ({OLDEST_PYTHON_VERSION}, macos-latest)',
f'test ({oldest_python_version}, macos-latest)',
f'test ({NEWEST_PYTHON_VERSION}, macos-latest)',
f'test ({OLDEST_PYTHON_VERSION}, ubuntu-latest)',
f'test ({oldest_python_version}, ubuntu-latest)',
f'test ({NEWEST_PYTHON_VERSION}, ubuntu-latest)',
'coveralls_finish',
])
Expand All @@ -93,23 +95,23 @@ def set_branch_protection(
required.append(f'test (pypy-{PYPY_VERSION}, windows-latest)')
if with_windows:
required.extend([
f'test ({OLDEST_PYTHON_VERSION}, windows-latest)',
f'test ({oldest_python_version}, windows-latest)',
f'test ({NEWEST_PYTHON_VERSION}, windows-latest)',
])
elif with_windows or with_macos:
required.extend([
'ubuntu-coverage',
f'ubuntu-{OLDEST_PYTHON}',
f'ubuntu-{oldest_python}',
f'ubuntu-{NEWEST_PYTHON}',
])
if with_windows:
required.extend([
f'windows-{OLDEST_PYTHON}',
f'windows-{oldest_python}',
f'windows-{NEWEST_PYTHON}',
])
if with_macos:
required.extend([
f'macos-{OLDEST_PYTHON}',
f'macos-{oldest_python}',
f'macos-{NEWEST_PYTHON}',
])
if with_pypy:
Expand All @@ -120,7 +122,7 @@ def set_branch_protection(
if with_docs:
required.append('ubuntu-docs')
else: # default for most packages
required.extend([OLDEST_PYTHON, NEWEST_PYTHON])
required.extend([oldest_python, NEWEST_PYTHON])
if template != 'toolkit':
required.append('coverage')
if with_docs:
Expand Down
9 changes: 6 additions & 3 deletions src/zope/meta/shared/packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,20 +145,23 @@ def parse_additional_config(cfg):
return data


def supported_python_versions(short_version=False):
def supported_python_versions(oldest_version=OLDEST_PYTHON_VERSION,
short_version=False):
"""Create a list containing all supported Python versions
Uses the configured oldest and newest Python versions to compute a list
containing all versions from oldest to newest that can be iterated over in
the templates.
Kwargs:
oldest_version (str):
The oldest supported Python version, e.g. '3.8'.
short_version (bool):
Return short versions like "313" instead of "3.13"
Return short versions like "313" instead of "3.13". Default False.
"""
minor_versions = []
oldest_python = parse_version(OLDEST_PYTHON_VERSION)
oldest_python = parse_version(oldest_version)
newest_python = parse_version(NEWEST_PYTHON_VERSION)
for minor in range(oldest_python.minor, newest_python.minor + 1):
minor_versions.append(minor)
Expand Down
43 changes: 25 additions & 18 deletions src/zope/meta/update_python_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,20 @@ def main():
with open('.meta.toml', 'rb') as meta_f:
meta_toml = collections.defaultdict(dict, **tomlkit.load(meta_f))
config_type = meta_toml['meta']['template']
oldest_python_version = meta_toml['python'].get('oldest-python',
OLDEST_PYTHON_VERSION)
branch_name = get_branch_name(args.branch_name, config_type)
updating = git_branch(branch_name)

current_python_versions = get_tox_ini_python_versions('tox.ini')
no_longer_supported = (set(current_python_versions) -
set(supported_python_versions()))
not_yet_supported = (set(supported_python_versions()) -
set(current_python_versions))
no_longer_supported = (
set(current_python_versions) -
set(supported_python_versions(oldest_python_version))
)
not_yet_supported = (
set(supported_python_versions(oldest_python_version)) -
set(current_python_versions)
)

non_interactive_params = []
python_versions_args = []
Expand All @@ -107,21 +113,23 @@ def main():
sys.exit(0)

if no_longer_supported:
for version in sorted(list(no_longer_supported)):
call(bin_dir / 'addchangelogentry',
f'Drop support for Python {version}.',
*non_interactive_params)
version_spec = ', '.join(sorted(no_longer_supported))
call(bin_dir / 'addchangelogentry',
f'Drop support for Python {version_spec}.',
*non_interactive_params)
python_versions_args.append(
'--drop=' + ','.join(no_longer_supported))

if not_yet_supported:
for version in sorted(list(not_yet_supported)):
call(
bin_dir / 'addchangelogentry',
f'Add support for Python {version}.',
*non_interactive_params)
python_versions_args = ['--add=' +
','.join(supported_python_versions())]
version_spec = ', '.join(sorted(not_yet_supported))
call(
bin_dir / 'addchangelogentry',
f'Add support for Python {version_spec}.',
*non_interactive_params)
python_versions_args = [
'--add=' +
','.join(supported_python_versions(oldest_python_version))
]

if no_longer_supported or not_yet_supported:
call(bin_dir / 'check-python-versions', '--only=setup.py',
Expand All @@ -130,8 +138,7 @@ def main():
call(os.environ['EDITOR'], '.meta.toml')

config_package_args = [
sys.executable,
'config-package.py',
bin_dir / 'config-package',
path,
f'--branch={branch_name}',
'--no-push',
Expand All @@ -140,7 +147,7 @@ def main():
config_package_args.append('--no-commit')
call(*config_package_args, cwd=cwd_str)
src = path.resolve() / 'src'
py_ver_plus = f'--py{OLDEST_PYTHON_VERSION.replace(".", "")}-plus'
py_ver_plus = f'--py{oldest_python_version.replace(".", "")}-plus'
call('find', src, '-name', '*.py', '-exec', bin_dir / 'pyupgrade',
'--py3-plus', py_ver_plus, '{}', ';')
call(bin_dir / 'pyupgrade',
Expand Down

0 comments on commit d345584

Please sign in to comment.