diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 971c3b5..da5fa27 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -6,7 +6,7 @@ on: - "v*.*.*" pull_request: - branches: [main, master] + branches: main workflow_dispatch: diff --git a/requirements-dev.txt b/requirements-dev.txt index 01d305d..ad13351 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,4 @@ # DO NOT EDIT THIS FILE DIRECTLY - use ./script/update-requirements to update -PyYAML==6.0.2 Pygments==2.19.1 SecretStorage==3.3.3 black==24.10.0 @@ -11,11 +10,8 @@ click==8.2.1 cmarkgfm==2024.11.20 coverage==7.9.1 cryptography==45.0.4 -dnspython==2.7.0 docutils==0.21.2 -fqdn==1.5.1 id==1.5.0 -idna==3.10 iniconfig==2.1.0 isort==6.0.1 jaraco.classes==3.4.0 @@ -33,7 +29,6 @@ mdit-py-plugins==0.4.2 mdurl==0.1.2 more-itertools==10.7.0 mypy_extensions==1.1.0 -natsort==8.4.0 nh3==0.2.21 packaging==25.0 pathspec==0.12.1 @@ -47,9 +42,8 @@ pyflakes==3.3.2 pyproject_hooks==1.2.0 pytest-cov==6.2.1 pytest-mock==3.14.1 -pytest==8.4.0 +pytest==8.4.1 pytest_network==0.0.1 -python-dateutil==2.9.0.post0 readme_renderer==44.0 repoze.lru==0.7 requests-toolbelt==1.0.0 @@ -58,8 +52,7 @@ rfc3986==2.0.0 rich==14.0.0 ruamel.yaml.clib==0.2.12 ruamel.yaml==0.18.14 -six==1.17.0 twine==6.1.0 -urllib3==2.4.0 +urllib3==2.5.0 wcwidth==0.2.13 wheel==0.45.1 diff --git a/requirements-docs.txt b/requirements-docs.txt index 3f96ad7..ec8a8c8 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,74 +1,23 @@ # DO NOT EDIT THIS FILE DIRECTLY - use ./script/update-requirements to update Jinja2==3.1.6 MarkupSafe==3.0.2 -PyYAML==6.0.2 Pygments==2.19.1 -SecretStorage==3.3.3 Sphinx==8.2.3 alabaster==1.0.0 babel==2.17.0 beautifulsoup4==4.13.4 -black==24.10.0 -build==1.2.2.post1 certifi==2025.6.15 -cffi==1.17.1 charset-normalizer==3.4.2 -click==8.2.1 -cmarkgfm==2024.11.20 -coverage==7.9.1 -cryptography==45.0.4 -dnspython==2.7.0 docutils==0.21.2 -fqdn==1.5.1 furo==2024.8.6 -id==1.5.0 -idna==3.10 imagesize==1.4.1 -iniconfig==2.1.0 -isort==6.0.1 -jaraco.classes==3.4.0 -jaraco.context==6.0.1 -jaraco.functools==4.1.0 -jeepney==0.9.0 -keyring==25.6.0 markdown-it-py==3.0.0 -mdformat==0.7.22 -mdformat_footnote==0.1.1 -mdformat_frontmatter==2.0.8 -mdformat_myst==0.2.1 -mdformat_tables==1.0.0 mdit-py-plugins==0.4.2 mdurl==0.1.2 -more-itertools==10.7.0 -mypy_extensions==1.1.0 myst-parser==4.0.1 -natsort==8.4.0 -nh3==0.2.21 packaging==25.0 -pathspec==0.12.1 -platformdirs==4.3.8 -pluggy==1.6.0 -pprintpp==0.4.0 -pycountry-convert==0.7.2 -pycountry==24.6.1 -pycparser==2.22 -pyflakes==3.3.2 -pyproject_hooks==1.2.0 -pytest-cov==6.2.1 -pytest-mock==3.14.1 -pytest==8.4.0 -pytest_network==0.0.1 -python-dateutil==2.9.0.post0 -readme_renderer==44.0 -repoze.lru==0.7 -requests-toolbelt==1.0.0 requests==2.32.4 -rfc3986==2.0.0 -rich==14.0.0 roman-numerals-py==3.1.0 -ruamel.yaml.clib==0.2.12 -ruamel.yaml==0.18.14 -six==1.17.0 snowballstemmer==3.0.1 soupsieve==2.7 sphinx-basic-ng==1.0.0b2 @@ -79,8 +28,5 @@ sphinxcontrib-htmlhelp==2.1.0 sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==2.0.0 sphinxcontrib-serializinghtml==2.0.0 -twine==6.1.0 typing_extensions==4.14.0 -urllib3==2.4.0 -wcwidth==0.2.13 -wheel==0.45.1 +urllib3==2.5.0 diff --git a/script/update-requirements b/script/update-requirements index b6d194f..452fd5d 100755 --- a/script/update-requirements +++ b/script/update-requirements @@ -1,12 +1,14 @@ #!/usr/bin/env python3 import re -from os.path import join -from subprocess import check_call, check_output +import subprocess +from pathlib import Path from sys import argv from tempfile import TemporaryDirectory -TARGETS = ['', 'dev', 'docs'] # the empty string is used for the project itself +SCRIPT = argv[0] +TARGETS = ('', 'dev', 'docs') # empty string is used for the base requirements +FILE_HEADER = f'# DO NOT EDIT THIS FILE DIRECTLY - use {SCRIPT} to update\n' def print_packages(packages, heading): @@ -15,40 +17,83 @@ def print_packages(packages, heading): print('\n '.join(packages)) -# would be nice if there was a cleaner way to get this, but I've not found a -# more reliable one. -with open('setup.py') as fh: - match = re.search(r"name='(?P[\w-]+)',", fh.read()) +def get_package_name() -> str: + match = re.search( + r"name='(?P[\w-]+)',", Path('setup.py').read_text(encoding='utf8') + ) if not match: raise Exception('failed to determine our package name') our_package_name = match.group('pkg') print(f'our_package_name: {our_package_name}') -with TemporaryDirectory() as tmpdir: - check_call(['python3', '-m', 'venv', tmpdir]) + return our_package_name - for target in TARGETS: - to_install = f'.[{target}]' if target else '.' - - check_call([join(tmpdir, 'bin', 'pip'), 'install', to_install]) - frozen = check_output([join(tmpdir, 'bin', 'pip'), 'freeze']) - frozen = set(frozen.decode('utf-8').strip().split('\n')) - # pip installs the module itself along with deps so we need to get that out of - # our list by finding the thing that was file installed during dev - frozen = sorted( - [p for p in frozen if not p.startswith(our_package_name)] +def run_command(command: str) -> tuple[str, str, int]: + try: + proc = subprocess.run( + command.split(), + check=True, + capture_output=True, + encoding='utf8', + timeout=30, ) + except Exception as exc: + print(f"can't run command='{command}'") + raise exc + + return proc.stdout.strip(), proc.stderr.strip(), proc.returncode + + +def save_base_reqs(our_package_name: str, tmpdir: str) -> list[str]: + print('installing base') + + run_command(f'python3 -m venv {tmpdir}') + run_command(f'{tmpdir}/bin/pip install .') + stdout, _, _ = run_command(f'{tmpdir}/bin/pip freeze') + + frozen = [*{*stdout.splitlines()}] # dedup items + frozen = sorted([p for p in frozen if not p.startswith(our_package_name)]) + + print_packages(frozen, 'base-frozen') + + Path('requirements.txt').write_text( + FILE_HEADER + '\n'.join(frozen) + '\n', encoding='utf8' + ) + + return frozen - print_packages(frozen, f'{target}-frozen') - script = argv[0] +def save_extra_reqs( + our_package_name: str, tmpdir: str, base_frozen: list[str], extra: str +) -> None: + print(f'installing extra: {extra}') + + run_command(f'{tmpdir}/bin/pip install .[{extra}]') + stdout, _, _ = run_command(f'{tmpdir}/bin/pip freeze') + + frozen = [*{*stdout.splitlines()} - {*base_frozen}] # remove base deps + frozen = sorted([p for p in frozen if not p.startswith(our_package_name)]) + + print_packages(frozen, f'{extra}-frozen') + + Path(f'requirements-{extra}.txt').write_text( + FILE_HEADER + '\n'.join(frozen) + '\n', encoding='utf8' + ) + + +def main() -> None: + our_package_name = get_package_name() + + # install all extra dependencies in a new venv, so we don't get duplicate/unwanted deps + for target in TARGETS: + with TemporaryDirectory() as tmpdir: + print(f'using tmpdir={tmpdir}') + base_frozen = save_base_reqs(our_package_name, tmpdir) + if not target: + continue # skip extra requirements for base package + save_extra_reqs(our_package_name, tmpdir, base_frozen, target) + - with open( - f'requirements{"-" + target if target else ""}.txt', 'w' - ) as fh: - fh.write( - f'# DO NOT EDIT THIS FILE DIRECTLY - use {script} to update\n' - ) - fh.write('\n'.join(frozen)) - fh.write('\n') +if __name__ == '__main__': + main()