1# event/api.py 
    2# Copyright (C) 2005-2021 the SQLAlchemy authors and contributors 
    3# <see AUTHORS file> 
    4# 
    5# This module is part of SQLAlchemy and is released under 
    6# the MIT License: http://www.opensource.org/licenses/mit-license.php 
    7 
    8"""Public API functions for the event system. 
    9 
    10""" 
    11from __future__ import absolute_import 
    12 
    13from .base import _registrars 
    14from .registry import _EventKey 
    15from .. import exc 
    16from .. import util 
    17 
    18 
    19CANCEL = util.symbol("CANCEL") 
    20NO_RETVAL = util.symbol("NO_RETVAL") 
    21 
    22 
    23def _event_key(target, identifier, fn): 
    24    for evt_cls in _registrars[identifier]: 
    25        tgt = evt_cls._accept_with(target) 
    26        if tgt is not None: 
    27            return _EventKey(target, identifier, fn, tgt) 
    28    else: 
    29        raise exc.InvalidRequestError( 
    30            "No such event '%s' for target '%s'" % (identifier, target) 
    31        ) 
    32 
    33 
    34def listen(target, identifier, fn, *args, **kw): 
    35    """Register a listener function for the given target. 
    36 
    37    The :func:`.listen` function is part of the primary interface for the 
    38    SQLAlchemy event system, documented at :ref:`event_toplevel`. 
    39 
    40    e.g.:: 
    41 
    42        from sqlalchemy import event 
    43        from sqlalchemy.schema import UniqueConstraint 
    44 
    45        def unique_constraint_name(const, table): 
    46            const.name = "uq_%s_%s" % ( 
    47                table.name, 
    48                list(const.columns)[0].name 
    49            ) 
    50        event.listen( 
    51                UniqueConstraint, 
    52                "after_parent_attach", 
    53                unique_constraint_name) 
    54 
    55 
    56    A given function can also be invoked for only the first invocation 
    57    of the event using the ``once`` argument:: 
    58 
    59        def on_config(): 
    60            do_config() 
    61 
    62        event.listen(Mapper, "before_configure", on_config, once=True) 
    63 
    64    .. versionadded:: 0.9.4 Added ``once=True`` to :func:`.event.listen` 
    65       and :func:`.event.listens_for`. 
    66 
    67    .. warning:: The ``once`` argument does not imply automatic de-registration 
    68       of the listener function after it has been invoked a first time; a 
    69       listener entry will remain associated with the target object. 
    70       Associating an arbitrarily high number of listeners without explicitly 
    71       removing them will cause memory to grow unbounded even if ``once=True`` 
    72       is specified. 
    73 
    74    .. note:: 
    75 
    76        The :func:`.listen` function cannot be called at the same time 
    77        that the target event is being run.   This has implications 
    78        for thread safety, and also means an event cannot be added 
    79        from inside the listener function for itself.  The list of 
    80        events to be run are present inside of a mutable collection 
    81        that can't be changed during iteration. 
    82 
    83        Event registration and removal is not intended to be a "high 
    84        velocity" operation; it is a configurational operation.  For 
    85        systems that need to quickly associate and deassociate with 
    86        events at high scale, use a mutable structure that is handled 
    87        from inside of a single listener. 
    88 
    89        .. versionchanged:: 1.0.0 - a ``collections.deque()`` object is now 
    90           used as the container for the list of events, which explicitly 
    91           disallows collection mutation while the collection is being 
    92           iterated. 
    93 
    94    .. seealso:: 
    95 
    96        :func:`.listens_for` 
    97 
    98        :func:`.remove` 
    99 
    100    """ 
    101 
    102    _event_key(target, identifier, fn).listen(*args, **kw) 
    103 
    104 
    105def listens_for(target, identifier, *args, **kw): 
    106    """Decorate a function as a listener for the given target + identifier. 
    107 
    108    The :func:`.listens_for` decorator is part of the primary interface for the 
    109    SQLAlchemy event system, documented at :ref:`event_toplevel`. 
    110 
    111    e.g.:: 
    112 
    113        from sqlalchemy import event 
    114        from sqlalchemy.schema import UniqueConstraint 
    115 
    116        @event.listens_for(UniqueConstraint, "after_parent_attach") 
    117        def unique_constraint_name(const, table): 
    118            const.name = "uq_%s_%s" % ( 
    119                table.name, 
    120                list(const.columns)[0].name 
    121            ) 
    122 
    123    A given function can also be invoked for only the first invocation 
    124    of the event using the ``once`` argument:: 
    125 
    126        @event.listens_for(Mapper, "before_configure", once=True) 
    127        def on_config(): 
    128            do_config() 
    129 
    130 
    131    .. versionadded:: 0.9.4 Added ``once=True`` to :func:`.event.listen` 
    132       and :func:`.event.listens_for`. 
    133 
    134    .. warning:: The ``once`` argument does not imply automatic de-registration 
    135       of the listener function after it has been invoked a first time; a 
    136       listener entry will remain associated with the target object. 
    137       Associating an arbitrarily high number of listeners without explicitly 
    138       removing them will cause memory to grow unbounded even if ``once=True`` 
    139       is specified. 
    140 
    141    .. seealso:: 
    142 
    143        :func:`.listen` - general description of event listening 
    144 
    145    """ 
    146 
    147    def decorate(fn): 
    148        listen(target, identifier, fn, *args, **kw) 
    149        return fn 
    150 
    151    return decorate 
    152 
    153 
    154def remove(target, identifier, fn): 
    155    """Remove an event listener. 
    156 
    157    The arguments here should match exactly those which were sent to 
    158    :func:`.listen`; all the event registration which proceeded as a result 
    159    of this call will be reverted by calling :func:`.remove` with the same 
    160    arguments. 
    161 
    162    e.g.:: 
    163 
    164        # if a function was registered like this... 
    165        @event.listens_for(SomeMappedClass, "before_insert", propagate=True) 
    166        def my_listener_function(*arg): 
    167            pass 
    168 
    169        # ... it's removed like this 
    170        event.remove(SomeMappedClass, "before_insert", my_listener_function) 
    171 
    172    Above, the listener function associated with ``SomeMappedClass`` was also 
    173    propagated to subclasses of ``SomeMappedClass``; the :func:`.remove` 
    174    function will revert all of these operations. 
    175 
    176    .. versionadded:: 0.9.0 
    177 
    178    .. note:: 
    179 
    180        The :func:`.remove` function cannot be called at the same time 
    181        that the target event is being run.   This has implications 
    182        for thread safety, and also means an event cannot be removed 
    183        from inside the listener function for itself.  The list of 
    184        events to be run are present inside of a mutable collection 
    185        that can't be changed during iteration. 
    186 
    187        Event registration and removal is not intended to be a "high 
    188        velocity" operation; it is a configurational operation.  For 
    189        systems that need to quickly associate and deassociate with 
    190        events at high scale, use a mutable structure that is handled 
    191        from inside of a single listener. 
    192 
    193        .. versionchanged:: 1.0.0 - a ``collections.deque()`` object is now 
    194           used as the container for the list of events, which explicitly 
    195           disallows collection mutation while the collection is being 
    196           iterated. 
    197 
    198    .. seealso:: 
    199 
    200        :func:`.listen` 
    201 
    202    """ 
    203    _event_key(target, identifier, fn).remove() 
    204 
    205 
    206def contains(target, identifier, fn): 
    207    """Return True if the given target/ident/fn is set up to listen. 
    208 
    209    .. versionadded:: 0.9.0 
    210 
    211    """ 
    212 
    213    return _event_key(target, identifier, fn).contains()