1212# See the License for the specific language governing permissions and
1313# limitations under the License.
1414
15+
1516"""Synchronization of asynchronous modules.
1617
1718Used as part of our build system to generate synchronous code.
2122
2223import os
2324import re
25+ import subprocess
2426import sys
2527from pathlib import Path
2628
27- from unasync import Rule , unasync_files # type: ignore[import-not-found]
29+ from unasync import Rule , unasync_files # type: ignore[import-untyped,import-not-found]
30+
31+ MANIFEST = ".synchro-modified"
2832
2933replacements = {
3034 "AsyncCollection" : "Collection" ,
@@ -424,6 +428,7 @@ def unasync_directory(files: list[str], src: str, dest: str, replacements: dict[
424428
425429
426430def main () -> None :
431+ is_ci = bool (os .environ .get ("CI" ))
427432 modified_files = [f"./{ f } " for f in sys .argv [1 :]]
428433 errored = False
429434 for fname in async_files + gridfs_files + test_files :
@@ -438,29 +443,77 @@ def main() -> None:
438443 print (f"Refusing to overwrite { test_sync_name } " )
439444 errored = True
440445 if errored :
441- raise ValueError ("Aborting synchro due to errors" )
442-
443- unasync_directory (async_files , _pymongo_base , _pymongo_dest_base , replacements )
444- unasync_directory (gridfs_files , _gridfs_base , _gridfs_dest_base , replacements )
445- unasync_directory (test_files , _test_base , _test_dest_base , replacements )
446-
447- sync_files = [
448- _pymongo_dest_base + f .name for f in Path (_pymongo_dest_base ).iterdir () if f .is_file ()
449- ]
446+ sys .exit (1 )
447+
448+ # When called with specific files, only process those; otherwise process everything.
449+ modified_set = set (modified_files )
450+ filtered_async = [f for f in async_files if not modified_set or f in modified_set ]
451+ filtered_gridfs = [f for f in gridfs_files if not modified_set or f in modified_set ]
452+ filtered_tests = [f for f in test_files if not modified_set or f in modified_set ]
453+
454+ ruff_extra = [] if is_ci else ["--silent" ]
455+
456+ # Check async source files for problems before generating sync output.
457+ async_sources = filtered_async + filtered_gridfs + filtered_tests
458+ if async_sources :
459+ subprocess .run ( # noqa: S603
460+ [sys .executable , "-m" , "ruff" , "check" , * async_sources , * ruff_extra ],
461+ check = True ,
462+ )
450463
451- sync_gridfs_files = [
452- _gridfs_dest_base + f .name for f in Path (_gridfs_dest_base ).iterdir () if f .is_file ()
453- ]
454- sync_test_files = [
455- _test_dest_base + f for f in converted_tests if (Path (_test_dest_base ) / f ).is_file ()
464+ unasync_directory (filtered_async , _pymongo_base , _pymongo_dest_base , replacements )
465+ unasync_directory (filtered_gridfs , _gridfs_base , _gridfs_dest_base , replacements )
466+ unasync_directory (filtered_tests , _test_base , _test_dest_base , replacements )
467+
468+ # Derive generated output paths directly from filtered source paths.
469+ converted_tests_set = set (converted_tests )
470+ generated_pymongo = [_pymongo_dest_base + Path (f ).name for f in filtered_async ]
471+ generated_gridfs = [_gridfs_dest_base + Path (f ).name for f in filtered_gridfs ]
472+ generated_tests = [
473+ _test_dest_base + Path (f ).name
474+ for f in filtered_tests
475+ if Path (f ).name in converted_tests_set and (Path (_test_dest_base ) / Path (f ).name ).is_file ()
456476 ]
457477
458- docstring_translate_files = sync_files + sync_gridfs_files + sync_test_files
478+ docstring_translate_files = generated_pymongo + generated_gridfs + generated_tests
459479
460480 process_files (
461- sync_files + sync_gridfs_files + sync_test_files , docstring_translate_files , sync_test_files
481+ generated_pymongo + generated_gridfs + generated_tests ,
482+ docstring_translate_files ,
483+ generated_tests ,
462484 )
463485
486+ generated_files = generated_pymongo + generated_gridfs + generated_tests
487+
488+ if is_ci and generated_files :
489+ print (f"Synchro generated { len (generated_files )} file(s):" )
490+ for f in generated_files :
491+ print (f" { f } " )
492+
493+ subprocess .run ( # noqa: S603
494+ [sys .executable , "-m" , "ruff" , "check" , * generated_files , "--fix" , * ruff_extra ],
495+ check = is_ci ,
496+ )
497+ subprocess .run ( # noqa: S603
498+ [sys .executable , "-m" , "ruff" , "format" , * generated_files , * ruff_extra ],
499+ check = is_ci ,
500+ )
501+
502+ if is_ci and generated_files :
503+ result = subprocess .run ( # noqa: S603
504+ ["git" , "diff" , "--name-only" , "--" , * generated_files ], # noqa: S607
505+ capture_output = True ,
506+ text = True ,
507+ check = True ,
508+ )
509+ if result .stdout .strip ():
510+ print ("Sync files are out of date. Run `just lint --all-files synchro` to regenerate:" )
511+ for f in result .stdout .strip ().splitlines ():
512+ print (f" { f } " )
513+ sys .exit (1 )
514+
515+ Path (MANIFEST ).write_text ("\n " .join (generated_files ) + ("\n " if generated_files else "" ))
516+
464517
465518if __name__ == "__main__" :
466519 main ()
0 commit comments