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+ if not sys .flags .isolated :
29+ print ("running without -I option." )
30+ print ("python -I whats_left.py" )
31+ exit (1 )
32+
33+ GENERATED_FILE = "extra_tests/snippets/not_impl.py"
34+
35+ implementation = platform .python_implementation ()
36+ if implementation != "CPython" :
37+ sys .exit ("whats_left.py must be run under CPython, got {implementation} instead" )
38+
39+
40+ def parse_args ():
41+ parser = argparse .ArgumentParser (description = "Process some integers." )
42+ parser .add_argument (
43+ "--signature" ,
44+ action = "store_true" ,
45+ help = "print functions whose signatures don't match CPython's" ,
46+ )
47+ parser .add_argument (
48+ "--json" ,
49+ action = "store_true" ,
50+ help = "print output as JSON (instead of line by line)" ,
51+ )
52+
53+ args = parser .parse_args ()
54+ return args
55+
56+
57+ args = parse_args ()
58+
1859
1960# modules suggested for deprecation by PEP 594 (www.python.org/dev/peps/pep-0594/)
2061# some of these might be implemented, but they are not a priority
5495 '_testbuffer' , '_testcapi' , '_testimportmultiple' , '_testinternalcapi' , '_testmultiphase' ,
5596}
5697
57- IGNORED_MODULES = {' this' , ' antigravity' } | PEP_594_MODULES | CPYTHON_SPECIFIC_MODS
98+ IGNORED_MODULES = {" this" , " antigravity" } | PEP_594_MODULES | CPYTHON_SPECIFIC_MODS
5899
59100sys .path = [
60101 path
@@ -188,6 +229,7 @@ def onerror(modname):
188229def import_module (module_name ):
189230 import io
190231 from contextlib import redirect_stdout
232+
191233 # Importing modules causes ('Constant String', 2, None, 4) and
192234 # "Hello world!" to be printed to stdout.
193235 f = io .StringIO ()
@@ -203,6 +245,7 @@ def import_module(module_name):
203245
204246def is_child (module , item ):
205247 import inspect
248+
206249 item_mod = inspect .getmodule (item )
207250 return item_mod is module
208251
@@ -250,7 +293,7 @@ def gen_modules():
250293output += gen_methods ()
251294output += f"""
252295cpymods = { gen_modules ()!r}
253- libdir = { os .path .abspath ("../ Lib/" ).encode ('utf8' )!r}
296+ libdir = { os .path .abspath ("Lib/" ).encode ('utf8' )!r}
254297
255298"""
256299
@@ -260,7 +303,7 @@ def gen_modules():
260303 extra_info ,
261304 dir_of_mod_or_error ,
262305 import_module ,
263- is_child
306+ is_child ,
264307]
265308for fn in REUSED :
266309 output += "" .join (inspect .getsourcelines (fn )[0 ]) + "\n \n "
@@ -278,7 +321,7 @@ def compare():
278321 import sys
279322 import warnings
280323 from contextlib import redirect_stdout
281-
324+ import json
282325 import platform
283326
284327 def method_incompatability_reason (typ , method_name , real_method_value ):
@@ -288,7 +331,7 @@ def method_incompatability_reason(typ, method_name, real_method_value):
288331
289332 is_inherited = not attr_is_not_inherited (typ , method_name )
290333 if is_inherited :
291- return "inherited"
334+ return "( inherited) "
292335
293336 value = extra_info (getattr (typ , method_name ))
294337 if value != real_method_value :
@@ -321,16 +364,20 @@ def method_incompatability_reason(typ, method_name, real_method_value):
321364
322365 rustpymods = {mod : dir_of_mod_or_error (mod ) for mod in mod_names }
323366
324- not_implemented = {}
325- failed_to_import = {}
326- missing_items = {}
327- mismatched_items = {}
367+ result = {
368+ "cpython_modules" : {},
369+ "implemented" : {},
370+ "not_implemented" : {},
371+ "failed_to_import" : {},
372+ "missing_items" : {},
373+ "mismatched_items" : {},
374+ }
328375 for modname , cpymod in cpymods .items ():
329376 rustpymod = rustpymods .get (modname )
330377 if rustpymod is None :
331- not_implemented [modname ] = None
378+ result [ " not_implemented" ] [modname ] = None
332379 elif isinstance (rustpymod , Exception ):
333- failed_to_import [modname ] = rustpymod
380+ result [ " failed_to_import" ] [modname ] = rustpymod . __class__ . __name__ + str ( rustpymod )
334381 else :
335382 implemented_items = sorted (set (cpymod ) & set (rustpymod ))
336383 mod_missing_items = set (cpymod ) - set (rustpymod )
@@ -343,48 +390,18 @@ def method_incompatability_reason(typ, method_name, real_method_value):
343390 if rustpymod [item ] != cpymod [item ]
344391 and not isinstance (cpymod [item ], Exception )
345392 ]
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 } " )
393+ if mod_missing_items or mod_mismatched_items :
394+ if mod_missing_items :
395+ result ["missing_items" ][modname ] = mod_missing_items
396+ if mod_mismatched_items :
397+ result ["mismatched_items" ][modname ] = mod_mismatched_items
398+ else :
399+ result ["implemented" ][modname ] = None
376400
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- }
401+ result ["cpython_modules" ] = cpymods
402+ result ["not_implementeds" ] = not_implementeds
383403
384- print ()
385- print ("# out of" , len (cpymods ), "modules:" )
386- for error_type , modules in result .items ():
387- print ("# " , error_type , len (modules ))
404+ print (json .dumps (result ))
388405
389406
390407def remove_one_indent (s ):
@@ -395,5 +412,54 @@ def remove_one_indent(s):
395412compare_src = inspect .getsourcelines (compare )[0 ][1 :]
396413output += "" .join (remove_one_indent (line ) for line in compare_src )
397414
398- with open ("not_impl.py" , "w" ) as f :
415+ with open (GENERATED_FILE , "w" ) as f :
399416 f .write (output + "\n " )
417+
418+
419+ subprocess .run (["cargo" , "build" , "--release" , "--features=ssl" ], check = True )
420+ result = subprocess .run (
421+ ["cargo" , "run" , "--release" , "--features=ssl" , "-q" , "--" , GENERATED_FILE ],
422+ env = {** os .environ .copy (), "RUSTPYTHONPATH" : "Lib" },
423+ text = True ,
424+ capture_output = True ,
425+ )
426+ # The last line should be json output, the rest of the lines can contain noise
427+ # because importing certain modules can print stuff to stdout/stderr
428+ result = json .loads (result .stdout .splitlines ()[- 1 ])
429+
430+ if args .json :
431+ print (json .dumps (result ))
432+ sys .exit ()
433+
434+
435+ # missing entire modules
436+ print ("# modules" )
437+ for modname in result ["not_implemented" ]:
438+ print (modname , "(entire module)" )
439+ for modname , exception in result ["failed_to_import" ].items ():
440+ print (f"{ modname } (exists but not importable: { exception } )" )
441+
442+ # missing from builtins
443+ print ("\n # builtin items" )
444+ for module , missing_methods in result ["not_implementeds" ].items ():
445+ for method , reason in missing_methods .items ():
446+ print (f"{ module } .{ method } " + (f" { reason } " if reason else "" ))
447+
448+ # missing from modules
449+ print ("\n # stdlib items" )
450+ for modname , missing in result ["missing_items" ].items ():
451+ for item in missing :
452+ print (item )
453+
454+ if args .signature :
455+ print ("\n # mismatching signatures (warnings)" )
456+ for modname , mismatched in result ["mismatched_items" ].items ():
457+ for (item , rustpy_value , cpython_value ) in mismatched :
458+ if cpython_value == "ValueError('no signature found')" :
459+ continue # these items will never match
460+ print (f"{ item } { rustpy_value } != { cpython_value } " )
461+
462+ print ()
463+ print ("# summary" )
464+ for error_type , modules in result .items ():
465+ print ("# " , error_type , len (modules ))
0 commit comments