diff --git a/utils_maintenance/autopep8_format_paths.py b/utils_maintenance/autopep8_format_paths.py
new file mode 100644
index 0000000000000000000000000000000000000000..158ec327bd42a903836726132ce7d571b236bcd5
--- /dev/null
+++ b/utils_maintenance/autopep8_format_paths.py
@@ -0,0 +1,143 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+import os
+import sys
+import subprocess
+
+VERSION_MIN = (1, 6, 0)
+VERSION_MAX_RECOMMENDED = (1, 6, 0)
+AUTOPEP8_FORMAT_CMD = "autopep8"
+
+BASE_DIR = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
+os.chdir(BASE_DIR)
+
+
+extensions = (
+    ".py",
+)
+
+ignore_files = {
+    "release/scripts/modules/rna_manual_reference.py",  # Large generated file, don't format.
+}
+
+
+def compute_paths(paths, use_default_paths):
+    # Optionally pass in files to operate on.
+    if use_default_paths:
+        paths = (
+            "build_files",
+            "release",
+            "doc",
+            "source",
+            "tests",
+        )
+
+    if os.sep != "/":
+        paths = [f.replace("/", os.sep) for f in paths]
+    return paths
+
+
+def source_files_from_git(paths, changed_only):
+    if changed_only:
+        cmd = ("git", "diff", "HEAD", "--name-only", "-z", "--", *paths)
+    else:
+        cmd = ("git", "ls-tree", "-r", "HEAD", *paths, "--name-only", "-z")
+    files = subprocess.check_output(cmd).split(b'\0')
+    return [f.decode('ascii') for f in files]
+
+
+def autopep8_ensure_version():
+    global AUTOPEP8_FORMAT_CMD
+    autopep8_format_cmd = None
+    version_output = None
+    # TODO: search paths.
+    for _ in (None,):
+        autopep8_format_cmd = "autopep8"
+        try:
+            version_output = subprocess.check_output((autopep8_format_cmd, "--version")).decode('utf-8')
+        except FileNotFoundError as e:
+            continue
+        AUTOPEP8_FORMAT_CMD = autopep8_format_cmd
+        break
+    version = next(iter(v for v in version_output.split() if v[0].isdigit()), None)
+    if version is not None:
+        version = version.split("-")[0]
+        version = tuple(int(n) for n in version.split("."))
+    if version is not None:
+        print("Using %s (%d.%d.%d)..." % (AUTOPEP8_FORMAT_CMD, version[0], version[1], version[2]))
+    return version
+
+
+def autopep8_format(files):
+    cmd = [AUTOPEP8_FORMAT_CMD, "--recursive", "--in-place", "--jobs=0"] + files
+    return subprocess.check_output(cmd, stderr=subprocess.STDOUT)
+
+
+def argparse_create():
+    import argparse
+
+    # When --help or no args are given, print this help
+    usage_text = "Format source code"
+    epilog = "This script runs autopep8 on multiple files/directories"
+    parser = argparse.ArgumentParser(description=usage_text, epilog=epilog)
+    parser.add_argument(
+        "--changed-only",
+        dest="changed_only",
+        default=False,
+        action='store_true',
+        help=(
+            "Format only edited files, including the staged ones. "
+            "Using this with \"paths\" will pick the edited files lying on those paths. "
+            "(default=False)"
+        ),
+        required=False,
+    )
+    parser.add_argument(
+        "paths",
+        nargs=argparse.REMAINDER,
+        help="All trailing arguments are treated as paths."
+    )
+
+    return parser
+
+
+def main():
+    version = autopep8_ensure_version()
+    if version is None:
+        print("Unable to detect 'autopep8 --version'")
+        sys.exit(1)
+    if version < VERSION_MIN:
+        print("Version of autopep8 is too old:", version, "<", VERSION_MIN)
+        sys.exit(1)
+    if version > VERSION_MAX_RECOMMENDED:
+        print(
+            "WARNING: Version of autopep8 is too recent:",
+            version, ">", VERSION_MAX_RECOMMENDED,
+        )
+        print(
+            "You may want to install autopep8-%d.%d, "
+            "or use the precompiled libs repository." %
+            (VERSION_MAX_RECOMMENDED[0], VERSION_MAX_RECOMMENDED[1]),
+        )
+
+    args = argparse_create().parse_args()
+
+    use_default_paths = not (bool(args.paths) or bool(args.changed_only))
+
+    paths = compute_paths(args.paths, use_default_paths)
+    print("Operating on:" + (" (%d changed paths)" % len(paths) if args.changed_only else ""))
+    for p in paths:
+        print(" ", p)
+
+    files = [
+        f for f in source_files_from_git(paths, args.changed_only)
+        if f.endswith(extensions)
+        if f not in ignore_files
+    ]
+
+    autopep8_format(files)
+
+
+if __name__ == "__main__":
+    main()