]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
refactor: drop 'convert_async_to_sync.sh' script
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Fri, 13 Oct 2023 00:19:03 +0000 (02:19 +0200)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Fri, 13 Oct 2023 01:29:42 +0000 (03:29 +0200)
Do all in 'async_to_sync.py:' added --all and --check options.

.github/workflows/lint.yml
tools/async_to_sync.py
tools/convert_async_to_sync.sh [deleted file]

index ad8379d035ae9a027c173be95a2642558b8297e3..3fea1c28fb2e28210b4a5e2db4207cb83249e358 100644 (file)
@@ -37,7 +37,7 @@ jobs:
         run: mypy
 
       - name: Check for sync/async inconsistencies
-        run: ./tools/convert_async_to_sync.sh --check
+        run: ./tools/async_to_sync.py --all --check
 
       - name: Check spelling
         run: codespell
index 7b949d137b6e2b1fd5760dfec584db16fc6e6124..14ea4d36e62c469f05735321093b87b2c907c1d9 100755 (executable)
@@ -1,15 +1,20 @@
 #!/usr/bin/env python
-"""Convert an async module to a sync module.
+"""Convert async code in the project to sync code.
+
+Note: the version of Python used to run this script affects the output. Please
+use the `async-to-sync.sh` wrapper to use a version consistent with CI checks.
 """
 
 from __future__ import annotations
 
 import os
 import sys
+import logging
+import subprocess as sp
 from copy import deepcopy
 from typing import Any
 from pathlib import Path
-from argparse import ArgumentParser, Namespace
+from argparse import ArgumentParser, Namespace, RawDescriptionHelpFormatter
 
 import ast_comments as ast
 
@@ -26,21 +31,89 @@ ast.Dict = ast_orig.Dict
 ast.List = ast_orig.List
 ast.Tuple = ast_orig.Tuple
 
+ALL_INPUTS = """
+    psycopg/psycopg/_copy_async.py
+    psycopg/psycopg/connection_async.py
+    psycopg/psycopg/cursor_async.py
+    psycopg_pool/psycopg_pool/null_pool_async.py
+    psycopg_pool/psycopg_pool/pool_async.py
+    psycopg_pool/psycopg_pool/sched_async.py
+    tests/pool/test_pool_async.py
+    tests/pool/test_pool_common_async.py
+    tests/pool/test_pool_null_async.py
+    tests/pool/test_sched_async.py
+    tests/test_connection_async.py
+    tests/test_copy_async.py
+    tests/test_cursor_async.py
+    tests/test_cursor_client_async.py
+    tests/test_cursor_common_async.py
+    tests/test_cursor_raw_async.py
+    tests/test_cursor_server_async.py
+    tests/test_pipeline_async.py
+    tests/test_prepared_async.py
+    tests/test_tpc_async.py
+    tests/test_transaction_async.py
+""".split()
+
+PROJECT_DIR = Path(__file__).parent.parent
+SCRIPT_NAME = os.path.basename(sys.argv[0])
+
+logger = logging.getLogger()
+logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
+
 
 def main() -> int:
     opt = parse_cmdline()
-    with opt.filepath.open() as f:
-        source = f.read()
+    outputs = []
+    for fpin in opt.inputs:
+        fpout = fpin.parent / fpin.name.replace("_async", "")
+        outputs.append(str(fpout))
+        logger.info("converting %s", fpin)
+        with fpin.open() as f:
+            source = f.read()
+
+        tree = ast.parse(source, filename=str(fpin))
+        tree = async_to_sync(tree, filepath=fpin)
+        output = tree_to_str(tree, fpin)
+
+        with fpout.open("w") as f:
+            print(output, file=f)
 
-    tree = ast.parse(source, filename=str(opt.filepath))
-    tree = async_to_sync(tree, filepath=opt.filepath)
-    output = tree_to_str(tree, opt.filepath)
+        sp.check_call(["black", "-q", str(fpout)])
 
-    if opt.output:
-        with open(opt.output, "w") as f:
-            print(output, file=f)
-    else:
-        print(output)
+    if opt.check:
+        return check(outputs)
+
+    return 0
+
+
+def check(outputs: list[str]) -> int:
+    try:
+        sp.check_call(["git", "diff", "--exit-code"] + outputs)
+    except sp.CalledProcessError:
+        logger.error("sync and async files... out of sync!")
+        return 1
+
+    # Check that all the files to convert are included in the --all list
+    cmdline = ["git", "ls-files", "**.py"]
+    git_pys = sp.check_output(cmdline, cwd=str(PROJECT_DIR)).decode().split()
+
+    cmdline = ["grep", "-l", f"auto-generated by '{SCRIPT_NAME}'"]
+    cmdline += git_pys
+    maybe_conv = sp.check_output(cmdline, cwd=str(PROJECT_DIR)).decode().split()
+    if not maybe_conv:
+        logger.error("no file to check? Maybe this script bitrot?")
+        return 1
+    unk_conv = sorted(
+        set(maybe_conv) - set(fn.replace("_async", "") for fn in ALL_INPUTS)
+    )
+    if unk_conv:
+        logger.error(
+            "files converted by %s but not included in --all list: %s",
+            SCRIPT_NAME,
+            ", ".join(unk_conv),
+        )
+        return 1
 
     return 0
 
@@ -54,7 +127,7 @@ def async_to_sync(tree: ast.AST, filepath: Path | None = None) -> ast.AST:
 
 def tree_to_str(tree: ast.AST, filepath: Path) -> str:
     rv = f"""\
