mirror of git://git.psyced.org/git/pypsyc
255 lines
7.3 KiB
Python
255 lines
7.3 KiB
Python
"""
|
|
pypsyc.core.psyc
|
|
~~~~~~~~~~~~~~~~
|
|
|
|
Classes which are related to the entity layer.
|
|
|
|
:copyright: 2010 by Manuel Jacob
|
|
:license: MIT
|
|
"""
|
|
import logging
|
|
from collections import defaultdict
|
|
from os import urandom
|
|
|
|
from greenlet import greenlet
|
|
|
|
from pypsyc.core.mmp import Header
|
|
from pypsyc.util import Waiter
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
def _get_binary_arg(it, must_len, first_fragment):
|
|
fragments = [first_fragment]
|
|
cur_len = len(first_fragment)
|
|
while cur_len < must_len:
|
|
f = it.next()
|
|
fragments.append(f)
|
|
cur_len += len(f) + 1
|
|
return '\n'.join(fragments)
|
|
|
|
class PSYCPacket(object):
|
|
"""
|
|
Initialize a new PSYCPacket with the given arguments.
|
|
|
|
:param modifiers: entity modifiers
|
|
:type modifiers: dict
|
|
:param mc: method
|
|
:type mc: string or None
|
|
:param data: data
|
|
:type data: string
|
|
|
|
Attributes:
|
|
|
|
.. attribute:: modifiers
|
|
|
|
entity modifiers as described `here <http://about.psyc.eu/Spec:Packet>`_
|
|
|
|
.. attribute:: cvars
|
|
|
|
same as :attr:`modifiers`\[':'\]
|
|
|
|
.. attribute:: mc
|
|
|
|
method as described `here <http://about.psyc.eu/Spec:Packet>`_
|
|
|
|
.. attribute:: data
|
|
|
|
data as described `here <http://about.psyc.eu/Spec:Packet>`_
|
|
"""
|
|
|
|
def __init__(self, modifiers=None, mc=None, data=''):
|
|
self.modifiers = modifiers or {}
|
|
self.cvars = self.modifiers.setdefault(':', {})
|
|
self.mc = mc
|
|
self.data = data
|
|
|
|
@classmethod
|
|
def from_kwds(cls, mc=None, data='', **kwds):
|
|
"""
|
|
Return a new PSYCPacket which is initialized with mc and data given by
|
|
arguments and cvars given by keyword arguments.
|
|
|
|
::
|
|
|
|
packet1 = PSYCPacket.from_kwds(mc='_message_public',
|
|
data="hello", _somevar='foo')
|
|
|
|
packet2 = PSYCPacket(mc='_message_public', data="hello",
|
|
modifiers={':': {'_somevar': 'foo'}})
|
|
|
|
packet1 == packet2 # True
|
|
|
|
:param mc: method
|
|
:type mc: string or None
|
|
:param data: data
|
|
:type data: string
|
|
:rtype: PSYCPacket
|
|
"""
|
|
return cls({':': kwds}, mc, data)
|
|
|
|
def render(self):
|
|
"""Return a generator of lines which represent this packet."""
|
|
for glyph, vars in self.modifiers.iteritems():
|
|
for x, y in vars.iteritems():
|
|
if '\n' in y:
|
|
yield '%s%s %s\t%s' % (glyph, x, len(y), y)
|
|
else:
|
|
yield '%s%s\t%s' % (glyph, x, y)
|
|
if self.mc:
|
|
yield self.mc
|
|
if self.data:
|
|
yield self.data
|
|
else:
|
|
assert not self.data
|
|
|
|
@classmethod
|
|
def parse(cls, content):
|
|
"""Parse a list of lines to a packet."""
|
|
modifiers = defaultdict(dict)
|
|
it = iter(content)
|
|
for line in it:
|
|
if line.startswith('_'): # body
|
|
return cls(modifiers, line, '\n'.join(it))
|
|
else: # entity-modifier
|
|
glyph = line[0]
|
|
x, y = line[1:].split('\t', 1)
|
|
if ' ' in x: # binary-arg
|
|
var, l = x.split(' ')
|
|
modifiers[glyph][var] = _get_binary_arg(it, int(l), y)
|
|
else: # simple-arg
|
|
modifiers[glyph][x] = y
|
|
return cls(modifiers, None, '')
|
|
|
|
def __repr__(self):
|
|
return '\n'.join((
|
|
"PSYCPacket(",
|
|
" modifiers={",
|
|
",\n".join(' '*8 + "'%s': %s" % i for i in self.modifiers.items()),
|
|
" },",
|
|
" mc=%r,",
|
|
" data=%r",
|
|
")"
|
|
)) % (self.mc, self.data)
|
|
|
|
def __eq__(self, other):
|
|
return (self.modifiers == other.modifiers and
|
|
self.mc == other.mc and
|
|
self.data == other.data)
|
|
|
|
def __ne__(self, other):
|
|
return not self.__eq__(other)
|
|
|
|
|
|
class PSYCObject(object):
|
|
"""
|
|
This is the base class for all classes that provide PSYC functionality,
|
|
such as receiving and sending :class:`PSYCPacket`\s.
|
|
|
|
Subclassed by :class:`pypsyc.server.Entity`. Used by
|
|
:class:`pypsyc.server.routing.ServerCircuit`.
|
|
"""
|
|
|
|
def __init__(self, sendfunc, uni=None):
|
|
self._send = sendfunc
|
|
self.uni = uni
|
|
self.handlers = {}
|
|
self.tags = {}
|
|
|
|
def handle_packet(self, header, content):
|
|
packet = PSYCPacket.parse(content)
|
|
packet.header = header
|
|
|
|
tag_relay = header.get('_tag_relay')
|
|
if tag_relay:
|
|
self.tags.pop(tag_relay).callback(packet)
|
|
return packet
|
|
|
|
mc = packet.mc
|
|
while mc:
|
|
f = self.handlers.get(mc)
|
|
if f is not None:
|
|
break
|
|
mc = mc.rpartition('_')[0]
|
|
else:
|
|
return packet
|
|
|
|
tag = header.get('_tag')
|
|
if tag is None:
|
|
try:
|
|
f(packet)
|
|
except Exception:
|
|
log.exception("error calling handler %s of %s", mc, self)
|
|
else:
|
|
def respond():
|
|
try:
|
|
response = f(packet)
|
|
except Exception:
|
|
log.exception("error calling handler %s of %s", mc, self)
|
|
response = PSYCPacket(mc='_error_internal')
|
|
self.sendmsg(header.source, response, {'_tag_relay': tag})
|
|
greenlet(respond).switch()
|
|
return packet
|
|
|
|
def sendmsg(self, target, packet=None, header=None, **kwds):
|
|
"""
|
|
Send a packet.
|
|
|
|
1.
|
|
Simple example usage::
|
|
|
|
packet = PSYCPacket(mc='_message_public',
|
|
modifiers={':': {'_somevar': 'foo'}})
|
|
psyc.sendmsg(target, packet)
|
|
|
|
This simply sends the packet to the target.
|
|
|
|
|
|
2.
|
|
The following code block is equal to the previous::
|
|
|
|
psyc.sendmsg(target, mc='_message_public', _somevar='foo')
|
|
|
|
See also :meth:`PSYCPacket.from_kwds`.
|
|
|
|
:param target: target of the packet
|
|
:type target: :class:`pypsyc.core.mmp.Uni` or \
|
|
:class:`pypsyc.core.mmp.Circuit`
|
|
:param packet: packet to send, use keyword arguments if None
|
|
:type packet: :class:`PSYCPacket` or None
|
|
:param header: additional header variables
|
|
:type header: dict or None
|
|
:rtype: PSYCPacket or None
|
|
"""
|
|
if packet is None:
|
|
packet = PSYCPacket.from_kwds(**kwds)
|
|
|
|
header = Header() if header is None else Header(header)
|
|
if self.uni is not None:
|
|
header['_source'] = self.uni
|
|
|
|
if packet.mc and packet.mc.startswith('_request'):
|
|
tag = urandom(20).replace('\n', '')
|
|
header['_tag'] = tag
|
|
waiter = self.tags[tag] = Waiter()
|
|
else:
|
|
waiter = None
|
|
|
|
if isinstance(target, str):
|
|
header['_target'] = target
|
|
header._init()
|
|
self._send(header, packet.render())
|
|
else: # the target is a circuit
|
|
target.send(header, packet.render())
|
|
|
|
if waiter is not None:
|
|
return waiter.get()
|
|
|
|
def add_handler(self, handler):
|
|
self.handlers.update((i[6:], getattr(handler, i)) for i in
|
|
dir(handler) if i.startswith('handle_'))
|
|
|
|
def __repr__(self):
|
|
return '<PSYCObject uni=%s>' % self.uni
|