pg.detour

Accessible via pg.detour, pg.detouring.detour.

detour(mappings)

Context manager for detouring object creation.

At times, we want to replace an object of a class to an object of a different class. Usually, we do so by passing the object as a function argument using dependency injection. However, it’s not always possible to expose those internal objects as parameters to the class, as we cannot predict what needs to be customized in future. Also, exposing too many arguments will hurt usability, it’s big burden to figure out 20 arguments of a function for a user to get started.

pg.detour provides another option for object replacement in Python, which creates a context in which some source classes can be detoured to specified destination classes or functions. For example, the code snippet below will detour instantation of class A to class B, and vice-versa:

class A:
  pass

class B:
  pass

# Exchange class A and class B.
with pg.detour([(A, B), (B, A)]):
  a = A()   # a is a B object.
  b = B()   # b is an A object.

Detour destination can be a function, which allows users to intercept the arguments passed to the class constructor. For example:

class Foo:
  def __init__(self, value):
    self.value = value

class Bar:
  def __init__(self, value):
    self.value = value

def detoured_foo(cls, value):
  # cls is the original class before detour.
  return Bar(value + 1)

with pg.detour([(Foo, detoured_foo)]):
  f = Foo(1)   # f will be Bar(2).

Detour can be nested. The outer scope mappings take precedence over the mappings from the inner loop, allowing users to change object creation behaviors from the outside. For example, the following code will detour class A to class C:

with pg.detour([(A, C)]):
  with pg.detour([A, B]):
    a = A()   # a is a C object.

Detour is transisive across the inner and outer scope. For example, the code below will detour class A to class C through B:

with pg.detour([(B, C)]):
  a1 = A()     # a1 is an A object.
  with pg.detour([A, B]):
    a2 = A()    # a2 is a C object. (A -> B -> C)

Detour is thread-sfe.

Parameters:

mappings – A sequence of tuple (src_cls, dest_cls_or_fn) as mappings for the detour - ‘src_cls’ is the source class to be detoured, while ‘dest_cls_or_fn’ is the destination class or function. When it’s a class, its __init__ method should have the same signature as the __init__ of the original class. When it’s a function, it should accept a positional argument cls, for passing the original class that is being detoured, followed by all the arguments that the original class should accept. For example, a class with __init__(self, x, *args, y, **kwargs) can be detoured to a function with signature (cls, x, *args, y, **kwargs).

Yields:

Resolved detour mappings.

Raises:

TypeError – If the first item in each mapping is not a class, or the second item in each mapping is neither a class nor a function.