From e1990d6491b25f367ed9afbe5532de77b90dda3b Mon Sep 17 00:00:00 2001 From: elisallenens Date: Fri, 30 Jun 2023 10:53:01 +0200 Subject: [PATCH 01/65] reimplement configparser getboolean function in utils.py --- zest/releaser/utils.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/zest/releaser/utils.py b/zest/releaser/utils.py index c9068977..edd7169c 100644 --- a/zest/releaser/utils.py +++ b/zest/releaser/utils.py @@ -978,3 +978,15 @@ def history_format(config_value, history_file): ext = history_file.split(".")[-1].lower() history_format = "md" if ext in ["md", "markdown"] else default return history_format + + +def string_to_bool(value): + """Reimplementation of configparser.ConfigParser.getboolean()""" + if value.isalpha(): + value=value.lower() + if value in ["1", "yes", "true", "on"]: + return True + elif value in ["0", "no", "false", "off"]: + return False + else: + raise ValueError(f"Cannot convert string {value} to a bool") From 3b55a7167178bedf85fdb8b49f83f967964ad70d Mon Sep 17 00:00:00 2001 From: elisallenens Date: Fri, 30 Jun 2023 12:17:10 +0200 Subject: [PATCH 02/65] also run pypi release on pyproject.toml --- zest/releaser/release.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zest/releaser/release.py b/zest/releaser/release.py index 59cd8b3e..b28e2a0d 100644 --- a/zest/releaser/release.py +++ b/zest/releaser/release.py @@ -265,9 +265,9 @@ def _release(self): # override it in the exceptional case but just hit Enter in # the usual case. main_files = os.listdir(self.data["workingdir"]) - if "setup.py" not in main_files and "setup.cfg" not in main_files: - # Neither setup.py nor setup.cfg, so this is no python - # package, so at least a pypi release is not useful. + if not {"setup.py", "setup.cfg", "pyproject.toml"}.intersection(main_files): + # No setup.py, setup.cfg, or pyproject.toml, so this is no + # python package, so at least a pypi release is not useful. # Expected case: this is a buildout directory. default_answer = False else: From d7187588fcc3bd83d43d445d2a3fe524c79348ba Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 3 Jul 2023 09:01:40 +0200 Subject: [PATCH 03/65] add config class for pyproject.toml --- zest/releaser/pypi.py | 62 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index c967b5d0..852dd5b9 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -7,6 +7,13 @@ import pkg_resources import sys +try: + # Python 3.11+ + import tomllib +except ImportError: + # Python 3.10- + import tomli as tomllib + try: pkg_resources.get_distribution("wheel") except pkg_resources.DistributionNotFound: @@ -15,6 +22,7 @@ USE_WHEEL = True DIST_CONFIG_FILE = ".pypirc" SETUP_CONFIG_FILE = "setup.cfg" +PYPROJECTTOML_CONFIG_FILE = "pyproject.toml" DEFAULT_REPOSITORY = "https://upload.pypi.org/legacy/" logger = logging.getLogger(__name__) @@ -688,3 +696,57 @@ def run_pre_commit(self): """ return self._get_boolean("zest.releaser", "run-pre-commit", default=False) + + +class PyprojectTomlConfig(BaseConfig): + """Wrapper around the pyproject.toml file if available. + + This is for optional zest.releaser-specific settings:: + + [zest-releaser] + python-file-with-version = "reinout/maurits.py" + + + """ + + config_filename = PYPROJECTTOML_CONFIG_FILE + + def __init__(self): + """Grab the configuration (overridable for test purposes)""" + # If there is a pyproject.toml in the package, parse it + if not os.path.exists(self.config_filename): + self.config = None + return + with open(self.config_filename, "rb") as tomlconfig: + self.config = tomllib.load(tomlconfig) + + def zest_releaser_config(self): + default = None + if self.config is None: + return default + try: + result = self.config["tools"]["zest-releaser"] + except KeyError: + return default + return result + + def python_file_with_version(self): + """Return Python filename with ``__version__`` marker, if configured. + + Enable this by adding a ``python-file-with-version`` option:: + + [zest-releaser] + python-file-with-version = reinout/maurits.py + + Return None when nothing has been configured. + + """ + default = None + zest_releaser_config = self.zest_releaser_config() + if zest_releaser_config is None: + return default + try: + result = zest_releaser_config["python-file-with-version"] + except KeyError: + return default + return result From 2737b87cab73efb5d78fddf33c22b1cc8b6e634d Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 3 Jul 2023 10:14:05 +0200 Subject: [PATCH 04/65] add zest-releaser-config section to setup.cfg config class --- zest/releaser/pypi.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index 852dd5b9..7d8d0d9a 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -7,6 +7,8 @@ import pkg_resources import sys +from utils import string_to_bool + try: # Python 3.11+ import tomllib @@ -136,6 +138,29 @@ def fix_config(self): with open(self.config_filename) as config_file: print("".join(config_file.readlines())) + def zest_releaser_config(self): + default = None + if self.config is None: + return default + try: + result = dict(self.config["zest-releaser"].items()) + boolean_keys = [ + "release", + "create-wheel", + "no-input", + "register", + "push-changes", + "less-zeroes", + "tag-signing", + "run-pre-commit", + ] + for key, value in result.items(): + if key in boolean_keys: + result[key] = string_to_bool(value) + except KeyError: + return default + return result + def python_file_with_version(self): """Return Python filename with ``__version__`` marker, if configured. From aba59f6a3cc2b2560ba724698ad68d3ce41b657a Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 3 Jul 2023 10:14:54 +0200 Subject: [PATCH 05/65] also add zest-releaser-config section to pypirc class --- zest/releaser/pypi.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index 7d8d0d9a..cd0bbb12 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -210,6 +210,29 @@ def reload(self): settings, and tell release to retry the command. """ self._read_configfile(use_setup_cfg=self.use_setup_cfg) + + def zest_releaser_config(self): + default = None + if self.config is None: + return default + try: + result = dict(self.config["zest-releaser"].items()) + boolean_keys = [ + "release", + "create-wheel", + "no-input", + "register", + "push-changes", + "less-zeroes", + "tag-signing", + "run-pre-commit", + ] + for key, value in result.items(): + if key in boolean_keys: + result[key] = string_to_bool(value) + except KeyError: + return default + return result def _read_configfile(self, use_setup_cfg=True): """Read the PyPI config file and store it (when valid). From 61249570d482ac9c8adecc109afcf126582f97da Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 3 Jul 2023 10:51:48 +0200 Subject: [PATCH 06/65] write class which pulls together zest.releaser settings from all files --- zest/releaser/pypi.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index cd0bbb12..acceeb56 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -798,3 +798,15 @@ def python_file_with_version(self): except KeyError: return default return result + + +class ZestReleaserConfig: + def load_configs(self): + setup_config = SetupConfig() + pypi_config = PypiConfig() + pyproject_config = PyprojectTomlConfig() + config = setup_config | pypi_config | pyproject_config + return config + + def __init__(self): + self.config = self.load_configs() From da80a0e8dccb1255900e61dfaab348a68407f4d2 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 3 Jul 2023 11:42:21 +0200 Subject: [PATCH 07/65] copy PypiConfig zest-releaser functions to ZestReleaserConfig class --- zest/releaser/pypi.py | 419 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 416 insertions(+), 3 deletions(-) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index acceeb56..82b8c9aa 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -216,6 +216,7 @@ def zest_releaser_config(self): if self.config is None: return default try: + # TODO: raw read items from configparser so as not to format version tags result = dict(self.config["zest-releaser"].items()) boolean_keys = [ "release", @@ -230,6 +231,7 @@ def zest_releaser_config(self): for key, value in result.items(): if key in boolean_keys: result[key] = string_to_bool(value) + # TODO: also convert integers except KeyError: return default return result @@ -802,11 +804,422 @@ def python_file_with_version(self): class ZestReleaserConfig: def load_configs(self): - setup_config = SetupConfig() - pypi_config = PypiConfig() - pyproject_config = PyprojectTomlConfig() + setup_config = SetupConfig().zest_releaser_config() + pypi_config = PypiConfig().zest_releaser_config() + pyproject_config = PyprojectTomlConfig().zest_releaser_config() config = setup_config | pypi_config | pyproject_config return config def __init__(self): self.config = self.load_configs() + + def want_release(self): + """Does the user normally want to release this package. + + Some colleagues find it irritating to have to remember to + answer the question "Check out the tag (for tweaks or + pypi/distutils server upload)" with the non-default 'no' when + in 99 percent of the cases they just make a release specific + for a customer, so they always answer 'no' here. This is + where an extra config option comes in handy: you can influence + the default answer so you can just keep hitting 'Enter' until + zest.releaser is done. + + Either in your ~/.pypirc or in a setup.cfg or pyproject.toml in a specific + package, add this when you want the default answer to this + question to be 'no': + + [zest.releaser] + release = no + + The default when this option has not been set is True. + + Standard config rules apply, so you can use upper or lower or + mixed case and specify 0, false, no or off for boolean False, + and 1, on, true or yes for boolean True. + """ + try: + return self.config["release"] + except KeyError: + return True + + def extra_message(self): + """Return extra text to be added to commit messages. + + This can for example be used to skip CI builds. This at least + works for Travis. See + http://docs.travis-ci.com/user/how-to-skip-a-build/ + + Enable this mode by adding a ``extra-message`` option, either in the + package you want to release, or in your ~/.pypirc:: + + [zest.releaser] + extra-message = [ci skip] + """ + try: + return self.config["extra-message"] + except KeyError: + return None + + def prefix_message(self): + """Return extra text to be added before the commit message. + + This can for example be used follow internal policies on commit messages. + + Enable this mode by adding a ``prefix-message`` option, either in the + package you want to release, or in your ~/.pypirc:: + + [zest.releaser] + prefix-message = [TAG] + """ + try: + return self.config["prefix-message"] + except KeyError: + return None + + def history_file(self): + """Return path of history file. + + Usually zest.releaser can find the correct one on its own. + But sometimes it may not find anything, or it finds multiple + and selects the wrong one. + + Configure this by adding a ``history-file`` option, either in the + package you want to release, or in your ~/.pypirc:: + + [zest.releaser] + history-file = deep/down/historie.doc + """ + try: + result = self.config["history-file"] + except KeyError: + try: + # We were reading an underscore instead of a dash at first. + result = self.config["history_file"] + except KeyError: + result = None + return result + + def encoding(self): + """Return encoding to use for text files. + + Mostly the changelog and if needed `setup.py`. + + The encoding cannot always be determined correctly. + This setting is a way to fix that. + See https://github.com/zestsoftware/zest.releaser/issues/264 + + Configure this by adding an ``encoding`` option, either in the + package you want to release, or in your ~/.pypirc:: + + [zest.releaser] + encoding = utf-8 + """ + try: + return self.config["encoding"] + except KeyError: + return "" + + def history_format(self): + """Return the format to be used for Changelog files. + + Configure this by adding an ``history_format`` option, either in the + package you want to release, or in your ~/.pypirc, and using ``rst`` for + Restructured Text and ``md`` for Markdown:: + + [zest.releaser] + history_format = md + """ + try: + return self.config["history_format"] + except KeyError: + return "" + + def create_wheel(self): + """Should we create a Python wheel for this package? + + This is next to the standard source distribution that we always create + when releasing a Python package. + + Changed in version 8.0.0a2: we ALWAYS create a wheel, + unless this is explicitly switched off. + The `wheel` package must be installed though, which is in our + 'recommended' extra. + + To switch this OFF, either in your ~/.pypirc or in a setup.cfg in + a specific package, add this: + + [zest.releaser] + create-wheel = no + """ + if not USE_WHEEL: + # If the wheel package is not available, we obviously + # cannot create wheels. + return False + try: + return self.config["create-wheel"] + except KeyError: + return True + + def register_package(self): + """Should we try to register this package with a package server? + + For the standard Python Package Index (PyPI), registering a + package is no longer needed: this is done automatically when + uploading a distribution for a package. In fact, trying to + register may fail. See + https://github.com/zestsoftware/zest.releaser/issues/191 + So by default zest.releaser will no longer register a package. + + But you may be using your own package server, and registering + may be wanted or even required there. In this case + you will need to turn on the register function. + In your setup.cfg or ~/.pypirc, use the following to ensure that + register is called on the package server: + + [zest.releaser] + register = yes + + Note that if you have specified multiple package servers, this + option is used for all of them. There is no way to register and + upload to server A, and only upload to server B. + """ + try: + return self.config["register"] + except KeyError: + return True + + def no_input(self): + """Return whether the user wants to run in no-input mode. + + Enable this mode by adding a ``no-input`` option:: + + [zest.releaser] + no-input = yes + + The default when this option has not been set is False. + """ + try: + return self.config["no-input"] + except KeyError: + return True + + def development_marker(self): + """Return development marker to be appended in postrelease. + + Override the default ``.dev0`` in ~/.pypirc or setup.cfg using + a ``development-marker`` option:: + + [zest.releaser] + development-marker = .dev1 + + Returns default of ``.dev0`` when nothing has been configured. + """ + try: + return self.config["no-input"] + except KeyError: + return ".dev0" + + def push_changes(self): + """Return whether the user wants to push the changes to the remote. + + Configure this mode by adding a ``push-changes`` option:: + + [zest.releaser] + push-changes = no + + The default when this option has not been set is True. + """ + try: + return self.config["push-changes"] + except KeyError: + return True + + def less_zeroes(self): + """Return whether the user prefers less zeroes at the end of a version. + + Configure this mode by adding a ``less-zeroes`` option:: + + [zest.releaser] + less-zeroes = yes + + The default when this option has not been set is False. + + When set to true: + - Instead of 1.3.0 we will suggest 1.3. + - Instead of 2.0.0 we will suggest 2.0. + + This only makes sense for the bumpversion command. + In the postrelease command we read this option too, + but with the current logic it has no effect there. + """ + try: + return self.config["less-zeroes"] + except KeyError: + return False + + def version_levels(self): + """How many levels does the user prefer in a version number? + + Configure this mode by adding a ``version-levels`` option:: + + [zest.releaser] + version-levels = 3 + + The default when this option has not been set is 0, which means: + no preference, so use the length of the current number. + + This means when suggesting a next version after 1.2: + - with levels=0 we will suggest 1.3: no change + - with levels=1 we will still suggest 1.3, as we will not + use this to remove numbers, only to add them + - with levels=2 we will suggest 1.3 + - with levels=3 we will suggest 1.2.1 + + If the current version number has more levels, we keep them. + So next version for 1.2.3.4 with levels=1 is 1.2.3.5. + + Tweaking version-levels and less-zeroes should give you the + version number strategy that you prefer. + """ + default = 0 + try: + result = self.config["version-levels"] + except KeyError: + return default + if result < 0: + return default + return result + + _tag_format_deprecated_message = "\n".join( + line.strip() + for line in """ + `tag-format` contains deprecated `%%(version)s` format. Please change to: + + [zest.releaser] + tag-format = %s + """.strip().splitlines() + ) + + def tag_format(self, version): + """Return the formatted tag that should be used in the release. + + Configure it in ~/.pypirc or setup.cfg using a ``tag-format`` option:: + + [zest.releaser] + tag-format = v{version} + + ``tag-format`` must contain exactly one formatting instruction: for the + ``version`` key. + + Accepts also ``%(version)s`` format for backward compatibility. + + The default format, when nothing has been configured, is ``{version}``. + """ + fmt = "{version}" + try: + fmt = self.config["tag-format"] + except KeyError: + pass + if "{version}" in fmt: + return fmt.format(version=version) + # BBB: + if "%(version)s" in fmt: + proposed_fmt = fmt.replace("%(version)s", "{version}") + print(self._tag_format_deprecated_message % proposed_fmt) + return fmt % {"version": version} + print("{version} needs to be part of 'tag-format': %s" % fmt) + sys.exit(1) + + def tag_message(self, version): + """Return the commit message to be used when tagging. + + Configure it in ~/.pypirc or setup.cfg using a ``tag-message`` + option:: + + [zest.releaser] + tag-message = Creating v{version} tag. + + ``tag-message`` must contain exactly one formatting + instruction: for the ``version`` key. + + The default format is ``Tagging {version}``. + """ + fmt = "Tagging {version}" + try: + fmt = self.config["tag-message"] + except KeyError: + pass + if "{version}" not in fmt: + print("{version} needs to be part of 'tag-message': '%s'" % fmt) + sys.exit(1) + return fmt.format(version=version) + + def tag_signing(self): + """Return whether the tag should be signed. + + Configure it in ~/.pypirc or setup.cfg using a ``tag-signing`` option:: + + [zest.releaser] + tag-signing = yes + + ``tag-signing`` must contain exactly one word which will be + converted to a boolean. Currently are accepted (case + insensitively): 0, false, no, off for False, and 1, true, yes, + on for True). + + The default when this option has not been set is False. + + """ + try: + return self.config("tag-signing") + except KeyError: + return False + + def date_format(self): + """Return the string format for the date used in the changelog. + + Override the default ``%Y-%m-%d`` in ~/.pypirc or setup.cfg using + a ``date-format`` option:: + + [zest.releaser] + date-format = %%B %%e, %%Y + + Note: the % signs should be doubled for compatibility with other tools + (i.e. pip) that parse setup.cfg using the interpolating ConfigParser. + + Returns default of ``%Y-%m-%d`` when nothing has been configured. + """ + default = "%Y-%m-%d" + try: + result = self.config["date-format"].replace("%%", "%") + except (KeyError, ValueError): + return default + return result + + def run_pre_commit(self): + """Return whether we should run pre commit hooks. + + At least in git you have pre commit hooks. + These may interfere with releasing: + zest.releaser changes your setup.py, a pre commit hook + runs black or isort and gives an error, so the commit is cancelled. + By default (since version 7.3.0) we do not run pre commit hooks. + + Configure it in ~/.pypirc or setup.cfg using a ``tag-signing`` option:: + + [zest.releaser] + run-pre-commit = yes + + ``run-pre-commit`` must contain exactly one word which will be + converted to a boolean. Currently are accepted (case + insensitively): 0, false, no, off for False, and 1, true, yes, + on for True). + + The default when this option has not been set is False. + + """ + try: + return self.config("run-pre-commit") + except KeyError: + return False From ddb4aa1183b3f973aef450c79966ed1da8b3b1fa Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 3 Jul 2023 11:46:22 +0200 Subject: [PATCH 08/65] slightly change zestreleaser config load call --- zest/releaser/pypi.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index 82b8c9aa..3e46f860 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -807,11 +807,10 @@ def load_configs(self): setup_config = SetupConfig().zest_releaser_config() pypi_config = PypiConfig().zest_releaser_config() pyproject_config = PyprojectTomlConfig().zest_releaser_config() - config = setup_config | pypi_config | pyproject_config - return config + self.config = setup_config | pypi_config | pyproject_config def __init__(self): - self.config = self.load_configs() + self.load_configs() def want_release(self): """Does the user normally want to release this package. From 35856cb8133debfb4394ee89cd762a8504c8ed82 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 3 Jul 2023 14:44:13 +0200 Subject: [PATCH 09/65] read raw config to avoid string formatting, and convert version number value to int --- zest/releaser/pypi.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index 3e46f860..a4f7f3b2 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -216,8 +216,7 @@ def zest_releaser_config(self): if self.config is None: return default try: - # TODO: raw read items from configparser so as not to format version tags - result = dict(self.config["zest-releaser"].items()) + result = dict(self.config["zest-releaser"].items(raw=True)) boolean_keys = [ "release", "create-wheel", @@ -231,7 +230,8 @@ def zest_releaser_config(self): for key, value in result.items(): if key in boolean_keys: result[key] = string_to_bool(value) - # TODO: also convert integers + if key in ["version-levels"]: + result[key] = int(value) except KeyError: return default return result From 6ee4c2016ff81a5fdd8ac0d6cd62384bff3f89d9 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 3 Jul 2023 14:47:39 +0200 Subject: [PATCH 10/65] remove now-redundant functions from PypiConfig --- zest/releaser/pypi.py | 437 ------------------------------------------ 1 file changed, 437 deletions(-) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index a4f7f3b2..009d1abc 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -310,443 +310,6 @@ def distutils_servers(self): # The servers all need to have a section in the config file. return [server for server in index_servers if self.config.has_section(server)] - def want_release(self): - """Does the user normally want to release this package. - - Some colleagues find it irritating to have to remember to - answer the question "Check out the tag (for tweaks or - pypi/distutils server upload)" with the non-default 'no' when - in 99 percent of the cases they just make a release specific - for a customer, so they always answer 'no' here. This is - where an extra config option comes in handy: you can influence - the default answer so you can just keep hitting 'Enter' until - zest.releaser is done. - - Either in your ~/.pypirc or in a setup.cfg in a specific - package, add this when you want the default answer to this - question to be 'no': - - [zest.releaser] - release = no - - The default when this option has not been set is True. - - Standard config rules apply, so you can use upper or lower or - mixed case and specify 0, false, no or off for boolean False, - and 1, on, true or yes for boolean True. - """ - return self._get_boolean("zest.releaser", "release", default=True) - - def __get_message_config__(self, config_name): - """ - Return the value of the message configuration based on its name. - - If the configuration does not exist or can't be retrieved, return an empty string. - - :param config_name: Name of the configuration to obtain from configuration file - :return: The configuration value or an empty string - """ - default = "" - if self.config is None: - return default - try: - result = self._get_text("zest.releaser", config_name, default=default) - except (NoSectionError, NoOptionError, ValueError): - return default - return result - - def extra_message(self): - """Return extra text to be added to commit messages. - - This can for example be used to skip CI builds. This at least - works for Travis. See - http://docs.travis-ci.com/user/how-to-skip-a-build/ - - Enable this mode by adding a ``extra-message`` option, either in the - package you want to release, or in your ~/.pypirc:: - - [zest.releaser] - extra-message = [ci skip] - """ - return self.__get_message_config__("extra-message") - - def prefix_message(self): - """Return extra text to be added before the commit message. - - This can for example be used follow internal policies on commit messages. - - Enable this mode by adding a ``prefix-message`` option, either in the - package you want to release, or in your ~/.pypirc:: - - [zest.releaser] - prefix-message = [TAG] - """ - return self.__get_message_config__("prefix-message") - - def history_file(self): - """Return path of history file. - - Usually zest.releaser can find the correct one on its own. - But sometimes it may not find anything, or it finds multiple - and selects the wrong one. - - Configure this by adding a ``history-file`` option, either in the - package you want to release, or in your ~/.pypirc:: - - [zest.releaser] - history-file = deep/down/historie.doc - """ - default = "" - if self.config is None: - return default - marker = object() - try: - result = self._get_text("zest.releaser", "history-file", default=marker) - except (NoSectionError, NoOptionError, ValueError): - return default - if result == marker: - # We were reading an underscore instead of a dash at first. - try: - result = self._get_text( - "zest.releaser", "history_file", default=default - ) - except (NoSectionError, NoOptionError, ValueError): - return default - return result - - def encoding(self): - """Return encoding to use for text files. - - Mostly the changelog and if needed `setup.py`. - - The encoding cannot always be determined correctly. - This setting is a way to fix that. - See https://github.com/zestsoftware/zest.releaser/issues/264 - - Configure this by adding an ``encoding`` option, either in the - package you want to release, or in your ~/.pypirc:: - - [zest.releaser] - encoding = utf-8 - """ - default = "" - if self.config is None: - return default - try: - result = self._get_text( - "zest.releaser", "encoding", default=default, raw=True - ) - except (NoSectionError, NoOptionError, ValueError): - return default - return result - - def history_format(self): - """Return the format to be used for Changelog files. - - Configure this by adding an ``history_format`` option, either in the - package you want to release, or in your ~/.pypirc, and using ``rst`` for - Restructured Text and ``md`` for Markdown:: - - [zest.releaser] - history_format = md - """ - default = "" - if self.config is None: - return default - try: - result = self._get_text( - "zest.releaser", "history_format", default=default, raw=True - ) - except (NoSectionError, NoOptionError, ValueError): - return default - return result - - def create_wheel(self): - """Should we create a Python wheel for this package? - - This is next to the standard source distribution that we always create - when releasing a Python package. - - Changed in version 8.0.0a2: we ALWAYS create a wheel, - unless this is explicitly switched off. - The `wheel` package must be installed though, which is in our - 'recommended' extra. - - To switch this OFF, either in your ~/.pypirc or in a setup.cfg in - a specific package, add this: - - [zest.releaser] - create-wheel = no - """ - if not USE_WHEEL: - # If the wheel package is not available, we obviously - # cannot create wheels. - return False - return self._get_boolean("zest.releaser", "create-wheel", True) - - def register_package(self): - """Should we try to register this package with a package server? - - For the standard Python Package Index (PyPI), registering a - package is no longer needed: this is done automatically when - uploading a distribution for a package. In fact, trying to - register may fail. See - https://github.com/zestsoftware/zest.releaser/issues/191 - So by default zest.releaser will no longer register a package. - - But you may be using your own package server, and registering - may be wanted or even required there. In this case - you will need to turn on the register function. - In your setup.cfg or ~/.pypirc, use the following to ensure that - register is called on the package server: - - [zest.releaser] - register = yes - - Note that if you have specified multiple package servers, this - option is used for all of them. There is no way to register and - upload to server A, and only upload to server B. - """ - return self._get_boolean("zest.releaser", "register") - - def no_input(self): - """Return whether the user wants to run in no-input mode. - - Enable this mode by adding a ``no-input`` option:: - - [zest.releaser] - no-input = yes - - The default when this option has not been set is False. - """ - return self._get_boolean("zest.releaser", "no-input") - - def development_marker(self): - """Return development marker to be appended in postrelease. - - Override the default ``.dev0`` in ~/.pypirc or setup.cfg using - a ``development-marker`` option:: - - [zest.releaser] - development-marker = .dev1 - - Returns default of ``.dev0`` when nothing has been configured. - """ - default = ".dev0" - if self.config is None: - return default - try: - result = self._get_text( - "zest.releaser", "development-marker", default=default - ) - except (NoSectionError, NoOptionError, ValueError): - return default - return result - - def push_changes(self): - """Return whether the user wants to push the changes to the remote. - - Configure this mode by adding a ``push-changes`` option:: - - [zest.releaser] - push-changes = no - - The default when this option has not been set is True. - """ - return self._get_boolean("zest.releaser", "push-changes", default=True) - - def less_zeroes(self): - """Return whether the user prefers less zeroes at the end of a version. - - Configure this mode by adding a ``less-zeroes`` option:: - - [zest.releaser] - less-zeroes = yes - - The default when this option has not been set is False. - - When set to true: - - Instead of 1.3.0 we will suggest 1.3. - - Instead of 2.0.0 we will suggest 2.0. - - This only makes sense for the bumpversion command. - In the postrelease command we read this option too, - but with the current logic it has no effect there. - """ - return self._get_boolean("zest.releaser", "less-zeroes") - - def version_levels(self): - """How many levels does the user prefer in a version number? - - Configure this mode by adding a ``version-levels`` option:: - - [zest.releaser] - version-levels = 3 - - The default when this option has not been set is 0, which means: - no preference, so use the length of the current number. - - This means when suggesting a next version after 1.2: - - with levels=0 we will suggest 1.3: no change - - with levels=1 we will still suggest 1.3, as we will not - use this to remove numbers, only to add them - - with levels=2 we will suggest 1.3 - - with levels=3 we will suggest 1.2.1 - - If the current version number has more levels, we keep them. - So next version for 1.2.3.4 with levels=1 is 1.2.3.5. - - Tweaking version-levels and less-zeroes should give you the - version number strategy that you prefer. - """ - default = 0 - if self.config is None: - return default - try: - result = self.config.getint("zest.releaser", "version-levels") - except (NoSectionError, NoOptionError, ValueError): - return default - if result < 0: - return default - return result - - _tag_format_deprecated_message = "\n".join( - line.strip() - for line in """ - `tag-format` contains deprecated `%%(version)s` format. Please change to: - - [zest.releaser] - tag-format = %s - """.strip().splitlines() - ) - - def tag_format(self, version): - """Return the formatted tag that should be used in the release. - - Configure it in ~/.pypirc or setup.cfg using a ``tag-format`` option:: - - [zest.releaser] - tag-format = v{version} - - ``tag-format`` must contain exactly one formatting instruction: for the - ``version`` key. - - Accepts also ``%(version)s`` format for backward compatibility. - - The default format, when nothing has been configured, is ``{version}``. - """ - fmt = "{version}" - if self.config is not None: - try: - fmt = self._get_text( - "zest.releaser", "tag-format", default=fmt, raw=True - ) - except (NoSectionError, NoOptionError, ValueError): - pass - if "{version}" in fmt: - return fmt.format(version=version) - # BBB: - if "%(version)s" in fmt: - proposed_fmt = fmt.replace("%(version)s", "{version}") - print(self._tag_format_deprecated_message % proposed_fmt) - return fmt % {"version": version} - print("{version} needs to be part of 'tag-format': %s" % fmt) - sys.exit(1) - - def tag_message(self, version): - """Return the commit message to be used when tagging. - - Configure it in ~/.pypirc or setup.cfg using a ``tag-message`` - option:: - - [zest.releaser] - tag-message = Creating v{version} tag. - - ``tag-message`` must contain exactly one formatting - instruction: for the ``version`` key. - - The default format is ``Tagging {version}``. - """ - fmt = "Tagging {version}" - if self.config: - try: - fmt = self._get_text( - "zest.releaser", "tag-message", default=fmt, raw=True - ) - except (NoSectionError, NoOptionError, ValueError): - pass - if "{version}" not in fmt: - print("{version} needs to be part of 'tag-message': '%s'" % fmt) - sys.exit(1) - return fmt.format(version=version) - - def tag_signing(self): - """Return whether the tag should be signed. - - Configure it in ~/.pypirc or setup.cfg using a ``tag-signing`` option:: - - [zest.releaser] - tag-signing = yes - - ``tag-signing`` must contain exactly one word which will be - converted to a boolean. Currently are accepted (case - insensitively): 0, false, no, off for False, and 1, true, yes, - on for True). - - The default when this option has not been set is False. - - """ - return self._get_boolean("zest.releaser", "tag-signing", default=False) - - def date_format(self): - """Return the string format for the date used in the changelog. - - Override the default ``%Y-%m-%d`` in ~/.pypirc or setup.cfg using - a ``date-format`` option:: - - [zest.releaser] - date-format = %%B %%e, %%Y - - Note: the % signs should be doubled for compatibility with other tools - (i.e. pip) that parse setup.cfg using the interpolating ConfigParser. - - Returns default of ``%Y-%m-%d`` when nothing has been configured. - """ - default = "%Y-%m-%d" - if self.config is None: - return default - try: - result = self._get_text( - "zest.releaser", "date-format", default=default - ).replace("%%", "%") - except (NoSectionError, NoOptionError, ValueError): - return default - return result - - def run_pre_commit(self): - """Return whether we should run pre commit hooks. - - At least in git you have pre commit hooks. - These may interfere with releasing: - zest.releaser changes your setup.py, a pre commit hook - runs black or isort and gives an error, so the commit is cancelled. - By default (since version 7.3.0) we do not run pre commit hooks. - - Configure it in ~/.pypirc or setup.cfg using a ``tag-signing`` option:: - - [zest.releaser] - run-pre-commit = yes - - ``run-pre-commit`` must contain exactly one word which will be - converted to a boolean. Currently are accepted (case - insensitively): 0, false, no, off for False, and 1, true, yes, - on for True). - - The default when this option has not been set is False. - - """ - return self._get_boolean("zest.releaser", "run-pre-commit", default=False) - class PyprojectTomlConfig(BaseConfig): """Wrapper around the pyproject.toml file if available. From 5add43920b2893e400cf5b9aec6b67827f4d4e90 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 3 Jul 2023 14:49:05 +0200 Subject: [PATCH 11/65] update zest_releaser_config() in SetupConfig --- zest/releaser/pypi.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index 009d1abc..64fc0716 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -143,7 +143,7 @@ def zest_releaser_config(self): if self.config is None: return default try: - result = dict(self.config["zest-releaser"].items()) + result = dict(self.config["zest-releaser"].items(raw=True)) boolean_keys = [ "release", "create-wheel", @@ -157,6 +157,8 @@ def zest_releaser_config(self): for key, value in result.items(): if key in boolean_keys: result[key] = string_to_bool(value) + if key in ["version-levels"]: + result[key] = int(value) except KeyError: return default return result From 38296da27a7e79bd25bcf83d47fc6d459bf32a64 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 3 Jul 2023 15:02:50 +0200 Subject: [PATCH 12/65] update zest releaser config dict only if config type is a dict --- zest/releaser/pypi.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index 64fc0716..1b558a54 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -372,7 +372,12 @@ def load_configs(self): setup_config = SetupConfig().zest_releaser_config() pypi_config = PypiConfig().zest_releaser_config() pyproject_config = PyprojectTomlConfig().zest_releaser_config() - self.config = setup_config | pypi_config | pyproject_config + combined_config = {} + # overwrite any duplicate keys in the following order: + for config in [setup_config, pypi_config, pyproject_config]: + if isinstance(config, dict): + combined_config |= config + self.config = combined_config def __init__(self): self.load_configs() From e25b5f1775663ae2353f38bd8bcfc9b10e8472c1 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 3 Jul 2023 15:03:32 +0200 Subject: [PATCH 13/65] use dict.update() method instead of |=, to support python 3.7+ --- zest/releaser/pypi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index 1b558a54..4ba681ef 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -376,7 +376,7 @@ def load_configs(self): # overwrite any duplicate keys in the following order: for config in [setup_config, pypi_config, pyproject_config]: if isinstance(config, dict): - combined_config |= config + combined_config.update(config) self.config = combined_config def __init__(self): From 3fec40f5919544b43b4b77a4cba030cfad551653 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 3 Jul 2023 15:16:30 +0200 Subject: [PATCH 14/65] move function from SetupConfig and PyprojectTomlConfig to ZestReleaserConfig --- zest/releaser/pypi.py | 59 ++++++++++++------------------------------- 1 file changed, 16 insertions(+), 43 deletions(-) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index 4ba681ef..1263ef19 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -163,28 +163,6 @@ def zest_releaser_config(self): return default return result - def python_file_with_version(self): - """Return Python filename with ``__version__`` marker, if configured. - - Enable this by adding a ``python-file-with-version`` option:: - - [zest.releaser] - python-file-with-version = reinout/maurits.py - - Return None when nothing has been configured. - - """ - default = None - if self.config is None: - return default - try: - result = self._get_text( - "zest.releaser", "python-file-with-version", default=default - ) - except (NoSectionError, NoOptionError, ValueError): - return default - return result - class PypiConfig(BaseConfig): """Wrapper around the pypi config file""" @@ -345,27 +323,6 @@ def zest_releaser_config(self): return default return result - def python_file_with_version(self): - """Return Python filename with ``__version__`` marker, if configured. - - Enable this by adding a ``python-file-with-version`` option:: - - [zest-releaser] - python-file-with-version = reinout/maurits.py - - Return None when nothing has been configured. - - """ - default = None - zest_releaser_config = self.zest_releaser_config() - if zest_releaser_config is None: - return default - try: - result = zest_releaser_config["python-file-with-version"] - except KeyError: - return default - return result - class ZestReleaserConfig: def load_configs(self): @@ -469,6 +426,22 @@ def history_file(self): result = None return result + def python_file_with_version(self): + """Return Python filename with ``__version__`` marker, if configured. + + Enable this by adding a ``python-file-with-version`` option:: + + [zest-releaser] + python-file-with-version = reinout/maurits.py + + Return None when nothing has been configured. + + """ + try: + return self.config["python-file-with-version"] + except KeyError: + return None + def encoding(self): """Return encoding to use for text files. From 7af64f7c54d6d61e62ff0a8fc229f830f28f99eb Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 3 Jul 2023 15:21:42 +0200 Subject: [PATCH 15/65] remove setup.cfg loading from PypiConfig class --- zest/releaser/pypi.py | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index 1263ef19..87677375 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -167,17 +167,13 @@ def zest_releaser_config(self): class PypiConfig(BaseConfig): """Wrapper around the pypi config file""" - def __init__(self, config_filename=DIST_CONFIG_FILE, use_setup_cfg=True): + def __init__(self, config_filename=DIST_CONFIG_FILE): """Grab the PyPI configuration. This is .pypirc in the home directory. It is overridable for test purposes. - - If there is a setup.cfg file in the current directory, we read - it too. """ self.config_filename = config_filename - self.use_setup_cfg = use_setup_cfg self.reload() def reload(self): @@ -189,7 +185,7 @@ def reload(self): upload fails, you edit the .pypirc file to fix the account settings, and tell release to retry the command. """ - self._read_configfile(use_setup_cfg=self.use_setup_cfg) + self._read_configfile() def zest_releaser_config(self): default = None @@ -216,24 +212,13 @@ def zest_releaser_config(self): return default return result - def _read_configfile(self, use_setup_cfg=True): - """Read the PyPI config file and store it (when valid). - - Usually read the setup.cfg too. - """ - rc = self.config_filename - if not os.path.isabs(rc): - rc = os.path.join(os.path.expanduser("~"), self.config_filename) - filenames = [rc] - if use_setup_cfg: - # If there is a setup.cfg in the package, parse it - filenames.append("setup.cfg") - files = [f for f in filenames if os.path.exists(f)] - if not files: + def _read_configfile(self): + """Read the PyPI config file and store it (when valid).""" + if not os.path.exists(self.config_filename): self.config = None return self.config = ConfigParser() - self.config.read(files) + self.config.read(self.config_filename) def twine_repository(self): """Gets the repository from Twine environment variables.""" From f38a4cffdb8a3882b61a8d6db497af492732e181 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 3 Jul 2023 15:22:41 +0200 Subject: [PATCH 16/65] clean up imports --- zest/releaser/pypi.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index 87677375..5c2b3184 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -1,6 +1,4 @@ -from configparser import ConfigParser -from configparser import NoOptionError -from configparser import NoSectionError +from configparser import ConfigParser, NoOptionError, NoSectionError import logging import os From 081164c7daa1924aec534ae4110ae0677f95e77a Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 3 Jul 2023 15:26:31 +0200 Subject: [PATCH 17/65] add pypirc path option to zest release config class for compatibility --- zest/releaser/pypi.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index 5c2b3184..93aa5940 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -308,9 +308,9 @@ def zest_releaser_config(self): class ZestReleaserConfig: - def load_configs(self): + def load_configs(self, pypirc_config_filename=DIST_CONFIG_FILE): setup_config = SetupConfig().zest_releaser_config() - pypi_config = PypiConfig().zest_releaser_config() + pypi_config = PypiConfig(config_filename=pypirc_config_filename).zest_releaser_config() pyproject_config = PyprojectTomlConfig().zest_releaser_config() combined_config = {} # overwrite any duplicate keys in the following order: @@ -319,8 +319,8 @@ def load_configs(self): combined_config.update(config) self.config = combined_config - def __init__(self): - self.load_configs() + def __init__(self, pypirc_config_filename=DIST_CONFIG_FILE): + self.load_configs(pypirc_config_filename=pypirc_config_filename) def want_release(self): """Does the user normally want to release this package. From 804857289d7bb6fdab4b84c9b1e622f23087c1c9 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 3 Jul 2023 15:33:52 +0200 Subject: [PATCH 18/65] update internal function calls to point to zest release config instead of pypi config --- zest/releaser/baserelease.py | 16 +++++++++------- zest/releaser/bumpversion.py | 6 +++--- zest/releaser/postrelease.py | 8 ++++---- zest/releaser/prerelease.py | 2 +- zest/releaser/release.py | 12 ++++++------ 5 files changed, 23 insertions(+), 21 deletions(-) diff --git a/zest/releaser/baserelease.py b/zest/releaser/baserelease.py index a5b2cb55..beb772ee 100644 --- a/zest/releaser/baserelease.py +++ b/zest/releaser/baserelease.py @@ -77,15 +77,17 @@ def __init__(self, vcs=None): "zest.releaser.tests", "pypirc_old.txt" ) self.pypiconfig = pypi.PypiConfig(pypirc_old) + self.zest_releaser_config = pypi.ZestReleaserConfig(pypirc_config_filename=pypirc_old) else: self.pypiconfig = pypi.PypiConfig() - if self.pypiconfig.no_input(): + self.zest_releaser_config = pypi.ZestReleaserConfig() + if self.zest_releaser_config.no_input(): utils.AUTO_RESPONSE = True @property def history_format(self): default = "rst" - config_value = self.pypiconfig.history_format() + config_value = self.zest_releaser_config.history_format() history_file = self.data.get("history_file") or "" return utils.history_format(config_value, history_file) @@ -124,8 +126,8 @@ def _grab_history(self): self.data["headings"] = [] self.data["history_last_release"] = "" self.data["history_insert_line_here"] = 0 - default_location = self.pypiconfig.history_file() - fallback_encoding = self.pypiconfig.encoding() + default_location = self.zest_releaser_config.history_file() + fallback_encoding = self.zest_releaser_config.encoding() history_file = self.vcs.history_file(location=default_location) self.data["history_file"] = history_file if not history_file: @@ -416,7 +418,7 @@ def _push(self): push_cmds = self.vcs.push_commands() if not push_cmds: return - default_anwer = self.pypiconfig.push_changes() + default_anwer = self.zest_releaser_config.push_changes() if utils.ask("OK to push commits to the server?", default=default_anwer): for push_cmd in push_cmds: output = execute_command(push_cmd) @@ -440,8 +442,8 @@ def execute(self): raise NotImplementedError() def update_commit_message(self, msg): - prefix_message = self.pypiconfig.prefix_message() - extra_message = self.pypiconfig.extra_message() + prefix_message = self.zest_releaser_config.prefix_message() + extra_message = self.zest_releaser_config.extra_message() if prefix_message: msg = "%s %s" % (prefix_message, msg) if extra_message: diff --git a/zest/releaser/bumpversion.py b/zest/releaser/bumpversion.py index dd4e9902..00fd4d6c 100644 --- a/zest/releaser/bumpversion.py +++ b/zest/releaser/bumpversion.py @@ -109,9 +109,9 @@ def _grab_version(self, initial=False): feature=feature, breaking=breaking, final=final, - less_zeroes=self.pypiconfig.less_zeroes(), - levels=self.pypiconfig.version_levels(), - dev_marker=self.pypiconfig.development_marker(), + less_zeroes=self.zest_releaser_config.less_zeroes(), + levels=self.zest_releaser_config.version_levels(), + dev_marker=self.zest_releaser_config.development_marker(), ) if final: minimum_version = utils.suggest_version(original_version, **params) diff --git a/zest/releaser/postrelease.py b/zest/releaser/postrelease.py index 9d6b0a54..09ec9dff 100644 --- a/zest/releaser/postrelease.py +++ b/zest/releaser/postrelease.py @@ -47,7 +47,7 @@ def __init__(self, vcs=None, breaking=False, feature=False, final=False): feature=feature, final=final, dev_version_template=DEV_VERSION_TEMPLATE, - development_marker=self.pypiconfig.development_marker(), + development_marker=self.zest_releaser_config.development_marker(), history_header=HISTORY_HEADER, update_history=True, ) @@ -82,9 +82,9 @@ def _ask_for_new_dev_version(self): breaking=self.data["breaking"], feature=self.data["feature"], final=self.data["final"], - less_zeroes=self.pypiconfig.less_zeroes(), - levels=self.pypiconfig.version_levels(), - dev_marker=self.pypiconfig.development_marker(), + less_zeroes=self.zest_releaser_config.less_zeroes(), + levels=self.zest_releaser_config.version_levels(), + dev_marker=self.zest_releaser_config.development_marker(), ) suggestion = utils.suggest_version(current, **params) print("Current version is %s" % current) diff --git a/zest/releaser/prerelease.py b/zest/releaser/prerelease.py index ad9025fa..8829cc1f 100644 --- a/zest/releaser/prerelease.py +++ b/zest/releaser/prerelease.py @@ -45,7 +45,7 @@ class Prereleaser(baserelease.Basereleaser): def __init__(self, vcs=None): baserelease.Basereleaser.__init__(self, vcs=vcs) # Prepare some defaults for potential overriding. - date_format = self.pypiconfig.date_format() + date_format = self.zest_releaser_config.date_format() self.data.update( dict( commit_msg=PRERELEASE_COMMIT_MSG, diff --git a/zest/releaser/release.py b/zest/releaser/release.py index b28e2a0d..3fd9653e 100644 --- a/zest/releaser/release.py +++ b/zest/releaser/release.py @@ -73,10 +73,10 @@ def __init__(self, vcs=None): def prepare(self): """Collect some data needed for releasing""" self._grab_version() - tag = self.pypiconfig.tag_format(self.data["version"]) + tag = self.zest_releaser_config.tag_format(self.data["version"]) self.data["tag"] = tag - self.data["tag-message"] = self.pypiconfig.tag_message(self.data["version"]) - self.data["tag-signing"] = self.pypiconfig.tag_signing() + self.data["tag-message"] = self.zest_releaser_config.tag_message(self.data["version"]) + self.data["tag-signing"] = self.zest_releaser_config.tag_signing() self.data["tag_already_exists"] = self.vcs.tag_exists(tag) def execute(self): @@ -133,7 +133,7 @@ def _upload_distributions(self, package): ) result = utils.execute_command(utils.setup_py("sdist")) utils.show_interesting_lines(result) - if self.pypiconfig.create_wheel(): + if self.zest_releaser_config.create_wheel(): logger.info( "Making a wheel of a fresh tag checkout (in %s).", self.data["tagworkingdir"], @@ -158,7 +158,7 @@ def _upload_distributions(self, package): os.path.join("dist", filename) for filename in os.listdir("dist") ) - register = self.pypiconfig.register_package() + register = self.zest_releaser_config.register_package() # If TWINE_REPOSITORY_URL is set, use it. if self.pypiconfig.twine_repository_url(): @@ -271,7 +271,7 @@ def _release(self): # Expected case: this is a buildout directory. default_answer = False else: - default_answer = self.pypiconfig.want_release() + default_answer = self.zest_releaser_config.want_release() if not utils.ask( "Check out the tag (for tweaks or pypi/distutils " "server upload)", From bad5e821d756af8881400a041fea66747f4fb0f0 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 3 Jul 2023 16:00:29 +0200 Subject: [PATCH 19/65] fix incorrect list indexing and import --- zest/releaser/pypi.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index 93aa5940..cb03559c 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -5,7 +5,7 @@ import pkg_resources import sys -from utils import string_to_bool +from .utils import string_to_bool try: # Python 3.11+ @@ -697,7 +697,7 @@ def tag_signing(self): """ try: - return self.config("tag-signing") + return self.config["tag-signing"] except KeyError: return False @@ -745,6 +745,6 @@ def run_pre_commit(self): """ try: - return self.config("run-pre-commit") + return self.config["run-pre-commit"] except KeyError: return False From 7faee51688e0670eb8ff43c01859e422ad8c23ca Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 3 Jul 2023 16:00:46 +0200 Subject: [PATCH 20/65] update internal function reference --- zest/releaser/git.py | 2 +- zest/releaser/vcs.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/zest/releaser/git.py b/zest/releaser/git.py index 485f7fac..5c28ab40 100644 --- a/zest/releaser/git.py +++ b/zest/releaser/git.py @@ -68,7 +68,7 @@ def cmd_diff(self): def cmd_commit(self, message): parts = ["git", "commit", "-a", "-m", message] - if not self.pypi_cfg.run_pre_commit(): + if not self.zest_releaser_config.run_pre_commit(): parts.append("-n") return parts diff --git a/zest/releaser/vcs.py b/zest/releaser/vcs.py index 339655d2..5667e6c4 100644 --- a/zest/releaser/vcs.py +++ b/zest/releaser/vcs.py @@ -66,7 +66,8 @@ def __init__(self, reporoot=None): self.relative_path_in_repo = os.path.relpath(self.workingdir, reporoot) self.setup_cfg = pypi.SetupConfig() self.pypi_cfg = pypi.PypiConfig() - self.fallback_encoding = self.pypi_cfg.encoding() + self.zest_releaser_config = pypi.ZestReleaserConfig() + self.fallback_encoding = self.zest_releaser_config.encoding() def __repr__(self): return "<{} at {} {}>".format( @@ -115,7 +116,7 @@ def get_version_txt_version(self): return utils.strip_version(version) def get_python_file_version(self): - python_version_file = self.setup_cfg.python_file_with_version() + python_version_file = self.zest_releaser_config.python_file_with_version() if not python_version_file: return lines, encoding = utils.read_text_file( @@ -233,7 +234,7 @@ def _extract_version(self): ) def _update_python_file_version(self, version): - filename = self.setup_cfg.python_file_with_version() + filename = self.zest_releaser_config.python_file_with_version() lines, encoding = utils.read_text_file( filename, fallback_encoding=self.fallback_encoding, From 5ec529331509009f0af24ce669cf808374be9499 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 3 Jul 2023 16:11:35 +0200 Subject: [PATCH 21/65] fix incorrect default arguments --- zest/releaser/pypi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index cb03559c..ddf0b5ad 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -512,7 +512,7 @@ def register_package(self): try: return self.config["register"] except KeyError: - return True + return False def no_input(self): """Return whether the user wants to run in no-input mode. @@ -527,7 +527,7 @@ def no_input(self): try: return self.config["no-input"] except KeyError: - return True + return False def development_marker(self): """Return development marker to be appended in postrelease. From 9ecc96eab54844673ade7ff02d9288035682ac61 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Tue, 4 Jul 2023 09:21:22 +0200 Subject: [PATCH 22/65] clean up zest releaser config functions for pypirc and setup config classes --- zest/releaser/pypi.py | 81 ++++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index ddf0b5ad..f43ed3b0 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -137,28 +137,29 @@ def fix_config(self): print("".join(config_file.readlines())) def zest_releaser_config(self): - default = None - if self.config is None: - return default + if not self.config: + return None + try: result = dict(self.config["zest-releaser"].items(raw=True)) - boolean_keys = [ - "release", - "create-wheel", - "no-input", - "register", - "push-changes", - "less-zeroes", - "tag-signing", - "run-pre-commit", - ] - for key, value in result.items(): - if key in boolean_keys: - result[key] = string_to_bool(value) - if key in ["version-levels"]: - result[key] = int(value) except KeyError: - return default + return None + + boolean_keys = [ + "release", + "create-wheel", + "no-input", + "register", + "push-changes", + "less-zeroes", + "tag-signing", + "run-pre-commit", + ] + for key, value in result.items(): + if key in boolean_keys: + result[key] = string_to_bool(value) + if key in ["version-levels"]: + result[key] = int(value) return result @@ -186,28 +187,29 @@ def reload(self): self._read_configfile() def zest_releaser_config(self): - default = None - if self.config is None: - return default + if not self.config: + return None + try: result = dict(self.config["zest-releaser"].items(raw=True)) - boolean_keys = [ - "release", - "create-wheel", - "no-input", - "register", - "push-changes", - "less-zeroes", - "tag-signing", - "run-pre-commit", - ] - for key, value in result.items(): - if key in boolean_keys: - result[key] = string_to_bool(value) - if key in ["version-levels"]: - result[key] = int(value) except KeyError: - return default + return None + + boolean_keys = [ + "release", + "create-wheel", + "no-input", + "register", + "push-changes", + "less-zeroes", + "tag-signing", + "run-pre-commit", + ] + for key, value in result.items(): + if key in boolean_keys: + result[key] = string_to_bool(value) + if key in ["version-levels"]: + result[key] = int(value) return result def _read_configfile(self): @@ -315,7 +317,8 @@ def load_configs(self, pypirc_config_filename=DIST_CONFIG_FILE): combined_config = {} # overwrite any duplicate keys in the following order: for config in [setup_config, pypi_config, pyproject_config]: - if isinstance(config, dict): + if config: + assert isinstance(config, dict) combined_config.update(config) self.config = combined_config From 1543847941324bba3d9f3f967983f3747c4df864 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Tue, 4 Jul 2023 09:40:56 +0200 Subject: [PATCH 23/65] update docstring, change zest_releaser_config() function of PyprojectTomlConfig --- zest/releaser/pypi.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index f43ed3b0..8f3e25d0 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -164,7 +164,10 @@ def zest_releaser_config(self): class PypiConfig(BaseConfig): - """Wrapper around the pypi config file""" + """Wrapper around the pypi config file. + Contains functions which return information about + the pypi configuration. + """ def __init__(self, config_filename=DIST_CONFIG_FILE): """Grab the PyPI configuration. @@ -299,13 +302,12 @@ def __init__(self): self.config = tomllib.load(tomlconfig) def zest_releaser_config(self): - default = None if self.config is None: - return default + return None try: result = self.config["tools"]["zest-releaser"] except KeyError: - return default + return None return result From 94f758cdce7d592a003c63c819e8390db244478b Mon Sep 17 00:00:00 2001 From: elisallenens Date: Tue, 4 Jul 2023 09:41:55 +0200 Subject: [PATCH 24/65] remove unused pypi_cfg attribute --- zest/releaser/vcs.py | 1 - 1 file changed, 1 deletion(-) diff --git a/zest/releaser/vcs.py b/zest/releaser/vcs.py index 5667e6c4..34c03df6 100644 --- a/zest/releaser/vcs.py +++ b/zest/releaser/vcs.py @@ -65,7 +65,6 @@ def __init__(self, reporoot=None): # Determine relative path from root of repo. self.relative_path_in_repo = os.path.relpath(self.workingdir, reporoot) self.setup_cfg = pypi.SetupConfig() - self.pypi_cfg = pypi.PypiConfig() self.zest_releaser_config = pypi.ZestReleaserConfig() self.fallback_encoding = self.zest_releaser_config.encoding() From 96066e07e5519265a7ecb64e0ecc53cff010153b Mon Sep 17 00:00:00 2001 From: elisallenens Date: Tue, 4 Jul 2023 09:50:56 +0200 Subject: [PATCH 25/65] remove setup_cfg instances where inapplicable and replace with zest_releaser_config --- zest/releaser/baserelease.py | 5 ++--- zest/releaser/vcs.py | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/zest/releaser/baserelease.py b/zest/releaser/baserelease.py index beb772ee..518a2bea 100644 --- a/zest/releaser/baserelease.py +++ b/zest/releaser/baserelease.py @@ -279,10 +279,9 @@ def _insert_changelog_entry(self, message): "the existing file. This might give problems.", orig_encoding, ) - config = self.setup_cfg.config fallback_encodings = [] - if config.has_option("zest.releaser", "encoding"): - encoding = config.get("zest.releaser", "encoding") + if "encoding" in self.zest_releaser_config: + encoding = self.zest_releaser_config["encoding"] if encoding != orig_encoding: fallback_encodings.append(encoding) encoding = "utf-8" diff --git a/zest/releaser/vcs.py b/zest/releaser/vcs.py index 34c03df6..8d640400 100644 --- a/zest/releaser/vcs.py +++ b/zest/releaser/vcs.py @@ -64,7 +64,6 @@ def __init__(self, reporoot=None): self.reporoot = reporoot # Determine relative path from root of repo. self.relative_path_in_repo = os.path.relpath(self.workingdir, reporoot) - self.setup_cfg = pypi.SetupConfig() self.zest_releaser_config = pypi.ZestReleaserConfig() self.fallback_encoding = self.zest_releaser_config.encoding() From 41788f5719d2b4b2a8a4ec65a718cae732831601 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Tue, 4 Jul 2023 10:13:39 +0200 Subject: [PATCH 26/65] use dict.get() instead of try-except with KeyError --- zest/releaser/pypi.py | 101 ++++++++++-------------------------------- 1 file changed, 23 insertions(+), 78 deletions(-) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index 8f3e25d0..e784efbf 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -352,10 +352,7 @@ def want_release(self): mixed case and specify 0, false, no or off for boolean False, and 1, on, true or yes for boolean True. """ - try: - return self.config["release"] - except KeyError: - return True + return self.config.get("release", True) def extra_message(self): """Return extra text to be added to commit messages. @@ -370,10 +367,7 @@ def extra_message(self): [zest.releaser] extra-message = [ci skip] """ - try: - return self.config["extra-message"] - except KeyError: - return None + return self.config.get("extra-message") def prefix_message(self): """Return extra text to be added before the commit message. @@ -386,10 +380,7 @@ def prefix_message(self): [zest.releaser] prefix-message = [TAG] """ - try: - return self.config["prefix-message"] - except KeyError: - return None + return self.config.get("prefix-message") def history_file(self): """Return path of history file. @@ -404,14 +395,10 @@ def history_file(self): [zest.releaser] history-file = deep/down/historie.doc """ - try: - result = self.config["history-file"] - except KeyError: - try: - # We were reading an underscore instead of a dash at first. - result = self.config["history_file"] - except KeyError: - result = None + # we were using an underscore at first + result = self.config.get("history_file") + # but if they're both defined, the hyphenated key takes precedence + result = self.config.get("history-file", result) return result def python_file_with_version(self): @@ -425,10 +412,7 @@ def python_file_with_version(self): Return None when nothing has been configured. """ - try: - return self.config["python-file-with-version"] - except KeyError: - return None + return self.config.get("python-file-with-version") def encoding(self): """Return encoding to use for text files. @@ -445,10 +429,7 @@ def encoding(self): [zest.releaser] encoding = utf-8 """ - try: - return self.config["encoding"] - except KeyError: - return "" + return self.config.get("encoding", "") def history_format(self): """Return the format to be used for Changelog files. @@ -460,10 +441,7 @@ def history_format(self): [zest.releaser] history_format = md """ - try: - return self.config["history_format"] - except KeyError: - return "" + return self.config.get("history_format", "") def create_wheel(self): """Should we create a Python wheel for this package? @@ -486,10 +464,7 @@ def create_wheel(self): # If the wheel package is not available, we obviously # cannot create wheels. return False - try: - return self.config["create-wheel"] - except KeyError: - return True + return self.config.get("create-wheel", True) def register_package(self): """Should we try to register this package with a package server? @@ -514,10 +489,7 @@ def register_package(self): option is used for all of them. There is no way to register and upload to server A, and only upload to server B. """ - try: - return self.config["register"] - except KeyError: - return False + return self.config.get("register", False) def no_input(self): """Return whether the user wants to run in no-input mode. @@ -529,10 +501,7 @@ def no_input(self): The default when this option has not been set is False. """ - try: - return self.config["no-input"] - except KeyError: - return False + return self.config.get("no-input", False) def development_marker(self): """Return development marker to be appended in postrelease. @@ -545,10 +514,7 @@ def development_marker(self): Returns default of ``.dev0`` when nothing has been configured. """ - try: - return self.config["no-input"] - except KeyError: - return ".dev0" + return self.config.get("no-input", ".dev0") def push_changes(self): """Return whether the user wants to push the changes to the remote. @@ -560,10 +526,7 @@ def push_changes(self): The default when this option has not been set is True. """ - try: - return self.config["push-changes"] - except KeyError: - return True + return self.config.get("push-changes", True) def less_zeroes(self): """Return whether the user prefers less zeroes at the end of a version. @@ -583,10 +546,7 @@ def less_zeroes(self): In the postrelease command we read this option too, but with the current logic it has no effect there. """ - try: - return self.config["less-zeroes"] - except KeyError: - return False + return self.config.get("less-zeroes", False) def version_levels(self): """How many levels does the user prefer in a version number? @@ -613,10 +573,7 @@ def version_levels(self): version number strategy that you prefer. """ default = 0 - try: - result = self.config["version-levels"] - except KeyError: - return default + result = self.config.get("version-levels", default) if result < 0: return default return result @@ -646,11 +603,8 @@ def tag_format(self, version): The default format, when nothing has been configured, is ``{version}``. """ - fmt = "{version}" - try: - fmt = self.config["tag-format"] - except KeyError: - pass + default_fmt = "{version}" + fmt = self.config.get("tag-format", default_fmt) if "{version}" in fmt: return fmt.format(version=version) # BBB: @@ -675,11 +629,8 @@ def tag_message(self, version): The default format is ``Tagging {version}``. """ - fmt = "Tagging {version}" - try: - fmt = self.config["tag-message"] - except KeyError: - pass + default_fmt = "Tagging {version}" + fmt = self.config.get("tag-message", default_fmt) if "{version}" not in fmt: print("{version} needs to be part of 'tag-message': '%s'" % fmt) sys.exit(1) @@ -701,10 +652,7 @@ def tag_signing(self): The default when this option has not been set is False. """ - try: - return self.config["tag-signing"] - except KeyError: - return False + return self.config.get("tag-signing", False) def date_format(self): """Return the string format for the date used in the changelog. @@ -749,7 +697,4 @@ def run_pre_commit(self): The default when this option has not been set is False. """ - try: - return self.config["run-pre-commit"] - except KeyError: - return False + return self.config.get("run-pre-commit", False) From 51b6e976c744678c44ceb2fb07f21c834c205d09 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Tue, 4 Jul 2023 11:30:18 +0200 Subject: [PATCH 27/65] set interpolation=None in configparser instead of item fetching --- zest/releaser/pypi.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index e784efbf..4e3fef8c 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -86,7 +86,7 @@ def __init__(self): if not os.path.exists(self.config_filename): self.config = None return - self.config = ConfigParser() + self.config = ConfigParser(interpolation=None) self.config.read(self.config_filename) def has_bad_commands(self): @@ -141,7 +141,7 @@ def zest_releaser_config(self): return None try: - result = dict(self.config["zest-releaser"].items(raw=True)) + result = dict(self.config["zest.releaser"].items()) except KeyError: return None @@ -194,7 +194,7 @@ def zest_releaser_config(self): return None try: - result = dict(self.config["zest-releaser"].items(raw=True)) + result = dict(self.config["zest.releaser"].items()) except KeyError: return None @@ -220,7 +220,7 @@ def _read_configfile(self): if not os.path.exists(self.config_filename): self.config = None return - self.config = ConfigParser() + self.config = ConfigParser(interpolation=None) self.config.read(self.config_filename) def twine_repository(self): From 021c36a1c16879cb96c99512a100acc65b239b5a Mon Sep 17 00:00:00 2001 From: elisallenens Date: Tue, 4 Jul 2023 11:36:09 +0200 Subject: [PATCH 28/65] update function references in pypi test --- zest/releaser/tests/pypi.txt | 65 ++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/zest/releaser/tests/pypi.txt b/zest/releaser/tests/pypi.txt index 22723c88..0617ee38 100644 --- a/zest/releaser/tests/pypi.txt +++ b/zest/releaser/tests/pypi.txt @@ -21,7 +21,7 @@ name yourself. We'll do that in the rest of these tests. A missing file doesn't lead to an error: - >>> pypiconfig = pypi.PypiConfig(config_filename='non/existing', use_setup_cfg=False) + >>> pypiconfig = pypi.PypiConfig(config_filename='non/existing') >>> pypiconfig.config is None True @@ -127,28 +127,27 @@ use a different default answer when it asks to make a checkout for a release. This means you can usually just press Enter on all questions that zest.releaser asks. -We try to read a [zest.releaser] section, either in pypirc or -setup.cfg and look for a ``release`` option. We can -ask for the result like this, which by default is True: +We try to read a [zest.releaser] section and look for a ``release`` +option. We can ask for the result like this, which by default is True: - >>> pypiconfig = pypi.PypiConfig(config_filename=pypirc_both) - >>> pypiconfig.want_release() + >>> zestreleaserconfig = pypi.ZestReleaserConfig(pypirc_config_filename=pypirc_both) + >>> zestreleaserconfig.want_release() True We have a pypirc for this: >>> pypirc_no_release = pkg_resources.resource_filename( ... 'zest.releaser.tests', 'pypirc_no_release.txt') - >>> pypiconfig = pypi.PypiConfig(config_filename=pypirc_no_release) - >>> pypiconfig.want_release() + >>> zestreleaserconfig = pypi.ZestReleaserConfig(pypirc_config_filename=pypirc_no_release) + >>> zestreleaserconfig.want_release() False We can also specify to always do that checkout during a release: >>> pypirc_yes_release = pkg_resources.resource_filename( ... 'zest.releaser.tests', 'pypirc_yes_release.txt') - >>> pypiconfig = pypi.PypiConfig(config_filename=pypirc_yes_release) - >>> pypiconfig.want_release() + >>> zestreleaserconfig = pypi.ZestReleaserConfig(pypirc_config_filename=pypirc_yes_release) + >>> zestreleaserconfig.want_release() True @@ -173,16 +172,16 @@ zest.releaser, which now contains a setup.cfg. We can ask for the result like this, which by default is True: - >>> pypiconfig = pypi.PypiConfig(config_filename=pypirc_both, use_setup_cfg=False) - >>> pypiconfig.create_wheel() + >>> zestreleaserconfig = pypi.ZestReleaserConfig(pypirc_config_filename=pypirc_both) + >>> zestreleaserconfig.create_wheel() True We can also specify to not create the wheel, even when (universal) wheels could be created: >>> pypirc_yes_release = pkg_resources.resource_filename( ... 'zest.releaser.tests', 'pypirc_universal_nocreate.txt') - >>> pypiconfig = pypi.PypiConfig(config_filename=pypirc_yes_release, use_setup_cfg=False) - >>> pypiconfig.create_wheel() + >>> zestreleaserconfig = pypi.ZestReleaserConfig(pypirc_config_filename=pypirc_yes_release) + >>> zestreleaserconfig.create_wheel() False Fixing setup.cfg @@ -274,8 +273,8 @@ By default the tag format is just the version itself: >>> version = '1.2.3' >>> os.remove(pypi.SETUP_CONFIG_FILE) - >>> pypiconfig = pypi.PypiConfig() - >>> pypiconfig.tag_format(version) + >>> zestreleaserconfig = pypi.ZestReleaserConfig() + >>> zestreleaserconfig.tag_format(version) '1.2.3' But it can be customized with a format string, as long as it contains @@ -287,8 +286,8 @@ the string ``{version}``: ... """ >>> with open(pypi.SETUP_CONFIG_FILE, 'w') as f: ... _ = f.write(SETUP_CFG) - >>> pypiconfig = pypi.PypiConfig() - >>> pypiconfig.tag_format(version) + >>> zestreleaserconfig = pypi.ZestReleaserConfig() + >>> zestreleaserconfig.tag_format(version) 'mytag-1.2.3' Or, for backward compatibility, a Python % interpolation format: @@ -299,8 +298,8 @@ Or, for backward compatibility, a Python % interpolation format: ... """ >>> with open(pypi.SETUP_CONFIG_FILE, 'w') as f: ... _ = f.write(SETUP_CFG) - >>> pypiconfig = pypi.PypiConfig() - >>> pypiconfig.tag_format(version) + >>> zestreleaserconfig = pypi.ZestReleaserConfig() + >>> zestreleaserconfig.tag_format(version) `tag-format` contains deprecated `%(version)s` format. Please change to: [zest.releaser] @@ -323,8 +322,8 @@ By default the tag message is ``Tagging`` plus the version: >>> version = '1.2.3' >>> os.remove(pypi.SETUP_CONFIG_FILE) - >>> pypiconfig = pypi.PypiConfig() - >>> pypiconfig.tag_message(version) + >>> zestreleaserconfig = pypi.ZestReleaserConfig() + >>> zestreleaserconfig.tag_message(version) 'Tagging 1.2.3' But it can be customized with a message string, as long as it contains @@ -336,8 +335,8 @@ the string ``{version}``: ... """ >>> with open(pypi.SETUP_CONFIG_FILE, 'w') as f: ... _ = f.write(SETUP_CFG) - >>> pypiconfig = pypi.PypiConfig() - >>> pypiconfig.tag_message(version) + >>> zestreleaserconfig = pypi.ZestReleaserConfig() + >>> zestreleaserconfig.tag_message(version) 'Creating v1.2.3 tag.' @@ -352,19 +351,19 @@ behaviour with a ``--no-input`` commandline option, but also by adding The default is False: - >>> pypiconfig = pypi.PypiConfig(config_filename=pypirc_yes_release, use_setup_cfg=False) - >>> pypiconfig.no_input() + >>> zestreleaserconfig = pypi.ZestReleaserConfig(pypirc_config_filename=pypirc_yes_release) + >>> zestreleaserconfig.no_input() False Enable the option in ``.pypirc``: >>> pypirc_no_input = pkg_resources.resource_filename( ... 'zest.releaser.tests', 'pypirc_no_input.txt') - >>> pypiconfig = pypi.PypiConfig(config_filename=pypirc_no_input) + >>> zestreleaserconfig = pypi.ZestReleaserConfig(pypirc_config_filename=pypirc_no_input) Now the option should be set to True: - >>> pypiconfig.no_input() + >>> zestreleaserconfig.no_input() True Let's enable the option also in setup.cfg: @@ -375,11 +374,11 @@ Let's enable the option also in setup.cfg: ... """ >>> with open(pypi.SETUP_CONFIG_FILE, 'w') as f: ... _ = f.write(SETUP_CFG) - >>> pypiconfig = pypi.PypiConfig() + >>> zestreleaserconfig = pypi.ZestReleaserConfig() The option should be set to True here as well: - >>> pypiconfig.no_input() + >>> zestreleaserconfig.no_input() True @@ -391,7 +390,7 @@ in it. For that, there's the ``python-file-with-version`` option. The default is None: - >>> setup_config.python_file_with_version() + >>> zestreleaserconfig.python_file_with_version() Enable the option: @@ -401,6 +400,6 @@ Enable the option: ... """ >>> with open(pypi.SETUP_CONFIG_FILE, 'w') as f: ... _ = f.write(SETUP_CFG) - >>> setup_config = pypi.SetupConfig() - >>> setup_config.python_file_with_version() + >>> zestreleaserconfig = pypi.ZestReleaserConfig() + >>> zestreleaserconfig.python_file_with_version() 'reinout/maurits.py' From bab792f8db1823d8dea2469151b6bd83e367afa1 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Tue, 4 Jul 2023 12:29:26 +0200 Subject: [PATCH 29/65] fix incorrect dict key --- zest/releaser/pypi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index 4e3fef8c..5eda5245 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -514,7 +514,7 @@ def development_marker(self): Returns default of ``.dev0`` when nothing has been configured. """ - return self.config.get("no-input", ".dev0") + return self.config.get("development-marker", ".dev0") def push_changes(self): """Return whether the user wants to push the changes to the remote. From 53b2fd7afc74cea19417510b26028ec63b899c9f Mon Sep 17 00:00:00 2001 From: elisallenens Date: Tue, 4 Jul 2023 14:45:10 +0200 Subject: [PATCH 30/65] add functions to extract package name from setup.cfg and pyproject.toml --- zest/releaser/vcs.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/zest/releaser/vcs.py b/zest/releaser/vcs.py index 8d640400..b184caae 100644 --- a/zest/releaser/vcs.py +++ b/zest/releaser/vcs.py @@ -6,6 +6,8 @@ import re import sys +from configparser import ConfigParser + try: # Python 3.11+ import tomllib @@ -103,6 +105,15 @@ def get_setup_py_name(self): utils.execute_command(utils.setup_py("egg_info")) return utils.execute_command(utils.setup_py("--name")).strip() + def get_setup_cfg_name(self): + if os.path.exists("setup.cfg"): + setup_cfg = ConfigParser() + setup_cfg.read("setup.cfg") + try: + return setup_cfg["metadata"]["name"] + except KeyError: + return None + def get_version_txt_version(self): filenames = ["version"] for extension in TXT_EXTENSIONS: @@ -139,6 +150,14 @@ def get_pyproject_toml_version(self): # Might be None, but that is fine. return result.get("project", {}).get("version") + def get_pyproject_toml_name(self): + if not os.path.exists("pyproject.toml"): + return + with open("pyproject.toml", "rb") as myfile: + result = tomllib.load(myfile) + # Might be None, but that is fine. + return result.get("project", {}).get("name") + def filefind(self, names): """Return first found file matching name (case-insensitive). @@ -231,6 +250,14 @@ def _extract_version(self): or self.get_version_txt_version() ) + def _extract_name(self): + """Extract the package name from setup.py or pyproject.toml or similar.""" + return ( + self.get_setup_py_name() + or self.get_setup_cfg_name() + or self.get_pyproject_toml_name() + ) + def _update_python_file_version(self, version): filename = self.zest_releaser_config.python_file_with_version() lines, encoding = utils.read_text_file( From 9e0f53852c72994df01582d0079b42f0e18b0051 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Tue, 4 Jul 2023 14:48:05 +0200 Subject: [PATCH 31/65] fetch package name from any file in git.py --- zest/releaser/git.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zest/releaser/git.py b/zest/releaser/git.py index 5c28ab40..281d8344 100644 --- a/zest/releaser/git.py +++ b/zest/releaser/git.py @@ -28,10 +28,10 @@ def is_setuptools_helper_package_installed(self): @property def name(self): - package_name = self.get_setup_py_name() + package_name = self._extract_name() if package_name: return package_name - # No setup.py? With git we can probably only fall back to the directory + # No python package name? With git we can probably only fall back to the directory # name as there's no svn-url with a usable name in it. dir_name = os.path.basename(os.getcwd()) dir_name = fs_to_text(dir_name) From 9527a6392fbcfa8cb9b3d0af7e218eca491426f1 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Tue, 4 Jul 2023 16:12:51 +0200 Subject: [PATCH 32/65] use build instead of setup.py to build the python package --- pyproject.toml | 1 + zest/releaser/release.py | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a25db3ee..ec6db096 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ dependencies = [ "colorama", "requests", "twine >= 1.6.0", + "build >= 0.6.0.post1", # 0.6.0 wasn't compatible with Python 3.7 "tomli; python_version<'3.11'", ] requires-python = ">=3.7" diff --git a/zest/releaser/release.py b/zest/releaser/release.py index 3fd9653e..7d8b2454 100644 --- a/zest/releaser/release.py +++ b/zest/releaser/release.py @@ -3,6 +3,8 @@ from colorama import Fore from urllib import request from urllib.error import HTTPError +from build import ProjectBuilder +import pyproject_hooks import logging import os @@ -131,15 +133,14 @@ def _upload_distributions(self, package): "Making a source distribution of a fresh tag checkout (in %s).", self.data["tagworkingdir"], ) - result = utils.execute_command(utils.setup_py("sdist")) - utils.show_interesting_lines(result) + builder = ProjectBuilder(srcdir='.', runner=pyproject_hooks.quiet_subprocess_runner) + builder.build('sdist', './dist/') if self.zest_releaser_config.create_wheel(): logger.info( "Making a wheel of a fresh tag checkout (in %s).", self.data["tagworkingdir"], ) - result = utils.execute_command(utils.setup_py("bdist_wheel")) - utils.show_interesting_lines(result) + builder.build('wheel', './dist/') if not self.pypiconfig.is_pypi_configured(): logger.error( "You must have a properly configured %s file in " From 6aeeffe7f6138ba81a538d8ce9d14e49544cbe5a Mon Sep 17 00:00:00 2001 From: elisallenens Date: Thu, 6 Jul 2023 10:29:03 +0200 Subject: [PATCH 33/65] use own runner for project building to properly handle warnings and errors --- zest/releaser/release.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/zest/releaser/release.py b/zest/releaser/release.py index 7d8b2454..7f75589a 100644 --- a/zest/releaser/release.py +++ b/zest/releaser/release.py @@ -4,7 +4,7 @@ from urllib import request from urllib.error import HTTPError from build import ProjectBuilder -import pyproject_hooks +from subprocess import check_output, STDOUT import logging import os @@ -59,6 +59,27 @@ def package_in_pypi(package): logger.debug("Package not found on pypi: %s", e) return False +def _project_builder_runner(cmd, cwd=None, extra_environ=None): + """Call the subprocess, and format warnings and error sin red so that + they will work correctly with utils.show_interesting_lines() + """ + env = os.environ.copy() + if extra_environ: + env.update(extra_environ) + + result = check_output(cmd, cwd=cwd, env=env, stderr=STDOUT) + result_split = result.split(b"\n") + formatted_result = [] + for line in result_split: + line = line.decode() + if "warning" in line.lower() or "error" in line.lower(): + line = Fore.RED + line + else: + line = Fore.RESET + line # so that not all the lines after a warning are red + formatted_result.append(line) + formatted_result_joined = "\n".join(formatted_result) + utils.show_interesting_lines(formatted_result_joined) + class Releaser(baserelease.Basereleaser): """Release the project by tagging it and optionally uploading to pypi.""" @@ -133,7 +154,7 @@ def _upload_distributions(self, package): "Making a source distribution of a fresh tag checkout (in %s).", self.data["tagworkingdir"], ) - builder = ProjectBuilder(srcdir='.', runner=pyproject_hooks.quiet_subprocess_runner) + builder = ProjectBuilder(srcdir='.', runner=_project_builder_runner) builder.build('sdist', './dist/') if self.zest_releaser_config.create_wheel(): logger.info( From 3fe45d32cd4f80b899bf581f1a098320d168d234 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Thu, 6 Jul 2023 13:38:09 +0200 Subject: [PATCH 34/65] improve error/warning matching in build subprocess runner --- zest/releaser/release.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zest/releaser/release.py b/zest/releaser/release.py index 7f75589a..eaa8ffb0 100644 --- a/zest/releaser/release.py +++ b/zest/releaser/release.py @@ -60,7 +60,7 @@ def package_in_pypi(package): return False def _project_builder_runner(cmd, cwd=None, extra_environ=None): - """Call the subprocess, and format warnings and error sin red so that + """Call the subprocess, and format warnings and errors in red so that they will work correctly with utils.show_interesting_lines() """ env = os.environ.copy() @@ -72,7 +72,7 @@ def _project_builder_runner(cmd, cwd=None, extra_environ=None): formatted_result = [] for line in result_split: line = line.decode() - if "warning" in line.lower() or "error" in line.lower(): + if line.lower().startswith(("warning", "error")): line = Fore.RED + line else: line = Fore.RESET + line # so that not all the lines after a warning are red From ca53bbec49171ea6097da8221841e7474e8c065c Mon Sep 17 00:00:00 2001 From: elisallenens Date: Thu, 6 Jul 2023 13:38:58 +0200 Subject: [PATCH 35/65] fix incorrect MANIFEST.in in example package for tests --- zest/releaser/tests/example.tar | Bin 20480 -> 20480 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/zest/releaser/tests/example.tar b/zest/releaser/tests/example.tar index 0690d6206712c2d99ba234600777a034daafb258..100cd4968877fd5b91fb8bdae123f598f2529fc7 100644 GIT binary patch delta 1104 zcmajfO-jQ+6bJAmh9qhbEJaEZO9;h9k@Ds%2_jP5yAV$xX%~fJMD5B=k6<$wC?26+ z#REABbn}Wc*-SRSdGp@Zn2lp!2ySA48AJgoKv2U# zY39i6`T=F7OuU?$qB3bI$CS5qyYp(ZohOb=Mo*~jDE4z`KPRodN?e(g*p|xj+!Uaj ag8<#OHH`tfJ8)BVFQEPa+OvM&pZx(!P8%Hn delta 1255 zcma)*-AcnS9L3u*+cC<OZ0Wv_|)gk#QvL-zcw9Q@5}c7 zK@}sY*81YuGq~5ilKi^@0SO9}x^q+_`ZA)wJVlv7p?=UcxO*Z`!FSCNs{u~e`gp!q$TetuK From 0a446ab5da97158358f825fbfed1fecd8add95b4 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Thu, 6 Jul 2023 13:58:07 +0200 Subject: [PATCH 36/65] move colour reset code to end of red line --- zest/releaser/release.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/zest/releaser/release.py b/zest/releaser/release.py index eaa8ffb0..b6ede413 100644 --- a/zest/releaser/release.py +++ b/zest/releaser/release.py @@ -73,9 +73,7 @@ def _project_builder_runner(cmd, cwd=None, extra_environ=None): for line in result_split: line = line.decode() if line.lower().startswith(("warning", "error")): - line = Fore.RED + line - else: - line = Fore.RESET + line # so that not all the lines after a warning are red + line = Fore.RED + line + Fore.RESET # reset so that not all the lines after a warning are red formatted_result.append(line) formatted_result_joined = "\n".join(formatted_result) utils.show_interesting_lines(formatted_result_joined) From 413f27fc5e580aba295bcfbf99eb145935d957ec Mon Sep 17 00:00:00 2001 From: elisallenens Date: Thu, 6 Jul 2023 15:10:44 +0200 Subject: [PATCH 37/65] convert colorama reset code to plain text for tests --- zest/releaser/tests/test_setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zest/releaser/tests/test_setup.py b/zest/releaser/tests/test_setup.py index 698590f9..2d64d22c 100644 --- a/zest/releaser/tests/test_setup.py +++ b/zest/releaser/tests/test_setup.py @@ -47,9 +47,10 @@ def mock_dispatch(*args): "nothing to commit, working tree clean", ), # We should ignore coloring by colorama. Or actually print it - # clearly. This catches Fore.RED and Fore.MAGENTA. + # clearly. This catches Fore.RED, Fore.MAGENTA and Fore.RESET. (re.compile(re.escape(Fore.RED)), "RED "), (re.compile(re.escape(Fore.MAGENTA)), "MAGENTA "), + (re.compile(re.escape(Fore.RESET)), "RESET "), ] ) From 57831c23e01c06d4da1175c791c32d643cad344b Mon Sep 17 00:00:00 2001 From: elisallenens Date: Thu, 6 Jul 2023 15:11:25 +0200 Subject: [PATCH 38/65] further modification of test package MANIFEST.in --- zest/releaser/tests/example.tar | Bin 20480 -> 20480 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/zest/releaser/tests/example.tar b/zest/releaser/tests/example.tar index 100cd4968877fd5b91fb8bdae123f598f2529fc7..d87fa178340adf3db9749a107221443950c3243b 100644 GIT binary patch delta 58 zcmZozz}T>WaY7%zv5~pCIfH?rp^1SxgM#T~L&lWNjEud!a0z2W6GH|C1Be9we+K}s CB@QwG delta 58 zcmZozz}T>WaY7%zp^>q%DT9Hbp|QC!gM#T~L&lWNjEud!a0z2WBU1(i1Be9we+K}p CS`Hon From 586d419d8837e09a72311a3d3d128ece2f8e4acb Mon Sep 17 00:00:00 2001 From: elisallenens Date: Thu, 6 Jul 2023 15:15:21 +0200 Subject: [PATCH 39/65] change expected output from setuptools logging to build logging --- zest/releaser/tests/functional-git.txt | 2 +- zest/releaser/tests/functional-with-hooks.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/zest/releaser/tests/functional-git.txt b/zest/releaser/tests/functional-git.txt index a18c7071..e287c96d 100644 --- a/zest/releaser/tests/functional-git.txt +++ b/zest/releaser/tests/functional-git.txt @@ -97,8 +97,8 @@ known on PyPI: Showing first few lines... running sdist running egg_info + creating src/tha.example.egg-info ... - creating dist Creating ... removing 'tha.example-0.1' ... Question: Upload to pypi (y/N)? diff --git a/zest/releaser/tests/functional-with-hooks.txt b/zest/releaser/tests/functional-with-hooks.txt index 1c7dffa9..a55306fd 100644 --- a/zest/releaser/tests/functional-with-hooks.txt +++ b/zest/releaser/tests/functional-with-hooks.txt @@ -99,8 +99,8 @@ The release script tags the release and uploads it: Showing first few lines... running sdist running egg_info + creating src/tha.example.egg-info ... - creating dist Creating ... removing 'tha.example-0.1' ... releaser_before_upload From 3457c90e4d80c2dcee8588a2ad9d7c059c6b70bc Mon Sep 17 00:00:00 2001 From: elisallenens Date: Thu, 6 Jul 2023 15:15:37 +0200 Subject: [PATCH 40/65] workaround conflicting pyc files --- zest/releaser/tests/functional-with-hooks.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/zest/releaser/tests/functional-with-hooks.txt b/zest/releaser/tests/functional-with-hooks.txt index a55306fd..53dcc3c8 100644 --- a/zest/releaser/tests/functional-with-hooks.txt +++ b/zest/releaser/tests/functional-with-hooks.txt @@ -51,7 +51,12 @@ Run the prerelease script. Note that pyroma and check-manifest have hooks here so they are run too, but the order may differ. With the bin/test script first pyroma is run, then check-manifest. With tox it is the other way around. So we use ellipsis for that part. +sys.dont_write_bytecode is set to True to avoid writing .pyc files so +that check-manifest doesn't complain about differences between the sdist +directory and the vcs. + >>> import sys + >>> sys.dont_write_bytecode = True >>> from zest.releaser import prerelease >>> utils.test_answer_book.set_answers(['', '', '', '', '']) >>> prerelease.main() From 9d92868ee1060598460a03436ee348e03bbfba5e Mon Sep 17 00:00:00 2001 From: elisallenens Date: Thu, 6 Jul 2023 15:22:43 +0200 Subject: [PATCH 41/65] update pyproject.toml test to new default answer --- zest/releaser/tests/pyproject-toml.txt | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/zest/releaser/tests/pyproject-toml.txt b/zest/releaser/tests/pyproject-toml.txt index 03fa03ec..adff70fb 100644 --- a/zest/releaser/tests/pyproject-toml.txt +++ b/zest/releaser/tests/pyproject-toml.txt @@ -88,7 +88,7 @@ known on PyPI: Our reply: y Question: Check out the tag - (for tweaks or pypi/distutils server upload) (y/N)? + (for tweaks or pypi/distutils server upload) (Y/n)? Our reply: y RED Note: ...0.1... ... @@ -96,13 +96,6 @@ known on PyPI: Preparing release 0.1 -TODO No source distribution or wheel is generated yet!!!!! -This currently only happens when you have a setup.py. -One related small thing is that for the question "Check out the tag" -the default answer is No, instead of Yes. -So there is much more to do. -But writing pyproject.toml has worked, which is a start. - There is now a tag: >>> print(execute_command(["git", "tag"])) From 0e568dbbc61395af158c46d5aa06442bb76dc2e1 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Thu, 6 Jul 2023 16:02:13 +0200 Subject: [PATCH 42/65] use entrypoints from any config file, not just setup.cfg --- zest/releaser/baserelease.py | 2 +- zest/releaser/pypi.py | 5 +++++ zest/releaser/utils.py | 16 +++++++--------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/zest/releaser/baserelease.py b/zest/releaser/baserelease.py index 518a2bea..bf888af1 100644 --- a/zest/releaser/baserelease.py +++ b/zest/releaser/baserelease.py @@ -425,7 +425,7 @@ def _push(self): def _run_hooks(self, when): which_releaser = self.__class__.__name__.lower() - utils.run_hooks(self.setup_cfg, which_releaser, when, self.data) + utils.run_hooks(self.zest_releaser_config, which_releaser, when, self.data) def run(self): self._run_hooks("before") diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index 5eda5245..b20c6f87 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -312,6 +312,8 @@ def zest_releaser_config(self): class ZestReleaserConfig: + hooks_filename = None + def load_configs(self, pypirc_config_filename=DIST_CONFIG_FILE): setup_config = SetupConfig().zest_releaser_config() pypi_config = PypiConfig(config_filename=pypirc_config_filename).zest_releaser_config() @@ -322,6 +324,9 @@ def load_configs(self, pypirc_config_filename=DIST_CONFIG_FILE): if config: assert isinstance(config, dict) combined_config.update(config) + # store which config file contained the hooks + if any([x for x in config.keys() if x.lower().startswith(("prereleaser.", "releaser.", "postreleaser."))]): + self.hooks_filename = config.config_filename() self.config = combined_config def __init__(self, pypirc_config_filename=DIST_CONFIG_FILE): diff --git a/zest/releaser/utils.py b/zest/releaser/utils.py index edd7169c..a4b033ea 100644 --- a/zest/releaser/utils.py +++ b/zest/releaser/utils.py @@ -561,7 +561,7 @@ def resolve_name(name): return ret -def run_hooks(setup_cfg, which_releaser, when, data): +def run_hooks(zest_releaser_config, which_releaser, when, data): """Run all release hooks for the given release step, including project-specific hooks from setup.cfg, and globally installed entry-points. @@ -571,12 +571,12 @@ def run_hooks(setup_cfg, which_releaser, when, data): """ hook_group = f"{which_releaser}.{when}" - config = setup_cfg.config + config = zest_releaser_config.config if config is not None and config.has_option("zest.releaser", hook_group): # Multiple hooks may be specified, each one separated by whitespace # (including newlines) - hook_names = config.get("zest.releaser", hook_group).split() + hook_names = config.get(hook_group).split() hooks = [] # The following code is adapted from the 'packaging' package being @@ -586,15 +586,13 @@ def run_hooks(setup_cfg, which_releaser, when, data): # distributed with the project # an optional package_dir option adds support for source layouts where # Python packages are not directly in the root of the source - config_dir = os.path.dirname(setup_cfg.config_filename) - sys.path.insert(0, os.path.dirname(setup_cfg.config_filename)) + config_dir = os.path.dirname(zest_releaser_config.hooks_filename) + sys.path.insert(0, os.path.dirname(zest_releaser_config.hooks_filename)) - if config.has_option("zest.releaser", "hook_package_dir"): - package_dir = config.get("zest.releaser", "hook_package_dir") + package_dir = config.get("hook_package_dir") + if package_dir: package_dir = os.path.join(config_dir, package_dir) sys.path.insert(0, package_dir) - else: - package_dir = None try: for hook_name in hook_names: From 76317b5b36dec2e7dd67af7ef7a04183f76e2c8c Mon Sep 17 00:00:00 2001 From: elisallenens Date: Thu, 6 Jul 2023 16:09:23 +0200 Subject: [PATCH 43/65] fix bug in config loading from config files --- zest/releaser/pypi.py | 19 ++++++++++--------- zest/releaser/utils.py | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index b20c6f87..9a368646 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -315,18 +315,19 @@ class ZestReleaserConfig: hooks_filename = None def load_configs(self, pypirc_config_filename=DIST_CONFIG_FILE): - setup_config = SetupConfig().zest_releaser_config() - pypi_config = PypiConfig(config_filename=pypirc_config_filename).zest_releaser_config() - pyproject_config = PyprojectTomlConfig().zest_releaser_config() + setup_config = SetupConfig() + pypi_config = PypiConfig(config_filename=pypirc_config_filename) + pyproject_config = PyprojectTomlConfig() combined_config = {} # overwrite any duplicate keys in the following order: for config in [setup_config, pypi_config, pyproject_config]: - if config: - assert isinstance(config, dict) - combined_config.update(config) - # store which config file contained the hooks - if any([x for x in config.keys() if x.lower().startswith(("prereleaser.", "releaser.", "postreleaser."))]): - self.hooks_filename = config.config_filename() + if zest_config := config.zest_releaser_config(): + assert isinstance(zest_config, dict) + combined_config.update(zest_config) + + # store which config file contained entrypoint hooks + if any([x for x in zest_config.keys() if x.lower().startswith(("prereleaser.", "releaser.", "postreleaser."))]): + self.hooks_filename = config.config_filename self.config = combined_config def __init__(self, pypirc_config_filename=DIST_CONFIG_FILE): diff --git a/zest/releaser/utils.py b/zest/releaser/utils.py index a4b033ea..90289ad7 100644 --- a/zest/releaser/utils.py +++ b/zest/releaser/utils.py @@ -573,7 +573,7 @@ def run_hooks(zest_releaser_config, which_releaser, when, data): hook_group = f"{which_releaser}.{when}" config = zest_releaser_config.config - if config is not None and config.has_option("zest.releaser", hook_group): + if config is not None and config.get(hook_group): # Multiple hooks may be specified, each one separated by whitespace # (including newlines) hook_names = config.get(hook_group).split() From d6fae90604933b4b95b3bf2fb917f5844130b747 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Thu, 6 Jul 2023 16:47:37 +0200 Subject: [PATCH 44/65] run upload distributions on pyproject.toml as well --- pyproject.toml | 1 + zest/releaser/release.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ec6db096..2bfd7ca5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ dependencies = [ "twine >= 1.6.0", "build >= 0.6.0.post1", # 0.6.0 wasn't compatible with Python 3.7 "tomli; python_version<'3.11'", + "setuptools >= 61.0.0", # older versions can't read pyproject.toml configurations ] requires-python = ">=3.7" classifiers = [ diff --git a/zest/releaser/release.py b/zest/releaser/release.py index b6ede413..c59c4a64 100644 --- a/zest/releaser/release.py +++ b/zest/releaser/release.py @@ -338,7 +338,7 @@ def _release(self): # Run extra entry point self._run_hooks("after_checkout") - if "setup.py" in os.listdir(self.data["tagworkingdir"]): + if any(filename in os.listdir(self.data["tagworkingdir"]) for filename in ["setup.py", "pyproject.toml"]): self._upload_distributions(package) # Make sure we are in the expected directory again. From c31b8580f38506f91b26c903ba41ea2b6689af89 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Fri, 7 Jul 2023 10:25:05 +0200 Subject: [PATCH 45/65] print tracebacks in build process to terminal instead of just crashing --- zest/releaser/release.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/zest/releaser/release.py b/zest/releaser/release.py index c59c4a64..e132a91e 100644 --- a/zest/releaser/release.py +++ b/zest/releaser/release.py @@ -4,7 +4,7 @@ from urllib import request from urllib.error import HTTPError from build import ProjectBuilder -from subprocess import check_output, STDOUT +from subprocess import check_output, CalledProcessError, STDOUT import logging import os @@ -67,7 +67,11 @@ def _project_builder_runner(cmd, cwd=None, extra_environ=None): if extra_environ: env.update(extra_environ) - result = check_output(cmd, cwd=cwd, env=env, stderr=STDOUT) + try: + result = check_output(cmd, cwd=cwd, env=env, stderr=STDOUT) + except CalledProcessError as e: + print(e.output.decode()) + raise SystemExit from e result_split = result.split(b"\n") formatted_result = [] for line in result_split: From abb848962ee9a56b0e0538c4664d518486451339 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Fri, 7 Jul 2023 11:45:33 +0200 Subject: [PATCH 46/65] switch to implicit namespacing on test package to avoid deprecation warning --- zest/releaser/tests/example.tar | Bin 20480 -> 17408 bytes zest/releaser/tests/git.txt | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/zest/releaser/tests/example.tar b/zest/releaser/tests/example.tar index d87fa178340adf3db9749a107221443950c3243b..ac24f829d96cc9fd873521975ad0724b79779163 100644 GIT binary patch delta 1751 zcmb7_y-EW?6os>E3PC{y3qh12h$vXx{{agNpTJiT42xjpPhpo%OOw2SXk)D}=$$N) zB!#mx%{DXljdqfKg%b3-c3DL{XVyi~*3$VlU_vtDB9dhsW!C ze>=Rp9q$}WPph!izDkAC34ZNsQa5HIi^b=#_7*AzBtRub6LyG*aW6mtI~GeRd&9M#m;L8@90rraPM&U!0Fpe-LZzM#T_7G7i0(JG$8QlZsOGFJ0RxS^LQh)^2N zvn(b0I$Ng98dkK2vQo(^NbcCvcE;6Hq%0- NKY)*m`t$0t{|ij_a?Ah# delta 1670 zcma)+&uSA<6vk&_1VNDsU5KKLMNA?h=g&R&qF@&;UAdQX8PZ`aZEeV;`*i2hCNCg9 zftZDl6ZWV^A%ph@^`1oqM>lL`m2OaXEGm&w z*IK#LO5hCYu0bWzQ==otqJC26E1I|Uu)6i=OL`*#{?KubQ0Q< zFavvH9a5-Su!fJT`)ObQcfdPuRqw5#6-Z577g`k8YPG8OW52g3c9(lE-(ME@-?Z~K zl?ScC4UYwMawJD_i1FxjayFgCI2=rl>o~2Nv-9b=Y6hd%ht>F`oL2E)`G>K4k8!km Tw(}=n{oMa}`)Zl@KjeP_cKL>v diff --git a/zest/releaser/tests/git.txt b/zest/releaser/tests/git.txt index 85215f04..d5766971 100644 --- a/zest/releaser/tests/git.txt +++ b/zest/releaser/tests/git.txt @@ -43,7 +43,7 @@ Make a change: index 9c14143..54fa3b9 100644 --- a/setup.py +++ b/setup.py - @@ -42,3 +42,5 @@ setup(name='tha.example', + @@ -41,3 +41,5 @@ setup(name='tha.example', 'console_scripts': [ ]}, ) @@ -117,7 +117,7 @@ Now we can request the changes since a specific tag: index 9c14143..54fa3b9 100644 --- a/setup.py +++ b/setup.py - @@ -44,3 +44,5 @@ setup(name='tha.example', + @@ -43,3 +43,5 @@ setup(name='tha.example', ) a = 2 From 8e0c239d0521a0f7ceb6c335c218c4c24748c1cc Mon Sep 17 00:00:00 2001 From: elisallenens Date: Fri, 7 Jul 2023 12:34:09 +0200 Subject: [PATCH 47/65] fix pyproject.toml release test --- zest/releaser/tests/pyproject-toml.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/zest/releaser/tests/pyproject-toml.txt b/zest/releaser/tests/pyproject-toml.txt index adff70fb..04ae33d1 100644 --- a/zest/releaser/tests/pyproject-toml.txt +++ b/zest/releaser/tests/pyproject-toml.txt @@ -95,6 +95,16 @@ known on PyPI: RED HEAD is now at ... Preparing release 0.1 + Showing first few lines... + running sdist + running egg_info + creating src/tha.example.egg-info + ... + Creating ... + removing 'tha.example-0.1' ... + Question: Upload to pypi (y/N)? + Our reply: yes + MOCK twine dispatch upload ... dist/tha.example-0.1.tar.gz There is now a tag: From 40e0c5d355f8b65307b204fbe1916d53686a2339 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 10 Jul 2023 15:18:32 +0200 Subject: [PATCH 48/65] remove walrus operator incompatible with py37 --- zest/releaser/pypi.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index 9a368646..d7c65d67 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -321,7 +321,8 @@ def load_configs(self, pypirc_config_filename=DIST_CONFIG_FILE): combined_config = {} # overwrite any duplicate keys in the following order: for config in [setup_config, pypi_config, pyproject_config]: - if zest_config := config.zest_releaser_config(): + if config.zest_releaser_config() is not None: + zest_config = config.zest_releaser_config() assert isinstance(zest_config, dict) combined_config.update(zest_config) From a5ded05df1ff6a2bf13a35a5afe86efe9aa0c7d5 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 10 Jul 2023 15:27:05 +0200 Subject: [PATCH 49/65] remove print, log failed build from SystemExit --- zest/releaser/release.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/zest/releaser/release.py b/zest/releaser/release.py index e132a91e..58370cde 100644 --- a/zest/releaser/release.py +++ b/zest/releaser/release.py @@ -70,8 +70,7 @@ def _project_builder_runner(cmd, cwd=None, extra_environ=None): try: result = check_output(cmd, cwd=cwd, env=env, stderr=STDOUT) except CalledProcessError as e: - print(e.output.decode()) - raise SystemExit from e + raise SystemExit(f"Build failed with the following error:\n{e.output.decode()}\nExiting") from e result_split = result.split(b"\n") formatted_result = [] for line in result_split: From f617ecbf8858db7946eeb3377a5b7315b605db74 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 10 Jul 2023 15:39:17 +0200 Subject: [PATCH 50/65] add pyproject.toml settings option to readme --- README.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 09374ebd..a49306d4 100644 --- a/README.rst +++ b/README.rst @@ -68,9 +68,10 @@ of ``zest.releaser`` users: - wheel_ for creating a Python wheel that we upload to PyPI next to the standard source distribution. Wheels are the new Python package - format. Create or edit ``setup.cfg`` in your project (or globally - in your ``~/.pypirc``) and create a section ``[zest.releaser]`` with - ``create-wheel = yes`` to create a wheel to upload to PyPI. See + format. Create or edit ``setup.cfg`` or ``pyproject.toml`` in your + project (or globally in your ``~/.pypirc``) and create a section + ``[zest.releaser]`` (``[tool.zest-releaser]`` in ``pyproject.toml``) + with ``create-wheel = true`` to create a wheel to upload to PyPI. See http://pythonwheels.com for deciding whether this is a good idea for your package. Briefly, if it is a pure Python 2 *or* pure Python 3 package: just do it. If it is a pure Python 2 *and* a pure Python 3 From f485e7bc2ca4237bf8c71b6a4c34e337ee761e38 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 10 Jul 2023 15:40:42 +0200 Subject: [PATCH 51/65] update changelog --- CHANGES.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 04ab8856..448e1f16 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,7 +4,9 @@ Changelog for zest.releaser 8.0.1 (unreleased) ------------------ -- Nothing changed yet. +- Change build system to pypa/build instead of setup.py. + +- Add support for pyproject.toml projects. 8.0.0 (2023-05-05) From 5f53205aa40258d5b59a782ab076662f0ef4703d Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 10 Jul 2023 15:49:23 +0200 Subject: [PATCH 52/65] add pyproject.toml assumptions to assumptions.rst --- doc/source/assumptions.rst | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/doc/source/assumptions.rst b/doc/source/assumptions.rst index cb7d7fd8..e93db3ba 100644 --- a/doc/source/assumptions.rst +++ b/doc/source/assumptions.rst @@ -6,19 +6,22 @@ are some assumptions built-in that might or might not fit you. Lots of people are using it in various companies and open source projects, so it'll probably fit :-) -- We absolutely need a version. There's a ``version.txt`` or ``setup.py`` in - your project. The ``version.txt`` has a single line with the version number - (newline optional). The ``setup.py`` should have a single ``version = - '0.3'`` line somewhere. You can also have it in the actual ``setup()`` call, - on its own line still, as `` version = '0.3',``. Indentation and comma are - preserved. If your ``setup.py`` actually reads the version from your - ``setup.cfg`` (as `it does automatically +- We absolutely need a version. There's a ``version.txt``, ``setup.py``, or + ``pyproject.toml`` in your project. The ``version.txt`` has a single line + with the version number (newline optional). The ``setup.py`` should have a + single ``version = '0.3'`` line somewhere. You can also have it in the + actual ``setup()`` call, on its own line still, as `` version = '0.3',``. + Indentation and comma are preserved. If your ``setup.py`` actually reads + the version from your ``setup.cfg`` (as `it does automatically `_ using ``setuptools`` since version 30.3.0), then the version will be modified there too. If you need something special, you can always do a ``version=version`` and put the actual version statement in a zest.releaser- friendly format near the top of the file. Reading (in - Plone products) a version.txt into setup.py works great, too. + Plone products) a version.txt into setup.py works great, too. If you use + ``pyproject.toml``, you should put the version under the ``project`` + metadata section, which also contains the package name, as a single line like + ``version = "0.3"``. - The history/changes file restriction is probably the most severe at the moment. zest.releaser searches for a restructuredtext header with From b437e9de1d517123ef7463028f272fb4266d25da Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 10 Jul 2023 16:58:28 +0200 Subject: [PATCH 53/65] fix entrypoints support in pyproject.toml --- zest/releaser/utils.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/zest/releaser/utils.py b/zest/releaser/utils.py index 90289ad7..26d1e213 100644 --- a/zest/releaser/utils.py +++ b/zest/releaser/utils.py @@ -574,9 +574,16 @@ def run_hooks(zest_releaser_config, which_releaser, when, data): config = zest_releaser_config.config if config is not None and config.get(hook_group): - # Multiple hooks may be specified, each one separated by whitespace + # Multiple hooks may be specified + # in setup.cfg or .pypirc each one is separated by whitespace # (including newlines) - hook_names = config.get(hook_group).split() + if zest_releaser_config.hooks_filename in ["setup.py", "setup.cfg", ".pypirc"]: + hook_names = config.get(hook_group).split() + # in pyproject.toml, a list is passed with the hooks + elif zest_releaser_config.hooks_filename == "pyproject.toml": + hook_names = config.get(hook_group) + else: + hook_names = [] hooks = [] # The following code is adapted from the 'packaging' package being From e63795ba00e60ff25b698dcddcda8d3872995932 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 10 Jul 2023 17:05:28 +0200 Subject: [PATCH 54/65] update developing documentation; setuptools is no longer required in path --- doc/source/developing.rst | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/doc/source/developing.rst b/doc/source/developing.rst index 9b009a81..a7b5851c 100644 --- a/doc/source/developing.rst +++ b/doc/source/developing.rst @@ -75,19 +75,6 @@ In the past, ``git`` commands would give slightly different output. If the output of a command changes again, we may need extra compatibility code in ``test_setup.py`` -Setuptools is needed --------------------- - -You also need ``setuptools`` in your system path. This is because -we basically call 'sys.executable setup.py egg_info' directly (in the tests -and in the actual code), which will give an import error on setuptools -otherwise. There is a branch with a hack that solves this but it sounds too -hacky. - -TODO: calling ``setup.py`` from the command line is not recommended anymore. -We should upgrade the code to no longer do that. - - Building the documentation locally ------------------------------------- From 39e7107019c5f15f89e8c38a5b408b8e7391a4db Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 10 Jul 2023 17:23:05 +0200 Subject: [PATCH 55/65] use true/false instead of yes/no as default documented boolean options --- doc/source/options.rst | 47 ++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/doc/source/options.rst b/doc/source/options.rst index a16cf866..bd48461a 100644 --- a/doc/source/options.rst +++ b/doc/source/options.rst @@ -68,7 +68,7 @@ Lots of things may be in this file, but zest.releaser looks for a [zest.releaser] some-option = some value -For yes/no options, you can use no/false/off/0 or yes/true/on/1 as +For true/false options, you can use no/false/off/0 or yes/true/on/1 as answers; upper, lower or mixed case are all fine. Various options change the default answer of a question. @@ -78,19 +78,19 @@ see if you can tweak the default answers by setting one of these options We have these options: -release = yes / no - Default: yes. When this is false, zest.releaser sets ``no`` as +release = true / false + Default: true. When this is false, zest.releaser sets ``false`` as default answer for the question if you want to create a checkout of the tag. -create-wheel = yes / no - Default: no. Set to yes if you want zest.releaser to create +create-wheel = true / false + Default: false. Set to true if you want zest.releaser to create Python wheels. You need to install the ``wheel`` package for this to work. If the package is a universal wheel, indicated by having ``universal = 1`` in the ``[bdist_wheel]`` section of - ``setup.cfg``, then the default for this value is yes. + ``setup.cfg``, then the default for this value is true. extra-message = [ci skip] Extra message to add to each commit (prerelease, postrelease). @@ -98,22 +98,22 @@ extra-message = [ci skip] prefix-message = [TAG] Prefix message to add at the beginning of each commit (prerelease, postrelease). -no-input = yes / no - Default: no. Set this to yes to accept default answers for all +no-input = true / false + Default: false. Set this to true to accept default answers for all questions. -register = yes / no - Default: no. Set this to yes to register a package before uploading. +register = true / false + Default: false. Set this to true to register a package before uploading. On the official Python Package Index registering a package is no longer needed, and may even fail. -push-changes = yes / no - Default: yes. When this is false, zest.releaser sets ``no`` as +push-changes = true / false + Default: true. When this is false, zest.releaser sets ``false`` as default answer for the question if you want to push the changes to the remote. -less-zeroes = yes / no - Default: no. +less-zeroes = true / false + Default: false. This influences the version suggested by the bumpversion command. When set to true: @@ -153,8 +153,8 @@ tag-message = a string command of the VCS. It must contain ``{version}``. -tag-signing = yes / no - Default: no. +tag-signing = true / false + Default: false. When set to true, tags are signed using the signing feature of the respective vcs. Currently tag-signing is only supported for git. Note: When you enable it, everyone releasing the project is @@ -183,8 +183,8 @@ history_format = a string Default: empty. Set this to ``md`` to handle changelog entries in Markdown. -run-pre-commit = yes / no - Default: no. +run-pre-commit = true / false + Default: false. New in version 7.3.0. When set to true, pre commit hooks are run. This may interfere with releasing when they fail. @@ -199,3 +199,14 @@ Python package. These are the same options as the global ones. If you set an option locally in a project, this will override the global option. + +You can also set these options in a ``pyproject.toml`` file. If you do +so, instead of having a ``[zest.releaser]`` section, you should use a +``[tool.zest-releaser]`` section. For true/false options in a +``pyproject.toml``, you must use lowercase true or false; for string +options like ``extra-message`` or ``prefix-message``, you should put +the value between double quotes, like this:: + + [tool.zest-releaser] + create-wheel = true + extra-message = "[ci skip]" From 5dad81c55be5d5a6bd577e524d351a2979e0f4d3 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 10 Jul 2023 17:23:55 +0200 Subject: [PATCH 56/65] reference tool.zest-releaser instead of tools.zest-releaser in pyproject.toml config wrapper --- zest/releaser/pypi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index d7c65d67..69eb2212 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -305,7 +305,7 @@ def zest_releaser_config(self): if self.config is None: return None try: - result = self.config["tools"]["zest-releaser"] + result = self.config["tool"]["zest-releaser"] except KeyError: return None return result From 23d190fb9b32f0cfd74d258ecb583c0763b6d5e6 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Mon, 10 Jul 2023 17:32:28 +0200 Subject: [PATCH 57/65] name python-file-with-version option for pyproject.toml in docs --- doc/source/versions.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/source/versions.rst b/doc/source/versions.rst index c2f68eb6..9096b96d 100644 --- a/doc/source/versions.rst +++ b/doc/source/versions.rst @@ -49,9 +49,15 @@ A version number can come from various different locations: [zest.releaser] python-file-with-version = mypackage/__init__.py + + Alternatively, in ``pyproject.toml``, you can use the following:: + + [tool.zest-releaser] + python-file-with-version = "mypackage/__init__.py" Because you need to configure this explicitly, this option takes precedence - over any ``setup.py`` or ``version.txt`` file. + over any ``setup.py``, ``setup.cfg`` or ``pyproject.toml`` package version, + or ``version.txt`` file. Where is the version number being set? From 1c7bb008e11eae7d2d0e624a8fff34bd761dee68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eli=20Sall=C3=A9?= Date: Tue, 11 Jul 2023 09:51:06 +0200 Subject: [PATCH 58/65] Clarify changelog Co-authored-by: Reinout van Rees --- CHANGES.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 448e1f16..bce7bc9f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,9 +4,9 @@ Changelog for zest.releaser 8.0.1 (unreleased) ------------------ -- Change build system to pypa/build instead of setup.py. +- Changed build system to pypa/build instead of explicitly using setuptools. -- Add support for pyproject.toml projects. +- Zest.releaser's settings can now also be placed in ``pyproject.toml``. 8.0.0 (2023-05-05) From 072f46a6aed9717109b03cbd853da9a1ba16f878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eli=20Sall=C3=A9?= Date: Tue, 11 Jul 2023 10:04:10 +0200 Subject: [PATCH 59/65] Change docstring formatting Co-authored-by: Reinout van Rees --- zest/releaser/pypi.py | 1 + zest/releaser/release.py | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index 69eb2212..01211eb3 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -165,6 +165,7 @@ def zest_releaser_config(self): class PypiConfig(BaseConfig): """Wrapper around the pypi config file. + Contains functions which return information about the pypi configuration. """ diff --git a/zest/releaser/release.py b/zest/releaser/release.py index 58370cde..374da2c4 100644 --- a/zest/releaser/release.py +++ b/zest/releaser/release.py @@ -60,8 +60,11 @@ def package_in_pypi(package): return False def _project_builder_runner(cmd, cwd=None, extra_environ=None): - """Call the subprocess, and format warnings and errors in red so that - they will work correctly with utils.show_interesting_lines() + """Run the build command and format warnings and errors. + + It runs the build command in a subprocess. Warnings and errors are formatted + in red so that they will work correctly with utils.show_interesting_lines(). We + mimic the setuptools/wheels output that way. """ env = os.environ.copy() if extra_environ: From 00589a7e8e339986c2ff70dc5c5279f6972008cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eli=20Sall=C3=A9?= Date: Tue, 11 Jul 2023 10:08:42 +0200 Subject: [PATCH 60/65] Give a nicer error message Co-authored-by: Reinout van Rees --- zest/releaser/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zest/releaser/utils.py b/zest/releaser/utils.py index 26d1e213..3781fbeb 100644 --- a/zest/releaser/utils.py +++ b/zest/releaser/utils.py @@ -994,4 +994,4 @@ def string_to_bool(value): elif value in ["0", "no", "false", "off"]: return False else: - raise ValueError(f"Cannot convert string {value} to a bool") + raise ValueError(f"Cannot convert string '{value}' to a bool") From 85f92e94dcc0ceb023ee59e24aad81836ace0d5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eli=20Sall=C3=A9?= Date: Tue, 11 Jul 2023 10:18:06 +0200 Subject: [PATCH 61/65] Fix incorrect documentation in docstring Co-authored-by: Reinout van Rees --- zest/releaser/pypi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index 01211eb3..15921bd5 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -285,7 +285,7 @@ class PyprojectTomlConfig(BaseConfig): This is for optional zest.releaser-specific settings:: - [zest-releaser] + [tool.zest-releaser] python-file-with-version = "reinout/maurits.py" From e62367534f7b5099a7d15b83a7a0425f3fac3d8e Mon Sep 17 00:00:00 2001 From: elisallenens Date: Tue, 11 Jul 2023 10:20:36 +0200 Subject: [PATCH 62/65] move integer keys to be converted to int into a separate list --- zest/releaser/pypi.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index 15921bd5..ed371230 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -155,10 +155,13 @@ def zest_releaser_config(self): "tag-signing", "run-pre-commit", ] + integer_keys = [ + "version-levels", + ] for key, value in result.items(): if key in boolean_keys: result[key] = string_to_bool(value) - if key in ["version-levels"]: + if key in integer_keys: result[key] = int(value) return result From 554daf35e65a616b2b3cec7ee41b48e412a08eec Mon Sep 17 00:00:00 2001 From: elisallenens Date: Tue, 11 Jul 2023 10:28:47 +0200 Subject: [PATCH 63/65] add debug log messages for zest-releaser section not found in config --- zest/releaser/pypi.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index ed371230..76e35629 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -143,6 +143,7 @@ def zest_releaser_config(self): try: result = dict(self.config["zest.releaser"].items()) except KeyError: + logger.debug(f"No [zest.releaser] section found in the {self.config_filename}") return None boolean_keys = [ @@ -200,6 +201,7 @@ def zest_releaser_config(self): try: result = dict(self.config["zest.releaser"].items()) except KeyError: + logger.debug(f"No [zest.releaser] section found in the {self.config_filename}") return None boolean_keys = [ @@ -311,6 +313,7 @@ def zest_releaser_config(self): try: result = self.config["tool"]["zest-releaser"] except KeyError: + logger.debug(f"No [tool.zest-releaser] section found in the {self.config_filename}") return None return result From 9bebc8b79b161ff0e72f2cea0ddde6c699d78685 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Tue, 11 Jul 2023 10:42:10 +0200 Subject: [PATCH 64/65] move zest-releaser config extraction for configparser to separate utils function --- zest/releaser/pypi.py | 57 +++--------------------------------------- zest/releaser/utils.py | 31 +++++++++++++++++++++++ 2 files changed, 34 insertions(+), 54 deletions(-) diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py index 76e35629..77e31107 100644 --- a/zest/releaser/pypi.py +++ b/zest/releaser/pypi.py @@ -5,7 +5,7 @@ import pkg_resources import sys -from .utils import string_to_bool +from .utils import string_to_bool, extract_zestreleaser_configparser try: # Python 3.11+ @@ -137,34 +137,7 @@ def fix_config(self): print("".join(config_file.readlines())) def zest_releaser_config(self): - if not self.config: - return None - - try: - result = dict(self.config["zest.releaser"].items()) - except KeyError: - logger.debug(f"No [zest.releaser] section found in the {self.config_filename}") - return None - - boolean_keys = [ - "release", - "create-wheel", - "no-input", - "register", - "push-changes", - "less-zeroes", - "tag-signing", - "run-pre-commit", - ] - integer_keys = [ - "version-levels", - ] - for key, value in result.items(): - if key in boolean_keys: - result[key] = string_to_bool(value) - if key in integer_keys: - result[key] = int(value) - return result + return extract_zestreleaser_configparser(self.config, self.config_filename) class PypiConfig(BaseConfig): @@ -195,31 +168,7 @@ def reload(self): self._read_configfile() def zest_releaser_config(self): - if not self.config: - return None - - try: - result = dict(self.config["zest.releaser"].items()) - except KeyError: - logger.debug(f"No [zest.releaser] section found in the {self.config_filename}") - return None - - boolean_keys = [ - "release", - "create-wheel", - "no-input", - "register", - "push-changes", - "less-zeroes", - "tag-signing", - "run-pre-commit", - ] - for key, value in result.items(): - if key in boolean_keys: - result[key] = string_to_bool(value) - if key in ["version-levels"]: - result[key] = int(value) - return result + return extract_zestreleaser_configparser(self.config, self.config_filename) def _read_configfile(self): """Read the PyPI config file and store it (when valid).""" diff --git a/zest/releaser/utils.py b/zest/releaser/utils.py index 3781fbeb..2adaf8c3 100644 --- a/zest/releaser/utils.py +++ b/zest/releaser/utils.py @@ -995,3 +995,34 @@ def string_to_bool(value): return False else: raise ValueError(f"Cannot convert string '{value}' to a bool") + + +def extract_zestreleaser_configparser(config, config_filename): + if not config: + return None + + try: + result = dict(config["zest.releaser"].items()) + except KeyError: + logger.debug(f"No [zest.releaser] section found in the {config_filename}") + return None + + boolean_keys = [ + "release", + "create-wheel", + "no-input", + "register", + "push-changes", + "less-zeroes", + "tag-signing", + "run-pre-commit", + ] + integer_keys = [ + "version-levels", + ] + for key, value in result.items(): + if key in boolean_keys: + result[key] = string_to_bool(value) + if key in integer_keys: + result[key] = int(value) + return result From 7f20a28b258398eb20d8b876f985290ed7175ba8 Mon Sep 17 00:00:00 2001 From: elisallenens Date: Tue, 11 Jul 2023 12:58:31 +0200 Subject: [PATCH 65/65] add pyproject.toml documentation to entrypoints section --- doc/source/entrypoints.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/doc/source/entrypoints.rst b/doc/source/entrypoints.rst index 336a754d..5593cde2 100644 --- a/doc/source/entrypoints.rst +++ b/doc/source/entrypoints.rst @@ -66,6 +66,11 @@ An entry point is configured like this in your setup.py:: 'dosomething = my.package.some:some_entrypoint', ]}, +or like this in your pyproject.toml:: + + [project.entry-points."zest.releaser.prereleaser.middle"] + dosomething = "my.package.some:some_entrypoint" + Entry-points can also be specified in the setup.cfg file like this (The options will be split by white-space and processed in the given order.):: @@ -84,6 +89,15 @@ and ``middle`` with the respective hook name (`before`, `middle`, `after`, `after_checkout`, etc.) as needed. +Similarly, they can also be placed in the ``tool.zest-releaser`` config section of +pyproject.toml like this:: + + [tool.zest-releaser] + prereleaser.middle = [ + "my.package.some.some_entrypoint", + "our.package.other_module.other_function" + ] + See the ``setup.py`` of ``zest.releaser`` itself for some real world examples. You'll have to make sure that the zest.releaser scripts know about your entry