1212import logging
1313import sys
1414import time
15+ import traceback
1516import weakref
1617
1718import yaml
@@ -1090,12 +1091,18 @@ async def ast_while(self, arg):
10901091 async def ast_classdef (self , arg ):
10911092 """Evaluate class definition."""
10921093 bases = [(await self .aeval (base )) for base in arg .bases ]
1094+ keywords = {kw .arg : await self .aeval (kw .value ) for kw in arg .keywords }
1095+ metaclass = keywords .pop ("metaclass" , type (bases [0 ]) if bases else type )
1096+
10931097 if self .curr_func and arg .name in self .curr_func .global_names :
10941098 sym_table_assign = self .global_sym_table
10951099 else :
10961100 sym_table_assign = self .sym_table
10971101 sym_table_assign [arg .name ] = EvalLocalVar (arg .name )
1098- sym_table = {}
1102+ if hasattr (metaclass , "__prepare__" ):
1103+ sym_table = metaclass .__prepare__ (arg .name , tuple (bases ), ** keywords )
1104+ else :
1105+ sym_table = {}
10991106 self .sym_table_stack .append (self .sym_table )
11001107 self .sym_table = sym_table
11011108 for arg1 in arg .body :
@@ -1106,11 +1113,17 @@ async def ast_classdef(self, arg):
11061113 raise SyntaxError (f"{ val .name ()} statement outside loop" )
11071114 self .sym_table = self .sym_table_stack .pop ()
11081115
1116+ decorators = [await self .aeval (dec ) for dec in arg .decorator_list ]
11091117 sym_table ["__init__evalfunc_wrap__" ] = None
11101118 if "__init__" in sym_table :
11111119 sym_table ["__init__evalfunc_wrap__" ] = sym_table ["__init__" ]
11121120 del sym_table ["__init__" ]
1113- sym_table_assign [arg .name ].set (type (arg .name , tuple (bases ), sym_table ))
1121+ cls = metaclass (arg .name , tuple (bases ), sym_table , ** keywords )
1122+ if inspect .iscoroutine (cls ):
1123+ cls = await cls
1124+ for dec_func in reversed (decorators ):
1125+ cls = await self .call_func (dec_func , None , cls )
1126+ sym_table_assign [arg .name ].set (cls )
11141127
11151128 async def ast_functiondef (self , arg , async_func = False ):
11161129 """Evaluate function definition."""
@@ -1487,7 +1500,11 @@ async def ast_augassign(self, arg):
14871500 await self .recurse_assign (arg .target , new_val )
14881501
14891502 async def ast_annassign (self , arg ):
1490- """Execute type hint assignment statement (just ignore the type hint)."""
1503+ """Execute type hint assignment statement and track __annotations__."""
1504+ if isinstance (arg .target , ast .Name ):
1505+ annotations = self .sym_table .setdefault ("__annotations__" , {})
1506+ if arg .annotation :
1507+ annotations [arg .target .id ] = await self .aeval (arg .annotation )
14911508 if arg .value is not None :
14921509 rhs = await self .aeval (arg .value )
14931510 await self .recurse_assign (arg .target , rhs )
@@ -1961,19 +1978,25 @@ async def call_func(self, func, func_name, *args, **kwargs):
19611978 if isinstance (func , (EvalFunc , EvalFuncVar )):
19621979 return await func .call (self , * args , ** kwargs )
19631980 if inspect .isclass (func ) and hasattr (func , "__init__evalfunc_wrap__" ):
1964- inst = func ()
1981+ has_init_wrapper = getattr (func , "__init__evalfunc_wrap__" ) is not None
1982+ inst = func (* args , ** kwargs ) if not has_init_wrapper else func ()
19651983 #
19661984 # we use weak references when we bind the method calls to the instance inst;
19671985 # otherwise these self references cause the object to not be deleted until
19681986 # it is later garbage collected
19691987 #
19701988 inst_weak = weakref .ref (inst )
19711989 for name in dir (inst ):
1972- value = getattr (inst , name )
1990+ try :
1991+ value = getattr (inst , name )
1992+ except AttributeError :
1993+ # same effect as hasattr (which also catches AttributeError)
1994+ # dir() may list names that aren't actually accessible attributes
1995+ continue
19731996 if type (value ) is not EvalFuncVar :
19741997 continue
19751998 setattr (inst , name , EvalFuncVarClassInst (value .get_func (), value .get_ast_ctx (), inst_weak ))
1976- if getattr ( func , "__init__evalfunc_wrap__" ) is not None :
1999+ if has_init_wrapper :
19772000 #
19782001 # since our __init__ function is async, call the renamed one
19792002 #
@@ -2197,11 +2220,9 @@ def format_exc(self, exc, lineno=None, col_offset=None, short=False, code_list=N
21972220 else :
21982221 mesg = f"Exception in <{ self .filename } >:\n "
21992222 mesg += f"{ type (exc ).__name__ } : { exc } "
2200- #
2201- # to get a more detailed traceback on exception (eg, when chasing an internal
2202- # error), add an "import traceback" above, and uncomment this next line
2203- #
2204- # return mesg + "\n" + traceback.format_exc(-1)
2223+
2224+ if _LOGGER .isEnabledFor (logging .DEBUG ):
2225+ mesg += "\n " + traceback .format_exc ()
22052226 return mesg
22062227
22072228 def get_exception (self ):
0 commit comments