1- # It's recommended to run this with `python3 -I not_impl_gen.py`, to make sure
2- # that nothing in your global Python environment interferes with what's being
3- # extracted here.
4- #
1+ #!/usr/bin/env python3 -I
2+
53# This script generates Lib/snippets/whats_left_data.py with these variables defined:
64# expected_methods - a dictionary mapping builtin objects to their methods
75# cpymods - a dictionary mapping module names to their contents
86# libdir - the location of RustPython's Lib/ directory.
97
10- import inspect
11- import io
8+ #
9+ # TODO: include this:
10+ # which finds all modules it has available and
11+ # creates a Python dictionary mapping module names to their contents, which is
12+ # in turn used to generate a second Python script that also finds which modules
13+ # it has available and compares that against the first dictionary we generated.
14+ # We then run this second generated script with RustPython.
15+
16+ import argparse
17+ import re
1218import os
1319import re
1420import sys
21+ import json
1522import warnings
23+ import inspect
24+ import subprocess
25+ import platform
1626from pydoc import ModuleScanner
1727
28+ GENERATED_FILE = "extra_tests/snippets/not_impl.py"
29+
30+ implementation = platform .python_implementation ()
31+ if implementation != "CPython" :
32+ sys .exit ("whats_left.py must be run under CPython, got {implementation} instead" )
33+
34+
35+ def parse_args ():
36+ parser = argparse .ArgumentParser (description = "Process some integers." )
37+ parser .add_argument (
38+ "--signature" ,
39+ action = "store_true" ,
40+ help = "print functions whose signatures don't match CPython's" ,
41+ )
42+ parser .add_argument (
43+ "--json" ,
44+ action = "store_true" ,
45+ help = "print output as JSON (instead of line by line)" ,
46+ )
47+
48+ args = parser .parse_args ()
49+ return args
50+
51+
52+ args = parse_args ()
53+
1854
1955# modules suggested for deprecation by PEP 594 (www.python.org/dev/peps/pep-0594/)
2056# some of these might be implemented, but they are not a priority
5490 '_testbuffer' , '_testcapi' , '_testimportmultiple' , '_testinternalcapi' , '_testmultiphase' ,
5591}
5692
57- IGNORED_MODULES = {' this' , ' antigravity' } | PEP_594_MODULES | CPYTHON_SPECIFIC_MODS
93+ IGNORED_MODULES = {" this" , " antigravity" } | PEP_594_MODULES | CPYTHON_SPECIFIC_MODS
5894
5995sys .path = [
6096 path
@@ -188,6 +224,7 @@ def onerror(modname):
188224def import_module (module_name ):
189225 import io
190226 from contextlib import redirect_stdout
227+
191228 # Importing modules causes ('Constant String', 2, None, 4) and
192229 # "Hello world!" to be printed to stdout.
193230 f = io .StringIO ()
@@ -203,6 +240,7 @@ def import_module(module_name):
203240
204241def is_child (module , item ):
205242 import inspect
243+
206244 item_mod = inspect .getmodule (item )
207245 return item_mod is module
208246
@@ -250,7 +288,7 @@ def gen_modules():
250288output += gen_methods ()
251289output += f"""
252290cpymods = { gen_modules ()!r}
253- libdir = { os .path .abspath ("../ Lib/" ).encode ('utf8' )!r}
291+ libdir = { os .path .abspath ("Lib/" ).encode ('utf8' )!r}
254292
255293"""
256294
@@ -260,7 +298,7 @@ def gen_modules():
260298 extra_info ,
261299 dir_of_mod_or_error ,
262300 import_module ,
263- is_child
301+ is_child ,
264302]
265303for fn in REUSED :
266304 output += "" .join (inspect .getsourcelines (fn )[0 ]) + "\n \n "
@@ -278,7 +316,7 @@ def compare():
278316 import sys
279317 import warnings
280318 from contextlib import redirect_stdout
281-
319+ import json
282320 import platform
283321
284322 def method_incompatability_reason (typ , method_name , real_method_value ):
@@ -288,7 +326,7 @@ def method_incompatability_reason(typ, method_name, real_method_value):
288326
289327 is_inherited = not attr_is_not_inherited (typ , method_name )
290328 if is_inherited :
291- return "inherited"
329+ return "( inherited) "
292330
293331 value = extra_info (getattr (typ , method_name ))
294332 if value != real_method_value :
@@ -321,16 +359,20 @@ def method_incompatability_reason(typ, method_name, real_method_value):
321359
322360 rustpymods = {mod : dir_of_mod_or_error (mod ) for mod in mod_names }
323361
324- not_implemented = {}
325- failed_to_import = {}
326- missing_items = {}
327- mismatched_items = {}
362+ result = {
363+ "cpython_modules" : {},
364+ "implemented" : {},
365+ "not_implemented" : {},
366+ "failed_to_import" : {},
367+ "missing_items" : {},
368+ "mismatched_items" : {},
369+ }
328370 for modname , cpymod in cpymods .items ():
329371 rustpymod = rustpymods .get (modname )
330372 if rustpymod is None :
331- not_implemented [modname ] = None
373+ result [ " not_implemented" ] [modname ] = None
332374 elif isinstance (rustpymod , Exception ):
333- failed_to_import [modname ] = rustpymod
375+ result [ " failed_to_import" ] [modname ] = rustpymod . __class__ . __name__ + str ( rustpymod )
334376 else :
335377 implemented_items = sorted (set (cpymod ) & set (rustpymod ))
336378 mod_missing_items = set (cpymod ) - set (rustpymod )
@@ -343,48 +385,18 @@ def method_incompatability_reason(typ, method_name, real_method_value):
343385 if rustpymod [item ] != cpymod [item ]
344386 and not isinstance (cpymod [item ], Exception )
345387 ]
346- if mod_missing_items :
347- missing_items [modname ] = mod_missing_items
348- if mod_mismatched_items :
349- mismatched_items [modname ] = mod_mismatched_items
350-
351- # missing entire module
352- print ("# modules" )
353- for modname in not_implemented :
354- print (modname , "(entire module)" )
355- for modname , exception in failed_to_import .items ():
356- print (f"{ modname } (exists but not importable: { exception } )" )
357-
358- # missing from builtins
359- print ("\n # builtin items" )
360- for module , missing_methods in not_implementeds .items ():
361- for method , reason in missing_methods .items ():
362- print (f"{ module } .{ method } " + (f" ({ reason } )" if reason else "" ))
363-
364- # missing from modules
365- print ("\n # stdlib items" )
366- for modname , missing in missing_items .items ():
367- for item in missing :
368- print (item )
369-
370- print ("\n # mismatching signatures (warnings)" )
371- for modname , mismatched in mismatched_items .items ():
372- for (item , rustpy_value , cpython_value ) in mismatched :
373- if cpython_value == "ValueError('no signature found')" :
374- continue # these items will never match
375- print (f"{ item } { rustpy_value } != { cpython_value } " )
388+ if mod_missing_items or mod_mismatched_items :
389+ if mod_missing_items :
390+ result ["missing_items" ][modname ] = mod_missing_items
391+ if mod_mismatched_items :
392+ result ["mismatched_items" ][modname ] = mod_mismatched_items
393+ else :
394+ result ["implemented" ][modname ] = None
376395
377- result = {
378- "not_implemented" : not_implemented ,
379- "failed_to_import" : failed_to_import ,
380- "missing_items" : missing_items ,
381- "mismatched_items" : mismatched_items ,
382- }
396+ result ["cpython_modules" ] = cpymods
397+ result ["not_implementeds" ] = not_implementeds
383398
384- print ()
385- print ("# out of" , len (cpymods ), "modules:" )
386- for error_type , modules in result .items ():
387- print ("# " , error_type , len (modules ))
399+ print (json .dumps (result ))
388400
389401
390402def remove_one_indent (s ):
@@ -395,5 +407,54 @@ def remove_one_indent(s):
395407compare_src = inspect .getsourcelines (compare )[0 ][1 :]
396408output += "" .join (remove_one_indent (line ) for line in compare_src )
397409
398- with open ("not_impl.py" , "w" ) as f :
410+ with open (GENERATED_FILE , "w" ) as f :
399411 f .write (output + "\n " )
412+
413+
414+ subprocess .run (["cargo" , "build" , "--release" , "--features=ssl" ], check = True )
415+ result = subprocess .run (
416+ ["cargo" , "run" , "--release" , "--features=ssl" , "-q" , "--" , GENERATED_FILE ],
417+ env = {** os .environ .copy (), "RUSTPYTHONPATH" : "Lib" },
418+ text = True ,
419+ capture_output = True ,
420+ )
421+ # The last line should be json output, the rest of the lines can contain noise
422+ # because importing certain modules can print stuff to stdout/stderr
423+ result = json .loads (result .stdout .splitlines ()[- 1 ])
424+
425+ if args .json :
426+ print (json .dumps (result ))
427+ sys .exit ()
428+
429+
430+ # missing entire modules
431+ print ("# modules" )
432+ for modname in result ["not_implemented" ]:
433+ print (modname , "(entire module)" )
434+ for modname , exception in result ["failed_to_import" ].items ():
435+ print (f"{ modname } (exists but not importable: { exception } )" )
436+
437+ # missing from builtins
438+ print ("\n # builtin items" )
439+ for module , missing_methods in result ["not_implementeds" ].items ():
440+ for method , reason in missing_methods .items ():
441+ print (f"{ module } .{ method } " + (f" { reason } " if reason else "" ))
442+
443+ # missing from modules
444+ print ("\n # stdlib items" )
445+ for modname , missing in result ["missing_items" ].items ():
446+ for item in missing :
447+ print (item )
448+
449+ if args .signature :
450+ print ("\n # mismatching signatures (warnings)" )
451+ for modname , mismatched in result ["mismatched_items" ].items ():
452+ for (item , rustpy_value , cpython_value ) in mismatched :
453+ if cpython_value == "ValueError('no signature found')" :
454+ continue # these items will never match
455+ print (f"{ item } { rustpy_value } != { cpython_value } " )
456+
457+ print ()
458+ print ("# summary" )
459+ for error_type , modules in result .items ():
460+ print ("# " , error_type , len (modules ))
0 commit comments