File: unasync.py

package info (click to toggle)
python-confluent-kafka 2.11.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,660 kB
  • sloc: python: 30,428; ansic: 9,487; sh: 1,477; makefile: 192
file content (193 lines) | stat: -rw-r--r-- 6,506 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
#!/usr/bin/env python

import os
import re
import sys
import argparse
import difflib

# List of directories to convert from async to sync
# Each tuple contains the async directory and its sync counterpart
# If you add a new _async directory and want the _sync directory to be
# generated, you must add it to this list.
ASYNC_TO_SYNC = [
    ("src/confluent_kafka/schema_registry/_async", "src/confluent_kafka/schema_registry/_sync"),
    ("tests/integration/schema_registry/_async", "tests/integration/schema_registry/_sync"),
    ("tests/schema_registry/_async", "tests/schema_registry/_sync"),
]

SUBS = [
    ('from confluent_kafka.schema_registry.common import asyncinit', ''),
    ('@asyncinit', ''),
    ('import asyncio', ''),
    ('asyncio.sleep', 'time.sleep'),
    ('._async.', '._sync.'),

    ('Async([A-Z][A-Za-z0-9_]*)', r'\2'),
    ('_Async([A-Z][A-Za-z0-9_]*)', r'_\2'),
    ('async_([a-z][A-Za-z0-9_]*)', r'\2'),

    ('async def', 'def'),
    ('await ', ''),
    ('aclose', 'close'),
    ('__aenter__', '__enter__'),
    ('__aexit__', '__exit__'),
    ('__aiter__', '__iter__'),
    (r'asyncio.run\((.*)\)', r'\2'),
]

COMPILED_SUBS = [
    (re.compile(r'(^|\b)' + regex + r'($|\b)'), repl)
    for regex, repl in SUBS
]

USED_SUBS = set()


def unasync_line(line):
    for index, (regex, repl) in enumerate(COMPILED_SUBS):
        old_line = line
        line = re.sub(regex, repl, line)
        if old_line != line:
            USED_SUBS.add(index)
    return line


def unasync_file(in_path, out_path):
    with open(in_path, "r") as in_file:
        with open(out_path, "w", newline="") as out_file:
            for line in in_file.readlines():
                line = unasync_line(line)
                out_file.write(line)


def unasync_file_check(in_path, out_path):
    """Check if the sync file matches the expected generated content.

    Args:
        in_path: Path to the async file
        out_path: Path to the sync file

    Returns:
        bool: True if files match, False if they don't

    Raises:
        ValueError: If there's an error reading the files
    """
    try:
        with open(in_path, "r") as in_file:
            async_content = in_file.read()
            expected_content = "".join(unasync_line(line) for line in async_content.splitlines(keepends=True))

        with open(out_path, "r") as out_file:
            actual_content = out_file.read()

        if actual_content != expected_content:
            diff = difflib.unified_diff(
                expected_content.splitlines(keepends=True),
                actual_content.splitlines(keepends=True),
                fromfile=in_path,
                tofile=out_path,
                n=3  # Show 3 lines of context
            )
            print(''.join(diff))
            return False
        return True
    except Exception as e:
        print(f"Error comparing {in_path} and {out_path}: {e}")
        raise ValueError(f"Error comparing {in_path} and {out_path}: {e}")


def check_sync_files(dir_pairs):
    """Check if all sync files match their expected generated content.
    Returns a list of files that have differences."""
    files_with_diff = []

    for async_dir, sync_dir in dir_pairs:
        for dirpath, _, filenames in os.walk(async_dir):
            for filename in filenames:
                if not filename.endswith('.py'):
                    continue
                rel_dir = os.path.relpath(dirpath, async_dir)
                async_path = os.path.normpath(os.path.join(async_dir, rel_dir, filename))
                sync_path = os.path.normpath(os.path.join(sync_dir, rel_dir, filename))

                if not os.path.exists(sync_path):
                    files_with_diff.append(sync_path)
                    continue

                if not unasync_file_check(async_path, sync_path):
                    files_with_diff.append(sync_path)

    return files_with_diff


def unasync_dir(in_dir, out_dir):
    # Create the output directory if it doesn't exist
    os.makedirs(out_dir, exist_ok=True)

    # Create README.md in the sync directory
    readme_path = os.path.join(out_dir, "README.md")
    readme_content = """# Auto-generated Directory

This directory contains auto-generated code. Do not edit these files directly.

To make changes:
1. Edit the corresponding files in the sibling `_async` directory
2. Run `python tools/unasync.py` to propagate the changes to this `_sync` directory
"""
    with open(readme_path, "w") as f:
        f.write(readme_content)

    for dirpath, _, filenames in os.walk(in_dir):
        for filename in filenames:
            if not filename.endswith('.py'):
                continue
            rel_dir = os.path.relpath(dirpath, in_dir)
            in_path = os.path.normpath(os.path.join(in_dir, rel_dir, filename))
            out_path = os.path.normpath(os.path.join(out_dir, rel_dir, filename))
            # Create the subdirectory if it doesn't exist
            os.makedirs(os.path.dirname(out_path), exist_ok=True)
            print(in_path, '->', out_path)
            unasync_file(in_path, out_path)


def unasync(dir_pairs=None, check=False):
    """Convert async code to sync code.

    Args:
        dir_pairs: List of (async_dir, sync_dir) tuples to process. If None, uses ASYNC_TO_SYNC.
        check: If True, only check if sync files are up to date without modifying them.
    """
    if dir_pairs is None:
        dir_pairs = ASYNC_TO_SYNC

    files_with_diff = []
    if check:
        files_with_diff = check_sync_files(dir_pairs)

    if files_with_diff:
        print("\n⚠️  Detected differences between async and sync files.")
        print("\nFiles that need to be regenerated:")
        for file in files_with_diff:
            print(f"  - {file}")
        print("\nPlease run this script again (without the --check flag) to regenerate the sync files.")
        sys.exit(1)
    else:
        print("\n✅ All _sync directories are up to date!")
    if not check:
        print("Converting async code to sync code...")
        for async_dir, sync_dir in dir_pairs:
            unasync_dir(async_dir, sync_dir)

        print("\n✅ Generated sync code from async code.")


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Convert async code to sync code')
    parser.add_argument(
        '--check',
        action='store_true',
        help='Exit with non-zero status if sync directory has any differences')
    args = parser.parse_args()
    unasync(check=args.check)