From 821c11d0a09d953ce8aedc703e779a608d824a8f Mon Sep 17 00:00:00 2001 From: Julien Palard Date: Thu, 3 Dec 2020 10:25:32 +0100 Subject: [PATCH] Fix: KeyError 'filename'. By dropping the `f()` function (and dropping Python 3.5 compatibility), as the real `f-string` do not have the issue with non-locals. closes #299, #300 --- blurb/blurb.py | 109 ++++++++++++++++--------------------------- blurb/pyproject.toml | 9 ++-- 2 files changed, 46 insertions(+), 72 deletions(-) diff --git a/blurb/blurb.py b/blurb/blurb.py index 2b9475b..cdc787b 100755 --- a/blurb/blurb.py +++ b/blurb/blurb.py @@ -46,7 +46,6 @@ import atexit import base64 import builtins -from collections import OrderedDict import glob import hashlib import inspect @@ -61,9 +60,7 @@ import tempfile import textwrap import time -import types import unittest -import uuid # @@ -112,32 +109,6 @@ sections.append(section.strip()) -def f(s): - """ - Basic support for 3.6's f-strings, in 3.5! - - Formats "s" using appropriate globals and locals - dictionaries. This f-string: - f"hello a is {a}" - simply becomes - f("hello a is {a}") - In other words, just throw parentheses around the - string, and you're done! - - Implemented internally using str.format_map(). - This means it doesn't support expressions: - f("two minus three is {2-3}") - And it doesn't support function calls: - f("how many elements? {len(my_list)}") - But most other f-string features work. - """ - frame = sys._getframe(1) - d = dict(builtins.__dict__) - d.update(frame.f_globals) - d.update(frame.f_locals) - return s.format_map(d) - - def sanitize_section(section): """ Cleans up a section string, making it viable as a directory name. @@ -250,10 +221,10 @@ def sortable_datetime(): def prompt(prompt): - return input(f("[{prompt}> ")) + return input(f"[{prompt}> ") def require_ok(prompt): - prompt = f("[{prompt}> ") + prompt = f"[{prompt}> " while True: s = input(prompt).strip() if s == 'ok': @@ -483,7 +454,7 @@ def parse(self, text, *, metadata=None, filename="input"): line_number = None def throw(s): - raise BlurbError(f("Error in {filename}:{line_number}:\n{s}")) + raise BlurbError(f"Error in {filename}:{line_number}:\n{s}") def finish_entry(): nonlocal body @@ -563,7 +534,7 @@ def __str__(self): add_separator = True if metadata: for name, value in sorted(metadata.items()): - add(f(".. {name}: {value}\n")) + add(f".. {name}: {value}\n") add("\n") add(textwrap_body(body)) return "".join(output) @@ -585,10 +556,10 @@ def _parse_next_filename(filename): components = filename.split(os.sep) section, filename = components[-2:] section = unsanitize_section(section) - assert section in sections, f("Unknown section {section}") + assert section in sections, f"Unknown section {section}" fields = [x.strip() for x in filename.split(".")] - assert len(fields) >= 4, f("Can't parse 'next' filename! filename {filename!r} fields {fields}") + assert len(fields) >= 4, f"Can't parse 'next' filename! filename {filename!r} fields {fields}" assert fields[-1] == "rst" metadata = {"date": fields[0], "nonce": fields[-2], "section": section} @@ -777,7 +748,7 @@ def subcommand(fn): def get_subcommand(subcommand): fn = subcommands.get(subcommand) if not fn: - error(f("Unknown subcommand: {subcommand}\nRun 'blurb help' for help.")) + error(f"Unknown subcommand: {subcommand}\nRun 'blurb help' for help.") return fn @@ -839,19 +810,19 @@ def help(subcommand=None): for name, p in inspect.signature(fn).parameters.items(): if p.kind == inspect.Parameter.KEYWORD_ONLY: short_option = name[0] - options.append(f(" [-{short_option}|--{name}]")) + options.append(f" [-{short_option}|--{name}]") elif p.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD: positionals.append(" ") has_default = (p.default != inspect._empty) if has_default: positionals.append("[") nesting += 1 - positionals.append(f("<{name}>")) + positionals.append(f"<{name}>") positionals.append("]" * nesting) parameters = "".join(options + positionals) - print(f("blurb {subcommand}{parameters}")) + print(f"blurb {subcommand}{parameters}") print() print(doc) sys.exit(0) @@ -942,7 +913,7 @@ def init_tmp_with_template(): else: args = list(shlex.split(editor)) if not shutil.which(args[0]): - sys.exit(f("Invalid GIT_EDITOR / EDITOR value: {editor}")) + sys.exit(f"Invalid GIT_EDITOR / EDITOR value: {editor}") args.append(tmp_path) while True: @@ -962,7 +933,7 @@ def init_tmp_with_template(): if failure: print() - print(f("Error: {failure}")) + print(f"Error: {failure}") print() try: prompt("Hit return to retry (or Ctrl-C to abort)") @@ -996,20 +967,20 @@ def release(version): if existing_filenames: error("Sorry, can't handle appending 'next' files to an existing version (yet).") - output = f("Misc/NEWS.d/{version}.rst") + output = f"Misc/NEWS.d/{version}.rst" filenames = glob_blurbs("next") blurbs = Blurbs() date = current_date() if not filenames: - print(f("No blurbs found. Setting {version} as having no changes.")) - body = f("There were no new changes in version {version}.\n") + print(f"No blurbs found. Setting {version} as having no changes.") + body = f"There were no new changes in version {version}.\n" metadata = {"no changes": "True", "bpo": "0", "section": "Library", "date": date, "nonce": nonceify(body)} blurbs.append((metadata, body)) else: no_changes = None count = len(filenames) - print(f('Merging {count} blurbs to "{output}".')) + print(f'Merging {count} blurbs to "{output}".') for filename in filenames: if not filename.endswith(".rst"): @@ -1026,14 +997,14 @@ def release(version): flush_git_add_files() how_many = len(filenames) - print(f("Removing {how_many} 'next' files from git.")) + print(f"Removing {how_many} 'next' files from git.") git_rm_files.extend(filenames) flush_git_rm_files() # sanity check: ensuring that saving/reloading the merged blurb file works. blurbs2 = Blurbs() blurbs2.load(output) - assert blurbs2 == blurbs, f("Reloading {output} isn't reproducible?!") + assert blurbs2 == blurbs, f"Reloading {output} isn't reproducible?!" print() print("Ready for commit.") @@ -1104,7 +1075,7 @@ def print(*a, sep=" "): metadata, body = blurbs[0] release_date = metadata["release date"] - print(f("*Release date: {release_date}*")) + print(f"*Release date: {release_date}*") print() if "no changes" in metadata: @@ -1172,11 +1143,11 @@ def populate(): for section in sections: dir_name = sanitize_section(section) - dir_path = f("NEWS.d/next/{dir_name}") + dir_path = f"NEWS.d/next/{dir_name}" safe_mkdir(dir_path) - readme_path = f("NEWS.d/next/{dir_name}/README.rst") + readme_path = f"NEWS.d/next/{dir_name}/README.rst" with open(readme_path, "wt", encoding="utf-8") as readme: - readme.write(f("Put news entry ``blurb`` files for the *{section}* section in this directory.\n")) + readme.write(f"Put news entry ``blurb`` files for the *{section}* section in this directory.\n") git_add_files.append(dir_path) git_add_files.append(readme_path) flush_git_add_files() @@ -1197,7 +1168,7 @@ def export(): # """ # Test function for blurb command-line processing. # """ -# print(f("arg: boolean {boolean} option {option}")) +# print(f"arg: boolean {boolean} option {option}") @subcommand @@ -1282,7 +1253,7 @@ def flush_blurb(): fields.append(field) see_also = ", ".join(fields) # print("see_also: ", repr(see_also)) - accumulator.append(f("(See also: {see_also})")) + accumulator.append(f"(See also: {see_also})") see_also = None if not accumulator: return @@ -1328,8 +1299,8 @@ def flush_version(): if version is None: assert not blurbs, "version should only be None initially, we shouldn't have blurbs yet" return - assert blurbs, f("No blurbs defined when flushing version {version}!") - output = f("NEWS.d/{version}.rst") + assert blurbs, f"No blurbs defined when flushing version {version}!" + output = f"NEWS.d/{version}.rst" if released: # saving merged blurb file for version, e.g. Misc/NEWS.d/3.7.0a1.rst @@ -1447,11 +1418,11 @@ def flush_version(): elif line.startswith("- Issue #9516: Issue #9516: avoid errors in sysconfig when MACOSX_DEPLOYMENT_TARGET"): line = "- Issue #9516 and Issue #9516: avoid errors in sysconfig when MACOSX_DEPLOYMENT_TARGET" elif line.title().startswith(("- Request #", "- Bug #", "- Patch #", "- Patches #")): - # print(f("FIXING LINE {line_number}: {line!r}")) + # print(f"FIXING LINE {line_number}: {line!r}") line = "- Issue #" + line.partition('#')[2] - # print(f("FIXED LINE {line_number}: {line!r}")) + # print(f"FIXED LINE {line_number}: {line!r}") # else: - # print(f("NOT FIXING LINE {line_number}: {line!r}")) + # print(f"NOT FIXING LINE {line_number}: {line!r}") # 4. determine the actual content of the line @@ -1516,7 +1487,7 @@ def flush_version(): line = line[4:] parse_bpo = True else: - # print(f("[[{line_number:8} no bpo]] {line}")) + # print(f"[[{line_number:8} no bpo]] {line}") parse_bpo = False if parse_bpo: # GAAAH @@ -1556,9 +1527,9 @@ def flush_version(): try: int(bpo) # this will throw if it's not a legal int except ValueError: - sys.exit(f("Couldn't convert bpo number to int on line {line_number}! {bpo!r}")) + sys.exit(f"Couldn't convert bpo number to int on line {line_number}! {bpo!r}") if see_also == "partially": - sys.exit(f("What the hell on line {line_number}! {bpo!r}")) + sys.exit(f"What the hell on line {line_number}! {bpo!r}") # 4.6.1 continuation of blurb elif line.startswith(" "): @@ -1567,7 +1538,7 @@ def flush_version(): elif line.startswith(" * "): line = line[3:] elif line: - sys.exit(f("Didn't recognize line {line_number}! {line!r}")) + sys.exit(f"Didn't recognize line {line_number}! {line!r}") # only add blank lines if we have an initial line in the accumulator if line or accumulator: accumulator.append(line) @@ -1579,7 +1550,7 @@ def flush_version(): git_rm_files.append("NEWS") flush_git_rm_files() - print(f("Wrote {blurb_count} news items across {version_count} versions.")) + print(f"Wrote {blurb_count} news items across {version_count} versions.") print() print("Ready for commit.") @@ -1628,10 +1599,10 @@ def main(): def handle_option(s, dict): name = dict.get(s, None) if not name: - sys.exit(f('blurb: Unknown option for {subcommand}: "{s}"')) + sys.exit(f'blurb: Unknown option for {subcommand}: "{s}"') kwargs[name] = not kwargs[name] - # print(f("short_options {short_options} long_options {long_options}")) + # print(f"short_options {short_options} long_options {long_options}") for a in args: if done_with_options: filtered_args.append(a) @@ -1665,7 +1636,7 @@ def handle_option(s, dict): # whoops, must be a real type error, reraise raise e - how_many = f("{specified} argument") + how_many = f"{specified} argument" if specified != 1: how_many += "s" @@ -1676,12 +1647,12 @@ def handle_option(s, dict): middle = "requires" else: plural = "" if required == 1 else "s" - middle = f("requires at least {required} argument{plural} and at most") - middle += f(" {total} argument") + middle = f"requires at least {required} argument{plural} and at most" + middle += f" {total} argument" if total != 1: middle += "s" - print(f('Error: Wrong number of arguments!\n\nblurb {subcommand} {middle},\nand you specified {how_many}.')) + print(f'Error: Wrong number of arguments!\n\nblurb {subcommand} {middle},\nand you specified {how_many}.') print() print("usage: ", end="") help(subcommand) diff --git a/blurb/pyproject.toml b/blurb/pyproject.toml index 2603863..38d9ac0 100644 --- a/blurb/pyproject.toml +++ b/blurb/pyproject.toml @@ -9,10 +9,13 @@ author-email = "larry@hastings.org" maintainer = "Python Core Developers" maintainer-email = "core-workflow@mail.python.org" home-page = "https://github.com/python/core-workflow/tree/master/blurb" -requires-python = ">=3.5" +requires-python = ">=3.6" description-file = "README.rst" -classifiers = [ "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Programming Language :: Python :: 3 :: Only",] - +classifiers = [ + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Programming Language :: Python :: 3 :: Only" +] [tool.flit.scripts] blurb = "blurb:main"