added celery and tasks but most important added tag_list
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
"""Low-level AMQP client for Python (fork of amqplib)"""
|
||||
# Copyright (C) 2007-2008 Barry Pederson <bp@barryp.org>
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
|
||||
from __future__ import absolute_import
|
||||
|
||||
VERSION = (1, 4, 9)
|
||||
__version__ = '.'.join(map(str, VERSION[0:3])) + ''.join(VERSION[3:])
|
||||
__author__ = 'Barry Pederson'
|
||||
__maintainer__ = 'Ask Solem'
|
||||
__contact__ = 'pyamqp@celeryproject.org'
|
||||
__homepage__ = 'http://github.com/celery/py-amqp'
|
||||
__docformat__ = 'restructuredtext'
|
||||
|
||||
# -eof meta-
|
||||
|
||||
#
|
||||
# Pull in the public items from the various sub-modules
|
||||
#
|
||||
from .basic_message import Message # noqa
|
||||
from .channel import Channel # noqa
|
||||
from .connection import Connection # noqa
|
||||
from .exceptions import ( # noqa
|
||||
AMQPError,
|
||||
ConnectionError,
|
||||
RecoverableConnectionError,
|
||||
IrrecoverableConnectionError,
|
||||
ChannelError,
|
||||
RecoverableChannelError,
|
||||
IrrecoverableChannelError,
|
||||
ConsumerCancelled,
|
||||
ContentTooLarge,
|
||||
NoConsumers,
|
||||
ConnectionForced,
|
||||
InvalidPath,
|
||||
AccessRefused,
|
||||
NotFound,
|
||||
ResourceLocked,
|
||||
PreconditionFailed,
|
||||
FrameError,
|
||||
FrameSyntaxError,
|
||||
InvalidCommand,
|
||||
ChannelNotOpen,
|
||||
UnexpectedFrame,
|
||||
ResourceError,
|
||||
NotAllowed,
|
||||
AMQPNotImplementedError,
|
||||
InternalError,
|
||||
error_for_code,
|
||||
__all__ as _all_exceptions,
|
||||
)
|
||||
from .utils import promise # noqa
|
||||
|
||||
__all__ = [
|
||||
'Connection',
|
||||
'Channel',
|
||||
'Message',
|
||||
] + _all_exceptions
|
||||
@@ -0,0 +1,93 @@
|
||||
"""Code common to Connection and Channel objects."""
|
||||
# Copyright (C) 2007-2008 Barry Pederson <bp@barryp.org>)
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
|
||||
from __future__ import absolute_import
|
||||
|
||||
from .exceptions import AMQPNotImplementedError, RecoverableConnectionError
|
||||
from .serialization import AMQPWriter
|
||||
|
||||
__all__ = ['AbstractChannel']
|
||||
|
||||
|
||||
class AbstractChannel(object):
|
||||
"""Superclass for both the Connection, which is treated
|
||||
as channel 0, and other user-created Channel objects.
|
||||
|
||||
The subclasses must have a _METHOD_MAP class property, mapping
|
||||
between AMQP method signatures and Python methods.
|
||||
|
||||
"""
|
||||
def __init__(self, connection, channel_id):
|
||||
self.connection = connection
|
||||
self.channel_id = channel_id
|
||||
connection.channels[channel_id] = self
|
||||
self.method_queue = [] # Higher level queue for methods
|
||||
self.auto_decode = False
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *exc_info):
|
||||
self.close()
|
||||
|
||||
def _send_method(self, method_sig, args=bytes(), content=None):
|
||||
"""Send a method for our channel."""
|
||||
conn = self.connection
|
||||
if conn is None:
|
||||
raise RecoverableConnectionError('connection already closed')
|
||||
|
||||
if isinstance(args, AMQPWriter):
|
||||
args = args.getvalue()
|
||||
|
||||
conn.method_writer.write_method(
|
||||
self.channel_id, method_sig, args, content,
|
||||
)
|
||||
|
||||
def close(self):
|
||||
"""Close this Channel or Connection"""
|
||||
raise NotImplementedError('Must be overriden in subclass')
|
||||
|
||||
def wait(self, allowed_methods=None, timeout=None):
|
||||
"""Wait for a method that matches our allowed_methods parameter (the
|
||||
default value of None means match any method), and dispatch to it."""
|
||||
method_sig, args, content = self.connection._wait_method(
|
||||
self.channel_id, allowed_methods, timeout)
|
||||
|
||||
return self.dispatch_method(method_sig, args, content)
|
||||
|
||||
def dispatch_method(self, method_sig, args, content):
|
||||
if content and \
|
||||
self.auto_decode and \
|
||||
hasattr(content, 'content_encoding'):
|
||||
try:
|
||||
content.body = content.body.decode(content.content_encoding)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
amqp_method = self._METHOD_MAP[method_sig]
|
||||
except KeyError:
|
||||
raise AMQPNotImplementedError(
|
||||
'Unknown AMQP method {0!r}'.format(method_sig))
|
||||
|
||||
if content is None:
|
||||
return amqp_method(self, args)
|
||||
else:
|
||||
return amqp_method(self, args, content)
|
||||
|
||||
#: Placeholder, the concrete implementations will have to
|
||||
#: supply their own versions of _METHOD_MAP
|
||||
_METHOD_MAP = {}
|
||||
@@ -0,0 +1,124 @@
|
||||
"""Messages for AMQP"""
|
||||
# Copyright (C) 2007-2008 Barry Pederson <bp@barryp.org>
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
|
||||
from __future__ import absolute_import
|
||||
|
||||
from .serialization import GenericContent
|
||||
|
||||
__all__ = ['Message']
|
||||
|
||||
|
||||
class Message(GenericContent):
|
||||
"""A Message for use with the Channnel.basic_* methods."""
|
||||
|
||||
#: Instances of this class have these attributes, which
|
||||
#: are passed back and forth as message properties between
|
||||
#: client and server
|
||||
PROPERTIES = [
|
||||
('content_type', 'shortstr'),
|
||||
('content_encoding', 'shortstr'),
|
||||
('application_headers', 'table'),
|
||||
('delivery_mode', 'octet'),
|
||||
('priority', 'octet'),
|
||||
('correlation_id', 'shortstr'),
|
||||
('reply_to', 'shortstr'),
|
||||
('expiration', 'shortstr'),
|
||||
('message_id', 'shortstr'),
|
||||
('timestamp', 'timestamp'),
|
||||
('type', 'shortstr'),
|
||||
('user_id', 'shortstr'),
|
||||
('app_id', 'shortstr'),
|
||||
('cluster_id', 'shortstr')
|
||||
]
|
||||
|
||||
def __init__(self, body='', children=None, channel=None, **properties):
|
||||
"""Expected arg types
|
||||
|
||||
body: string
|
||||
children: (not supported)
|
||||
|
||||
Keyword properties may include:
|
||||
|
||||
content_type: shortstr
|
||||
MIME content type
|
||||
|
||||
content_encoding: shortstr
|
||||
MIME content encoding
|
||||
|
||||
application_headers: table
|
||||
Message header field table, a dict with string keys,
|
||||
and string | int | Decimal | datetime | dict values.
|
||||
|
||||
delivery_mode: octet
|
||||
Non-persistent (1) or persistent (2)
|
||||
|
||||
priority: octet
|
||||
The message priority, 0 to 9
|
||||
|
||||
correlation_id: shortstr
|
||||
The application correlation identifier
|
||||
|
||||
reply_to: shortstr
|
||||
The destination to reply to
|
||||
|
||||
expiration: shortstr
|
||||
Message expiration specification
|
||||
|
||||
message_id: shortstr
|
||||
The application message identifier
|
||||
|
||||
timestamp: datetime.datetime
|
||||
The message timestamp
|
||||
|
||||
type: shortstr
|
||||
The message type name
|
||||
|
||||
user_id: shortstr
|
||||
The creating user id
|
||||
|
||||
app_id: shortstr
|
||||
The creating application id
|
||||
|
||||
cluster_id: shortstr
|
||||
Intra-cluster routing identifier
|
||||
|
||||
Unicode bodies are encoded according to the 'content_encoding'
|
||||
argument. If that's None, it's set to 'UTF-8' automatically.
|
||||
|
||||
example::
|
||||
|
||||
msg = Message('hello world',
|
||||
content_type='text/plain',
|
||||
application_headers={'foo': 7})
|
||||
|
||||
"""
|
||||
super(Message, self).__init__(**properties)
|
||||
self.body = body
|
||||
self.channel = channel
|
||||
|
||||
def __eq__(self, other):
|
||||
"""Check if the properties and bodies of this Message and another
|
||||
Message are the same.
|
||||
|
||||
Received messages may contain a 'delivery_info' attribute,
|
||||
which isn't compared.
|
||||
|
||||
"""
|
||||
try:
|
||||
return (super(Message, self).__eq__(other) and
|
||||
self.body == other.body)
|
||||
except AttributeError:
|
||||
return NotImplemented
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,262 @@
|
||||
"""Exceptions used by amqp"""
|
||||
# Copyright (C) 2007-2008 Barry Pederson <bp@barryp.org>
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
|
||||
from __future__ import absolute_import
|
||||
|
||||
from struct import pack, unpack
|
||||
|
||||
__all__ = [
|
||||
'AMQPError',
|
||||
'ConnectionError', 'ChannelError',
|
||||
'RecoverableConnectionError', 'IrrecoverableConnectionError',
|
||||
'RecoverableChannelError', 'IrrecoverableChannelError',
|
||||
'ConsumerCancelled', 'ContentTooLarge', 'NoConsumers',
|
||||
'ConnectionForced', 'InvalidPath', 'AccessRefused', 'NotFound',
|
||||
'ResourceLocked', 'PreconditionFailed', 'FrameError', 'FrameSyntaxError',
|
||||
'InvalidCommand', 'ChannelNotOpen', 'UnexpectedFrame', 'ResourceError',
|
||||
'NotConfirmed', 'NotAllowed', 'AMQPNotImplementedError', 'InternalError',
|
||||
]
|
||||
|
||||
|
||||
class AMQPError(Exception):
|
||||
code = 0
|
||||
|
||||
def __init__(self, reply_text=None, method_sig=None,
|
||||
method_name=None, reply_code=None):
|
||||
self.message = reply_text
|
||||
self.reply_code = reply_code or self.code
|
||||
self.reply_text = reply_text
|
||||
self.method_sig = method_sig
|
||||
self.method_name = method_name or ''
|
||||
if method_sig and not self.method_name:
|
||||
self.method_name = METHOD_NAME_MAP.get(method_sig, '')
|
||||
Exception.__init__(self, reply_code,
|
||||
reply_text, method_sig, self.method_name)
|
||||
|
||||
def __str__(self):
|
||||
if self.method:
|
||||
return '{0.method}: ({0.reply_code}) {0.reply_text}'.format(self)
|
||||
return self.reply_text or '<AMQPError: unknown error>'
|
||||
|
||||
@property
|
||||
def method(self):
|
||||
return self.method_name or self.method_sig
|
||||
|
||||
|
||||
class ConnectionError(AMQPError):
|
||||
pass
|
||||
|
||||
|
||||
class ChannelError(AMQPError):
|
||||
pass
|
||||
|
||||
|
||||
class RecoverableChannelError(ChannelError):
|
||||
pass
|
||||
|
||||
|
||||
class IrrecoverableChannelError(ChannelError):
|
||||
pass
|
||||
|
||||
|
||||
class RecoverableConnectionError(ConnectionError):
|
||||
pass
|
||||
|
||||
|
||||
class IrrecoverableConnectionError(ConnectionError):
|
||||
pass
|
||||
|
||||
|
||||
class Blocked(RecoverableConnectionError):
|
||||
pass
|
||||
|
||||
|
||||
class ConsumerCancelled(RecoverableConnectionError):
|
||||
pass
|
||||
|
||||
|
||||
class ContentTooLarge(RecoverableChannelError):
|
||||
code = 311
|
||||
|
||||
|
||||
class NoConsumers(RecoverableChannelError):
|
||||
code = 313
|
||||
|
||||
|
||||
class ConnectionForced(RecoverableConnectionError):
|
||||
code = 320
|
||||
|
||||
|
||||
class InvalidPath(IrrecoverableConnectionError):
|
||||
code = 402
|
||||
|
||||
|
||||
class AccessRefused(IrrecoverableChannelError):
|
||||
code = 403
|
||||
|
||||
|
||||
class NotFound(IrrecoverableChannelError):
|
||||
code = 404
|
||||
|
||||
|
||||
class NotConfirmed(RecoverableConnectionError):
|
||||
pass
|
||||
|
||||
|
||||
class ResourceLocked(RecoverableChannelError):
|
||||
code = 405
|
||||
|
||||
|
||||
class PreconditionFailed(IrrecoverableChannelError):
|
||||
code = 406
|
||||
|
||||
|
||||
class FrameError(IrrecoverableConnectionError):
|
||||
code = 501
|
||||
|
||||
|
||||
class FrameSyntaxError(IrrecoverableConnectionError):
|
||||
code = 502
|
||||
|
||||
|
||||
class InvalidCommand(IrrecoverableConnectionError):
|
||||
code = 503
|
||||
|
||||
|
||||
class ChannelNotOpen(IrrecoverableConnectionError):
|
||||
code = 504
|
||||
|
||||
|
||||
class UnexpectedFrame(IrrecoverableConnectionError):
|
||||
code = 505
|
||||
|
||||
|
||||
class ResourceError(RecoverableConnectionError):
|
||||
code = 506
|
||||
|
||||
|
||||
class NotAllowed(IrrecoverableConnectionError):
|
||||
code = 530
|
||||
|
||||
|
||||
class AMQPNotImplementedError(IrrecoverableConnectionError):
|
||||
code = 540
|
||||
|
||||
|
||||
class InternalError(IrrecoverableConnectionError):
|
||||
code = 541
|
||||
|
||||
|
||||
ERROR_MAP = {
|
||||
311: ContentTooLarge,
|
||||
313: NoConsumers,
|
||||
320: ConnectionForced,
|
||||
402: InvalidPath,
|
||||
403: AccessRefused,
|
||||
404: NotFound,
|
||||
405: ResourceLocked,
|
||||
406: PreconditionFailed,
|
||||
501: FrameError,
|
||||
502: FrameSyntaxError,
|
||||
503: InvalidCommand,
|
||||
504: ChannelNotOpen,
|
||||
505: UnexpectedFrame,
|
||||
506: ResourceError,
|
||||
530: NotAllowed,
|
||||
540: AMQPNotImplementedError,
|
||||
541: InternalError,
|
||||
}
|
||||
|
||||
|
||||
def error_for_code(code, text, method, default):
|
||||
try:
|
||||
return ERROR_MAP[code](text, method, reply_code=code)
|
||||
except KeyError:
|
||||
return default(text, method, reply_code=code)
|
||||
|
||||
|
||||
def raise_for_code(code, text, method, default):
|
||||
raise error_for_code(code, text, method, default)
|
||||
|
||||
|
||||
METHOD_NAME_MAP = {
|
||||
(10, 10): 'Connection.start',
|
||||
(10, 11): 'Connection.start_ok',
|
||||
(10, 20): 'Connection.secure',
|
||||
(10, 21): 'Connection.secure_ok',
|
||||
(10, 30): 'Connection.tune',
|
||||
(10, 31): 'Connection.tune_ok',
|
||||
(10, 40): 'Connection.open',
|
||||
(10, 41): 'Connection.open_ok',
|
||||
(10, 50): 'Connection.close',
|
||||
(10, 51): 'Connection.close_ok',
|
||||
(20, 10): 'Channel.open',
|
||||
(20, 11): 'Channel.open_ok',
|
||||
(20, 20): 'Channel.flow',
|
||||
(20, 21): 'Channel.flow_ok',
|
||||
(20, 40): 'Channel.close',
|
||||
(20, 41): 'Channel.close_ok',
|
||||
(30, 10): 'Access.request',
|
||||
(30, 11): 'Access.request_ok',
|
||||
(40, 10): 'Exchange.declare',
|
||||
(40, 11): 'Exchange.declare_ok',
|
||||
(40, 20): 'Exchange.delete',
|
||||
(40, 21): 'Exchange.delete_ok',
|
||||
(40, 30): 'Exchange.bind',
|
||||
(40, 31): 'Exchange.bind_ok',
|
||||
(40, 40): 'Exchange.unbind',
|
||||
(40, 41): 'Exchange.unbind_ok',
|
||||
(50, 10): 'Queue.declare',
|
||||
(50, 11): 'Queue.declare_ok',
|
||||
(50, 20): 'Queue.bind',
|
||||
(50, 21): 'Queue.bind_ok',
|
||||
(50, 30): 'Queue.purge',
|
||||
(50, 31): 'Queue.purge_ok',
|
||||
(50, 40): 'Queue.delete',
|
||||
(50, 41): 'Queue.delete_ok',
|
||||
(50, 50): 'Queue.unbind',
|
||||
(50, 51): 'Queue.unbind_ok',
|
||||
(60, 10): 'Basic.qos',
|
||||
(60, 11): 'Basic.qos_ok',
|
||||
(60, 20): 'Basic.consume',
|
||||
(60, 21): 'Basic.consume_ok',
|
||||
(60, 30): 'Basic.cancel',
|
||||
(60, 31): 'Basic.cancel_ok',
|
||||
(60, 40): 'Basic.publish',
|
||||
(60, 50): 'Basic.return',
|
||||
(60, 60): 'Basic.deliver',
|
||||
(60, 70): 'Basic.get',
|
||||
(60, 71): 'Basic.get_ok',
|
||||
(60, 72): 'Basic.get_empty',
|
||||
(60, 80): 'Basic.ack',
|
||||
(60, 90): 'Basic.reject',
|
||||
(60, 100): 'Basic.recover_async',
|
||||
(60, 110): 'Basic.recover',
|
||||
(60, 111): 'Basic.recover_ok',
|
||||
(60, 120): 'Basic.nack',
|
||||
(90, 10): 'Tx.select',
|
||||
(90, 11): 'Tx.select_ok',
|
||||
(90, 20): 'Tx.commit',
|
||||
(90, 21): 'Tx.commit_ok',
|
||||
(90, 30): 'Tx.rollback',
|
||||
(90, 31): 'Tx.rollback_ok',
|
||||
(85, 10): 'Confirm.select',
|
||||
(85, 11): 'Confirm.select_ok',
|
||||
}
|
||||
|
||||
|
||||
for _method_id, _method_name in list(METHOD_NAME_MAP.items()):
|
||||
METHOD_NAME_MAP[unpack('>I', pack('>HH', *_method_id))[0]] = _method_name
|
||||
@@ -0,0 +1,191 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
celery.five
|
||||
~~~~~~~~~~~
|
||||
|
||||
Compatibility implementations of features
|
||||
only available in newer Python versions.
|
||||
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
# ############# py3k #########################################################
|
||||
import sys
|
||||
PY3 = sys.version_info[0] == 3
|
||||
|
||||
try:
|
||||
reload = reload # noqa
|
||||
except NameError: # pragma: no cover
|
||||
from imp import reload # noqa
|
||||
|
||||
try:
|
||||
from UserList import UserList # noqa
|
||||
except ImportError: # pragma: no cover
|
||||
from collections import UserList # noqa
|
||||
|
||||
try:
|
||||
from UserDict import UserDict # noqa
|
||||
except ImportError: # pragma: no cover
|
||||
from collections import UserDict # noqa
|
||||
|
||||
|
||||
if PY3:
|
||||
import builtins
|
||||
|
||||
from queue import Queue, Empty
|
||||
from itertools import zip_longest
|
||||
from io import StringIO, BytesIO
|
||||
|
||||
map = map
|
||||
string = str
|
||||
string_t = str
|
||||
long_t = int
|
||||
text_t = str
|
||||
range = range
|
||||
int_types = (int, )
|
||||
|
||||
open_fqdn = 'builtins.open'
|
||||
|
||||
def items(d):
|
||||
return d.items()
|
||||
|
||||
def keys(d):
|
||||
return d.keys()
|
||||
|
||||
def values(d):
|
||||
return d.values()
|
||||
|
||||
def nextfun(it):
|
||||
return it.__next__
|
||||
|
||||
exec_ = getattr(builtins, 'exec')
|
||||
|
||||
def reraise(tp, value, tb=None):
|
||||
if value.__traceback__ is not tb:
|
||||
raise value.with_traceback(tb)
|
||||
raise value
|
||||
|
||||
class WhateverIO(StringIO):
|
||||
|
||||
def write(self, data):
|
||||
if isinstance(data, bytes):
|
||||
data = data.encode()
|
||||
StringIO.write(self, data)
|
||||
|
||||
else:
|
||||
import __builtin__ as builtins # noqa
|
||||
from Queue import Queue, Empty # noqa
|
||||
from itertools import imap as map, izip_longest as zip_longest # noqa
|
||||
from StringIO import StringIO # noqa
|
||||
string = unicode # noqa
|
||||
string_t = basestring # noqa
|
||||
text_t = unicode
|
||||
long_t = long # noqa
|
||||
range = xrange
|
||||
int_types = (int, long)
|
||||
|
||||
open_fqdn = '__builtin__.open'
|
||||
|
||||
def items(d): # noqa
|
||||
return d.iteritems()
|
||||
|
||||
def keys(d): # noqa
|
||||
return d.iterkeys()
|
||||
|
||||
def values(d): # noqa
|
||||
return d.itervalues()
|
||||
|
||||
def nextfun(it): # noqa
|
||||
return it.next
|
||||
|
||||
def exec_(code, globs=None, locs=None):
|
||||
"""Execute code in a namespace."""
|
||||
if globs is None:
|
||||
frame = sys._getframe(1)
|
||||
globs = frame.f_globals
|
||||
if locs is None:
|
||||
locs = frame.f_locals
|
||||
del frame
|
||||
elif locs is None:
|
||||
locs = globs
|
||||
exec("""exec code in globs, locs""")
|
||||
|
||||
exec_("""def reraise(tp, value, tb=None): raise tp, value, tb""")
|
||||
|
||||
BytesIO = WhateverIO = StringIO # noqa
|
||||
|
||||
|
||||
def with_metaclass(Type, skip_attrs=set(['__dict__', '__weakref__'])):
|
||||
"""Class decorator to set metaclass.
|
||||
|
||||
Works with both Python 3 and Python 3 and it does not add
|
||||
an extra class in the lookup order like ``six.with_metaclass`` does
|
||||
(that is -- it copies the original class instead of using inheritance).
|
||||
|
||||
"""
|
||||
|
||||
def _clone_with_metaclass(Class):
|
||||
attrs = dict((key, value) for key, value in items(vars(Class))
|
||||
if key not in skip_attrs)
|
||||
return Type(Class.__name__, Class.__bases__, attrs)
|
||||
|
||||
return _clone_with_metaclass
|
||||
|
||||
# ############# time.monotonic ################################################
|
||||
|
||||
if sys.version_info < (3, 3):
|
||||
|
||||
import platform
|
||||
SYSTEM = platform.system()
|
||||
|
||||
try:
|
||||
import ctypes
|
||||
except ImportError: # pragma: no cover
|
||||
ctypes = None # noqa
|
||||
|
||||
if SYSTEM == 'Darwin' and ctypes is not None:
|
||||
from ctypes.util import find_library
|
||||
libSystem = ctypes.CDLL(find_library('libSystem.dylib'))
|
||||
CoreServices = ctypes.CDLL(find_library('CoreServices'),
|
||||
use_errno=True)
|
||||
mach_absolute_time = libSystem.mach_absolute_time
|
||||
mach_absolute_time.restype = ctypes.c_uint64
|
||||
absolute_to_nanoseconds = CoreServices.AbsoluteToNanoseconds
|
||||
absolute_to_nanoseconds.restype = ctypes.c_uint64
|
||||
absolute_to_nanoseconds.argtypes = [ctypes.c_uint64]
|
||||
|
||||
def _monotonic():
|
||||
return absolute_to_nanoseconds(mach_absolute_time()) * 1e-9
|
||||
|
||||
elif SYSTEM == 'Linux' and ctypes is not None:
|
||||
# from stackoverflow:
|
||||
# questions/1205722/how-do-i-get-monotonic-time-durations-in-python
|
||||
import os
|
||||
|
||||
CLOCK_MONOTONIC = 1 # see <linux/time.h>
|
||||
|
||||
class timespec(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('tv_sec', ctypes.c_long),
|
||||
('tv_nsec', ctypes.c_long),
|
||||
]
|
||||
|
||||
librt = ctypes.CDLL('librt.so.1', use_errno=True)
|
||||
clock_gettime = librt.clock_gettime
|
||||
clock_gettime.argtypes = [
|
||||
ctypes.c_int, ctypes.POINTER(timespec),
|
||||
]
|
||||
|
||||
def _monotonic(): # noqa
|
||||
t = timespec()
|
||||
if clock_gettime(CLOCK_MONOTONIC, ctypes.pointer(t)) != 0:
|
||||
errno_ = ctypes.get_errno()
|
||||
raise OSError(errno_, os.strerror(errno_))
|
||||
return t.tv_sec + t.tv_nsec * 1e-9
|
||||
else:
|
||||
from time import time as _monotonic
|
||||
try:
|
||||
from time import monotonic
|
||||
except ImportError:
|
||||
monotonic = _monotonic # noqa
|
||||
@@ -0,0 +1,231 @@
|
||||
"""Convert between frames and higher-level AMQP methods"""
|
||||
# Copyright (C) 2007-2008 Barry Pederson <bp@barryp.org>
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
|
||||
from __future__ import absolute_import
|
||||
|
||||
from collections import defaultdict, deque
|
||||
from struct import pack, unpack
|
||||
|
||||
from .basic_message import Message
|
||||
from .exceptions import AMQPError, UnexpectedFrame
|
||||
from .five import range, string
|
||||
from .serialization import AMQPReader
|
||||
|
||||
__all__ = ['MethodReader']
|
||||
|
||||
#
|
||||
# MethodReader needs to know which methods are supposed
|
||||
# to be followed by content headers and bodies.
|
||||
#
|
||||
_CONTENT_METHODS = [
|
||||
(60, 50), # Basic.return
|
||||
(60, 60), # Basic.deliver
|
||||
(60, 71), # Basic.get_ok
|
||||
]
|
||||
|
||||
|
||||
class _PartialMessage(object):
|
||||
"""Helper class to build up a multi-frame method."""
|
||||
|
||||
def __init__(self, method_sig, args, channel):
|
||||
self.method_sig = method_sig
|
||||
self.args = args
|
||||
self.msg = Message()
|
||||
self.body_parts = []
|
||||
self.body_received = 0
|
||||
self.body_size = None
|
||||
self.complete = False
|
||||
|
||||
def add_header(self, payload):
|
||||
class_id, weight, self.body_size = unpack('>HHQ', payload[:12])
|
||||
self.msg._load_properties(payload[12:])
|
||||
self.complete = (self.body_size == 0)
|
||||
|
||||
def add_payload(self, payload):
|
||||
parts = self.body_parts
|
||||
self.body_received += len(payload)
|
||||
if self.body_received == self.body_size:
|
||||
if parts:
|
||||
parts.append(payload)
|
||||
self.msg.body = bytes().join(parts)
|
||||
else:
|
||||
self.msg.body = payload
|
||||
self.complete = True
|
||||
else:
|
||||
parts.append(payload)
|
||||
|
||||
|
||||
class MethodReader(object):
|
||||
"""Helper class to receive frames from the broker, combine them if
|
||||
necessary with content-headers and content-bodies into complete methods.
|
||||
|
||||
Normally a method is represented as a tuple containing
|
||||
(channel, method_sig, args, content).
|
||||
|
||||
In the case of a framing error, an :exc:`ConnectionError` is placed
|
||||
in the queue.
|
||||
|
||||
In the case of unexpected frames, a tuple made up of
|
||||
``(channel, ChannelError)`` is placed in the queue.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, source):
|
||||
self.source = source
|
||||
self.queue = deque()
|
||||
self.running = False
|
||||
self.partial_messages = {}
|
||||
self.heartbeats = 0
|
||||
# For each channel, which type is expected next
|
||||
self.expected_types = defaultdict(lambda: 1)
|
||||
# not an actual byte count, just incremented whenever we receive
|
||||
self.bytes_recv = 0
|
||||
self._quick_put = self.queue.append
|
||||
self._quick_get = self.queue.popleft
|
||||
|
||||
def _next_method(self):
|
||||
"""Read the next method from the source, once one complete method has
|
||||
been assembled it is placed in the internal queue."""
|
||||
queue = self.queue
|
||||
put = self._quick_put
|
||||
read_frame = self.source.read_frame
|
||||
while not queue:
|
||||
try:
|
||||
frame_type, channel, payload = read_frame()
|
||||
except Exception as exc:
|
||||
#
|
||||
# Connection was closed? Framing Error?
|
||||
#
|
||||
put(exc)
|
||||
break
|
||||
|
||||
self.bytes_recv += 1
|
||||
|
||||
if frame_type not in (self.expected_types[channel], 8):
|
||||
put((
|
||||
channel,
|
||||
UnexpectedFrame(
|
||||
'Received frame {0} while expecting type: {1}'.format(
|
||||
frame_type, self.expected_types[channel]))))
|
||||
elif frame_type == 1:
|
||||
self._process_method_frame(channel, payload)
|
||||
elif frame_type == 2:
|
||||
self._process_content_header(channel, payload)
|
||||
elif frame_type == 3:
|
||||
self._process_content_body(channel, payload)
|
||||
elif frame_type == 8:
|
||||
self._process_heartbeat(channel, payload)
|
||||
|
||||
def _process_heartbeat(self, channel, payload):
|
||||
self.heartbeats += 1
|
||||
|
||||
def _process_method_frame(self, channel, payload):
|
||||
"""Process Method frames"""
|
||||
method_sig = unpack('>HH', payload[:4])
|
||||
args = AMQPReader(payload[4:])
|
||||
|
||||
if method_sig in _CONTENT_METHODS:
|
||||
#
|
||||
# Save what we've got so far and wait for the content-header
|
||||
#
|
||||
self.partial_messages[channel] = _PartialMessage(
|
||||
method_sig, args, channel,
|
||||
)
|
||||
self.expected_types[channel] = 2
|
||||
else:
|
||||
self._quick_put((channel, method_sig, args, None))
|
||||
|
||||
def _process_content_header(self, channel, payload):
|
||||
"""Process Content Header frames"""
|
||||
partial = self.partial_messages[channel]
|
||||
partial.add_header(payload)
|
||||
|
||||
if partial.complete:
|
||||
#
|
||||
# a bodyless message, we're done
|
||||
#
|
||||
self._quick_put((channel, partial.method_sig,
|
||||
partial.args, partial.msg))
|
||||
self.partial_messages.pop(channel, None)
|
||||
self.expected_types[channel] = 1
|
||||
else:
|
||||
#
|
||||
# wait for the content-body
|
||||
#
|
||||
self.expected_types[channel] = 3
|
||||
|
||||
def _process_content_body(self, channel, payload):
|
||||
"""Process Content Body frames"""
|
||||
partial = self.partial_messages[channel]
|
||||
partial.add_payload(payload)
|
||||
if partial.complete:
|
||||
#
|
||||
# Stick the message in the queue and go back to
|
||||
# waiting for method frames
|
||||
#
|
||||
self._quick_put((channel, partial.method_sig,
|
||||
partial.args, partial.msg))
|
||||
self.partial_messages.pop(channel, None)
|
||||
self.expected_types[channel] = 1
|
||||
|
||||
def read_method(self):
|
||||
"""Read a method from the peer."""
|
||||
self._next_method()
|
||||
m = self._quick_get()
|
||||
if isinstance(m, Exception):
|
||||
raise m
|
||||
if isinstance(m, tuple) and isinstance(m[1], AMQPError):
|
||||
raise m[1]
|
||||
return m
|
||||
|
||||
|
||||
class MethodWriter(object):
|
||||
"""Convert AMQP methods into AMQP frames and send them out
|
||||
to the peer."""
|
||||
|
||||
def __init__(self, dest, frame_max):
|
||||
self.dest = dest
|
||||
self.frame_max = frame_max
|
||||
self.bytes_sent = 0
|
||||
|
||||
def write_method(self, channel, method_sig, args, content=None):
|
||||
write_frame = self.dest.write_frame
|
||||
payload = pack('>HH', method_sig[0], method_sig[1]) + args
|
||||
|
||||
if content:
|
||||
# do this early, so we can raise an exception if there's a
|
||||
# problem with the content properties, before sending the
|
||||
# first frame
|
||||
body = content.body
|
||||
if isinstance(body, string):
|
||||
coding = content.properties.get('content_encoding', None)
|
||||
if coding is None:
|
||||
coding = content.properties['content_encoding'] = 'UTF-8'
|
||||
|
||||
body = body.encode(coding)
|
||||
properties = content._serialize_properties()
|
||||
|
||||
write_frame(1, channel, payload)
|
||||
|
||||
if content:
|
||||
payload = pack('>HHQ', method_sig[0], 0, len(body)) + properties
|
||||
|
||||
write_frame(2, channel, payload)
|
||||
|
||||
chunk_size = self.frame_max - 8
|
||||
for i in range(0, len(body), chunk_size):
|
||||
write_frame(3, channel, body[i:i + chunk_size])
|
||||
self.bytes_sent += 1
|
||||
@@ -0,0 +1,13 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
queue_declare_ok_t = namedtuple(
|
||||
'queue_declare_ok_t', ('queue', 'message_count', 'consumer_count'),
|
||||
)
|
||||
|
||||
basic_return_t = namedtuple(
|
||||
'basic_return_t',
|
||||
('reply_code', 'reply_text', 'exchange', 'routing_key', 'message'),
|
||||
)
|
||||
@@ -0,0 +1,509 @@
|
||||
"""
|
||||
Convert between bytestreams and higher-level AMQP types.
|
||||
|
||||
2007-11-05 Barry Pederson <bp@barryp.org>
|
||||
|
||||
"""
|
||||
# Copyright (C) 2007 Barry Pederson <bp@barryp.org>
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
|
||||
from __future__ import absolute_import
|
||||
|
||||
import calendar
|
||||
import sys
|
||||
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from io import BytesIO
|
||||
from struct import pack, unpack
|
||||
|
||||
from .exceptions import FrameSyntaxError
|
||||
from .five import int_types, long_t, string, string_t, items
|
||||
|
||||
IS_PY3K = sys.version_info[0] >= 3
|
||||
|
||||
if IS_PY3K:
|
||||
def byte(n):
|
||||
return bytes([n])
|
||||
else:
|
||||
byte = chr
|
||||
|
||||
|
||||
ILLEGAL_TABLE_TYPE_WITH_KEY = """\
|
||||
Table type {0!r} for key {1!r} not handled by amqp. [value: {2!r}]
|
||||
"""
|
||||
|
||||
ILLEGAL_TABLE_TYPE = """\
|
||||
Table type {0!r} not handled by amqp. [value: {1!r}]
|
||||
"""
|
||||
|
||||
|
||||
class AMQPReader(object):
|
||||
"""Read higher-level AMQP types from a bytestream."""
|
||||
def __init__(self, source):
|
||||
"""Source should be either a file-like object with a read() method, or
|
||||
a plain (non-unicode) string."""
|
||||
if isinstance(source, bytes):
|
||||
self.input = BytesIO(source)
|
||||
elif hasattr(source, 'read'):
|
||||
self.input = source
|
||||
else:
|
||||
raise ValueError(
|
||||
'AMQPReader needs a file-like object or plain string')
|
||||
|
||||
self.bitcount = self.bits = 0
|
||||
|
||||
def close(self):
|
||||
self.input.close()
|
||||
|
||||
def read(self, n):
|
||||
"""Read n bytes."""
|
||||
self.bitcount = self.bits = 0
|
||||
return self.input.read(n)
|
||||
|
||||
def read_bit(self):
|
||||
"""Read a single boolean value."""
|
||||
if not self.bitcount:
|
||||
self.bits = ord(self.input.read(1))
|
||||
self.bitcount = 8
|
||||
result = (self.bits & 1) == 1
|
||||
self.bits >>= 1
|
||||
self.bitcount -= 1
|
||||
return result
|
||||
|
||||
def read_octet(self):
|
||||
"""Read one byte, return as an integer"""
|
||||
self.bitcount = self.bits = 0
|
||||
return unpack('B', self.input.read(1))[0]
|
||||
|
||||
def read_short(self):
|
||||
"""Read an unsigned 16-bit integer"""
|
||||
self.bitcount = self.bits = 0
|
||||
return unpack('>H', self.input.read(2))[0]
|
||||
|
||||
def read_long(self):
|
||||
"""Read an unsigned 32-bit integer"""
|
||||
self.bitcount = self.bits = 0
|
||||
return unpack('>I', self.input.read(4))[0]
|
||||
|
||||
def read_longlong(self):
|
||||
"""Read an unsigned 64-bit integer"""
|
||||
self.bitcount = self.bits = 0
|
||||
return unpack('>Q', self.input.read(8))[0]
|
||||
|
||||
def read_float(self):
|
||||
"""Read float value."""
|
||||
self.bitcount = self.bits = 0
|
||||
return unpack('>d', self.input.read(8))[0]
|
||||
|
||||
def read_shortstr(self):
|
||||
"""Read a short string that's stored in up to 255 bytes.
|
||||
|
||||
The encoding isn't specified in the AMQP spec, so
|
||||
assume it's utf-8
|
||||
|
||||
"""
|
||||
self.bitcount = self.bits = 0
|
||||
slen = unpack('B', self.input.read(1))[0]
|
||||
return self.input.read(slen).decode('utf-8')
|
||||
|
||||
def read_longstr(self):
|
||||
"""Read a string that's up to 2**32 bytes.
|
||||
|
||||
The encoding isn't specified in the AMQP spec, so
|
||||
assume it's utf-8
|
||||
|
||||
"""
|
||||
self.bitcount = self.bits = 0
|
||||
slen = unpack('>I', self.input.read(4))[0]
|
||||
return self.input.read(slen).decode('utf-8')
|
||||
|
||||
def read_table(self):
|
||||
"""Read an AMQP table, and return as a Python dictionary."""
|
||||
self.bitcount = self.bits = 0
|
||||
tlen = unpack('>I', self.input.read(4))[0]
|
||||
table_data = AMQPReader(self.input.read(tlen))
|
||||
result = {}
|
||||
while table_data.input.tell() < tlen:
|
||||
name = table_data.read_shortstr()
|
||||
val = table_data.read_item()
|
||||
result[name] = val
|
||||
return result
|
||||
|
||||
def read_item(self):
|
||||
ftype = ord(self.input.read(1))
|
||||
|
||||
# 'S': long string
|
||||
if ftype == 83:
|
||||
val = self.read_longstr()
|
||||
# 's': short string
|
||||
elif ftype == 115:
|
||||
val = self.read_shortstr()
|
||||
# 'b': short-short int
|
||||
elif ftype == 98:
|
||||
val, = unpack('>B', self.input.read(1))
|
||||
# 'B': short-short unsigned int
|
||||
elif ftype == 66:
|
||||
val, = unpack('>b', self.input.read(1))
|
||||
# 'U': short int
|
||||
elif ftype == 85:
|
||||
val, = unpack('>h', self.input.read(2))
|
||||
# 'u': short unsigned int
|
||||
elif ftype == 117:
|
||||
val, = unpack('>H', self.input.read(2))
|
||||
# 'I': long int
|
||||
elif ftype == 73:
|
||||
val, = unpack('>i', self.input.read(4))
|
||||
# 'i': long unsigned int
|
||||
elif ftype == 105: # 'l'
|
||||
val, = unpack('>I', self.input.read(4))
|
||||
# 'L': long long int
|
||||
elif ftype == 76:
|
||||
val, = unpack('>q', self.input.read(8))
|
||||
# 'l': long long unsigned int
|
||||
elif ftype == 108:
|
||||
val, = unpack('>Q', self.input.read(8))
|
||||
# 'f': float
|
||||
elif ftype == 102:
|
||||
val, = unpack('>f', self.input.read(4))
|
||||
# 'd': double
|
||||
elif ftype == 100:
|
||||
val = self.read_float()
|
||||
# 'D': decimal
|
||||
elif ftype == 68:
|
||||
d = self.read_octet()
|
||||
n, = unpack('>i', self.input.read(4))
|
||||
val = Decimal(n) / Decimal(10 ** d)
|
||||
# 'F': table
|
||||
elif ftype == 70:
|
||||
val = self.read_table() # recurse
|
||||
# 'A': array
|
||||
elif ftype == 65:
|
||||
val = self.read_array()
|
||||
# 't' (bool)
|
||||
elif ftype == 116:
|
||||
val = self.read_bit()
|
||||
# 'T': timestamp
|
||||
elif ftype == 84:
|
||||
val = self.read_timestamp()
|
||||
# 'V': void
|
||||
elif ftype == 86:
|
||||
val = None
|
||||
else:
|
||||
raise FrameSyntaxError(
|
||||
'Unknown value in table: {0!r} ({1!r})'.format(
|
||||
ftype, type(ftype)))
|
||||
return val
|
||||
|
||||
def read_array(self):
|
||||
array_length = unpack('>I', self.input.read(4))[0]
|
||||
array_data = AMQPReader(self.input.read(array_length))
|
||||
result = []
|
||||
while array_data.input.tell() < array_length:
|
||||
val = array_data.read_item()
|
||||
result.append(val)
|
||||
return result
|
||||
|
||||
def read_timestamp(self):
|
||||
"""Read and AMQP timestamp, which is a 64-bit integer representing
|
||||
seconds since the Unix epoch in 1-second resolution.
|
||||
|
||||
Return as a Python datetime.datetime object,
|
||||
expressed as localtime.
|
||||
|
||||
"""
|
||||
return datetime.utcfromtimestamp(self.read_longlong())
|
||||
|
||||
|
||||
class AMQPWriter(object):
|
||||
"""Convert higher-level AMQP types to bytestreams."""
|
||||
|
||||
def __init__(self, dest=None):
|
||||
"""dest may be a file-type object (with a write() method). If None
|
||||
then a BytesIO is created, and the contents can be accessed with
|
||||
this class's getvalue() method."""
|
||||
self.out = BytesIO() if dest is None else dest
|
||||
self.bits = []
|
||||
self.bitcount = 0
|
||||
|
||||
def _flushbits(self):
|
||||
if self.bits:
|
||||
out = self.out
|
||||
for b in self.bits:
|
||||
out.write(pack('B', b))
|
||||
self.bits = []
|
||||
self.bitcount = 0
|
||||
|
||||
def close(self):
|
||||
"""Pass through if possible to any file-like destinations."""
|
||||
try:
|
||||
self.out.close()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def flush(self):
|
||||
"""Pass through if possible to any file-like destinations."""
|
||||
try:
|
||||
self.out.flush()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def getvalue(self):
|
||||
"""Get what's been encoded so far if we're working with a BytesIO."""
|
||||
self._flushbits()
|
||||
return self.out.getvalue()
|
||||
|
||||
def write(self, s):
|
||||
"""Write a plain Python string with no special encoding in Python 2.x,
|
||||
or bytes in Python 3.x"""
|
||||
self._flushbits()
|
||||
self.out.write(s)
|
||||
|
||||
def write_bit(self, b):
|
||||
"""Write a boolean value."""
|
||||
b = 1 if b else 0
|
||||
shift = self.bitcount % 8
|
||||
if shift == 0:
|
||||
self.bits.append(0)
|
||||
self.bits[-1] |= (b << shift)
|
||||
self.bitcount += 1
|
||||
|
||||
def write_octet(self, n):
|
||||
"""Write an integer as an unsigned 8-bit value."""
|
||||
if n < 0 or n > 255:
|
||||
raise FrameSyntaxError(
|
||||
'Octet {0!r} out of range 0..255'.format(n))
|
||||
self._flushbits()
|
||||
self.out.write(pack('B', n))
|
||||
|
||||
def write_short(self, n):
|
||||
"""Write an integer as an unsigned 16-bit value."""
|
||||
if n < 0 or n > 65535:
|
||||
raise FrameSyntaxError(
|
||||
'Octet {0!r} out of range 0..65535'.format(n))
|
||||
self._flushbits()
|
||||
self.out.write(pack('>H', int(n)))
|
||||
|
||||
def write_long(self, n):
|
||||
"""Write an integer as an unsigned2 32-bit value."""
|
||||
if n < 0 or n >= 4294967296:
|
||||
raise FrameSyntaxError(
|
||||
'Octet {0!r} out of range 0..2**31-1'.format(n))
|
||||
self._flushbits()
|
||||
self.out.write(pack('>I', n))
|
||||
|
||||
def write_longlong(self, n):
|
||||
"""Write an integer as an unsigned 64-bit value."""
|
||||
if n < 0 or n >= 18446744073709551616:
|
||||
raise FrameSyntaxError(
|
||||
'Octet {0!r} out of range 0..2**64-1'.format(n))
|
||||
self._flushbits()
|
||||
self.out.write(pack('>Q', n))
|
||||
|
||||
def write_shortstr(self, s):
|
||||
"""Write a string up to 255 bytes long (after any encoding).
|
||||
|
||||
If passed a unicode string, encode with UTF-8.
|
||||
|
||||
"""
|
||||
self._flushbits()
|
||||
if isinstance(s, string):
|
||||
s = s.encode('utf-8')
|
||||
if len(s) > 255:
|
||||
raise FrameSyntaxError(
|
||||
'Shortstring overflow ({0} > 255)'.format(len(s)))
|
||||
self.write_octet(len(s))
|
||||
self.out.write(s)
|
||||
|
||||
def write_longstr(self, s):
|
||||
"""Write a string up to 2**32 bytes long after encoding.
|
||||
|
||||
If passed a unicode string, encode as UTF-8.
|
||||
|
||||
"""
|
||||
self._flushbits()
|
||||
if isinstance(s, string):
|
||||
s = s.encode('utf-8')
|
||||
self.write_long(len(s))
|
||||
self.out.write(s)
|
||||
|
||||
def write_table(self, d):
|
||||
"""Write out a Python dictionary made of up string keys, and values
|
||||
that are strings, signed integers, Decimal, datetime.datetime, or
|
||||
sub-dictionaries following the same constraints."""
|
||||
self._flushbits()
|
||||
table_data = AMQPWriter()
|
||||
for k, v in items(d):
|
||||
table_data.write_shortstr(k)
|
||||
table_data.write_item(v, k)
|
||||
table_data = table_data.getvalue()
|
||||
self.write_long(len(table_data))
|
||||
self.out.write(table_data)
|
||||
|
||||
def write_item(self, v, k=None):
|
||||
if isinstance(v, (string_t, bytes)):
|
||||
if isinstance(v, string):
|
||||
v = v.encode('utf-8')
|
||||
self.write(b'S')
|
||||
self.write_longstr(v)
|
||||
elif isinstance(v, bool):
|
||||
self.write(pack('>cB', b't', int(v)))
|
||||
elif isinstance(v, float):
|
||||
self.write(pack('>cd', b'd', v))
|
||||
elif isinstance(v, int_types):
|
||||
self.write(pack('>ci', b'I', v))
|
||||
elif isinstance(v, Decimal):
|
||||
self.write(b'D')
|
||||
sign, digits, exponent = v.as_tuple()
|
||||
v = 0
|
||||
for d in digits:
|
||||
v = (v * 10) + d
|
||||
if sign:
|
||||
v = -v
|
||||
self.write_octet(-exponent)
|
||||
self.write(pack('>i', v))
|
||||
elif isinstance(v, datetime):
|
||||
self.write(b'T')
|
||||
self.write_timestamp(v)
|
||||
elif isinstance(v, dict):
|
||||
self.write(b'F')
|
||||
self.write_table(v)
|
||||
elif isinstance(v, (list, tuple)):
|
||||
self.write(b'A')
|
||||
self.write_array(v)
|
||||
elif v is None:
|
||||
self.write(b'V')
|
||||
else:
|
||||
err = (ILLEGAL_TABLE_TYPE_WITH_KEY.format(type(v), k, v) if k
|
||||
else ILLEGAL_TABLE_TYPE.format(type(v), v))
|
||||
raise FrameSyntaxError(err)
|
||||
|
||||
def write_array(self, a):
|
||||
array_data = AMQPWriter()
|
||||
for v in a:
|
||||
array_data.write_item(v)
|
||||
array_data = array_data.getvalue()
|
||||
self.write_long(len(array_data))
|
||||
self.out.write(array_data)
|
||||
|
||||
def write_timestamp(self, v):
|
||||
"""Write out a Python datetime.datetime object as a 64-bit integer
|
||||
representing seconds since the Unix epoch."""
|
||||
self.out.write(pack('>Q', long_t(calendar.timegm(v.utctimetuple()))))
|
||||
|
||||
|
||||
class GenericContent(object):
|
||||
"""Abstract base class for AMQP content.
|
||||
|
||||
Subclasses should override the PROPERTIES attribute.
|
||||
|
||||
"""
|
||||
PROPERTIES = [('dummy', 'shortstr')]
|
||||
|
||||
def __init__(self, **props):
|
||||
"""Save the properties appropriate to this AMQP content type
|
||||
in a 'properties' dictionary."""
|
||||
d = {}
|
||||
for propname, _ in self.PROPERTIES:
|
||||
if propname in props:
|
||||
d[propname] = props[propname]
|
||||
# FIXME: should we ignore unknown properties?
|
||||
|
||||
self.properties = d
|
||||
|
||||
def __eq__(self, other):
|
||||
"""Check if this object has the same properties as another
|
||||
content object."""
|
||||
try:
|
||||
return self.properties == other.properties
|
||||
except AttributeError:
|
||||
return NotImplemented
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Look for additional properties in the 'properties'
|
||||
dictionary, and if present - the 'delivery_info'
|
||||
dictionary."""
|
||||
if name == '__setstate__':
|
||||
# Allows pickling/unpickling to work
|
||||
raise AttributeError('__setstate__')
|
||||
|
||||
if name in self.properties:
|
||||
return self.properties[name]
|
||||
|
||||
if 'delivery_info' in self.__dict__ \
|
||||
and name in self.delivery_info:
|
||||
return self.delivery_info[name]
|
||||
|
||||
raise AttributeError(name)
|
||||
|
||||
def _load_properties(self, raw_bytes):
|
||||
"""Given the raw bytes containing the property-flags and property-list
|
||||
from a content-frame-header, parse and insert into a dictionary
|
||||
stored in this object as an attribute named 'properties'."""
|
||||
r = AMQPReader(raw_bytes)
|
||||
|
||||
#
|
||||
# Read 16-bit shorts until we get one with a low bit set to zero
|
||||
#
|
||||
flags = []
|
||||
while 1:
|
||||
flag_bits = r.read_short()
|
||||
flags.append(flag_bits)
|
||||
if flag_bits & 1 == 0:
|
||||
break
|
||||
|
||||
shift = 0
|
||||
d = {}
|
||||
for key, proptype in self.PROPERTIES:
|
||||
if shift == 0:
|
||||
if not flags:
|
||||
break
|
||||
flag_bits, flags = flags[0], flags[1:]
|
||||
shift = 15
|
||||
if flag_bits & (1 << shift):
|
||||
d[key] = getattr(r, 'read_' + proptype)()
|
||||
shift -= 1
|
||||
|
||||
self.properties = d
|
||||
|
||||
def _serialize_properties(self):
|
||||
"""serialize the 'properties' attribute (a dictionary) into
|
||||
the raw bytes making up a set of property flags and a
|
||||
property list, suitable for putting into a content frame header."""
|
||||
shift = 15
|
||||
flag_bits = 0
|
||||
flags = []
|
||||
raw_bytes = AMQPWriter()
|
||||
for key, proptype in self.PROPERTIES:
|
||||
val = self.properties.get(key, None)
|
||||
if val is not None:
|
||||
if shift == 0:
|
||||
flags.append(flag_bits)
|
||||
flag_bits = 0
|
||||
shift = 15
|
||||
|
||||
flag_bits |= (1 << shift)
|
||||
if proptype != 'bit':
|
||||
getattr(raw_bytes, 'write_' + proptype)(val)
|
||||
|
||||
shift -= 1
|
||||
|
||||
flags.append(flag_bits)
|
||||
result = AMQPWriter()
|
||||
for flag_bits in flags:
|
||||
result.write_short(flag_bits)
|
||||
result.write(raw_bytes.getvalue())
|
||||
|
||||
return result.getvalue()
|
||||
@@ -0,0 +1,85 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import sys
|
||||
|
||||
from functools import wraps
|
||||
from io import StringIO
|
||||
|
||||
import mock
|
||||
|
||||
from nose import SkipTest # noqa
|
||||
|
||||
try:
|
||||
import unittest
|
||||
unittest.skip
|
||||
except AttributeError:
|
||||
import unittest2 as unittest # noqa
|
||||
|
||||
PY3 = sys.version_info[0] == 3
|
||||
|
||||
patch = mock.patch
|
||||
call = mock.call
|
||||
|
||||
|
||||
class Case(unittest.TestCase):
|
||||
|
||||
def assertItemsEqual(self, a, b, *args, **kwargs):
|
||||
return self.assertEqual(sorted(a), sorted(b), *args, **kwargs)
|
||||
assertSameElements = assertItemsEqual
|
||||
|
||||
|
||||
class Mock(mock.Mock):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
attrs = kwargs.pop('attrs', None) or {}
|
||||
super(Mock, self).__init__(*args, **kwargs)
|
||||
for attr_name, attr_value in attrs.items():
|
||||
setattr(self, attr_name, attr_value)
|
||||
|
||||
|
||||
class _ContextMock(Mock):
|
||||
"""Dummy class implementing __enter__ and __exit__
|
||||
as the with statement requires these to be implemented
|
||||
in the class, not just the instance."""
|
||||
|
||||
def __enter__(self):
|
||||
pass
|
||||
|
||||
def __exit__(self, *exc_info):
|
||||
pass
|
||||
|
||||
|
||||
def ContextMock(*args, **kwargs):
|
||||
obj = _ContextMock(*args, **kwargs)
|
||||
obj.attach_mock(Mock(), '__enter__')
|
||||
obj.attach_mock(Mock(), '__exit__')
|
||||
obj.__enter__.return_value = obj
|
||||
# if __exit__ return a value the exception is ignored,
|
||||
# so it must return None here.
|
||||
obj.__exit__.return_value = None
|
||||
return obj
|
||||
|
||||
|
||||
class MockPool(object):
|
||||
|
||||
def __init__(self, value=None):
|
||||
self.value = value or ContextMock()
|
||||
|
||||
def acquire(self, **kwargs):
|
||||
return self.value
|
||||
|
||||
|
||||
def redirect_stdouts(fun):
|
||||
|
||||
@wraps(fun)
|
||||
def _inner(*args, **kwargs):
|
||||
sys.stdout = StringIO()
|
||||
sys.stderr = StringIO()
|
||||
try:
|
||||
return fun(*args, **dict(kwargs,
|
||||
stdout=sys.stdout, stderr=sys.stderr))
|
||||
finally:
|
||||
sys.stdout = sys.__stdout__
|
||||
sys.stderr = sys.__stderr__
|
||||
|
||||
return _inner
|
||||
@@ -0,0 +1,35 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from amqp.channel import Channel
|
||||
from amqp.exceptions import NotConfirmed
|
||||
from amqp.serialization import AMQPWriter, AMQPReader
|
||||
|
||||
from amqp.tests.case import Case, Mock
|
||||
|
||||
|
||||
class NoOpenChannel(Channel):
|
||||
|
||||
def _x_open(self):
|
||||
pass
|
||||
|
||||
|
||||
class test_Channel(Case):
|
||||
|
||||
def setUp(self):
|
||||
self.args = AMQPWriter()
|
||||
self.connection = Mock(name='connection')
|
||||
self.connection.channels = defaultdict(lambda: None)
|
||||
self.channel = NoOpenChannel(self.connection, channel_id=1)
|
||||
|
||||
def test_basic_nack(self, delivery_tag=3172312312):
|
||||
self.args.write_longlong(delivery_tag)
|
||||
self.args.write_bit(0)
|
||||
self.args.write_bit(0)
|
||||
with self.assertRaises(NotConfirmed):
|
||||
self.channel._basic_nack(AMQPReader(self.args.getvalue()))
|
||||
callback = Mock(name='callback')
|
||||
self.channel.events['basic_nack'].add(callback)
|
||||
self.channel._basic_nack(AMQPReader(self.args.getvalue()))
|
||||
callback.assert_called_with(delivery_tag, False, False)
|
||||
@@ -0,0 +1,299 @@
|
||||
# Copyright (C) 2009 Barry Pederson <bp@barryp.org>
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
|
||||
from __future__ import absolute_import
|
||||
|
||||
import errno
|
||||
import re
|
||||
import socket
|
||||
import ssl
|
||||
|
||||
# Jython does not have this attribute
|
||||
try:
|
||||
from socket import SOL_TCP
|
||||
except ImportError: # pragma: no cover
|
||||
from socket import IPPROTO_TCP as SOL_TCP # noqa
|
||||
|
||||
try:
|
||||
from ssl import SSLError
|
||||
except ImportError:
|
||||
class SSLError(Exception): # noqa
|
||||
pass
|
||||
|
||||
from struct import pack, unpack
|
||||
|
||||
from .exceptions import UnexpectedFrame
|
||||
from .utils import get_errno, set_cloexec
|
||||
|
||||
_UNAVAIL = errno.EAGAIN, errno.EINTR, errno.ENOENT
|
||||
|
||||
AMQP_PORT = 5672
|
||||
|
||||
EMPTY_BUFFER = bytes()
|
||||
|
||||
# Yes, Advanced Message Queuing Protocol Protocol is redundant
|
||||
AMQP_PROTOCOL_HEADER = 'AMQP\x01\x01\x00\x09'.encode('latin_1')
|
||||
|
||||
# Match things like: [fe80::1]:5432, from RFC 2732
|
||||
IPV6_LITERAL = re.compile(r'\[([\.0-9a-f:]+)\](?::(\d+))?')
|
||||
|
||||
|
||||
class _AbstractTransport(object):
|
||||
"""Common superclass for TCP and SSL transports"""
|
||||
connected = False
|
||||
|
||||
def __init__(self, host, connect_timeout):
|
||||
self.connected = True
|
||||
msg = None
|
||||
port = AMQP_PORT
|
||||
|
||||
m = IPV6_LITERAL.match(host)
|
||||
if m:
|
||||
host = m.group(1)
|
||||
if m.group(2):
|
||||
port = int(m.group(2))
|
||||
else:
|
||||
if ':' in host:
|
||||
host, port = host.rsplit(':', 1)
|
||||
port = int(port)
|
||||
|
||||
self.sock = None
|
||||
last_err = None
|
||||
for res in socket.getaddrinfo(host, port, 0,
|
||||
socket.SOCK_STREAM, SOL_TCP):
|
||||
af, socktype, proto, canonname, sa = res
|
||||
try:
|
||||
self.sock = socket.socket(af, socktype, proto)
|
||||
try:
|
||||
set_cloexec(self.sock, True)
|
||||
except NotImplementedError:
|
||||
pass
|
||||
self.sock.settimeout(connect_timeout)
|
||||
self.sock.connect(sa)
|
||||
except socket.error as exc:
|
||||
msg = exc
|
||||
self.sock.close()
|
||||
self.sock = None
|
||||
last_err = msg
|
||||
continue
|
||||
break
|
||||
|
||||
if not self.sock:
|
||||
# Didn't connect, return the most recent error message
|
||||
raise socket.error(last_err)
|
||||
|
||||
try:
|
||||
self.sock.settimeout(None)
|
||||
self.sock.setsockopt(SOL_TCP, socket.TCP_NODELAY, 1)
|
||||
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||
|
||||
self._setup_transport()
|
||||
|
||||
self._write(AMQP_PROTOCOL_HEADER)
|
||||
except (OSError, IOError, socket.error) as exc:
|
||||
if get_errno(exc) not in _UNAVAIL:
|
||||
self.connected = False
|
||||
raise
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
# socket module may have been collected by gc
|
||||
# if this is called by a thread at shutdown.
|
||||
if socket is not None:
|
||||
try:
|
||||
self.close()
|
||||
except socket.error:
|
||||
pass
|
||||
finally:
|
||||
self.sock = None
|
||||
|
||||
def _read(self, n, initial=False):
|
||||
"""Read exactly n bytes from the peer"""
|
||||
raise NotImplementedError('Must be overriden in subclass')
|
||||
|
||||
def _setup_transport(self):
|
||||
"""Do any additional initialization of the class (used
|
||||
by the subclasses)."""
|
||||
pass
|
||||
|
||||
def _shutdown_transport(self):
|
||||
"""Do any preliminary work in shutting down the connection."""
|
||||
pass
|
||||
|
||||
def _write(self, s):
|
||||
"""Completely write a string to the peer."""
|
||||
raise NotImplementedError('Must be overriden in subclass')
|
||||
|
||||
def close(self):
|
||||
if self.sock is not None:
|
||||
self._shutdown_transport()
|
||||
# Call shutdown first to make sure that pending messages
|
||||
# reach the AMQP broker if the program exits after
|
||||
# calling this method.
|
||||
self.sock.shutdown(socket.SHUT_RDWR)
|
||||
self.sock.close()
|
||||
self.sock = None
|
||||
self.connected = False
|
||||
|
||||
def read_frame(self, unpack=unpack):
|
||||
read = self._read
|
||||
read_frame_buffer = EMPTY_BUFFER
|
||||
try:
|
||||
frame_header = read(7, True)
|
||||
read_frame_buffer += frame_header
|
||||
frame_type, channel, size = unpack('>BHI', frame_header)
|
||||
payload = read(size)
|
||||
read_frame_buffer += payload
|
||||
ch = ord(read(1))
|
||||
except socket.timeout:
|
||||
self._read_buffer = read_frame_buffer + self._read_buffer
|
||||
raise
|
||||
except (OSError, IOError, socket.error) as exc:
|
||||
# Don't disconnect for ssl read time outs
|
||||
# http://bugs.python.org/issue10272
|
||||
if isinstance(exc, SSLError) and 'timed out' in str(exc):
|
||||
raise socket.timeout()
|
||||
if get_errno(exc) not in _UNAVAIL:
|
||||
self.connected = False
|
||||
raise
|
||||
if ch == 206: # '\xce'
|
||||
return frame_type, channel, payload
|
||||
else:
|
||||
raise UnexpectedFrame(
|
||||
'Received 0x{0:02x} while expecting 0xce'.format(ch))
|
||||
|
||||
def write_frame(self, frame_type, channel, payload):
|
||||
size = len(payload)
|
||||
try:
|
||||
self._write(pack(
|
||||
'>BHI%dsB' % size,
|
||||
frame_type, channel, size, payload, 0xce,
|
||||
))
|
||||
except socket.timeout:
|
||||
raise
|
||||
except (OSError, IOError, socket.error) as exc:
|
||||
if get_errno(exc) not in _UNAVAIL:
|
||||
self.connected = False
|
||||
raise
|
||||
|
||||
|
||||
class SSLTransport(_AbstractTransport):
|
||||
"""Transport that works over SSL"""
|
||||
|
||||
def __init__(self, host, connect_timeout, ssl):
|
||||
if isinstance(ssl, dict):
|
||||
self.sslopts = ssl
|
||||
self._read_buffer = EMPTY_BUFFER
|
||||
super(SSLTransport, self).__init__(host, connect_timeout)
|
||||
|
||||
def _setup_transport(self):
|
||||
"""Wrap the socket in an SSL object."""
|
||||
if hasattr(self, 'sslopts'):
|
||||
self.sock = ssl.wrap_socket(self.sock, **self.sslopts)
|
||||
else:
|
||||
self.sock = ssl.wrap_socket(self.sock)
|
||||
self.sock.do_handshake()
|
||||
self._quick_recv = self.sock.read
|
||||
|
||||
def _shutdown_transport(self):
|
||||
"""Unwrap a Python 2.6 SSL socket, so we can call shutdown()"""
|
||||
if self.sock is not None:
|
||||
try:
|
||||
unwrap = self.sock.unwrap
|
||||
except AttributeError:
|
||||
return
|
||||
self.sock = unwrap()
|
||||
|
||||
def _read(self, n, initial=False,
|
||||
_errnos=(errno.ENOENT, errno.EAGAIN, errno.EINTR)):
|
||||
# According to SSL_read(3), it can at most return 16kb of data.
|
||||
# Thus, we use an internal read buffer like TCPTransport._read
|
||||
# to get the exact number of bytes wanted.
|
||||
recv = self._quick_recv
|
||||
rbuf = self._read_buffer
|
||||
try:
|
||||
while len(rbuf) < n:
|
||||
try:
|
||||
s = recv(n - len(rbuf)) # see note above
|
||||
except socket.error as exc:
|
||||
# ssl.sock.read may cause ENOENT if the
|
||||
# operation couldn't be performed (Issue celery#1414).
|
||||
if not initial and exc.errno in _errnos:
|
||||
continue
|
||||
raise
|
||||
if not s:
|
||||
raise IOError('Socket closed')
|
||||
rbuf += s
|
||||
except:
|
||||
self._read_buffer = rbuf
|
||||
raise
|
||||
result, self._read_buffer = rbuf[:n], rbuf[n:]
|
||||
return result
|
||||
|
||||
def _write(self, s):
|
||||
"""Write a string out to the SSL socket fully."""
|
||||
try:
|
||||
write = self.sock.write
|
||||
except AttributeError:
|
||||
# Works around a bug in python socket library
|
||||
raise IOError('Socket closed')
|
||||
else:
|
||||
while s:
|
||||
n = write(s)
|
||||
if not n:
|
||||
raise IOError('Socket closed')
|
||||
s = s[n:]
|
||||
|
||||
|
||||
class TCPTransport(_AbstractTransport):
|
||||
"""Transport that deals directly with TCP socket."""
|
||||
|
||||
def _setup_transport(self):
|
||||
"""Setup to _write() directly to the socket, and
|
||||
do our own buffered reads."""
|
||||
self._write = self.sock.sendall
|
||||
self._read_buffer = EMPTY_BUFFER
|
||||
self._quick_recv = self.sock.recv
|
||||
|
||||
def _read(self, n, initial=False, _errnos=(errno.EAGAIN, errno.EINTR)):
|
||||
"""Read exactly n bytes from the socket"""
|
||||
recv = self._quick_recv
|
||||
rbuf = self._read_buffer
|
||||
try:
|
||||
while len(rbuf) < n:
|
||||
try:
|
||||
s = recv(n - len(rbuf))
|
||||
except socket.error as exc:
|
||||
if not initial and exc.errno in _errnos:
|
||||
continue
|
||||
raise
|
||||
if not s:
|
||||
raise IOError('Socket closed')
|
||||
rbuf += s
|
||||
except:
|
||||
self._read_buffer = rbuf
|
||||
raise
|
||||
|
||||
result, self._read_buffer = rbuf[:n], rbuf[n:]
|
||||
return result
|
||||
|
||||
|
||||
def create_transport(host, connect_timeout, ssl=False):
|
||||
"""Given a few parameters from the Connection constructor,
|
||||
select and create a subclass of _AbstractTransport."""
|
||||
if ssl:
|
||||
return SSLTransport(host, connect_timeout, ssl)
|
||||
else:
|
||||
return TCPTransport(host, connect_timeout)
|
||||
@@ -0,0 +1,102 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import sys
|
||||
|
||||
try:
|
||||
import fcntl
|
||||
except ImportError:
|
||||
fcntl = None # noqa
|
||||
|
||||
|
||||
class promise(object):
|
||||
if not hasattr(sys, 'pypy_version_info'):
|
||||
__slots__ = tuple(
|
||||
'fun args kwargs value ready failed '
|
||||
' on_success on_error calls'.split()
|
||||
)
|
||||
|
||||
def __init__(self, fun, args=(), kwargs=(),
|
||||
on_success=None, on_error=None):
|
||||
self.fun = fun
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.ready = False
|
||||
self.failed = False
|
||||
self.on_success = on_success
|
||||
self.on_error = on_error
|
||||
self.value = None
|
||||
self.calls = 0
|
||||
|
||||
def __repr__(self):
|
||||
return '<$: {0.fun.__name__}(*{0.args!r}, **{0.kwargs!r})'.format(
|
||||
self,
|
||||
)
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
try:
|
||||
self.value = self.fun(
|
||||
*self.args + args if self.args else args,
|
||||
**dict(self.kwargs, **kwargs) if self.kwargs else kwargs
|
||||
)
|
||||
except Exception as exc:
|
||||
self.set_error_state(exc)
|
||||
else:
|
||||
if self.on_success:
|
||||
self.on_success(self.value)
|
||||
finally:
|
||||
self.ready = True
|
||||
self.calls += 1
|
||||
|
||||
def then(self, callback=None, on_error=None):
|
||||
self.on_success = callback
|
||||
self.on_error = on_error
|
||||
return callback
|
||||
|
||||
def set_error_state(self, exc):
|
||||
self.failed = True
|
||||
if self.on_error is None:
|
||||
raise
|
||||
self.on_error(exc)
|
||||
|
||||
def throw(self, exc):
|
||||
try:
|
||||
raise exc
|
||||
except exc.__class__ as with_cause:
|
||||
self.set_error_state(with_cause)
|
||||
|
||||
|
||||
def noop():
|
||||
return promise(lambda *a, **k: None)
|
||||
|
||||
|
||||
try:
|
||||
from os import set_cloexec # Python 3.4?
|
||||
except ImportError:
|
||||
def set_cloexec(fd, cloexec): # noqa
|
||||
try:
|
||||
FD_CLOEXEC = fcntl.FD_CLOEXEC
|
||||
except AttributeError:
|
||||
raise NotImplementedError(
|
||||
'close-on-exec flag not supported on this platform',
|
||||
)
|
||||
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
|
||||
if cloexec:
|
||||
flags |= FD_CLOEXEC
|
||||
else:
|
||||
flags &= ~FD_CLOEXEC
|
||||
return fcntl.fcntl(fd, fcntl.F_SETFD, flags)
|
||||
|
||||
|
||||
def get_errno(exc):
|
||||
""":exc:`socket.error` and :exc:`IOError` first got
|
||||
the ``.errno`` attribute in Py2.7"""
|
||||
try:
|
||||
return exc.errno
|
||||
except AttributeError:
|
||||
try:
|
||||
# e.args = (errno, reason)
|
||||
if isinstance(exc.args, tuple) and len(exc.args) == 2:
|
||||
return exc.args[0]
|
||||
except AttributeError:
|
||||
pass
|
||||
return 0
|
||||
Reference in New Issue
Block a user