-# WARNING: this file is auto-generated by '{os.path.basename(sys.argv[0])}'
+# WARNING: this file is auto-generated by '{SCRIPT_NAME}'
 # from the original file '{filepath.name}'
 # DO NOT CHANGE! Change the original file instead.
 """
@@ -439,14 +512,40 @@ class Unparser(ast._Unparser):
 
 
 def parse_cmdline() -> Namespace:
-    parser = ArgumentParser(description=__doc__)
+    parser = ArgumentParser(
+        description=__doc__, formatter_class=RawDescriptionHelpFormatter
+    )
+
+    parser.add_argument(
+        "--check", action="store_true", help="return with error in case of differences"
+    )
     parser.add_argument(
-        "filepath", metavar="FILE", type=Path, help="the file to process"
+        "--all", action="store_true", help="process all the files of the project"
     )
     parser.add_argument(
-        "output", metavar="OUTPUT", nargs="?", help="file where to write (or stdout)"
+        "inputs",
+        metavar="FILE",
+        nargs="*",
+        type=Path,
+        help="the files to process (if --all is not specified)",
     )
+
     opt = parser.parse_args()
+    if opt.all and opt.inputs:
+        parser.error("can't specify input files and --all together")
+
+    if opt.all:
+        opt.inputs = [PROJECT_DIR / Path(fn) for fn in ALL_INPUTS]
+
+    if not opt.inputs:
+        parser.error("no input file provided")
+
+    fp: Path
+    for fp in opt.inputs:
+        if not fp.is_file():
+            parser.error("not a file: %s" % fp)
+        if "_async" not in fp.name:
+            parser.error("file should have '_async' in the name: %s" % fp)
 
     return opt
 
diff --git a/tools/convert_async_to_sync.sh b/tools/convert_async_to_sync.sh
deleted file mode 100755 (executable)
index 76a6e4b..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-#!/bin/bash
-
-# Convert all the auto-generated sync files from their async counterparts.
-
-set -euo pipefail
-
-dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-cd "${dir}/.."
-
-function log {
-    echo "$@" >&2
-}
-function error {
-    # Print an error message and exit.
-    log "
-ERROR: $@"
-    exit 1
-}
-
-check=
-
-# If --check is used, give an error if files are changed
-# (note: it's not a --dry-run)
-if [[ ${1:-} == '--check' ]]; then
-    check=1
-    shift
-fi
-
-all_inputs="
-    psycopg/psycopg/_copy_async.py
-    psycopg/psycopg/connection_async.py
-    psycopg/psycopg/cursor_async.py
-    psycopg_pool/psycopg_pool/null_pool_async.py
-    psycopg_pool/psycopg_pool/pool_async.py
-    psycopg_pool/psycopg_pool/sched_async.py
-    tests/pool/test_pool_async.py
-    tests/pool/test_pool_common_async.py
-    tests/pool/test_pool_null_async.py
-    tests/pool/test_sched_async.py
-    tests/test_connection_async.py
-    tests/test_copy_async.py
-    tests/test_cursor_async.py
-    tests/test_cursor_client_async.py
-    tests/test_cursor_common_async.py
-    tests/test_cursor_raw_async.py
-    tests/test_cursor_server_async.py
-    tests/test_pipeline_async.py
-    tests/test_prepared_async.py
-    tests/test_tpc_async.py
-    tests/test_transaction_async.py
-"
-
-# Take other arguments as file names if specified
-if [[ ${1:-} ]]; then
-    inputs="$@"
-else
-    inputs="$all_inputs"
-fi
-
-
-outputs=""
-
-for async in $inputs; do
-    test -f "${async}" || error "file not found: '${async}'"
-    sync=${async/_async/}
-    log "converting '${async}' -> '${sync}'"
-    python "${dir}/async_to_sync.py" ${async} > ${sync}
-    black -q ${sync}
-    outputs="$outputs ${sync}"
-done
-
-if [[ $check ]]; then
-    if ! git diff --exit-code $outputs; then
-        error "sync and async files... out of sync!"
-    fi
-
-    # Verify that all the transformed files are included in this script
-    # Note: the 'cd' early in the script ensures that cwd is the project root.
-    checked=0
-    errors=0
-    for fn in $(find psycopg psycopg_pool tests -type f -name \*.py); do
-        # Skip files ignored by git (build artifacts)
-        ! git status --porcelain --ignored "${fn}" | grep -q '!!' || continue
-        # Skip non-auto-generated files
-        grep -q "auto-generated by 'async_to_sync.py'" "${fn}" || continue
-
-        checked=$(( $checked + 1 ))
-        afn=${fn/.py/_async.py}
-        if ! grep -q $afn tools/$(basename $0); then
-            errors=$(( $errors + 1 ))
-            log "file '${fn}' seems converted but not included in '$(basename $0)'"
-        fi
-    done
-    [[ $checked -gt 0 ]] || error "No file found to check. Script bitrot?"
-    [[ $errors -eq 0 ]] || error "Some files are not included in async-to-sync conversion."
-fi