@@ -2333,98 +2333,117 @@ def components_data(block, ctype, sort=None, sort_by_keys=False, sort_by_names=F
2333
2333
BlockData ._Block_reserved_words = set (dir (Block ()))
2334
2334
2335
2335
2336
- class _IndexedCustomBlockMeta (type ):
2337
- """Metaclass for creating an indexed custom block."""
2338
-
2339
- pass
2340
-
2341
-
2342
- class _ScalarCustomBlockMeta (type ):
2343
- """Metaclass for creating a scalar custom block."""
2344
-
2345
- def __new__ (meta , name , bases , dct ):
2346
- def __init__ (self , * args , ** kwargs ):
2347
- # bases[0] is the custom block data object
2348
- bases [0 ].__init__ (self , component = self )
2349
- # bases[1] is the custom block object that
2350
- # is used for declaration
2351
- bases [1 ].__init__ (self , * args , ** kwargs )
2352
-
2353
- dct ["__init__" ] = __init__
2354
- return type .__new__ (meta , name , bases , dct )
2336
+ class ScalarCustomBlockMixin (object ):
2337
+ def __init__ (self , * args , ** kwargs ):
2338
+ # __bases__ for the ScalarCustomBlock is
2339
+ #
2340
+ # (ScalarCustomBlockMixin, {custom_data}, {custom_block})
2341
+ #
2342
+ # Unfortunately, we cannot guarantee that this is being called
2343
+ # from the ScalarCustomBlock (someone could have inherited from
2344
+ # that class to make another scalar class). We will walk up the
2345
+ # MRO to find the Scalar class (which should be the only class
2346
+ # that has this Mixin as the first base class)
2347
+ for cls in self .__class__ .__mro__ :
2348
+ if cls .__bases__ [0 ] is ScalarCustomBlockMixin :
2349
+ _mixin , _data , _block = cls .__bases__
2350
+ _data .__init__ (self , component = self )
2351
+ _block .__init__ (self , * args , ** kwargs )
2352
+ break
2355
2353
2356
2354
2357
2355
class CustomBlock (Block ):
2358
2356
"""The base class used by instances of custom block components"""
2359
2357
2360
- def __init__ (self , * args , ** kwds ):
2358
+ def __init__ (self , * args , ** kwargs ):
2361
2359
if self ._default_ctype is not None :
2362
- kwds .setdefault ('ctype' , self ._default_ctype )
2363
- Block .__init__ (self , * args , ** kwds )
2364
-
2365
- def __new__ (cls , * args , ** kwds ):
2366
- if cls .__name__ .startswith ('_Indexed' ) or cls .__name__ .startswith ('_Scalar' ):
2367
- # we are entering here the second time (recursive)
2368
- # therefore, we need to create what we have
2369
- return super (CustomBlock , cls ).__new__ (cls )
2360
+ kwargs .setdefault ('ctype' , self ._default_ctype )
2361
+ Block .__init__ (self , * args , ** kwargs )
2362
+
2363
+ def __new__ (cls , * args , ** kwargs ):
2364
+ if cls .__bases__ [0 ] is not CustomBlock :
2365
+ # we are creating a class other than the "generic" derived
2366
+ # custom block class. We can assume that the routing of the
2367
+ # generic block class to the specific Scalar or Indexed
2368
+ # subclass has already occurred and we can pass control up
2369
+ # to (toward) object.__new__()
2370
+ return super ().__new__ (cls , * args , ** kwargs )
2371
+ # If the first base class is this CustomBlock class, then the
2372
+ # user is attempting to create the "generic" block class.
2373
+ # Depending on the arguments, we need to map this to either the
2374
+ # Scalar or Indexed block subclass.
2370
2375
if not args or (args [0 ] is UnindexedComponent_set and len (args ) == 1 ):
2371
- n = _ScalarCustomBlockMeta (
2372
- "_Scalar%s" % (cls .__name__ ,), (cls ._ComponentDataClass , cls ), {}
2373
- )
2374
- return n .__new__ (n )
2376
+ return super ().__new__ (cls ._scalar_custom_block , * args , ** kwargs )
2375
2377
else :
2376
- n = _IndexedCustomBlockMeta ("_Indexed%s" % (cls .__name__ ,), (cls ,), {})
2377
- return n .__new__ (n )
2378
+ return super ().__new__ (cls ._indexed_custom_block , * args , ** kwargs )
2378
2379
2379
2380
2380
2381
def declare_custom_block (name , new_ctype = None ):
2381
2382
"""Decorator to declare components for a custom block data class
2382
2383
2383
- >>> @declare_custom_block(name=FooBlock)
2384
+ >>> @declare_custom_block(name=" FooBlock" )
2384
2385
... class FooBlockData(BlockData):
2385
2386
... # custom block data class
2386
2387
... pass
2387
2388
"""
2388
2389
2389
- def proc_dec ( cls ):
2390
- # this is the decorator function that
2391
- # creates the block component class
2390
+ def block_data_decorator ( block_data ):
2391
+ # this is the decorator function that creates the block
2392
+ # component classes
2392
2393
2393
- # Default (derived) Block attributes
2394
- clsbody = {
2395
- "__module__" : cls .__module__ , # magic to fix the module
2396
- # Default IndexedComponent data object is the decorated class:
2397
- "_ComponentDataClass" : cls ,
2398
- # By default this new block does not declare a new ctype
2399
- "_default_ctype" : None ,
2400
- }
2401
-
2402
- c = type (
2394
+ # Declare the new Block component (derived from CustomBlock)
2395
+ # corresponding to the BlockData that we are decorating
2396
+ #
2397
+ # Note the use of `type(CustomBlock)` to pick up the metaclass
2398
+ # that was used to create the CustomBlock (in general, it should
2399
+ # be `type`)
2400
+ comp = type (CustomBlock )(
2403
2401
name , # name of new class
2404
2402
(CustomBlock ,), # base classes
2405
- clsbody , # class body definitions (will populate __dict__)
2403
+ # class body definitions (populate the new class' __dict__)
2404
+ {
2405
+ # ensure the created class is associated with the calling module
2406
+ "__module__" : block_data .__module__ ,
2407
+ # Default IndexedComponent data object is the decorated class:
2408
+ "_ComponentDataClass" : block_data ,
2409
+ # By default this new block does not declare a new ctype
2410
+ "_default_ctype" : None ,
2411
+ },
2406
2412
)
2407
2413
2408
2414
if new_ctype is not None :
2409
2415
if new_ctype is True :
2410
- c ._default_ctype = c
2411
- elif type (new_ctype ) is type :
2412
- c ._default_ctype = new_ctype
2416
+ comp ._default_ctype = comp
2417
+ elif isinstance (new_ctype , type ) :
2418
+ comp ._default_ctype = new_ctype
2413
2419
else :
2414
2420
raise ValueError (
2415
2421
"Expected new_ctype to be either type "
2416
2422
"or 'True'; received: %s" % (new_ctype ,)
2417
2423
)
2418
2424
2419
- # Register the new Block type in the same module as the BlockData
2420
- setattr (sys .modules [cls .__module__ ], name , c )
2421
- # TODO: can we also register concrete Indexed* and Scalar*
2422
- # classes into the original BlockData module (instead of relying
2423
- # on metaclasses)?
2425
+ # Declare Indexed and Scalar versions of the custom block. We
2426
+ # will register them both with the calling module scope, and
2427
+ # with the CustomBlock (so that CustomBlock.__new__ can route
2428
+ # the object creation to the correct class)
2429
+ comp ._indexed_custom_block = type (comp )(
2430
+ "Indexed" + name ,
2431
+ (comp ,),
2432
+ { # ensure the created class is associated with the calling module
2433
+ "__module__" : block_data .__module__
2434
+ },
2435
+ )
2436
+ comp ._scalar_custom_block = type (comp )(
2437
+ "Scalar" + name ,
2438
+ (ScalarCustomBlockMixin , block_data , comp ),
2439
+ { # ensure the created class is associated with the calling module
2440
+ "__module__" : block_data .__module__
2441
+ },
2442
+ )
2424
2443
2425
- # are these necessary?
2426
- setattr ( cls , '_orig_name' , name )
2427
- setattr (cls , '_orig_module' , cls . __module__ )
2428
- return cls
2444
+ # Register the new Block types in the same module as the BlockData
2445
+ for _cls in ( comp , comp . _indexed_custom_block , comp . _scalar_custom_block ):
2446
+ setattr (sys . modules [ block_data . __module__ ], _cls . __name__ , _cls )
2447
+ return block_data
2429
2448
2430
- return proc_dec
2449
+ return block_data_decorator
0 commit comments