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)
|