@@ -357,7 +357,9 @@ def decode(self, json_obj):
357
357
# this connection will be converted to a local stub.
358
358
return self ._datatransferer .load (json_obj )
359
359
360
- def get_local_class (self , name , obj_id = None , is_returned_exception = False ):
360
+ def get_local_class (
361
+ self , name , obj_id = None , is_returned_exception = False , is_parent = False
362
+ ):
361
363
# Gets (and creates if needed), the class mapping to the remote
362
364
# class of name 'name'.
363
365
@@ -367,6 +369,15 @@ def get_local_class(self, name, obj_id=None, is_returned_exception=False):
367
369
# - classes that are proxied regular classes AND NOT proxied exceptions
368
370
# - clases that are NOT proxied regular classes AND are proxied exceptions
369
371
name = get_canonical_name (name , self ._aliases )
372
+
373
+ def name_to_parent_name (name ):
374
+ return "parent:%s" % name
375
+
376
+ if is_parent :
377
+ lookup_name = name_to_parent_name (name )
378
+ else :
379
+ lookup_name = name
380
+
370
381
if name == "function" :
371
382
# Special handling of pickled functions. We create a new class that
372
383
# simply has a __call__ method that will forward things back to
@@ -378,15 +389,15 @@ def get_local_class(self, name, obj_id=None, is_returned_exception=False):
378
389
self , "__function_%s" % obj_id , {}, {}, {}, {"__call__" : "" }, []
379
390
)
380
391
return self ._proxied_standalone_functions [obj_id ]
381
- local_class = self ._proxied_classes .get (name , None )
392
+ local_class = self ._proxied_classes .get (lookup_name , None )
382
393
if local_class is not None :
383
394
return local_class
384
395
385
396
is_proxied_exception = name in self ._exception_hierarchy
386
397
is_proxied_non_exception = name in self ._proxied_classnames
387
398
388
399
if not is_proxied_exception and not is_proxied_non_exception :
389
- if is_returned_exception :
400
+ if is_returned_exception or is_parent :
390
401
# In this case, it may be a local exception that we need to
391
402
# recreate
392
403
try :
@@ -405,7 +416,7 @@ def get_local_class(self, name, obj_id=None, is_returned_exception=False):
405
416
dict (getattr (local_exception , "__dict__" , {})),
406
417
)
407
418
wrapped_exception .__module__ = ex_module
408
- self ._proxied_classes [name ] = wrapped_exception
419
+ self ._proxied_classes [lookup_name ] = wrapped_exception
409
420
return wrapped_exception
410
421
411
422
raise ValueError ("Class '%s' is not known" % name )
@@ -422,31 +433,61 @@ def get_local_class(self, name, obj_id=None, is_returned_exception=False):
422
433
for parent in ex_parents :
423
434
# We always consider it to be an exception so that we wrap even non
424
435
# proxied builtins exceptions
425
- parents .append (self .get_local_class (parent , is_returned_exception = True ))
436
+ parents .append (self .get_local_class (parent , is_parent = True ))
426
437
# For regular classes, we get what it exposes from the server
427
438
if is_proxied_non_exception :
428
439
remote_methods = self .stub_request (None , OP_GETMETHODS , name )
429
440
else :
430
441
remote_methods = {}
431
442
432
- if is_proxied_exception and not is_proxied_non_exception :
433
- # This is a pure exception
443
+ parent_local_class = None
444
+ local_class = None
445
+ if is_proxied_exception :
446
+ # If we are a proxied exception AND a proxied class, we create two classes:
447
+ # actually:
448
+ # - the class itself (which is a stub)
449
+ # - the class in the capacity of a parent class (to another exception
450
+ # presumably). The reason for this is that if we have a exception/proxied
451
+ # class A and another B and B inherits from A, the MRO order would be all
452
+ # wrong since both A and B would also inherit from `Stub`. Here what we
453
+ # do is:
454
+ # - A_parent inherits from the actual parents of A (let's assume a
455
+ # builtin exception)
456
+ # - A inherits from (Stub, A_parent)
457
+ # - B_parent inherints from A_parent and the builtin Exception
458
+ # - B inherits from (Stub, B_parent)
434
459
ex_module , ex_name = name .rsplit ("." , 1 )
435
- local_class = ExceptionMetaClass (ex_name , (* parents ,), {})
436
- local_class .__module__ = ex_module
437
- else :
438
- # This method creates either a pure stub or a stub that is also an exception
460
+ parent_local_class = ExceptionMetaClass (ex_name , (* parents ,), {})
461
+ parent_local_class .__module__ = ex_module
462
+
463
+ if is_proxied_non_exception :
439
464
local_class = create_class (
440
465
self ,
441
466
name ,
442
467
self ._overrides .get (name , {}),
443
468
self ._getattr_overrides .get (name , {}),
444
469
self ._setattr_overrides .get (name , {}),
445
470
remote_methods ,
446
- parents ,
471
+ ( parent_local_class ,) if parent_local_class else None ,
447
472
)
448
- self ._proxied_classes [name ] = local_class
449
- return local_class
473
+ if parent_local_class :
474
+ self ._proxied_classes [name_to_parent_name (name )] = parent_local_class
475
+ if local_class :
476
+ self ._proxied_classes [name ] = local_class
477
+ else :
478
+ # This is for the case of pure proxied exceptions -- we want the lookup of
479
+ # foo.MyException to be the same class as looking of foo.MyException as a parent
480
+ # of another exception so `isinstance` works properly
481
+ self ._proxied_classes [name ] = parent_local_class
482
+
483
+ if is_parent :
484
+ # This should never happen but making sure
485
+ if not parent_local_class :
486
+ raise RuntimeError (
487
+ "Exception parent class %s is not a proxied exception" % name
488
+ )
489
+ return parent_local_class
490
+ return self ._proxied_classes [name ]
450
491
451
492
def can_pickle (self , obj ):
452
493
return getattr (obj , "___connection___" , None ) == self
0 commit comments