Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for specifying a minimum supported Python version #286

Merged
merged 6 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

dataflake marked this conversation as resolved.
Show resolved Hide resolved
--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(".", ""),
dataflake marked this conversation as resolved.
Show resolved Hide resolved
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))
dataflake marked this conversation as resolved.
Show resolved Hide resolved
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