• 
      

    Add support for spy operations.

    Review Request #11153 — Created Aug. 30, 2020 and submitted

    Information

    kgb
    master

    Reviewers

    kgb

    A spy operation is an action or set of possible actions that are
    performed when a function is called. They're an alternative to
    overriding a function explicitly, and instead work as reusable shortcuts
    for common patterns used when spying. Examples would be raising an
    exception, or dispatching a function based on the arguments provided in
    the call.

    These can be thought of as glorified functions, and in fact they
    basically are, under the hood, but are designed to be extensible so that
    projects can more easily maintain common spy patterns.

    There are some built-in operations provided by kgb:

    • SpyOpMatchAny allows a caller to provide a list of all possible sets
      of arguments that may be in one or more calls, triggering spy behavior
      for the particular match (allowing call_original/call_fake to be
      conditional on the arguments). Any call not provided in the list will
      raise an UnexpectedCallError assertion.

    • SpyOpMatchInOrder is similar to SpyOpMatchAny, but the calls
      must be in the order specified (which is useful for ensuring an order
      of operations).

    • SpyOpRaise takes an exception instance and raises it when the
      function is called (preventing a caller from having to define a
      wrapping function).

    • SpyOpReturn takes a return value and returns it when the function is
      called (similar to defining a simple lambda, but better specifying the
      intent).

    These are set with an op= argument, instead of a call_fake=. For
    example::

    spy_on(pen.emit_poison, op=kgb.SpyOpRaise(PoisonEmptyError()))
    

    Or, for one of the more complex examples:

    spy_on(traps.trigger, op=kgb.SpyOpMatchAny([
        {
            'args': ('hallway_lasers',),
            'call_fake': _send_wolves,
        },
        {
            'args': ('trap_tile',),
            'call_fake': _spill_hot_oil,
        },
        {
            'args': ('infrared_camera',),
            'kwargs': {
                'sector': 'underground_passage',
            },
            'call_original': False,
        },
    ]))
    

    Unit tests pass on all supported versions of Python.

    Plugged each of these operations into some real-world code and made sure
    they worked as expected.

    Summary ID
    Add support for spy operations.
    A spy operation is an action or set of possible actions that are performed when a function is called. They're an alternative to overriding a function explicitly, and instead work as reusable shortcuts for common patterns used when spying. Examples would be raising an exception, or dispatching a function based on the arguments provided in the call. These can be thought of as glorified functions, and in fact they basically are, under the hood, but are designed to be extensible so that projects can more easily maintain common spy patterns. There are some built-in operations provided by kgb: * `SpyOpMatchAny` allows a caller to provide a list of all possible sets of arguments that may be in one or more calls, triggering spy behavior for the particular match (allowing `call_original`/`call_fake` to be conditional on the arguments). Any call not provided in the list will raise an `UnexpectedCallError` assertion. * `SpyOpMatchInOrder` is similar to `SpyOpMatchAny`, but the calls must be in the order specified (which is useful for ensuring an order of operations). * `SpyOpRaise` takes an exception instance and raises it when the function is called (preventing a caller from having to define a wrapping function). * `SpyOpReturn` takes a return value and returns it when the function is called (similar to defining a simple lambda, but better specifying the intent). These are set with an `op=` argument, instead of a `call_fake=`. For example:: spy_on(pen.emit_poison, op=kgb.SpyOpRaise(PoisonEmptyError())) Or, for one of the more complex examples: spy_on(traps.trigger, op=kgb.SpyOpMatchAny([ { 'args': ('hallway_lasers',), 'call_fake': _send_wolves, }, { 'args': ('trap_tile',), 'call_fake': _spill_hot_oil, }, { 'args': ('infrared_camera',), 'kwargs': { 'sector': 'underground_passage', }, 'call_original': False, }, ]))
    7222f42482f4d8206ba3258681d01f4dcf543a85
    Description From Last Updated

    F401 'kgb.calls.SpyCall' imported but unused

    reviewbotreviewbot

    E501 line too long (82 > 79 characters)

    reviewbotreviewbot

    F401 'kgb.agency.SpyAgency' imported but unused

    reviewbotreviewbot

    F841 local variable 'spy' is assigned to but never used

    reviewbotreviewbot

    F841 local variable 'spy' is assigned to but never used

    reviewbotreviewbot

    F841 local variable 'spy' is assigned to but never used

    reviewbotreviewbot

    F841 local variable 'spy' is assigned to but never used

    reviewbotreviewbot

    F841 local variable 'spy' is assigned to but never used

    reviewbotreviewbot

    F841 local variable 'spy' is assigned to but never used

    reviewbotreviewbot

    F841 local variable 'spy' is assigned to but never used

    reviewbotreviewbot

    F841 local variable 'spy' is assigned to but never used

    reviewbotreviewbot

    F841 local variable 'spy' is assigned to but never used

    reviewbotreviewbot

    F841 local variable 'spy' is assigned to but never used

    reviewbotreviewbot

    F841 local variable 'spy' is assigned to but never used

    reviewbotreviewbot

    F841 local variable 'spy' is assigned to but never used

    reviewbotreviewbot

    F841 local variable 'spy' is assigned to but never used

    reviewbotreviewbot

    F841 local variable 'spy' is assigned to but never used

    reviewbotreviewbot

    F841 local variable 'spy' is assigned to but never used

    reviewbotreviewbot

    F841 local variable 'spy' is assigned to but never used

    reviewbotreviewbot

    F841 local variable 'spy' is assigned to but never used

    reviewbotreviewbot

    F841 local variable 'spy' is assigned to but never used

    reviewbotreviewbot

    F841 local variable 'spy' is assigned to but never used

    reviewbotreviewbot
    Checks run (1 failed, 1 succeeded)
    flake8 failed.
    JSHint passed.

    flake8

    chipx86
    david
    1. Ship It!
    2. 
        
    chipx86
    Review request changed
    Status:
    Completed
    Change Summary:
    Pushed to master (743d50e)