Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-invocations for openSUSE:Factory checked in at 2022-10-01 17:42:47 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-invocations (Old) and /work/SRC/openSUSE:Factory/.python-invocations.new.2275 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-invocations" Sat Oct 1 17:42:47 2022 rev:10 rq:1006935 version:2.6.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-invocations/python-invocations.changes 2022-05-06 18:59:13.673349295 +0200 +++ /work/SRC/openSUSE:Factory/.python-invocations.new.2275/python-invocations.changes 2022-10-01 17:42:59.705652522 +0200 @@ -1,0 +2,19 @@ +Thu Sep 29 02:48:24 UTC 2022 - Yogalakshmi Arunachalam <yarunacha...@suse.com> + +- Update to version 2.6.1 + no changelog +- Update to version 2.6.0 2022-03-25 + [Feature]: Update packaging.release.publish with a new config option, rebuild_with_env, + to support a downstream (Fabric) release use-case. + [Feature]: Enhance packaging.release.test-install so it???s more flexible about the primary directory argument + (re: a dist dir, or a parent of one) and errors usefully when you (probably) gave it an incorrect path. +- Update to version 2.5.0 2022-03-25 + [Feature]: Port make-sshable from the travis module to the new ci one. +- Update to version 2.4.0 2022-03-17 + [Feature]: Add additional CLI flags to the use of gpg when signing releases, + to support headless passphrase entry. It was found that modern GPG versions require --batch and + --pinentry-mode=loopback for --passphrase-fd to function correctly. + [Feature]: Add a new invocations.ci task module for somewhat-more-generic CI support than the now legacy invocations.travis tasks. + [Feature]: Allow supplying additional test runners to pytest.coverage; primarily useful for setting up multiple additive test runs before publishing reports. + +------------------------------------------------------------------- Old: ---- invocations-2.3.0.tar.gz New: ---- invocations-2.6.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-invocations.spec ++++++ --- /var/tmp/diff_new_pack.R6G8Vp/_old 2022-10-01 17:43:00.193653410 +0200 +++ /var/tmp/diff_new_pack.R6G8Vp/_new 2022-10-01 17:43:00.197653417 +0200 @@ -19,7 +19,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} %bcond_without python2 Name: python-invocations -Version: 2.3.0 +Version: 2.6.1 Release: 0 Summary: Reusable Invoke tasks License: BSD-2-Clause ++++++ invocations-2.3.0.tar.gz -> invocations-2.6.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invocations-2.3.0/.circleci/config.yml new/invocations-2.6.1/.circleci/config.yml --- old/invocations-2.3.0/.circleci/config.yml 2021-09-25 03:11:32.000000000 +0200 +++ new/invocations-2.6.1/.circleci/config.yml 2022-06-26 22:39:36.000000000 +0200 @@ -1,7 +1,7 @@ version: 2.1 orbs: - orb: invocations/orb@dev:debuggery + orb: invocations/orb@1.0.4 workflows: main: @@ -12,10 +12,8 @@ name: Style check - orb/coverage: name: Test 3.6 (w/ coverage) - context: invocations - orb/test-release: name: Release test - context: invocations - orb/test: name: Test << matrix.version >> # It's not worth testing on other interpreters if the baseline one @@ -24,3 +22,7 @@ matrix: parameters: version: ["3.7", "3.8", "3.9"] + - orb/docs: + name: "Docs" + requires: ["Test 3.6 (w/ coverage)"] + task: "docs --nitpick" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invocations-2.3.0/MANIFEST.in new/invocations-2.6.1/MANIFEST.in --- old/invocations-2.3.0/MANIFEST.in 2021-09-25 03:11:32.000000000 +0200 +++ new/invocations-2.6.1/MANIFEST.in 2022-06-26 22:39:36.000000000 +0200 @@ -4,7 +4,6 @@ recursive-include docs * recursive-exclude docs/_build * include dev-requirements.txt -include tasks-requirements.txt recursive-include tests * recursive-exclude * *.pyc *.pyo recursive-exclude **/__pycache__ * diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invocations-2.3.0/docs/api/ci.rst new/invocations-2.6.1/docs/api/ci.rst --- old/invocations-2.3.0/docs/api/ci.rst 1970-01-01 01:00:00.000000000 +0100 +++ new/invocations-2.6.1/docs/api/ci.rst 2022-06-26 22:39:36.000000000 +0200 @@ -0,0 +1,5 @@ +====== +``ci`` +====== + +.. automodule:: invocations.ci diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invocations-2.3.0/docs/changelog.rst new/invocations-2.6.1/docs/changelog.rst --- old/invocations-2.3.0/docs/changelog.rst 2021-09-25 03:11:32.000000000 +0200 +++ new/invocations-2.6.1/docs/changelog.rst 2022-06-26 22:39:36.000000000 +0200 @@ -2,6 +2,29 @@ Changelog ========= +- :release:`2.6.1 <2022-06-26>` +- :support:`- backported` Remove upper bounds pinning on many deps; this makes + it easier for related projects to test upgrades, run CI, etc. In general, + we're moving away from this tactic. +- :release:`2.6.0 <2022-03-25>` +- :feature:`-` Enhance ``packaging.release.test-install`` so it's more flexible + about the primary directory argument (re: a ``dist`` dir, or a parent of one) + and errors usefully when you (probably) gave it an incorrect path. +- :feature:`-` Update ``packaging.release.publish`` with a new config option, + ``rebuild_with_env``, to support a downstream (Fabric) release use-case. +- :release:`2.5.0 <2022-03-25>` +- :feature:`-` Port ``make-sshable`` from the ``travis`` module to the new + ``ci`` one. +- :release:`2.4.0 <2022-03-17>` +- :feature:`-` Allow supplying additional test runners to ``pytest.coverage``; + primarily useful for setting up multiple additive test runs before publishing + reports. +- :feature:`-` Add a new `invocations.ci` task module for somewhat-more-generic + CI support than the now legacy ``invocations.travis`` tasks. +- :feature:`-` Add additional CLI flags to the use of ``gpg`` when signing + releases, to support headless passphrase entry. It was found that modern GPG + versions require ``--batch`` and ``--pinentry-mode=loopback`` for + ``--passphrase-fd`` to function correctly. - :release:`2.3.0 <2021-09-24>` - :bug:`- major` Ensure that the venv used for ``packaging.release.test_install`` has its ``pip`` upgraded to match the diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invocations-2.3.0/invocations/_version.py new/invocations-2.6.1/invocations/_version.py --- old/invocations-2.3.0/invocations/_version.py 2021-09-25 03:11:32.000000000 +0200 +++ new/invocations-2.6.1/invocations/_version.py 2022-06-26 22:39:36.000000000 +0200 @@ -1,2 +1,2 @@ -__version_info__ = (2, 3, 0) +__version_info__ = (2, 6, 1) __version__ = ".".join(map(str, __version_info__)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invocations-2.3.0/invocations/checks.py new/invocations-2.6.1/invocations/checks.py --- old/invocations-2.3.0/invocations/checks.py 2021-09-25 03:11:32.000000000 +0200 +++ new/invocations-2.6.1/invocations/checks.py 2022-06-26 22:39:36.000000000 +0200 @@ -16,10 +16,6 @@ r""" Run black on the current source tree (all ``.py`` files). - .. warning:: - ``black`` only runs on Python 3.6 or above. (However, it can be - executed against Python 2 compatible code.) - :param int line_length: Line length argument. Default: ``79``. :param list folders: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invocations-2.3.0/invocations/ci.py new/invocations-2.6.1/invocations/ci.py --- old/invocations-2.3.0/invocations/ci.py 1970-01-01 01:00:00.000000000 +0100 +++ new/invocations-2.6.1/invocations/ci.py 2022-06-26 22:39:36.000000000 +0200 @@ -0,0 +1,105 @@ +""" +Tasks intended for use under continuous integration. + +Presently, this tends to assume CircleCI, but it is intended to be generic & +we'll accept patches to make any Circle-isms configurable. + +Most of it involves setting up to run a test suite under a special user who is +allowed to run ``sudo`` and who also needs a password to do so. This allows +testing sudo-related functionality which would otherwise suffer +false-positives, since most CI environments allow passwordless sudo for the +default user. + +Thus, the pattern is: + +- use that default user's sudo privileges to generate the special user (if they + don't already exist in the image) +- as the default user, execute the test suite runner via ``sudo -u <user>`` +- the test suite will then at times run its own ``sudo someprogram`` & be + prompted for its password (which the test suite should read from the config + data, same as this outer set of tasks does). + +.. note:: + This module defines default values for the ``ci.sudo`` config subtree, but + if you're using an execution environment where the default sudoers group + isn't ``sudo`` (eg ``wheel``) you'll want to override ``ci.sudo.group`` in + your own config files. +""" + +from invoke import task, Collection + + +@task +def make_sudouser(c): + """ + Create a passworded sudo-capable user. + + Used by other tasks to execute the test suite so sudo tests work. + """ + user = c.ci.sudo.user + password = c.ci.sudo.password + groups = c.ci.sudo.groups + # "--create-home" because we need a place to put conf files, keys etc + # "--groups xxx" for (non-passwordless) sudo access, eg 'sudo' group on + # Debian, plus any others, eg shared group membership with regular user for + # writing out artifact files (assuming $HOME is g+w, which it is on Circle) + c.sudo( + "useradd {} --create-home --groups {}".format(user, ",".join(groups)) + ) + # Password set noninteractively via chpasswd (assumes invoking user itself + # is able to passwordless sudo; this is true on CircleCI) + c.run("echo {}:{} | sudo chpasswd".format(user, password)) + + +@task +def sudo_run(c, command): + """ + Run some command under CI-oriented sudo subshell/virtualenv. + + :param str command: + Command string to run, e.g. ``inv coverage``, ``inv integration``, etc. + (Does not necessarily need to be an Invoke task, but...) + """ + # NOTE: due to circle sudoers config, circleci user can't do "sudo -u" w/o + # password prompt. However, 'sudo su' seems to work just as well... + # NOTE: well. provided you do this really asinine PATH preservation to work + # around su's path resetting. no, --preserve-environment doesn't work, even + # if you have --preserve-environment=PATH on the outer 'sudo' (which does + # work for what sudo directly calls) + # TODO: may want to rub --pty on the 'su' but so far seems irrelevant + c.run( + 'sudo su {} -c "export PATH=$PATH && {}"'.format( + c.ci.sudo.user, command + ) + ) + + +@task +def make_sshable(c): + """ + Set up passwordless SSH keypair & authorized_hosts access to localhost. + """ + user = c.ci.sudo.user + home = "~{}".format(user) + # Run sudo() as the new sudo user; means less chown'ing, etc. + c.config.sudo.user = user + c.config.sudo.password = c.ci.sudo.password + ssh_dir = "{}/.ssh".format(home) + for cmd in ("mkdir {0}", "chmod 0700 {0}"): + sudo_run(c, cmd.format(ssh_dir, user)) + sudo_run(c, 'ssh-keygen -t rsa -f {}/id_rsa -N \'\''.format(ssh_dir)) + sudo_run(c, f"cp {ssh_dir}/id_rsa.pub {ssh_dir}/authorized_keys") + + +ns = Collection(make_sudouser, sudo_run, make_sshable) +ns.configure( + { + "ci": { + "sudo": { + "user": "invoker", + "password": "secret", + "groups": ["sudo", "circleci"], + } + } + } +) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invocations-2.3.0/invocations/packaging/release.py new/invocations-2.6.1/invocations/packaging/release.py --- old/invocations-2.3.0/invocations/packaging/release.py 2021-09-25 03:11:32.000000000 +0200 +++ new/invocations-2.6.1/invocations/packaging/release.py 2022-06-26 22:39:36.000000000 +0200 @@ -16,6 +16,7 @@ import os import re import sys +from functools import partial from glob import glob from shutil import rmtree @@ -297,7 +298,6 @@ prepare(c, dry_run=dry_run) publish(c, dry_run=dry_run) push(c, dry_run=dry_run) - tidelift(c, dry_run=dry_run) @task @@ -780,7 +780,8 @@ # files) with tmpdir(skip_cleanup=dry_run, explicit=directory) as tmp: # Build default archives - build(c, sdist=sdist, wheel=wheel, directory=tmp) + builder = partial(build, c, sdist=sdist, wheel=wheel, directory=tmp) + builder() # Build opposing interpreter archive, if necessary # TODO: delete dual wheels when dropping Py2 support if dual_wheels: @@ -788,7 +789,22 @@ alt_python = "python2" if sys.version_info[0] == 2: alt_python = "python3" - build(c, sdist=False, wheel=True, directory=tmp, python=alt_python) + builder(sdist=False, wheel=True, python=alt_python) + # Rebuild with env (mostly for Fabric 2) + # TODO: code smell; implies this really wants to be class/hook based? + # TODO: or at least invert sometime so it's easier to say "do random + # stuff to arrive at dists, then test and upload". + rebuild_with_env = config.get("rebuild_with_env", None) + if rebuild_with_env: + old_environ = os.environ.copy() + os.environ.update(rebuild_with_env) + try: + builder() + finally: + os.environ.update(old_environ) + for key in rebuild_with_env: + if key not in old_environ: + del os.environ[key] # Use twine's check command on built artifacts (at present this just # validates long_description) print(c.config.run.echo_format.format(command="twine check")) @@ -803,9 +819,12 @@ @task -def test_install(c, directory): +def test_install(c, directory, verbose=False): """ - Test installation of build artifacts found in ``$directory/dist``. + Test installation of build artifacts found in ``$directory``. + + Directory should either be a ``dist`` directory itself, or the parent of + one. Uses the `venv` module to build temporary virtualenvs. """ @@ -816,8 +835,17 @@ return import venv + # TODO: wants contextmanager or similar for only altering a setting within + # a given scope or block - this may pollute subsequent subroutine calls + if verbose: + old_hide = c.config.run.hide + c.config.run.hide = False + builder = venv.EnvBuilder(with_pip=True) - for archive in get_archives(directory): + archives = get_archives(directory) + if not archives: + raise Exit("No archive files found in {}!".format(directory)) + for archive in archives: # Skip Python 2 wheels that aren't universal (we're dropping that # entirely soon) if "py2" in archive and "py3" not in archive: @@ -838,15 +866,20 @@ pip, archive ) ) + # TODO: install wheel and try again to make sure wheels work ok? + + if verbose: + c.config.run.hide = old_hide def get_archives(directory): # Obtain list of archive filenames, then ensure any wheels come first # so their improved metadata is what PyPI sees initially (otherwise, it # only honors the sdist's lesser data). + dist = "" if directory.endswith("dist") else "dist" return list( itertools.chain.from_iterable( - glob(os.path.join(directory, "dist", "*.{}".format(extension))) + glob(os.path.join(directory, dist, "*.{}".format(extension))) for extension in ("whl", "tar.gz") ) ) @@ -888,9 +921,9 @@ "installed to GPG-sign!" ) for archive in archives: - cmd = "{} --detach-sign -a --passphrase-fd 0 {{}}".format( + cmd = "{} --detach-sign --armor --passphrase-fd=0 --batch --pinentry-mode=loopback {{}}".format( # noqa gpg_bin - ) # noqa + ) c.run(cmd.format(archive), in_stream=input_, dry=dry_run) input_.seek(0) # So it can be replayed by subsequent iterations # Upload @@ -931,14 +964,6 @@ c.run("git push {}".format(opts), **kwargs) -@task -def tidelift(c, dry_run=False): - """ - Add current latest version to Tidelift & set changelog link. - """ - pass - - # TODO: still need time to solve the 'just myself pls' problem ns = Collection( "release", @@ -949,7 +974,6 @@ publish, push, test_install, - tidelift, upload, ) # Hide stdout by default, preferring to explicitly enable it when necessary. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invocations-2.3.0/invocations/pytest.py new/invocations-2.6.1/invocations/pytest.py --- old/invocations-2.3.0/invocations/pytest.py 2021-09-25 03:11:32.000000000 +0200 +++ new/invocations-2.6.1/invocations/pytest.py 2022-06-26 22:39:36.000000000 +0200 @@ -118,8 +118,15 @@ ) -@task -def coverage(c, report="term", opts="", tester=None, codecov=False): +@task(iterable=["additional_testers"]) +def coverage( + c, + report="term", + opts="", + tester=None, + codecov=False, + additional_testers=None, +): """ Run pytest with coverage enabled. @@ -138,12 +145,25 @@ :param bool codecov: Whether to build XML and upload to Codecov. Requires ``codecov`` tool. Default: ``False``. + + :param additional_testers: + List of additional test functions to call besides ``tester``. If given, + implies the use of ``--cov-append`` on these subsequent test runs. + + .. versionchanged:: 2.4 + Added the ``additional_testers`` argument. """ my_opts = "--cov --no-cov-on-fail --cov-report={}".format(report) if opts: my_opts += " " + opts # TODO: call attached suite's test(), not the one in here, if they differ + # TODO: arguably wants ability to lookup task string when tester(s) given + # on CLI, but, eh (tester or test)(c, opts=my_opts) + if additional_testers: + my_opts += " --cov-append" + for tester in additional_testers: + tester(c, opts=my_opts) if report == "html": c.run("open htmlcov/index.html") if codecov: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invocations-2.3.0/setup.py new/invocations-2.6.1/setup.py --- old/invocations-2.3.0/setup.py 2021-09-25 03:11:32.000000000 +0200 +++ new/invocations-2.6.1/setup.py 2022-06-26 22:39:36.000000000 +0200 @@ -10,15 +10,15 @@ requirements = [ # Core dependency - "invoke>=1.6,<2.0", + "invoke>=1.6", # Dependencies for various subpackages. # NOTE: these used to be all optional (only complained about at import # time if missing), but that got hairy fast, and these are all # pure-Python packages, so it shouldn't be a huge burden for users to # obtain them. - "blessings>=1.6,<2", + "blessings>=1.6", "enum34>=1.1,<2; python_version < '3'", - "releases>=1.6,<2", + "releases>=1.6", "semantic_version>=2.4,<2.7", "tabulate==0.7.5", "tqdm>=4.8.1", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invocations-2.3.0/tests/packaging/release.py new/invocations-2.6.1/tests/packaging/release.py --- old/invocations-2.3.0/tests/packaging/release.py 2021-09-25 03:11:32.000000000 +0200 +++ new/invocations-2.6.1/tests/packaging/release.py 2022-06-26 22:39:36.000000000 +0200 @@ -1013,7 +1013,7 @@ class upload_: - def _check_upload(self, c, kwargs=None, flags=None): + def _check_upload(self, c, kwargs=None, flags=None, extra=None): """ Expect/call upload() with common environment and settings/mocks. @@ -1036,6 +1036,8 @@ if flags: cmd += " {}".format(flags) cmd += " {}".format(self.files) + if extra: + cmd += " {}".format(extra) return cmd def twine_uploads_dist_contents_with_wheels_first(self): @@ -1056,9 +1058,26 @@ print.assert_any_call("Would publish via: {}".format(cmd)) c.run.assert_called_once_with("ls -l {}".format(self.files)) - def allows_signing_via_gpg(self): - # Kind of a pain to test :( - skip() + @patch("invocations.packaging.release.getpass.getpass") + def allows_signing_via_gpg(self, getpass): + c = MockContext(run=True, repeat=True) + getpass.return_value = "super sekrit" + twine_upload = self._check_upload( + c, kwargs=dict(sign=True), extra="somedir/dist/*.asc" + ) + calls = c.run.mock_calls + # Looked for gpg + assert calls[0] == call("which gpg", hide=True, warn=True) + # Signed wheel + flags = "--detach-sign --armor --passphrase-fd=0 --batch --pinentry-mode=loopback" # noqa + template = "gpg {} somedir/dist/foo.{{}}".format(flags) + assert calls[1][1][0] == template.format("whl") + # Spot check: did use in_stream to submit passphrase + assert "in_stream" in calls[1][2] + # Signed tgz + assert calls[2][1][0] == template.format("tar.gz") + # Uploaded (and w/ asc's) + c.run.assert_any_call(twine_upload) class Kaboom(Exception): @@ -1262,23 +1281,11 @@ c.run.assert_called_once_with("git push --follow-tags --no-verify") -class tidelift_: - def adds_new_version_with_changelog_link(self): - # new version created - # release line etc stuff? prob just defaults? - # changelog link - skip() - - def dry_run_does_not_hit_api(self): - skip() - - class all_task: @patch("invocations.packaging.release.prepare") @patch("invocations.packaging.release.publish") @patch("invocations.packaging.release.push") - @patch("invocations.packaging.release.tidelift") - def runs_primary_workflow(self, tidelift, push, publish, prepare): + def runs_primary_workflow(self, push, publish, prepare): c = MockContext(run=True) all_(c) # TODO: this doesn't actually prove order of operations. not seeing an @@ -1286,19 +1293,16 @@ prepare.assert_called_once_with(c, dry_run=False) publish.assert_called_once_with(c, dry_run=False) push.assert_called_once_with(c, dry_run=False) - tidelift.assert_called_once_with(c, dry_run=False) @patch("invocations.packaging.release.prepare") @patch("invocations.packaging.release.publish") @patch("invocations.packaging.release.push") - @patch("invocations.packaging.release.tidelift") - def passes_through_dry_run_flag(self, tidelift, push, publish, prepare): + def passes_through_dry_run_flag(self, push, publish, prepare): c = MockContext(run=True) all_(c, dry_run=True) prepare.assert_called_once_with(c, dry_run=True) publish.assert_called_once_with(c, dry_run=True) push.assert_called_once_with(c, dry_run=True) - tidelift.assert_called_once_with(c, dry_run=True) def bound_to_name_without_underscore(self): assert all_.name == "all" @@ -1314,7 +1318,6 @@ push status test-install - tidelift upload """.split() assert set(release_ns.task_names) == set(names)