Added:
/branches/sender-messages
Modified:
/trunk/demo/filters
/trunk/ib/ext/Makefile
/trunk/ib/lib/__init__.py
/trunk/ib/opt/connection.py
/trunk/ib/opt/dispatcher.py
/trunk/ib/opt/message.py
/trunk/ib/opt/receiver.py
/trunk/ib/opt/sender.py
=======================================
--- /trunk/demo/filters Tue Dec 15 00:13:41 2009
+++ /trunk/demo/filters Sat Jan 28 08:15:33 2012
@@ -11,11 +11,16 @@
from ib.opt import ibConnection, message
from ib.opt import messagetools
+all_messages = []
+
def my_account_handler(msg):
+ all_messages.append(msg)
print msg
+
def my_tick_handler(msg):
+ all_messages.append(msg)
print msg
@@ -33,18 +38,22 @@
# try out the new before and after send messages
def pre_req_account_updates(msg):
+ all_messages.append(msg)
print 'pre account updates: ', msg
+ return True
def post_req_account_updates(msg):
+ all_messages.append(msg)
print 'post account updates: ', msg
if __name__ == '__main__':
con = ibConnection()
+ #con.enableLogging()
con.register(cash_handler, 'UpdateAccountValue')
con.register(tick_handler, message.TickSize, message.TickPrice)
- con.register(pre_req_account_updates, 'ReqAccountUpdatesBefore')
- con.register(post_req_account_updates, 'ReqAccountUpdatesAfter')
+ con.register(pre_req_account_updates, 'ReqAccountUpdatesPre')
+ con.register(post_req_account_updates, 'PostReqAccountUpdatesPost')
con.connect()
def inner():
=======================================
--- /trunk/ib/ext/Makefile Mon Jul 7 21:48:10 2008
+++ /trunk/ib/ext/Makefile Sat Jan 28 08:15:33 2012
@@ -24,7 +24,8 @@
$(modules):
- j2py -i $(addprefix $(srcdir), $(addsuffix .java, $(basename $@))) -o $@
-c ib.ext.cfg -c ib.ext.cfg.$(basename $@) -s
+ #j2py -i $(addprefix $(srcdir), $(addsuffix .java, $(basename $@))) -o $@
-c ib.ext.cfg -c ib.ext.cfg.$(basename $@)
+ j2py -i $(addprefix $(srcdir), $(addsuffix .java, $(basename $@))) -o $@
-d cfg
modules-clean: clean-modules
clean-modules:
=======================================
--- /trunk/ib/lib/__init__.py Tue Dec 15 00:13:41 2009
+++ /trunk/ib/lib/__init__.py Sat Jan 28 08:15:33 2012
@@ -16,6 +16,21 @@
import struct
import sys
+def toTypeName(value):
+ return '%s%s' % (value[0].upper(), value[1:])
+
+
+def maybeName(obj):
+ """ Returns an object's __name__ attribute or it's string
representation.
+
+ @param obj any object
+ @return obj name or string representation
+ """
+ try:
+ return obj.__name__
+ except (AttributeError, ):
+ return str(obj)
+
class classmethod_(classmethod):
""" Classmethod that provides attribute delegation.
=======================================
--- /trunk/ib/opt/connection.py Tue Dec 15 00:13:41 2009
+++ /trunk/ib/opt/connection.py Sat Jan 28 08:15:33 2012
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
##
-# Defines Connection class to encapsulate a connection to IB TWS.
+# Defines the Connection class to encapsulate a connection to IB TWS.
#
# Connection instances defer failed attribute lookup to their receiver
# and sender member objects. This makes it easy to access the
@@ -17,7 +17,6 @@
# >>> con.placeOrder(...)
#
##
-from ib.lib.logger import logger
from ib.opt.dispatcher import Dispatcher
from ib.opt.receiver import Receiver
from ib.opt.sender import Sender
@@ -33,6 +32,9 @@
@param host name of host for connection; default is localhost
@param port port number for connection; default is 7496
@param clientId client identifier to send when connected
+ @param receiver instance for reading from the connected socket
+ @param sender instance for writing to the connected socket
+ @param dispatcher instance for dispatching socket messages
"""
self.host = host
self.port = port
@@ -46,17 +48,13 @@
@return attribute of instance dispatcher, receiver, or sender
"""
- try:
- return getattr(self.dispatcher, name)
- except (AttributeError, ):
+ for obj in (self.dispatcher, self.receiver, self.sender):
try:
- return getattr(self.receiver, name)
+ return getattr(obj, name)
except (AttributeError, ):
- try:
- return getattr(self.sender, name)
- except (AttributeError, ):
- pass
- raise AttributeError(name)
+ pass
+ err = "'%s' object has no attribute '%s'"
+ raise AttributeError(err % (self.__class__.__name__, name))
def connect(self):
""" Establish a connection to TWS with instance attributes.
@@ -66,43 +64,23 @@
return self.sender.connect(self.host, self.port, self.clientId,
self.receiver)
- def disconnect(self):
- """ Disconnect from TWS
-
- @return True if disconnected, False otherwise
- """
- return self.sender.disconnect()
-
- def enableLogging(self, enable=True):
- """ Enable or disable logging of all messages.
-
- @param enable if True (default), enables logging; otherwise
disables
- @return True if enabled, False otherwise
- """
- if enable:
- self.logger = logger()
- self.registerAll(self.logMessage)
- else:
- self.unregisterAll(self.logMessage)
- return enable
-
- def logMessage(self, message):
- """ Format and send a message values to the logger.
-
- @param message instance of Message
- @return None
- """
- line = str.join(', ', ['%s=%s' % item for item in message.items()])
- self.logger.debug('%s(%s)', message.typeName, line)
-
@classmethod
- def create(cls, host='localhost', port=7496, clientId=0, receiver=None,
- sender=None, dispatcher=None):
+ def create(cls, host='localhost', port=7496, clientId=0,
+ receiver=None, sender=None, dispatcher=None):
""" Creates and returns Connection class (or subclass) instance.
+ For the receiver, sender, and dispatcher parameters, pass in
+ an object instance for those duties; leave as None to have new
+ instances constructed.
+
@param host name of host for connection; default is localhost
@param port port number for connection; default is 7496
@param clientId client identifier to send when connected
+
+ @param receiver=None object for reading messages
+ @param sender=None object for writing requests
+ @param dispatcher=None object for dispatching messages
+
@return Connection (or subclass) instance
"""
dispatcher = Dispatcher() if dispatcher is None else dispatcher
=======================================
--- /trunk/ib/opt/dispatcher.py Tue Dec 15 00:13:41 2009
+++ /trunk/ib/opt/dispatcher.py Sat Jan 28 08:15:33 2012
@@ -7,46 +7,68 @@
##
from Queue import Queue, Empty
-from ib.lib.logger import logger
-from ib.opt.message import registry
+from ib.lib import maybeName, logger
+from ib.opt import message
class Dispatcher(object):
"""
"""
- def __init__(self, listeners=None, types=None):
+ def __init__(self, listeners=None, messageTypes=None):
""" Initializer.
@param listeners=None mapping of existing listeners
@param types=None method name to message type lookup
"""
self.listeners = listeners if listeners else {}
- self.types = types if types else registry
- self.logger = logger()
-
- def __call__(self, name, mapping):
+ self.messageTypes = messageTypes if messageTypes else
message.registry
+ self.logger = logger.logger()
+
+ def __call__(self, name, args):
""" Send message to each listener.
@param name method name
- @param mapping values for message instance
+ @param args arguments for message instance
@return None
"""
+ results = []
try:
- messagetype = self.types[name]
- listeners = self.listeners[self.key(messagetype)]
+ messageType = self.messageTypes[name]
+ listeners = self.listeners[maybeName(messageType)]
except (KeyError, ):
- pass
+ return results
+ message = messageType(**args)
+ for listener in listeners:
+ try:
+ results.append(listener(message))
+ except (Exception, ):
+ errmsg = ("Exception in message dispatch. "
+ "Handler '%s' for '%s'")
+ self.logger.exception(errmsg, maybeName(listener), name)
+ results.append(None)
+ return results
+
+ def enableLogging(self, enable=True):
+ """ Enable or disable logging of all messages.
+
+ @param enable if True (default), enables logging; otherwise
disables
+ @return True if enabled, False otherwise
+ """
+ if enable:
+ self.registerAll(self.logMessage)
else:
- message = messagetype(**mapping)
- for listener in listeners:
- try:
- listener(message)
- except (Exception, ):
- self.unregister(listener, messagetype)
- errmsg = ("Exception in message dispatch. "
- "Handler '%s' unregistered for '%s'")
- self.logger.exception(errmsg, self.key(listener), name)
+ self.unregisterAll(self.logMessage)
+ return enable
+
+ def logMessage(self, message):
+ """ Format and send a message values to the logger.
+
+ @param message instance of Message
+ @return None
+ """
+ line = str.join(', ', ('%s=%s' % item for item in message.items()))
+ self.logger.debug('%s(%s)', message.typeName, line)
def iterator(self, *types):
""" Create and return a function for iterating over messages.
@@ -79,7 +101,7 @@
"""
count = 0
for messagetype in types:
- key = self.key(messagetype)
+ key = maybeName(messagetype)
listeners = self.listeners.setdefault(key, [])
if listener not in listeners:
listeners.append(listener)
@@ -92,7 +114,7 @@
@param listener callable to receive messages
@return True if associated with one or more handler; otherwise
False
"""
- return self.register(listener, *self.types.values())
+ return self.register(listener, *self.messageTypes.values())
def unregister(self, listener, *types):
""" Disassociate listener with message types created by this
Dispatcher.
@@ -104,7 +126,7 @@
count = 0
for messagetype in types:
try:
- listeners = self.listeners[self.key(messagetype)]
+ listeners = self.listeners[maybeName(messagetype)]
except (KeyError, ):
pass
else:
@@ -119,16 +141,4 @@
@param listener callable to no longer receive messages
@return True if disassociated with one or more handler; otherwise
False
"""
- return self.unregister(listener, *self.types.values())
-
- @staticmethod
- def key(obj):
- """ Generates lookup key for given object.
-
- @param obj any object
- @return obj name or string representation
- """
- try:
- return obj.__name__
- except (AttributeError, ):
- return str(obj)
+ return self.unregister(listener, *self.messageTypes.values())
=======================================
--- /trunk/ib/opt/message.py Tue Dec 15 00:13:41 2009
+++ /trunk/ib/opt/message.py Sat Jan 28 08:15:33 2012
@@ -16,36 +16,33 @@
from ib.ext.AnyWrapper import AnyWrapper
from ib.ext.EWrapper import EWrapper
from ib.ext.EClientSocket import EClientSocket
+from ib.lib import toTypeName
class SignatureAccumulator(NodeVisitor):
- def __init__(self):
+ """
+
+ """
+ def __init__(self, classes):
NodeVisitor.__init__(self)
self.signatures = []
+ for filename in (getsourcefile(cls) for cls in classes):
+ self.visit(parse(open(filename).read()))
def visit_FunctionDef(self, node):
args = [arg.id for arg in node.args.args]
self.signatures.append((node.name, args[1:]))
- def getSignatures(self):
- for filename in self.filenames:
- self.visit(parse(open(filename).read()))
- return self.filterSignatures()
-
class EClientSocketAccumulator(SignatureAccumulator):
- filenames = (getsourcefile(EClientSocket), )
-
- def filterSignatures(self):
+ def getSignatures(self):
for name, args in self.signatures:
if match('(?i)req|cancel|place', name):
yield (name, args)
class EWrapperAccumulator(SignatureAccumulator):
- filenames = (getsourcefile(AnyWrapper), getsourcefile(EWrapper), )
-
- def filterSignatures(self):
+ def getSignatures(self):
for name, args in self.signatures:
if match('(?!((?i)error.*))', name):
yield (name, args)
@@ -57,33 +54,23 @@
registry = {}
-class MessageType(type):
- """ MessageType -> simple metaclass to track Message subclasses
-
- As new Message subclasses are defined (see below), they are saved
- to the registry mapping.
+def messageTypeNames():
+ """ Builds set of message type names.
+
+ @return set of all message type names as strings
"""
- def __init__(cls, name, bases, namespace):
- """ Constructor.
-
- @param name name of newly created type
- @param bases tuple of base classes for new type
- @param namespace dictionary with namespace of new type
- """
- setattr(cls, 'typeName', name)
- return
- try:
- registry[namespace['__assoc__']] = cls
- except (KeyError, ):
- pass
+ def typeNames():
+ for types in registry.values():
+ for typ in types:
+ yield typ.typeName
+ return set(typeNames())
class Message(object):
""" Base class for Message types.
"""
- __metaclass__ = MessageType
- __slots__ = []
+ __slots__ = ()
def __init__(self, **kwds):
""" Constructor.
@@ -136,53 +123,56 @@
The error family of method calls can't be built programmatically,
so we define one here.
"""
- __assoc__ = 'error'
- __slots__ = ['id', 'errorCode', 'errorMsg']
+ __slots__ = ('id', 'errorCode', 'errorMsg')
-def buildMessageTypes(seq, mapping, suffixes=('', ), bases=(Message, )):
+def buildMessageRegistry(seq, suffixes=[''], bases=(Message, )):
""" Construct message types and add to given mapping.
@param seq pairs of method (name, arguments)
- @param mapping dictionary for adding new message types
@param bases sequence of base classes for message types
@return None
"""
- for name, args in seq:
+ for name, args in sorted(seq):
for suffix in suffixes:
- typename = name[0].upper() + name[1:] + suffix
- methname = name + suffix
- typens = {'__slots__':args, '__assoc__':name}
- mapping[typename] = msgtype = type(typename, bases, typens)
- registry[methname] = msgtype
+ typename = toTypeName(name) + suffix
+ typens = {'__slots__':args, '__assoc__':name, 'typeName':name}
+ msgtype = type(typename, bases, typens)
+ if name in registry:
+ registry[name] = registry[name] + (msgtype, )
+ else:
+ registry[name] = (msgtype, )
-##
-# Sequences for accessing the same values we use to create
-# Message types.
-wrapperMethods = list(EWrapperAccumulator().getSignatures())
-clientSocketMethods = list(EClientSocketAccumulator().getSignatures())
-# create message types in the module namespace from the EWrapper
-# abstract class
-buildMessageTypes(wrapperMethods, globals())
-
-## create message types in the module namespace from the EClientSocket
-# concrete class
-buildMessageTypes(clientSocketMethods, globals(),
- suffixes=('Before', 'After'))
-
-
-
-def messageTypeNames():
- """ Builds set of message type names.
-
- @return set of all message type names as strings
- """
- return set([t.typeName for t in registry.values()])
+eWrapperAccum = EWrapperAccumulator((AnyWrapper, EWrapper))
+eClientAccum = EClientSocketAccumulator((EClientSocket, ))
+
+wrapperMethods = list(eWrapperAccum.getSignatures())
+clientSocketMethods = list(eClientAccum.getSignatures())
+errorMethods = [('error', Error.__slots__), ]
+
+buildMessageRegistry(wrapperMethods)
+buildMessageRegistry(clientSocketMethods, suffixes=('Pre', 'Post'))
+buildMessageRegistry(errorMethods)
+
+def initModule():
+ target = globals()
+ for messageTypes in registry.values():
+ for messageType in messageTypes:
+ target[messageType.typeName] = messageType
+
+try:
+ initModule()
+except (NameError, ):
+ pass
+else:
+ del(initModule)
del(AnyWrapper)
del(EWrapper)
del(EClientSocket)
+del(eWrapperAccum)
+del(eClientAccum)
=======================================
--- /trunk/ib/opt/receiver.py Tue Dec 15 00:13:41 2009
+++ /trunk/ib/opt/receiver.py Sat Jan 28 08:15:33 2012
@@ -15,18 +15,17 @@
from ib.opt.message import wrapperMethods
-def messageMethod(name, argnames):
+def messageMethod(name, parameters):
""" Creates method for dispatching messages.
@param name name of method as string
- @param argnames list of method argument names
+ @param parameters list of method argument names
@return newly created method (as closure)
"""
- def inner(self, *args):
- params = dict(zip(argnames, args))
- self.dispatcher(name, params)
- inner.__name__ = name
- return inner
+ def dispatchMethod(self, *arguments):
+ self.dispatcher(name, dict(zip(parameters, arguments)))
+ dispatchMethod.__name__ = name
+ return dispatchMethod
class ReceiverType(type):
@@ -43,8 +42,8 @@
@param namespace dictionary with namespace for new type
@return generated type
"""
- for methodname, methodargs in wrapperMethods:
- namespace[methodname] = messageMethod(methodname, methodargs)
+ for methodName, methodArgs in wrapperMethods:
+ namespace[methodName] = messageMethod(methodName, methodArgs)
return type(name, bases, namespace)
=======================================
--- /trunk/ib/opt/sender.py Tue Dec 15 00:13:41 2009
+++ /trunk/ib/opt/sender.py Sat Jan 28 08:15:33 2012
@@ -8,7 +8,10 @@
# EClientSocket member objects.
#
##
+from functools import wraps
+
from ib.ext.EClientSocket import EClientSocket
+from ib.lib import toTypeName
from ib.opt.message import registry, clientSocketMethods
@@ -64,12 +67,19 @@
value = getattr(self.client, name)
except (AttributeError, ):
raise
- if name in self.clientMethodNames:
- before, after = registry[name+'Before'], registry[name+'After']
- def wrapperMethod(*args):
- self.dispatcher(name+'Before', dict(zip(before.__slots__, args)))
- result = value(*args)
- self.dispatcher(name+'After', dict(zip(after.__slots__, args)))
- return result
- return wrapperMethod
- return value
+ if name not in self.clientMethodNames:
+ return value
+ typeName = toTypeName(name)
+ preName, postName = name+'Pre', name+'Post'
+ preType, postType = registry[preName], registry[postName]
+ @wraps(value)
+ def wrapperMethod(*args):
+ mapping = dict(zip(preType.__slots__, args))
+ results = self.dispatcher(preName, mapping)
+ if not all(results):
+ return # raise exception instead?
+ result = value(*args)
+ self.dispatcher(postName, mapping)
+ return result # or results?
+ return wrapperMethod
+