diff --git a/README.rst b/README.rst index a3469f5..a59498a 100644 --- a/README.rst +++ b/README.rst @@ -33,7 +33,7 @@ Overview Kconfiglib is a `Kconfig `__ -implementation in Python 2/3. It started out as a helper library, but now has a +implementation in Python 3. It started out as a helper library, but now has a enough functionality to also work well as a standalone Kconfig implementation (including `terminal and GUI menuconfig interfaces `_ and `Kconfig extensions`_). @@ -121,9 +121,7 @@ available in the C tools. the configuration and (optionally) information that can be used to rebuild only files that reference Kconfig symbols that have changed value. -Starting with Kconfiglib version 12.2.0, all utilities are compatible with both -Python 2 and Python 3. Previously, ``menuconfig.py`` only ran under Python 3 -(i.e., it's now more backwards compatible than before). +All utilities run under Python 3. **Note:** If you install Kconfiglib with ``pip``'s ``--user`` flag, make sure that your ``PATH`` includes the directory where the executables end up. You can @@ -164,18 +162,16 @@ Installation for the Linux kernel See the module docstring at the top of `kconfiglib.py `_. -Python version compatibility (2.7/3.2+) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Python version compatibility +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Kconfiglib and all utilities run under both Python 2.7 and Python 3.2 and -later. The code mostly uses basic Python features and has no third-party -dependencies, so keeping it backwards-compatible is pretty low effort. +Kconfiglib and all utilities run under Python 3.9 and later. The code mostly +uses basic Python features and has no third-party dependencies. The 3.2 requirement comes from ``argparse``. ``format()`` with unnumbered ``{}`` is used as well. -A recent Python 3 version is recommended if you have a choice, as it'll give -you better Unicode handling. +A recent Python 3 version is recommended for better Unicode handling. Getting started --------------- @@ -502,9 +498,9 @@ Other features - **Windows support** - Nothing Linux-specific is used. Universal newlines mode is used for both - Python 2 and Python 3. - + Nothing Linux-specific is used. Universal newlines mode is used for + interoperability between Linux and Windows. + The `Zephyr `_ project uses Kconfiglib to generate ``.config`` files and C headers on Linux as well as Windows. @@ -562,16 +558,15 @@ Three configuration interfaces are currently available: the terminal menuconfig. Only this mode distinguishes between symbols defined with ``config`` and symbols defined with ``menuconfig``. - ``guiconfig.py`` has been tested on X11, Windows, and macOS, and is - compatible with both Python 2 and Python 3. + ``guiconfig.py`` has been tested on X11, Windows, and macOS. Despite being part of the Python standard library, ``tkinter`` often isn't included by default in Python installations on Linux. These commands will install it on a few different distributions: - - Ubuntu: ``sudo apt install python-tk``/``sudo apt install python3-tk`` + - Ubuntu: ``sudo apt install python3-tk`` - - Fedora: ``dnf install python2-tkinter``/``dnf install python3-tkinter`` + - Fedora: ``dnf install python3-tkinter`` - Arch: ``sudo pacman -S tk`` @@ -591,10 +586,6 @@ Three configuration interfaces are currently available: I did my best with the images, but some are definitely only art adjacent. Touch-ups are welcome. :) -- `pymenuconfig `_, built by `RomaVis - `_, is an older portable Python 2/3 TkInter - menuconfig implementation. - Screenshot below: .. image:: https://raw.githubusercontent.com/RomaVis/pymenuconfig/master/screenshot.PNG @@ -784,7 +775,7 @@ configurations generated by the C tools, for a number of cases. See for the available options. The `tests/reltest `_ script runs the test suite -and all the example scripts for both Python 2 and Python 3, verifying that everything works. +and all the example scripts, verifying that everything works. Rarely, the output from the C tools is changed slightly (most recently due to a `change `_ I added). diff --git a/examples/menuconfig_example.py b/examples/menuconfig_example.py index 606f756..9221e4c 100755 --- a/examples/menuconfig_example.py +++ b/examples/menuconfig_example.py @@ -130,11 +130,6 @@ TRI_TO_STR -# Python 2/3 compatibility hack -if sys.version_info[0] < 3: - input = raw_input - - def indent_print(s, indent): print(indent*" " + s) diff --git a/genconfig.py b/genconfig.py index 62f065b..aa0b472 100755 --- a/genconfig.py +++ b/genconfig.py @@ -131,24 +131,15 @@ def main(): kconf.sync_deps(args.sync_deps) if args.file_list is not None: - with _open_write(args.file_list) as f: + with open(args.file_list, "w", encoding="utf-8") as f: for path in kconf.kconfig_filenames: f.write(path + "\n") if args.env_list is not None: - with _open_write(args.env_list) as f: + with open(args.env_list, "w", encoding="utf-8") as f: for env_var in kconf.env_vars: f.write("{}={}\n".format(env_var, os.environ[env_var])) -def _open_write(path): - # Python 2/3 compatibility. io.open() is available on both, but makes - # write() expect 'unicode' strings on Python 2. - - if sys.version_info[0] < 3: - return open(path, "w") - return open(path, "w", encoding="utf-8") - - if __name__ == "__main__": main() diff --git a/guiconfig.py b/guiconfig.py index 0594ba6..f52ae6b 100755 --- a/guiconfig.py +++ b/guiconfig.py @@ -9,7 +9,7 @@ A Tkinter-based menuconfig implementation, based around a treeview control and a help display. The interface should feel familiar to people used to qconf -('make xconfig'). Compatible with both Python 2 and Python 3. +('make xconfig'). The display can be toggled between showing the full tree and showing just a single menu (like menuconfig.py). Only single-menu mode distinguishes between @@ -55,23 +55,11 @@ import errno import os import re -import sys - -_PY2 = sys.version_info[0] < 3 - -if _PY2: - # Python 2 - from Tkinter import * - import ttk - import tkFont as font - import tkFileDialog as filedialog - import tkMessageBox as messagebox -else: - # Python 3 - from tkinter import * - import tkinter.ttk as ttk - import tkinter.font as font - from tkinter import filedialog, messagebox + +from tkinter import * +import tkinter.ttk as ttk +import tkinter.font as font +from tkinter import filedialog, messagebox from kconfiglib import Symbol, Choice, MENU, COMMENT, MenuNode, \ BOOL, TRISTATE, STRING, INT, HEX, \ @@ -395,8 +383,7 @@ def _init_misc_ui(): # Does misc. UI initialization, like setting the title, icon, and theme _root.title(_kconf.mainmenu_text) - # iconphoto() isn't available in Python 2's Tkinter - _root.tk.call("wm", "iconphoto", _root._w, "-default", _icon_img) + _root.iconphoto(True, _icon_img) # Reducing the width of the window to 1 pixel makes it move around, at # least on GNOME. Prevent weird stuff like that. _root.minsize(128, 128) @@ -514,9 +501,7 @@ def tree_select(_): desc["state"] = "disabled" return - # Text.replace() is not available in Python 2's Tkinter - desc.delete("1.0", "end") - desc.insert("end", _info_str(_id_to_node[sel[0]])) + desc.replace("1.0", "end", _info_str(_id_to_node[sel[0]])) desc["state"] = "disabled" @@ -1118,11 +1103,6 @@ def _change_node(node, parent): if sc.type in (INT, HEX, STRING): s = _set_val_dialog(node, parent) - # Tkinter can return 'unicode' strings on Python 2, which Kconfiglib - # can't deal with. UTF-8-encode the string to work around it. - if _PY2 and isinstance(s, unicode): - s = s.encode("utf-8", "ignore") - if s is not None: _set_val(sc, s) @@ -1174,9 +1154,10 @@ def _set_val_dialog(node, parent): # Pops up a dialog for setting the value of the string/int/hex # symbol at node 'node'. 'parent' is the parent window. + _entry_res = None + def ok(_=None): - # No 'nonlocal' in Python 2 - global _entry_res + nonlocal _entry_res s = entry.get() if sym.type == HEX and not s.startswith(("0x", "0X")): @@ -1187,7 +1168,7 @@ def ok(_=None): dialog.destroy() def cancel(_=None): - global _entry_res + nonlocal _entry_res _entry_res = None dialog.destroy() diff --git a/kconfiglib.py b/kconfiglib.py index 4506296..bce0e17 100644 --- a/kconfiglib.py +++ b/kconfiglib.py @@ -5,7 +5,7 @@ Overview ======== -Kconfiglib is a Python 2/3 library for scripting and extracting information +Kconfiglib is a Python 3 library for scripting and extracting information from Kconfig (https://www.kernel.org/doc/Documentation/kbuild/kconfig-language.txt) configuration systems. @@ -52,17 +52,14 @@ make kmenuconfig ---------------- -This target runs the curses menuconfig interface with Python 3. As of -Kconfiglib 12.2.0, both Python 2 and Python 3 are supported (previously, only -Python 3 was supported, so this was a backport). +This target runs the curses menuconfig interface with Python 3. make guiconfig -------------- -This target runs the Tkinter menuconfig interface. Both Python 2 and Python 3 -are supported. To change the Python interpreter used, pass -PYTHONCMD= to 'make'. The default is 'python'. +This target runs the Tkinter menuconfig interface. To change the Python +interpreter used, pass PYTHONCMD= to 'make'. The default is 'python'. make [ARCH=] iscriptconfig @@ -878,11 +875,8 @@ def __init__(self, filename="Kconfig", warn=True, warn_to_stderr=True, Raises KconfigError on syntax/semantic errors, and OSError or (possibly a subclass of) IOError on IO errors ('errno', 'strerror', and - 'filename' are available). Note that IOError is an alias for OSError on - Python 3, so it's enough to catch OSError there. If you need Python 2/3 - compatibility, it's easiest to catch EnvironmentError, which is a - common base class of OSError/IOError on Python 2 and an alias for - OSError on Python 3. + 'filename' are available). Note that IOError is an alias for OSError in + Python 3, so it's enough to catch OSError. filename (default: "Kconfig"): The Kconfig file to load. For the Linux kernel, you'll want "Kconfig" @@ -925,11 +919,6 @@ def __init__(self, filename="Kconfig", warn=True, warn_to_stderr=True, The "utf-8" default avoids exceptions on systems that are configured to use the C locale, which implies an ASCII encoding. - This parameter has no effect on Python 2, due to implementation - issues (regular strings turning into Unicode strings, which are - distinct in Python 2). Python 2 doesn't decode regular strings - anyway. - Related PEP: https://www.python.org/dev/peps/pep-0538/ suppress_traceback (default: False): @@ -2118,18 +2107,12 @@ def _open_config(self, filename): try: return self._open(join(self.srctree, filename), "r") except EnvironmentError as e2: - # This is needed for Python 3, because e2 is deleted after - # the try block: - # - # https://docs.python.org/3/reference/compound_stmts.html#the-try-statement - e = e2 - - raise _KconfigIOError( - e, "Could not open '{}' ({}: {}). Check that the $srctree " - "environment variable ({}) is set correctly." - .format(filename, errno.errorcode[e.errno], e.strerror, - "set to '{}'".format(self.srctree) if self.srctree - else "unset or blank")) + raise _KconfigIOError( + e2, "Could not open '{}' ({}: {}). Check that the $srctree " + "environment variable ({}) is set correctly." + .format(filename, errno.errorcode[e.errno], e.strerror, + "set to '{}'".format(self.srctree) if self.srctree + else "unset or blank")) def _enter_file(self, filename): # Jumps to the beginning of a sourced Kconfig file, saving the previous @@ -3893,41 +3876,8 @@ def _trailing_tokens_error(self): self._parse_error("extra tokens at end of line") def _open(self, filename, mode): - # open() wrapper: - # - # - Enable universal newlines mode on Python 2 to ease - # interoperability between Linux and Windows. It's already the - # default on Python 3. - # - # The "U" flag would currently work for both Python 2 and 3, but it's - # deprecated on Python 3, so play it future-safe. - # - # io.open() defaults to universal newlines on Python 2 (and is an - # alias for open() on Python 3), but it returns 'unicode' strings and - # slows things down: - # - # Parsing x86 Kconfigs on Python 2 - # - # with open(..., "rU"): - # - # real 0m0.930s - # user 0m0.905s - # sys 0m0.025s - # - # with io.open(): - # - # real 0m1.069s - # user 0m1.040s - # sys 0m0.029s - # - # There's no appreciable performance difference between "r" and - # "rU" for parsing performance on Python 2. - # - # - For Python 3, force the encoding. Forcing the encoding on Python 2 - # turns strings into Unicode strings, which gets messy. Python 2 - # doesn't decode regular strings anyway. - return open(filename, "rU" if mode == "r" else mode) if _IS_PY2 else \ - open(filename, mode, encoding=self._encoding) + # open() wrapper that forces the encoding + return open(filename, mode, encoding=self._encoding) def _check_undef_syms(self): # Prints warnings for all references to undefined symbols within the @@ -3956,7 +3906,7 @@ def is_num(s): return True - for sym in (self.syms.viewvalues if _IS_PY2 else self.syms.values)(): + for sym in self.syms.values(): # - sym.nodes empty means the symbol is undefined (has no # definition locations) # @@ -6511,16 +6461,9 @@ def copy(src, dst): if islink(path): # Preserve symlinks copy_fn = copy - elif hasattr(os, "replace"): - # Python 3 (3.3+) only. Best choice when available, because it - # removes .old on both *nix and Windows. - copy_fn = os.replace - elif os.name == "posix": - # Removes .old on POSIX systems - copy_fn = os.rename else: - # Fall back on copying - copy_fn = copy + # atomic replace of .old + copy_fn = os.replace try: copy_fn(path, path + ".old") @@ -6892,30 +6835,23 @@ def _error_if_fn(kconf, _, cond, msg): def _shell_fn(kconf, _, command): import subprocess # Only import as needed, to save some startup time - stdout, stderr = subprocess.Popen( - command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE - ).communicate() - - if not _IS_PY2: - try: - stdout = stdout.decode(kconf._encoding) - stderr = stderr.decode(kconf._encoding) - except UnicodeDecodeError as e: - _decoding_error(e, kconf.filename, kconf.linenr) + result = subprocess.run( + command, + shell=True, + capture_output=True, + text=True, + encoding=kconf._encoding + ) - if stderr: + if result.stderr: kconf._warn("'{}' wrote to stderr: {}".format( - command, "\n".join(stderr.splitlines())), + command, "\n".join(result.stderr.splitlines())), kconf.loc) # Universal newlines with splitlines() (to prevent e.g. stray \r's in # command output on Windows), trailing newline removal, and # newline-to-space conversion. - # - # On Python 3 versions before 3.6, it's not possible to specify the - # encoding when passing universal_newlines=True to Popen() (the 'encoding' - # parameter was added in 3.6), so we do this manual version instead. - return "\n".join(stdout.splitlines()).rstrip("\n").replace("\n", " ") + return "\n".join(result.stdout.splitlines()).rstrip("\n").replace("\n", " ") # # Global constants @@ -6938,9 +6874,6 @@ def _shell_fn(kconf, _, command): # Symbol will do. We test this with 'is'. _NO_CACHED_SELECTION = 0 -# Are we running on Python 2? -_IS_PY2 = sys.version_info[0] < 3 - try: _UNAME_RELEASE = os.uname()[2] except AttributeError: @@ -7233,11 +7166,11 @@ def _shell_fn(kconf, _, command): def _re_match(regex): - return re.compile(regex, 0 if _IS_PY2 else re.ASCII).match + return re.compile(regex, re.ASCII).match def _re_search(regex): - return re.compile(regex, 0 if _IS_PY2 else re.ASCII).search + return re.compile(regex, re.ASCII).search # Various regular expressions used during parsing diff --git a/listnewconfig.py b/listnewconfig.py index 8276de1..18209ce 100755 --- a/listnewconfig.py +++ b/listnewconfig.py @@ -15,6 +15,7 @@ import argparse import sys +import textwrap from kconfiglib import Kconfig, BOOL, TRISTATE, INT, HEX, STRING, TRI_TO_STR @@ -65,10 +66,8 @@ def main(): if args.show_help: for node in sym.nodes: if node.help is not None: - # Indent by two spaces. textwrap.indent() is not - # available in Python 2 (it's 3.3+). - print("\n".join(" " + line - for line in node.help.split("\n"))) + # Indent by two spaces. + print(textwrap.indent(node.help, " ")) break diff --git a/menuconfig.py b/menuconfig.py index 9c5732a..51b1ffb 100755 --- a/menuconfig.py +++ b/menuconfig.py @@ -7,7 +7,7 @@ Overview ======== -A curses-based Python 2/3 menuconfig implementation. The interface should feel +A curses-based Python 3 menuconfig implementation. The interface should feel familiar to people used to mconf ('make menuconfig'). Supports the same keys as mconf, and also supports a set of keybindings @@ -989,12 +989,7 @@ def _init(): # Looking for this in addition to KEY_BACKSPACE (which is unreliable) makes # backspace work with TERM=vt100. That makes it likely to work in sane # environments. - _ERASE_CHAR = curses.erasechar() - if sys.version_info[0] >= 3: - # erasechar() returns a one-byte bytes object on Python 3. This sets - # _ERASE_CHAR to a blank string if it can't be decoded, which should be - # harmless. - _ERASE_CHAR = _ERASE_CHAR.decode("utf-8", "ignore") + _ERASE_CHAR = curses.erasechar().decode("utf-8", "ignore") _init_styles() @@ -2141,11 +2136,7 @@ def select_prev_match(): except re.error as e: # Bad regex. Remember the error message so we can show it. - bad_re = "Bad regular expression" - # re.error.msg was added in Python 3.5 - if hasattr(e, "msg"): - bad_re += ": " + e.msg - + bad_re = f"Bad regular expression: {e.msg}" matches = [] # Reset scroll and jump to the top of the list of matches @@ -2648,7 +2639,7 @@ def _help_info(sc): for node in sc.nodes: if node.help is not None: - s += "Help:\n\n{}\n\n".format(_indent(node.help, 2)) + s += "Help:\n\n{}\n\n".format(textwrap.indent(node.help, " ")) return s @@ -2790,7 +2781,7 @@ def _kconfig_def_info(item): .format(node.filename, node.linenr, _include_path_info(node), _menu_path_info(node), - _indent(node.custom_str(_name_and_val_str), 2)) + textwrap.indent(node.custom_str(_name_and_val_str), " ")) return s @@ -2822,13 +2813,6 @@ def _menu_path_info(node): return "(Top)" + path -def _indent(s, n): - # Returns 's' with each line indented 'n' spaces. textwrap.indent() is not - # available in Python 2 (it's 3.3+). - - return "\n".join(n*" " + line for line in s.split("\n")) - - def _name_and_val_str(sc): # Custom symbol/choice printer that shows symbol values after symbols @@ -3155,9 +3139,7 @@ def _is_num(name): def _getch_compat(win): - # Uses get_wch() if available (Python 3.3+) and getch() otherwise. - # - # Also falls back on getch() if get_wch() raises curses.error, to work + # Fall back on getch() if get_wch() raises curses.error, to work # around an issue when resizing the terminal on at least macOS Catalina. # See https://github.com/ulfalizer/Kconfiglib/issues/84. # diff --git a/oldconfig.py b/oldconfig.py index 53434b2..b66c842 100755 --- a/oldconfig.py +++ b/oldconfig.py @@ -32,11 +32,6 @@ from kconfiglib import Symbol, Choice, BOOL, TRISTATE, HEX, standard_kconfig -# Python 2/3 compatibility hack -if sys.version_info[0] < 3: - input = raw_input - - def _main(): # Earlier symbols in Kconfig files might depend on later symbols and become # visible if their values change. This flag is set to True if the value of diff --git a/setup.cfg b/setup.cfg index 185845e..7604a2c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,3 @@ -[bdist_wheel] -# We support both Python 2 and Python 3 -universal = 1 - [metadata] # Include the license file in wheels license_file = LICENSE.txt diff --git a/setup.py b/setup.py index 6b8d905..1afadd8 100644 --- a/setup.py +++ b/setup.py @@ -9,17 +9,11 @@ # MAJOR.MINOR.PATCH, per http://semver.org version="14.1.1a4", description="A flexible Python Kconfig implementation", - - # Make sure that README.rst decodes on Python 3 in environments that use + # Make sure that README.rst decodes correctly in environments that use # the C locale (which implies ASCII), by explicitly giving the encoding. - # - # io.open() has the 'encoding' parameter on both Python 2 and 3. open() - # doesn't have it on Python 2. This lets us use the same code for both. long_description=io.open( - os.path.join(os.path.dirname(__file__), "README.rst"), - encoding="utf-8" + os.path.join(os.path.dirname(__file__), "README.rst"), encoding="utf-8" ).read(), - url="https://github.com/zephyrproject-rtos/Kconfiglib", author='Zephyr Project', author_email="ci@zephyrproject.org", @@ -64,9 +58,7 @@ # Note: windows-curses is not automatically installed on Windows anymore, # because it made Kconfiglib impossible to install on MSYS2 with pip - # Needs support for unnumbered {} in format() and argparse - python_requires=">=2.7,!=3.0.*,!=3.1.*", - + python_requires=">=3.9", project_urls={ "GitHub repository": "https://github.com/zephyrproject-rtos/Kconfiglib", "Examples": "https://github.com/zephyrproject-rtos/Kconfiglib/tree/master/examples", @@ -80,8 +72,6 @@ "License :: OSI Approved :: ISC License (ISCL)", "Operating System :: POSIX", "Operating System :: Microsoft :: Windows", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", diff --git a/tests/reltest b/tests/reltest index f0c0645..2a1d8db 100755 --- a/tests/reltest +++ b/tests/reltest @@ -1,8 +1,8 @@ #!/usr/bin/env bash -# Runs the test suite and all examples scripts with Python 2 and Python 3, -# bailing immediately if anything fails. For the examples that aren't tested in -# the test suite, we just confirm that they at least run. +# Runs the test suite and all examples scripts, bailing immediately if anything +# fails. For the examples that aren't tested in the test suite, we just confirm +# that they at least run. # # Should be run from the kernel root with $ Kconfiglib/tests/reltest @@ -21,7 +21,7 @@ test_script() { } if [ $# == "0" ]; then - py_execs="python2 python3" + py_execs="python3" else py_execs=$@ fi diff --git a/testsuite.py b/testsuite.py index b4f5e2b..7bc9804 100644 --- a/testsuite.py +++ b/testsuite.py @@ -2699,16 +2699,12 @@ def verify_bad_argno(name): sys.path.pop(0) - # This test can fail on older Python 3.x versions, because they don't - # preserve dict insertion order during iteration. The output is still - # correct, just different. - if not (3, 0) <= sys.version_info <= (3, 5): - print("Testing KCONFIG_WARN_UNDEF") + print("Testing KCONFIG_WARN_UNDEF") - os.environ["KCONFIG_WARN_UNDEF"] = "y" - c = Kconfig("Kconfiglib/tests/Kundef", warn_to_stderr=False) + os.environ["KCONFIG_WARN_UNDEF"] = "y" + c = Kconfig("Kconfiglib/tests/Kundef", warn_to_stderr=False) - verify_equal("\n".join(c.warnings), """ + verify_equal("\n".join(c.warnings), """ warning: the int symbol INT (defined at Kconfiglib/tests/Kundef:8) has a non-int range [UNDEF_2 (undefined), 8 (undefined)] warning: undefined symbol UNDEF_1: @@ -2747,7 +2743,7 @@ def verify_bad_argno(name): visible if UNDEF_3 """[1:-1]) - os.environ.pop("KCONFIG_WARN_UNDEF") + os.environ.pop("KCONFIG_WARN_UNDEF") print("\nAll selftests passed\n" if all_passed else @@ -2760,8 +2756,8 @@ def run_compatibility_tests(): # Referenced inside the kernel Kconfig files. # - # The str() makes the type of the value 'str' on both Python 2 and Python 3, - # which is nice for some later dictionary key sanity checks. + # The str() makes the type of the value 'str', which is nice for some later + # dictionary key sanity checks. os.environ["KERNELVERSION"] = str( subprocess.check_output("make kernelversion", shell=True) @@ -2932,13 +2928,10 @@ def test_sanity(arch, srcarch): kconf.write_autoconf("/dev/null") - # No tempfile.TemporaryDirectory in Python 2 - tmpdir = tempfile.mkdtemp() - kconf.sync_deps(os.path.join(tmpdir, "deps")) # Create - kconf.sync_deps(os.path.join(tmpdir, "deps")) # Update - shutil.rmtree(tmpdir) + with tempfile.TemporaryDirectory() as tmpdir: + kconf.sync_deps(os.path.join(tmpdir, "deps")) # Create + kconf.sync_deps(os.path.join(tmpdir, "deps")) # Update - # Python 2/3 compatible for key, sym in kconf.syms.items(): verify(isinstance(key, str), "weird key '{}' in syms dict".format(key))