| # |
| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| # |
| |
| from __future__ import absolute_import |
| |
| import threading |
| |
| from cproton import PN_CONNECTION_BOUND, PN_CONNECTION_FINAL, PN_CONNECTION_INIT, PN_CONNECTION_LOCAL_CLOSE, \ |
| PN_CONNECTION_LOCAL_OPEN, PN_CONNECTION_REMOTE_CLOSE, PN_CONNECTION_REMOTE_OPEN, PN_CONNECTION_UNBOUND, PN_DELIVERY, \ |
| PN_LINK_FINAL, PN_LINK_FLOW, PN_LINK_INIT, PN_LINK_LOCAL_CLOSE, PN_LINK_LOCAL_DETACH, PN_LINK_LOCAL_OPEN, \ |
| PN_LINK_REMOTE_CLOSE, PN_LINK_REMOTE_DETACH, PN_LINK_REMOTE_OPEN, PN_PYREF, PN_SESSION_FINAL, PN_SESSION_INIT, \ |
| PN_SESSION_LOCAL_CLOSE, PN_SESSION_LOCAL_OPEN, PN_SESSION_REMOTE_CLOSE, PN_SESSION_REMOTE_OPEN, PN_TIMER_TASK, \ |
| PN_TRANSPORT, PN_TRANSPORT_CLOSED, PN_TRANSPORT_ERROR, PN_TRANSPORT_HEAD_CLOSED, PN_TRANSPORT_TAIL_CLOSED, \ |
| pn_cast_pn_connection, pn_cast_pn_delivery, pn_cast_pn_link, pn_cast_pn_session, pn_cast_pn_transport, \ |
| pn_class_name, pn_collector, pn_collector_free, pn_collector_more, pn_collector_peek, pn_collector_pop, \ |
| pn_collector_put, pn_collector_release, pn_event_class, pn_event_connection, pn_event_context, pn_event_delivery, \ |
| pn_event_link, pn_event_session, pn_event_transport, pn_event_type, pn_event_type_name, pn_py2void, pn_void2py |
| |
| from ._delivery import Delivery |
| from ._endpoints import Connection, Link, Session |
| from ._transport import Transport |
| |
| |
| class Collector: |
| |
| def __init__(self): |
| self._impl = pn_collector() |
| |
| def put(self, obj, etype): |
| pn_collector_put(self._impl, PN_PYREF, pn_py2void(obj), etype.number) |
| |
| def peek(self): |
| return Event.wrap(pn_collector_peek(self._impl)) |
| |
| def more(self): |
| return pn_collector_more(self._impl) |
| |
| def pop(self): |
| ev = self.peek() |
| pn_collector_pop(self._impl) |
| |
| def release(self): |
| pn_collector_release(self._impl) |
| |
| def __del__(self): |
| pn_collector_free(self._impl) |
| del self._impl |
| |
| |
| if "TypeExtender" not in globals(): |
| class TypeExtender: |
| def __init__(self, number): |
| self.number = number |
| |
| def next(self): |
| try: |
| return self.number |
| finally: |
| self.number += 1 |
| |
| |
| class EventType(object): |
| """ |
| Connects an event number to an event name, and is used |
| internally by :class:`Event` to represent all known |
| event types. A global list of events is maintained. An |
| :class:`EventType` created with a name but no number is |
| treated as an *extended* event, and is assigned an |
| internal event number starting at 10000. |
| """ |
| _lock = threading.Lock() |
| _extended = TypeExtender(10000) |
| TYPES = {} |
| |
| def __init__(self, name=None, number=None, method=None): |
| if name is None and number is None: |
| raise TypeError("extended events require a name") |
| try: |
| self._lock.acquire() |
| if name is None: |
| name = pn_event_type_name(number) |
| |
| if number is None: |
| number = self._extended.next() |
| |
| if method is None: |
| method = "on_%s" % name |
| |
| self.name = name |
| self.number = number |
| self.method = method |
| |
| self.TYPES[number] = self |
| finally: |
| self._lock.release() |
| |
| def __repr__(self): |
| return "EventType(name=%s, number=%d)" % (self.name, self.number) |
| |
| def __str__(self): |
| return self.name |
| |
| |
| def _dispatch(handler, method, *args): |
| m = getattr(handler, method, None) |
| if m: |
| m(*args) |
| elif hasattr(handler, "on_unhandled"): |
| handler.on_unhandled(method, *args) |
| |
| |
| class EventBase(object): |
| |
| def __init__(self, type): |
| self._type = type |
| |
| @property |
| def type(self): |
| """ |
| The type name for this event |
| |
| :type: ``str`` |
| """ |
| return self._type |
| |
| @property |
| def handler(self): |
| """ |
| The handler for this event type. Not implemented, always returns ``None``. |
| |
| :type: ``None`` |
| """ |
| return None |
| |
| def dispatch(self, handler, type=None): |
| """ |
| Process this event by sending it to all known handlers that |
| are valid for this event type. |
| |
| :param handler: Parent handler to process this event |
| :type handler: :class:`Handler` |
| :param type: Event type |
| :type type: :class:`EventType` |
| """ |
| type = type or self._type |
| _dispatch(handler, type.method, self) |
| if hasattr(handler, "handlers"): |
| for h in handler.handlers: |
| self.dispatch(h, type) |
| |
| def __repr__(self): |
| return "%s(%r)" % (self._type, self.context) |
| |
| |
| def _core(number, method): |
| return EventType(number=number, method=method) |
| |
| |
| def _internal(name): |
| return EventType(name=name) |
| |
| |
| wrappers = { |
| "pn_void": lambda x: pn_void2py(x), |
| "pn_pyref": lambda x: pn_void2py(x), |
| "pn_connection": lambda x: Connection.wrap(pn_cast_pn_connection(x)), |
| "pn_session": lambda x: Session.wrap(pn_cast_pn_session(x)), |
| "pn_link": lambda x: Link.wrap(pn_cast_pn_link(x)), |
| "pn_delivery": lambda x: Delivery.wrap(pn_cast_pn_delivery(x)), |
| "pn_transport": lambda x: Transport.wrap(pn_cast_pn_transport(x)) |
| } |
| |
| |
| class Event(EventBase): |
| """ |
| Notification of a state change in the protocol engine. |
| """ |
| TIMER_TASK = _core(PN_TIMER_TASK, "on_timer_task") |
| """A timer event has occurred.""" |
| |
| |
| CONNECTION_INIT = _core(PN_CONNECTION_INIT, "on_connection_init") |
| """ |
| The connection has been created. This is the first event that |
| will ever be issued for a connection. Events of this type point |
| to the relevant connection. |
| """ |
| |
| CONNECTION_BOUND = _core(PN_CONNECTION_BOUND, "on_connection_bound") |
| """ |
| The connection has been bound to a transport. This event is |
| issued when the :meth:`Transport.bind` operation is invoked. |
| """ |
| |
| CONNECTION_UNBOUND = _core(PN_CONNECTION_UNBOUND, "on_connection_unbound") |
| """ |
| The connection has been unbound from its transport. This event is |
| issued when the :meth:`Transport.unbind` operation is invoked. |
| """ |
| |
| CONNECTION_LOCAL_OPEN = _core(PN_CONNECTION_LOCAL_OPEN, "on_connection_local_open") |
| """ |
| The local connection endpoint has been closed. Events of this |
| type point to the relevant connection. |
| """ |
| |
| CONNECTION_LOCAL_CLOSE = _core(PN_CONNECTION_LOCAL_CLOSE, "on_connection_local_close") |
| """ |
| The local connection endpoint has been closed. Events of this |
| type point to the relevant connection. |
| """ |
| |
| CONNECTION_REMOTE_OPEN = _core(PN_CONNECTION_REMOTE_OPEN, "on_connection_remote_open") |
| """ |
| The remote endpoint has opened the connection. Events of this |
| type point to the relevant connection. |
| """ |
| |
| CONNECTION_REMOTE_CLOSE = _core(PN_CONNECTION_REMOTE_CLOSE, "on_connection_remote_close") |
| """ |
| The remote endpoint has closed the connection. Events of this |
| type point to the relevant connection. |
| """ |
| |
| CONNECTION_FINAL = _core(PN_CONNECTION_FINAL, "on_connection_final") |
| """ |
| The connection has been freed and any outstanding processing has |
| been completed. This is the final event that will ever be issued |
| for a connection. |
| """ |
| |
| |
| SESSION_INIT = _core(PN_SESSION_INIT, "on_session_init") |
| """ |
| The session has been created. This is the first event that will |
| ever be issued for a session. |
| """ |
| |
| SESSION_LOCAL_OPEN = _core(PN_SESSION_LOCAL_OPEN, "on_session_local_open") |
| """ |
| The local session endpoint has been opened. Events of this type |
| point to the relevant session. |
| """ |
| |
| SESSION_LOCAL_CLOSE = _core(PN_SESSION_LOCAL_CLOSE, "on_session_local_close") |
| """ |
| The local session endpoint has been closed. Events of this type |
| point ot the relevant session. |
| """ |
| |
| SESSION_REMOTE_OPEN = _core(PN_SESSION_REMOTE_OPEN, "on_session_remote_open") |
| """ |
| The remote endpoint has opened the session. Events of this type |
| point to the relevant session. |
| """ |
| |
| SESSION_REMOTE_CLOSE = _core(PN_SESSION_REMOTE_CLOSE, "on_session_remote_close") |
| """ |
| The remote endpoint has closed the session. Events of this type |
| point to the relevant session. |
| """ |
| |
| SESSION_FINAL = _core(PN_SESSION_FINAL, "on_session_final") |
| """ |
| The session has been freed and any outstanding processing has |
| been completed. This is the final event that will ever be issued |
| for a session. |
| """ |
| |
| |
| LINK_INIT = _core(PN_LINK_INIT, "on_link_init") |
| """ |
| The link has been created. This is the first event that will ever |
| be issued for a link. |
| """ |
| |
| LINK_LOCAL_OPEN = _core(PN_LINK_LOCAL_OPEN, "on_link_local_open") |
| """ |
| The local link endpoint has been opened. Events of this type |
| point ot the relevant link. |
| """ |
| |
| LINK_LOCAL_CLOSE = _core(PN_LINK_LOCAL_CLOSE, "on_link_local_close") |
| """ |
| The local link endpoint has been closed. Events of this type |
| point to the relevant link. |
| """ |
| |
| LINK_LOCAL_DETACH = _core(PN_LINK_LOCAL_DETACH, "on_link_local_detach") |
| """ |
| The local link endpoint has been detached. Events of this type |
| point to the relevant link. |
| """ |
| |
| LINK_REMOTE_OPEN = _core(PN_LINK_REMOTE_OPEN, "on_link_remote_open") |
| """ |
| The remote endpoint has opened the link. Events of this type |
| point to the relevant link. |
| """ |
| |
| LINK_REMOTE_CLOSE = _core(PN_LINK_REMOTE_CLOSE, "on_link_remote_close") |
| """ |
| The remote endpoint has closed the link. Events of this type |
| point to the relevant link. |
| """ |
| |
| LINK_REMOTE_DETACH = _core(PN_LINK_REMOTE_DETACH, "on_link_remote_detach") |
| """ |
| The remote endpoint has detached the link. Events of this type |
| point to the relevant link. |
| """ |
| |
| LINK_FLOW = _core(PN_LINK_FLOW, "on_link_flow") |
| """ |
| The flow control state for a link has changed. Events of this |
| type point to the relevant link. |
| """ |
| |
| LINK_FINAL = _core(PN_LINK_FINAL, "on_link_final") |
| """ |
| The link has been freed and any outstanding processing has been |
| completed. This is the final event that will ever be issued for a |
| link. Events of this type point to the relevant link. |
| """ |
| |
| |
| DELIVERY = _core(PN_DELIVERY, "on_delivery") |
| """ |
| A delivery has been created or updated. Events of this type point |
| to the relevant delivery. |
| """ |
| |
| |
| TRANSPORT = _core(PN_TRANSPORT, "on_transport") |
| """ |
| The transport has new data to read and/or write. Events of this |
| type point to the relevant transport. |
| """ |
| |
| TRANSPORT_ERROR = _core(PN_TRANSPORT_ERROR, "on_transport_error") |
| """ |
| Indicates that a transport error has occurred. Use :attr:`Transport.condition` |
| to access the details of the error from the associated transport. |
| """ |
| |
| TRANSPORT_HEAD_CLOSED = _core(PN_TRANSPORT_HEAD_CLOSED, "on_transport_head_closed") |
| """ |
| Indicates that the "head" or writing end of the transport has been closed. This |
| means the transport will never produce more bytes for output to |
| the network. Events of this type point to the relevant transport. |
| """ |
| |
| TRANSPORT_TAIL_CLOSED = _core(PN_TRANSPORT_TAIL_CLOSED, "on_transport_tail_closed") |
| """ |
| Indicates that the "tail" of the transport has been closed. This |
| means the transport will never be able to process more bytes from |
| the network. Events of this type point to the relevant transport. |
| """ |
| |
| TRANSPORT_CLOSED = _core(PN_TRANSPORT_CLOSED, "on_transport_closed") |
| """ |
| Indicates that the both the "head" and "tail" of the transport are |
| closed. Events of this type point to the relevant transport. |
| """ |
| |
| |
| # These events are now internal events in the python code |
| REACTOR_INIT = _internal("reactor_init") |
| """ |
| A reactor has been started. Events of this type point to the |
| reactor. |
| """ |
| |
| REACTOR_QUIESCED = _internal("reactor_quiesced") |
| """ |
| A reactor has no more events to process. Events of this type |
| point to the reactor. |
| """ |
| |
| REACTOR_FINAL = _internal("reactor_final") |
| """ |
| A reactor has been stopped. Events of this type point to the |
| reactor. |
| """ |
| |
| SELECTABLE_INIT = _internal("selectable_init") |
| SELECTABLE_UPDATED = _internal("selectable_updated") |
| SELECTABLE_READABLE = _internal("selectable_readable") |
| SELECTABLE_WRITABLE = _internal("selectable_writable") |
| SELECTABLE_EXPIRED = _internal("selectable_expired") |
| SELECTABLE_ERROR = _internal("selectable_error") |
| SELECTABLE_FINAL = _internal("selectable_final") |
| |
| @staticmethod |
| def wrap(impl): |
| if impl is None: |
| return None |
| |
| number = pn_event_type(impl) |
| cls = pn_event_class(impl) |
| |
| if cls: |
| clsname = pn_class_name(cls) |
| context = wrappers[clsname](pn_event_context(impl)) |
| |
| # check for an application defined ApplicationEvent and return that. This |
| # avoids an expensive wrap operation invoked by event.context |
| if cls == PN_PYREF and isinstance(context, EventBase): |
| return context |
| else: |
| clsname = None |
| |
| event = Event(impl, number, clsname, context) |
| return event |
| |
| def __init__(self, impl, number, clsname, context): |
| self._type = EventType.TYPES[number] |
| self._clsname = clsname |
| self._context = context |
| |
| # Do all this messing around to avoid duplicate wrappers |
| if issubclass(type(context), Delivery): |
| self._delivery = context |
| else: |
| self._delivery = Delivery.wrap(pn_event_delivery(impl)) |
| if self._delivery: |
| self._link = self._delivery.link |
| elif issubclass(type(context), Link): |
| self._link = context |
| else: |
| self._link = Link.wrap(pn_event_link(impl)) |
| if self._link: |
| self._session = self._link.session |
| elif issubclass(type(context), Session): |
| self._session = context |
| else: |
| self._session = Session.wrap(pn_event_session(impl)) |
| if self._session: |
| self._connection = self._session.connection |
| elif issubclass(type(context), Connection): |
| self._connection = context |
| else: |
| self._connection = Connection.wrap(pn_event_connection(impl)) |
| |
| if issubclass(type(context), Transport): |
| self._transport = context |
| else: |
| self._transport = Transport.wrap(pn_event_transport(impl)) |
| |
| @property |
| def clazz(self): |
| """ |
| The name of the class associated with the event context. |
| |
| :type: ``str`` |
| """ |
| return self._clsname |
| |
| @property |
| def context(self): |
| """ |
| The context object associated with the event. |
| |
| :type: Depends on the type of event, and include the following: |
| - :class:`Connection` |
| - :class:`Session` |
| - :class:`Link` |
| - :class:`Delivery` |
| - :class:`Transport` |
| """ |
| return self._context |
| |
| @property |
| def handler(self): |
| """ |
| The handler for this event. The handler is determined by looking |
| at the following in order: |
| |
| - The link |
| - The session |
| - The connection |
| - The context object with an attribute "handler" |
| |
| If none of these has a handler, then ``None`` is returned. |
| """ |
| l = self.link |
| if l: |
| h = l.handler |
| if h: |
| return h |
| s = self.session |
| if s: |
| h = s.handler |
| if h: |
| return h |
| c = self.connection |
| if c: |
| h = c.handler |
| if h: |
| return h |
| c = self.context |
| if not c or not hasattr(c, 'handler'): |
| return None |
| h = c.handler |
| return h |
| |
| @property |
| def reactor(self): |
| """ |
| **Deprecated** - The :class:`reactor.Container` (was reactor) associated with the event. |
| """ |
| return self.container |
| |
| @property |
| def container(self): |
| """ |
| The :class:`reactor.Container` associated with the event. |
| """ |
| return self._transport._reactor |
| |
| def __getattr__(self, name): |
| """ |
| This will look for a property of the event as an attached context object of the same |
| type as the property (but lowercase) |
| """ |
| c = self.context |
| # Direct type or subclass of type |
| if type(c).__name__.lower() == name or name in [x.__name__.lower() for x in type(c).__bases__]: |
| return c |
| |
| # If the attached object is the wrong type then see if *it* has a property of that name |
| return getattr(c, name, None) |
| |
| @property |
| def transport(self): |
| """ |
| The transport associated with the event, or ``None`` if none |
| is associated with it. |
| |
| :type: :class:`Transport` |
| """ |
| return self._transport |
| |
| @property |
| def connection(self): |
| """ |
| The connection associated with the event, or ``None`` if none |
| is associated with it. |
| |
| :type: :class:`Connection` |
| """ |
| return self._connection |
| |
| @property |
| def session(self): |
| """ |
| The session associated with the event, or ``None`` if none |
| is associated with it. |
| |
| :type: :class:`Session` |
| """ |
| return self._session |
| |
| @property |
| def link(self): |
| """ |
| The link associated with the event, or ``None`` if none |
| is associated with it. |
| |
| :type: :class:`Link` |
| """ |
| return self._link |
| |
| @property |
| def sender(self): |
| """ |
| The sender link associated with the event, or ``None`` if |
| none is associated with it. This is essentially an alias for |
| link(), that does an additional check on the type of the |
| link. |
| |
| :type: :class:`Sender` (**<-- CHECK!**) |
| """ |
| l = self.link |
| if l and l.is_sender: |
| return l |
| else: |
| return None |
| |
| @property |
| def receiver(self): |
| """ |
| The receiver link associated with the event, or ``None`` if |
| none is associated with it. This is essentially an alias for |
| link(), that does an additional check on the type of the link. |
| |
| :type: :class:`Receiver` (**<-- CHECK!**) |
| """ |
| l = self.link |
| if l and l.is_receiver: |
| return l |
| else: |
| return None |
| |
| @property |
| def delivery(self): |
| """ |
| The delivery associated with the event, or ``None`` if none |
| is associated with it. |
| |
| :type: :class:`Delivery` |
| """ |
| return self._delivery |
| |
| |
| class LazyHandlers(object): |
| def __get__(self, obj, clazz): |
| if obj is None: |
| return self |
| ret = [] |
| obj.__dict__['handlers'] = ret |
| return ret |
| |
| |
| class Handler(object): |
| """ |
| An abstract handler for events which supports child handlers. |
| """ |
| handlers = LazyHandlers() |
| |
| # TODO What to do with on_error? |
| def add(self, handler, on_error=None): |
| """ |
| Add a child handler |
| |
| :param handler: A child handler |
| :type handler: :class:`Handler` or one of its derivatives. |
| :param on_error: Not used |
| """ |
| self.handlers.append(handler) |
| |
| def on_unhandled(self, method, *args): |
| """ |
| The callback for handling events which are not handled by |
| any other handler. |
| |
| :param method: The name of the intended handler method. |
| :type method: ``str`` |
| :param args: Arguments for the intended handler method. |
| """ |
| pass |