예제 #1
0
파일: test_ref.py 프로젝트: twx7d3/pyfixmsg
 def test_empty_rgroups(self, spec):
     if 'FIX.4.4' not in spec.version and 'FIX5.' not in spec.version:
         # only relevant for fix 4.4 or above
         return
     codec = Codec(spec=spec, decode_as='UTF-8')
     msg = b'35=AJ;17807=11;232=2;233=bli;234=blu;' \
           b'233=blih;234=bluh;555=0;10=000;'
     msg = codec.parse(msg, separator=';')
     assert {35: 'AJ',
             17807: '11',
             232: [
                 {233: 'bli', 234: 'blu'},
                 {233: 'blih', 234: 'bluh'}
             ],
             555: [],
             10: '000'
             } == msg
     lhs = tuple(codec._unmap(msg))
     assert lhs == ((35, 'AJ'),
                    (232, 2),
                    (233, 'bli'),
                    (234, 'blu'),
                    (233, 'blih'),
                    (234, 'bluh'),
                    (555, 0),
                    (17807, '11'),
                    (10, '000')
                    )
     serialised = '35=AJ;232=2;233=bli;234=blu;233=blih;234=bluh;' \
                  '555=0;17807=11;10=000;'.replace(';', chr(1)).encode('UTF-8')
     assert serialised == codec.serialise(msg)
예제 #2
0
 def test_nested_rgroup(self, spec):
     codec = Codec(spec=spec, decode_as='UTF-8')
     msg = b'35=AE;555=1;687=AA;683=2;688=1;689=1;' \
           b'688=2;689=2;17807=11;10=000;'
     msg = codec.parse(msg, separator=';')
     assert {
         35:
         'AE',
         555: [
             dict(((687, 'AA'), (683, [
                 dict(((688, '1'), (689, '1'))),
                 dict(((688, '2'), (689, '2')))
             ])))
         ],
         17807:
         '11',
         10:
         '000'
     } == msg
     lhs = tuple(codec._unmap(msg))
     assert lhs == ((35, 'AE'), (555, 1), (683, 2), (688, '1'), (689, '1'),
                    (688, '2'), (689, '2'), (687, 'AA'), (17807, '11'),
                    (10, '000'))
     serialised = '35=AE;555=1;683=2;688=1;689=1;' \
                  '688=2;689=2;687=AA;17807=11;10=000;'.replace(';', chr(1)).encode('UTF-8')
     assert serialised == codec.serialise(msg)
예제 #3
0
    def __init__(self, *args, **kwargs):
        """
        The constructor uses the ``dict()`` signature unmodified.
        You can set the following manually or through a factory function:

          * ``self.process`` an opaque value (to store the process that received or processed the message,
            defaults to empty string).
          * ``self.separator`` the default separator to use when parsing messages. Defaults to ``';'``
          * ``self.time`` the time the message has been created or received. Defaults to ``datetime.utcnow()``
          * ``self.recipient`` opaque value (to store for whom the message was intended)
          * ``self.direction`` Whether the message was received (``0``), sent (``1``) or unknown (``None``)
          * ``self.typed_values`` Whether the values in the message are typed. Defaults to ``False``
          * ``self.codec`` Default :py:class:`~pyfixmsg.codec.stringfix.Codec` to use to parse message. Defaults
            to a naive codec that doesn't support repeating groups
        """
        self.process = ''
        self.separator = ';'
        self.time = datetime.datetime.utcnow()
        self.recipient = ''
        self.direction = None
        self.typed_values = False
        self.codec = Codec()
        # Allows maintaining tag order if constructing msg from a FixFragment
        if args and isinstance(args[0], FixFragment):
            self.tag_order = getattr(args[0], 'tag_order', None)
        else:
            self.tag_order = None
        super(FixMessage, self).__init__(*args, **kwargs)
예제 #4
0
 def test_change_spec(self, spec):
     spec.tags.add_tag(10001, "MyTagName")
     assert spec.tags.by_tag(10001).name == "MyTagName"
     assert spec.tags.by_name("MyTagName").tag == 10001
     tag54 = spec.tags.by_tag(54)
     tag54.add_enum_value(name="SELLF", value="SF")
     assert tag54.enum_by_value("SF") == "SELLF"
     assert tag54.enum_by_name("SELLF") == "SF"
     with pytest.raises(TypeError):
         tag54.del_enum_value()
     with pytest.raises(ValueError):
         tag54.del_enum_value(name="SELLF", value="STF")
     with pytest.raises(KeyError):
         tag54.del_enum_value(name="SELLTOOF", value="SF")
     with pytest.raises(KeyError):
         tag54.del_enum_value(name="SELLTOOF")
     with pytest.raises(KeyError):
         tag54.del_enum_value(value="STF")
     tag54.del_enum_value(value="SF")
     with pytest.raises(KeyError):
         tag54.enum_by_name(name="SELLF")
     tag54.del_enum_value(name="BUY")
     with pytest.raises(KeyError):
         tag54.enum_by_value(value="1")
     tag54.add_enum_value(name="BUY", value="1")
     assert tag54.enum_by_value("1") == "BUY"
     data = (
         b'8=FIX.4.2|9=196|35=D|49=A|56=B|34=12|52=20100318-03:21:11.364'
         b'|262=A|268=2|279=0|269=0|278=BID|55=EUR/USD|270=1.37215'
         b'|15=EUR|271=2500000|346=1|279=0|269=1|278=OFFER|55=EUR/USD'
         b'|270=1.37224|15=EUR|271=2503200|346=1|10=171|')
     before = FixMessage()
     before.codec = Codec(spec=spec)
     before.load_fix(data, separator='|')
     composition = [(spec.tags.by_tag(i), False)
                    for i in (279, 269, 278, 55, 270, 15, 271, 346)]
     spec.msg_types[b'D'].add_group(spec.tags.by_tag(268), composition)
     after = FixMessage()
     after.codec = Codec(spec=spec, fragment_class=FixFragment)
     after.load_fix(data, separator='|')
     assert isinstance(
         before[268],
         (str, unicode, bytes))  # 268 is not parsed as a repeating group
     assert before[
         270] == b'1.37224'  # 268 is not parsed as a repeating group, so 270 takes the second value
     assert isinstance(after[268], RepeatingGroup)
     with pytest.raises(KeyError):
         after[270]
     assert list(after.find_all(270)) == [[268, 0, 270], [268, 1, 270]]
예제 #5
0
 def output_fix(self,
                separator=';',
                calc_checksum=True,
                remove_length=False):
     """ ouputs itself as a vanilla FIX message. This forces the output to String fix
      but tries to reuse the spec from the current codec"""
     if calc_checksum:
         self.set_len_and_chksum()
     if remove_length:
         del self[9]
     try:
         codec = Codec(spec=self.codec.spec)
     except AttributeError:
         codec = Codec()
     return codec.serialise(self, separator, delimiter='=')
예제 #6
0
파일: test_ref.py 프로젝트: twx7d3/pyfixmsg
 def test_serialisation_header_and_trailer(self, spec):
     msg = self.FixMessage()
     msg.codec = Codec(spec=spec)
     msg.load_fix(b'8=FIX.4.2;9=97;35=B;215=1;216=1;146=2;55=EURUSD;55=EURGBP;89=SIGSTR;93=6;10=000;')
     assert msg.output_fix() == b'8=FIX.4.2;9=58;35=B;215=1;216=1;146=2;55=EURUSD;55=EURGBP;93=6;89=SIGSTR;10=093;'
     msg = self.FixMessage({89:'SIGSTR', 93:6, 56:'B', 34:12, 10:100, 9:4, 35:8, 8:'FIX4.4'})
     assert msg.output_fix() == b'8=FIX4.4;9=31;35=8;56=B;34=12;93=6;89=SIGSTR;10=010;'
예제 #7
0
 def test_from_buffer(self):
     """
     Tests creating a fix message via the "from_buffer" static function
     """
     buff = b"9=10\x0135=D\x0134=3\x0110=154\x01"
     msg = FixMessage.from_buffer(buff, Codec())
     assert {9, 35, 34, 10} == set(msg.keys())
     assert b'10' == msg[9]
     assert b'D' == msg[35]
     assert b'3' == msg[34]
     assert b'154' == msg[10]
예제 #8
0
파일: test_ref.py 프로젝트: twx7d3/pyfixmsg
 def test_consecutive_rgroups(self, spec):
     codec = Codec(spec=spec, decode_as='UTF-8')
     msg = b'35=B;215=1;216=1;' \
           b'146=2;55=EURUSD;55=EURGBP;10=000;'
     msg = codec.parse(msg, separator=';')
     assert {35: 'B',
             215: [{216 : '1'}],
             146: [{55 : 'EURUSD'}, {55 : 'EURGBP'}],
             10: '000'
             } == msg
     lhs = tuple(codec._unmap(msg))
     assert lhs == ((35, 'B'),
                    (215, 1),
                    (216, '1'),
                    (146, 2),
                    (55, 'EURUSD'),
                    (55, 'EURGBP'),
                    (10, '000')
                    )
     serialised = '35=B;215=1;216=1;' \
                  '146=2;55=EURUSD;55=EURGBP;10=000;'.replace(';', chr(1)).encode('UTF-8')
     assert serialised == codec.serialise(msg)
예제 #9
0
 def test_serialisation_header(self, spec):
     msg = self.FixMessage()
     msg.codec = Codec(spec=spec)
     msg.load_fix(
         b'8=FIX.4.2;9=97;35=B;215=1;216=1;146=2;55=EURUSD;55=EURGBP;10=000;'
     )
     assert msg.output_fix(
     ) == b'8=FIX.4.2;9=43;35=B;215=1;216=1;146=2;55=EURUSD;55=EURGBP;10=236;'
     msg = self.FixMessage({
         56: 'B',
         34: 12,
         10: 100,
         9: 4,
         35: 8,
         8: 'FIX4.4'
     })
     assert msg.output_fix() == b'8=FIX4.4;9=16;35=8;56=B;34=12;10=162;'
예제 #10
0
import pytest

from tests.helpers.pytest_test_filters import skip_on_windows

pytestmark = skip_on_windows(
    reason='TCP cannot be used on platform "{}"'.format(os.name))

pytest.importorskip("pyfixmsg")

from pyfixmsg.fixmessage import FixMessage
from pyfixmsg.codecs.stringfix import Codec
from pyfixmsg.reference import FixSpec
from testplan.testing.multitest.driver.fix import FixServer, FixClient

SPEC_FILE = os.environ["FIX_SPEC_FILE"]
CODEC = Codec(spec=FixSpec(SPEC_FILE))


@pytest.fixture(scope="function")
def fix_server(mockplan):
    """Start and yield a TCP server driver."""
    server = FixServer(
        name="server",
        msgclass=FixMessage,
        codec=CODEC,
    )
    server.parent = mockplan

    with server:
        yield server
예제 #11
0
파일: test_ref.py 프로젝트: twx7d3/pyfixmsg
    def test_codec(self, spec):
        codec = Codec(spec=spec, decode_as='UTF-8')
        msg = (b'8=FIX.4.2;35=D;49=BLA;56=BLA;57=DEST;143=LN;11=eleven;18=1;21=2;54=2;40=2;59=0;55=PROD;'
               b'38=10;44=1;52=20110215-02:20:52.675;10=000;')
        res = codec.parse(msg, separator=';')
        assert {8: u'FIX.4.2',
                11: u'eleven',
                18: u'1',
                21: u'2',
                35: u'D',
                38: u'10',
                40: u'2',
                44: u'1',
                49: u'BLA',
                52: u'20110215-02:20:52.675',
                54: u'2',
                55: u'PROD',
                56: u'BLA',
                57: u'DEST',
                59: u'0',
                10: u'000',
                143: u'LN'} == res
        codec = Codec(spec=spec)
        msg = (b'8=FIX.4.2;35=D;49=BLA;56=BLA;57=DEST;143=LN;11=eleven;18=1;21=2;54=2;40=2;59=0;55=PROD;'
               b'38=10;44=1;52=20110215-02:20:52.675;10=000;')
        res = codec.parse(msg, separator=';')
        assert {8: 'FIX.4.2',
                11: 'eleven',
                18: '1',
                21: '2',
                35: 'D',
                38: '10',
                40: '2',
                44: '1',
                49: 'BLA',
                52: '20110215-02:20:52.675',
                54: '2',
                55: 'PROD',
                56: 'BLA',
                57: 'DEST',
                59: '0',
                10: '000',
                143: 'LN'} == res

        codec = Codec(spec=spec, decode_all_as_347=True)
        res = codec.parse(msg, separator=';')
        assert {8: 'FIX.4.2',
                11: 'eleven',
                18: '1',
                21: '2',
                35: 'D',
                38: '10',
                40: '2',
                44: '1',
                49: 'BLA',
                52: '20110215-02:20:52.675',
                54: '2',
                55: 'PROD',
                56: 'BLA',
                57: 'DEST',
                59: '0',
                10: '000',
                143: 'LN'} == res
        msg = (b'8=FIX.4.2;35=D;49=BLA;56=BLA;57=DEST;347=UTF-8;143=LN;11=eleven;18=1;21=2;54=2;40=2;59=0;55=PROD;'
               b'38=10;44=1;52=20110215-02:20:52.675;10=000;')
        codec = Codec(spec=spec, decode_all_as_347=True)
        res = codec.parse(msg, separator=';')
        assert {8: u'FIX.4.2',
                11: u'eleven',
                18: u'1',
                21: u'2',
                35: u'D',
                38: u'10',
                40: u'2',
                44: u'1',
                49: u'BLA',
                52: u'20110215-02:20:52.675',
                54: u'2',
                55: u'PROD',
                56: u'BLA',
                57: u'DEST',
                59: u'0',
                10: u'000',
                143: u'LN',
                347: u'UTF-8'} == res
        msg = (b'8=FIX.4.2;35=8;49=BLA;56=BLA;57=DEST;143=LN;11=eleven;18=1;21=2;54=2;40=2;59=0;55=PROD;'
               b'38=10;44=1;52=20110215-02:20:52.675;'
               b'382=2;'
               b'375=A;337=B;'
               b'375=B;437=B;'
               b'10=000;')
        codec = Codec(spec=spec)
        res = codec.parse(msg, separator=';')
        assert {8: 'FIX.4.2',
                11: 'eleven',
                382: [dict(((375, 'A'), (337, 'B'))),
                      dict(((375, 'B'), (437, 'B')))],
                18: '1',
                21: '2',
                35: '8',
                38: '10',
                40: '2',
                44: '1',
                49: 'BLA',
                52: '20110215-02:20:52.675',
                54: '2',
                55: 'PROD',
                56: 'BLA',
                57: 'DEST',
                59: '0',
                143: 'LN',
                10: '000'} == res
        # make sure that with a group finishing the message it still works
        msg = (b'8=FIX.4.2;35=8;49=BLA;56=BLA;57=DEST;143=LN;11=eleven;18=1;21=2;54=2;40=2;59=0;55=PROD;'
               b'38=10;44=1;52=20110215-02:20:52.675;'
               b'382=2;'
               b'375=A;337=B;'
               b'375=B;437=B;')


        res = codec.parse(msg, separator=';')
        assert {8: 'FIX.4.2',
                11: 'eleven',
                382: [dict(((375, 'A'), (337, 'B'))),
                      dict(((375, 'B'), (437, 'B')))],
                18: '1',
                21: '2',
                35: '8',
                38: '10',
                40: '2',
                44: '1',
                49: 'BLA',
                52: '20110215-02:20:52.675',
                54: '2',
                55: 'PROD',
                56: 'BLA',
                57: 'DEST',
                59: '0',
                143: 'LN',
                } == res
예제 #12
0
class FixMessage(FixFragment):  # pylint: disable=R0904
    # too many public methods. Needed for compatibility and functionality
    """ Simple dictionary-like object, for use with FIX raw messages. Note that the tags are converted (when possible)
    to integers, and that the values are kept as strings. The default separator is ``;``, but can be specified.
    Check the definition of :py:meth:`~pyfixmsg.FixMessage.load_fix` for details.

    Example:
      >>> fix = FixMessage()
      >>> fix.load_fix(line)
      >>> #print(fix)
      {6: '0', 8: 'FIX.4.2',
      10: '100', 10481: 'A', 14: '0',
      15: 'EUR', 10641: 'blabla',
      18: '1', 21: '2', 22: '5',
      151: '1',
      11951: 'HOOF7M0f4BGJ0rkaNTkkeAA',
      ....

    FixMessage also have a ``time`` attribute, a ``direction`` attribute (inbound : 0, outbound : 1)
    and a ``recipient`` which is rather where it's been received from or sent to.
    FixMessages sort by default on time, and will be considered equal if the dictionary values are the same AND the
    time is the same.

    This FixMessage is eager : it will parse the whole fix and store it locally. It is significantly faster in most
    usage patterns that we observed.

    useful shortcut methods : ::

        fix.tag_exact(tag, value)
        fix.tag_iexact(tag, value)
        fix.tag_contains(tag, value)
        fix.tag_icontains(tag, value)
        fix.tag_match_regex(tag, value)

    Note : the tag_* methods don't support repeating groups
    """

    # Class type of FIX message fragments
    FragmentType = FixFragment

    @classmethod
    def from_dict(cls, tags_dict):
        """
        Create a FixMessage from a dictionary.

        :param tags_dict: dictionary of FIX tags to values
        :type tags_dict: ``dict`` of ``int`` to ``str``
                         or ``int`` or ``float`` or ``long``

        :return: a FixMessage object
        :rtype: ``FixMessage``
        """
        msg = cls()
        msg.update(tags_dict)
        return msg

    @classmethod
    def from_buffer(cls, msg_buffer, fix_codec):
        """
        Create a FixMessage from a buffer and a codec

        :param msg_buffer: a buffer as a string
        :type msg_buffer: ``str``
        :param fix_codec: an object with static encode() and decode() calls
        :type fix_codec: ``Codec``

        :return: a FixMessage object
        :rtype: ``FixMessage``
        """
        msg = cls()
        msg.codec = fix_codec
        msg.from_wire(msg_buffer, fix_codec)
        return msg

    def __lt__(self, other):
        return self.time < other.time

    def __gt__(self, other):
        return self.time > other.time

    def __eq__(self, other):
        if other is None:
            return None
        for msg in (self, other):
            if 9 in msg:
                del msg[9]
            if 10 in msg:
                del msg[10]
        if super(FixMessage, self).__eq__(other):
            if (hasattr(other, 'time') and (other.time == self.time)
                    and (other.recipient == self.recipient)):
                return True
        return False

    def __ne__(self, other):
        if super(FixMessage, self).__ne__(other) or other.time != self.time:
            return True
        return False

    def __le__(self, other):
        return self.time <= other.time

    def __ge__(self, other):
        return self.time >= other.time

    def __copy__(self):
        """
        copy module support. This copies the message by serialising it and parsing the serialised
        data back into a new message. This is a lot faster than deepcopy or other techniques.
         """
        new_msg = self.__class__()
        new_msg.codec = self.codec
        new_msg.from_wire(self.to_wire())
        new_msg.time = self.time
        new_msg.process = self.process
        new_msg.recipient = self.recipient
        new_msg.direction = self.direction
        new_msg.set_len_and_chksum()
        return new_msg

    def copy(self):
        """ Copy interface without using the copy module"""
        return self.__copy__()

    def __init__(self, *args, **kwargs):
        """
        The constructor uses the ``dict()`` signature unmodified.
        You can set the following manually or through a factory function:

          * ``self.process`` an opaque value (to store the process that received or processed the message,
            defaults to empty string).
          * ``self.separator`` the default separator to use when parsing messages. Defaults to ``';'``
          * ``self.time`` the time the message has been created or received. Defaults to ``datetime.utcnow()``
          * ``self.recipient`` opaque value (to store for whom the message was intended)
          * ``self.direction`` Whether the message was received (``0``), sent (``1``) or unknown (``None``)
          * ``self.typed_values`` Whether the values in the message are typed. Defaults to ``False``
          * ``self.codec`` Default :py:class:`~pyfixmsg.codec.stringfix.Codec` to use to parse message. Defaults
            to a naive codec that doesn't support repeating groups
        """
        self.process = ''
        self.separator = ';'
        self.time = datetime.datetime.utcnow()
        self.recipient = ''
        self.direction = None
        self.typed_values = False
        self.codec = Codec()
        # Allows maintaining tag order if constructing msg from a FixFragment
        if args and isinstance(args[0], FixFragment):
            self.tag_order = getattr(args[0], 'tag_order', None)
        else:
            self.tag_order = None
        super(FixMessage, self).__init__(*args, **kwargs)

    @property
    def tags(self):
        """
        Note: this property is there to replace a self reference that existed before.

        Deprecated.
        """
        warnings.warn("FixMessage.tags is deprecated")
        return self

    def set_or_delete(self, tag, value):
        """
        Sets the tag if value is neither None or the empty string. Deletes the tag otherwise.
        Only works on top-level tags (not inside repeating groups)

        """
        if value is not None and value != "":  # don't remove if the tag is 0
            self[tag] = value
        else:
            if tag in self:
                del self[tag]

    def apply(self, update):
        """
        equivalent to :py:meth:`~pyfixmsg.FixMessage.update()` but if any value in the update dictionary
        is None and the tag is present in the current message, that tag is removed.
        Note: naive about repeating groups

        :param update: map of values to update the state with.
        :type update: ``dict``
        """
        for tag in update:
            self.set_or_delete(tag, update[tag])

    def load_fix(self, string, process=None, separator=';'):
        """
        Parses a FIX message from a string using default codecs and separators.

        :param string: the string containing the FIX message to be parsed
        :type string: ``bytes``
        :param process: Optional originator of the FIX message
        :type process: ``unicode``
        :param separator: Character delimiting "tag=val" pairs.
          Optional. By default this is a ';' character.
          Specify ``pyfixmsg.SEPARATOR`` when parsing standard FIX.
        :type separator: ``unicode``
        :return: A parsed fix message
        :rtype: ``FixMessage``
        """
        fix_msg = self.codec.parse(string.strip(), separator=separator)
        self.update(fix_msg)
        self.tag_order = getattr(fix_msg, 'tag_order', None)
        self.process = process
        return self

    @property
    def fix(self):
        """ Legacy compatibility, will be removed shortly"""
        return self.output_fix(self.separator)

    def output_fix(self,
                   separator=';',
                   calc_checksum=True,
                   remove_length=False):
        """ ouputs itself as a vanilla FIX message. This forces the output to String fix
         but tries to reuse the spec from the current codec"""
        if calc_checksum:
            self.set_len_and_chksum()
        if remove_length:
            del self[9]
        try:
            codec = Codec(spec=self.codec.spec)
        except AttributeError:
            codec = Codec()
        return codec.serialise(self, separator, delimiter='=')

    def to_wire(self, codec=None):
        """
        Return wire representation according to a codec
        """
        codec = codec or self.codec
        self.set_len_and_chksum()
        return codec.serialise(self)

    def from_wire(self, msg, codec=None):
        """
        Extract from a wire representation according to a codec
        """
        codec = codec or self.codec
        self.update(codec.parse(msg))
        self.typed_values = not getattr(codec, 'decoded_values_are_untyped',
                                        False)

    def __str__(self):
        """
        Human-readable representation
        """
        out = self.output_fix()
        try:
            out = unicode(out).encode('UTF-8')
        except (UnicodeDecodeError, NameError):
            pass
        return str(out)

    def calculate_checksum(self):
        """ calculates the standard fix checksum"""
        return self.checksum()

    def checksum(self, value=None):
        """
        FIX checksum
        """
        if value is None:
            value = pyfixmsg.len_and_chsum(self)[1] % 256
        return '{0:03d}'.format(value % 256)

    def set_len_and_chksum(self):
        """
        Assign length and checksum based on current contents
        """
        length, raw_checksum = pyfixmsg.len_and_chsum(self)
        self[9] = length
        self[10] = self.checksum(raw_checksum)

    def tag_exact(self, tag, value, case_insensitive=False):
        """ Returns True if self[tag] has the exact value. Returns False if the tag doesnt exist or is not exact """
        value = str(value)
        try:
            mine = str(self[tag])
        except KeyError:
            return False
        if case_insensitive:
            value = value.lower()
            mine = mine.lower()
        return mine == value

    def tag_iexact(self, tag, value):
        """ Returns True if self[tag] has the exact value (case insensitive).
           Returns False if the tag doesnt exist or is not exact """
        return self.tag_exact(tag, value, case_insensitive=True)

    def tag_contains(self, tag, value, case_insensitive=False):
        """ Returns True if self[tag] contains value. Returns False otherwise, or if the tag doesnt exist
        This is a string string comparison"""
        value = str(value)
        try:
            mine = str(self[tag])
        except KeyError:
            return False
        if case_insensitive:
            value = value.lower()
            mine = mine.lower()
        return value in mine

    def tag_icontains(self, tag, value):
        """ case-insensitive version of tag_contains"""
        return self.tag_contains(tag, value, case_insensitive=True)

    def tag_exact_dict(self, dictionary):
        """ check that all the keys and values of the passed dict are present and identical in the fixmsg"""
        return all(
            self.tag_exact(tag, value)
            for tag, value in list(dictionary.items()))

    def tag_match_regex(self, tag, regex):
        """ returns True of self[tag] matches regex, false otherwise or if the tag doesnt exist """
        regex = str(regex)
        try:
            if re.match(regex, str(self[tag])):
                return True
        except KeyError:
            pass
        return False

    def tag_lt(self, tag, value):
        """ Test tag is smaller than value. Uses decimal comparison if possible. Returns False if tag absent"""
        if not self.get(tag):
            return False
        if not value:
            return False
        tag = self.get(tag)
        try:
            tag = decimal.Decimal(tag)
            value = decimal.Decimal(value)
        except (ValueError, decimal.InvalidOperation):
            pass
        return tag < value

    def tag_le(self, tag, value):
        """ Test tag is smaller or equal value. Uses decimal comparison if possible. Returns False if tag absent"""
        if not self.get(tag):
            return False
        if not value:
            return False
        tag = self.get(tag)
        try:
            tag = decimal.Decimal(tag)
            value = decimal.Decimal(value)
        except (ValueError, decimal.InvalidOperation):
            pass
        return tag <= value

    def tag_gt(self, tag, value):
        """ Test tag is greater than value. Uses decimal comparison if possible. Returns False if tag absent"""
        if not self.get(tag):
            return False
        if not value:
            return False
        tag = self.get(tag)
        try:
            tag = decimal.Decimal(tag)
            value = decimal.Decimal(value)
        except (ValueError, decimal.InvalidOperation):
            pass
        return tag > value

    def tag_ge(self, tag, value):
        """ Test tag is greater or equal to value. Uses decimal comparison if possible. Returns False if tag absent"""
        if not self.get(tag):
            return False
        if not value:
            return False
        tag = self.get(tag)
        try:
            tag = decimal.Decimal(tag)
            value = decimal.Decimal(value)
        except (ValueError, decimal.InvalidOperation):
            pass

        return tag >= value

    def tag_in(self, tag, values):
        """ returns True of self[tag] is in values, false otherwise or if the tag doesnt exist """
        values = [str(i) for i in values]
        return str(self.get(tag, None)) in values

    def update_all(self, tag, value):
        """ this will force a tag (that already exists!) to a value at all appearances """
        for path in self.find_all(tag):
            point = self
            last_key = None
            last_point = point
            for key in path:
                last_point = point
                point = point[key]
                last_key = key
            if last_key is not None:
                last_point[last_key] = value
예제 #13
0
def main(spec_filename):
    """
    Illustration of the usage of FixMessage.

    :param spec_filename: a specification file from Quickfix.org
    :type spec_filename: ``str``
    """
    spec = FixSpec(spec_filename)
    codec = Codec(
        spec=spec,  # The codec will use the given spec to find repeating groups
        fragment_class=FixFragment
    )  # the codec will produce FixFragment objects inside repeating groups

    # (default is dict). This is required for the find_all() and anywhere()
    # methods to work. It would fail with AttributeError otherwise.

    def fixmsg(*args, **kwargs):
        """Factory function. This allows us to keep the dictionary __init__
        arguments unchanged and force the codec to our given spec and avoid
        passing codec to serialisation and parsing methods.

        The codec defaults to a reasonable parser but without repeating groups.

        An alternative method is to use the ``to_wire`` and ``from_wire`` methods
        to serialise and parse messages and pass the codec explicitly.
        """
        returned = FixMessage(*args, **kwargs)
        returned.codec = codec
        return returned

    ########################
    #    Vanilla tag/value
    #########################
    data = (b'8=FIX.4.2|9=97|35=6|49=ABC|56=CAB|34=14|52=20100204-09:18:42|'
            b'23=115685|28=N|55=BLAH|54=2|44=2200.75|27=S|25=H|10=248|')
    msg = fixmsg().load_fix(data, separator='|')

    # The message object is a dictionary, with integer keys
    # and string values. No validation is performed.
    # The spec contains the information, however so it can be retrieved.
    print("Message Type {} ({})".format(msg[35], spec.msg_types[msg[35]].name))
    print("Price {} (note type: {}, spec defined type {})".format(
        msg[44], type(msg[44]),
        spec.tags.by_tag(44).type))
    check_tags = (55, 44)
    for element, required in spec.msg_types[msg[35]].composition:
        if isinstance(element, FixTag) and element.tag in check_tags:
            if required:
                print("{} is required on this message type".format(
                    element.name))
            else:
                print("{} is not required on this message type".format(
                    element.name))
    print("Spec also allows looking up enums: {}  is {}".format(
        msg[54],
        spec.tags.by_tag(54).enum_by_value(msg[54])))
    # Although the values are stored as string there are rich operators provided that
    # allow to somewhat abstract the types
    print("exact comparison with decimal: ",
          msg.tag_exact(44, decimal.Decimal("2200.75")))
    print("exact comparing with int: ", msg.tag_exact(54, 2))
    print("lower than with float:", msg.tag_lt(44, 2500.0))
    print("greater than with float:", msg.tag_gt(23, 110000.1))
    print("contains, case sensitive and insensitive",
          msg.tag_contains(55, "MI"), msg.tag_icontains(55, "blah"))

    # Tags manipulation is as for a dictionary
    msg[56] = "ABC.1"  # There is no enforcement of what tags are used for, so changing 56 is no worry for the lib
    msg.update({55: 'ABC123.1', 28: 'M'})

    # note regex matching
    print("tag 56 changed", msg.tag_match_regex(56, r"..M\.N"))
    print("Tag 55 and 28 changed to {} and {}".format(msg[55], msg[28]))

    # There are additional tools for updating the messages' content though
    none_or_one = randint(0, 1) or None
    msg.set_or_delete(27, none_or_one)
    msg.apply({25: None, 26: 2})

    if none_or_one is None:
        print("Got None, the tag is deleted")
        assert 27 not in msg
    else:
        print("Got None, the tag is maintained")
        assert msg[27] == 1

    assert 25 not in msg
    assert msg.tag_exact(26, '2')

    ########################
    #    copying messages
    #########################

    # Because messages maintain a reference to the codec and the spec
    # a deepcopy() of messages is extremely inefficient. It is a lot faster
    # to serialise-deserialise to copy a message, which is what copy() does.
    # Alternatively do a shallow copy through dict constructor.
    new_msg = msg.copy()
    assert new_msg is not msg
    msg.set_len_and_chksum()
    # Note that this reverts the type of values that were set manually
    assert new_msg[26] != msg[26]
    print("tag 26 before {}, after {}".format(type(msg[26]),
                                              type(new_msg[26])))
    assert all(new_msg.tag_exact(t, msg[t]) for t in msg)

    msg = fixmsg().load_fix(data, separator='|')

    # if no types have changed, the copy() method returns a message that is identical
    # to the original one
    new_msg = copy(msg)
    assert new_msg == msg

    # note you can also use the method copy()
    new_msg = msg.copy()
    assert new_msg == msg
    assert new_msg is not msg

    # and codec is not copied
    assert new_msg.codec is msg.codec

    # note that msg equality is not true when using dict constructor
    new_msg = FixMessage(msg)
    assert new_msg != msg
    # That's because codec, time, recipient and direction are not preserved
    # when using this technique, only tags are
    assert dict(new_msg) == dict(msg)

    ########################
    #    Repeating Groups
    #########################

    # Repeating groups are indexed by count tag (268 below)
    # and stored as a list of FixMessages (technically FixFragments, but close enough)
    data = (b'8=FIX.4.2|9=196|35=X|49=A|56=B|34=12|52=20100318-03:21:11.364'
            b'|262=A|268=2|279=0|269=0|278=BID|55=EUR/USD|270=1.37215'
            b'|15=EUR|271=2500000|346=1|279=0|269=1|278=OFFER|55=EUR/USD'
            b'|270=1.37224|15=EUR|271=2503200|346=1|10=171|')
    msg = fixmsg().load_fix(data, separator='|')
    print("Message Type {} ({})".format(msg[35], spec.msg_types[msg[35]].name))
    print("Repeating group {} looks like {}".format(
        spec.tags.by_tag(268).name, msg[268]))
    print("Accessing repeating groups at depth: tag 278 "
          "in the second member of the group is '{}'".format(msg[268][1][278]))

    # finding out if a tag is present can be done at depth
    print(
        "Utility functions like anywhere() allow you to find out "
        "if a tag is present at depth, or find the path to it: {} is present : {}; "
        "it can be found at the following paths {}".format(
            278, msg.anywhere(278), list(msg.find_all(278))))

    ########################
    # Customise the spec
    #########################
    # Sometimes it may be desirable to tweak the spec a bit, especially add a custom tag
    # or a custom repeating group.

    # Create tag
    spec.tags.add_tag(10001, "MyTagName")
    assert spec.tags.by_tag(10001).name == "MyTagName"
    assert spec.tags.by_name("MyTagName").tag == 10001
    # Change enum values
    spec.tags.by_tag(54).add_enum_value(name="SELLFAST", value="SF")
    assert spec.tags.by_tag(54).enum_by_value("SF") == "SELLFAST"

    # Add repeating Group
    # let's add repeating group 268 to msg type 'D'
    # for illustration purposes, we'll create the group from scratch
    data = (b'8=FIX.4.2|9=196|35=D|49=A|56=B|34=12|52=20100318-03:21:11.364'
            b'|262=A|268=2|279=0|269=0|278=BID|55=EUR/USD|270=1.37215'
            b'|15=EUR|271=2500000|346=1|279=0|269=1|278=OFFER|55=EUR/USD'
            b'|270=1.37224|15=EUR|271=2503200|346=1|10=171|')
    before = FixMessage()
    before.codec = Codec(spec=spec)
    before.load_fix(data, separator='|')

    # The composition is a iterable of pairs (FixTag, bool), with the bool indicating whether
    # the tag is required or not (although it's not enforced in the codec at this time
    composition = [(spec.tags.by_tag(i), False)
                   for i in (279, 269, 278, 55, 270, 15, 271, 346)]

    # add_group takes a FixTag and the composition
    spec.msg_types['D'].add_group(spec.tags.by_tag(268), composition)

    after = FixMessage()
    after.codec = Codec(spec=spec, fragment_class=FixFragment)
    after.load_fix(data, separator='|')

    assert isinstance(before[268],
                      (str, unicode))  # 268 is not parsed as a repeating group
    assert before[
        270] == '1.37224'  # 268 is not parsed as a repeating group, so 271 takes the second value

    assert isinstance(
        after[268],
        RepeatingGroup)  # After the change, 268 becomes a repeating group
    assert list(after.find_all(270)) == [[268, 0, 270],
                                         [268, 1,
                                          270]]  # and both 270 can be found
예제 #14
0
from pyfixmsg.fixmessage import FixFragment, FixMessage
from pyfixmsg.codecs.stringfix import Codec
from pyfixmsg.reference import FixSpec, FixTag
import os

SPEC_FILE = os.environ["FIX_SPEC_FILE"]
spec = FixSpec(SPEC_FILE)
codec = Codec(spec=SPEC_FILE, fragment_class=FixFragment)


def fixmsg(*args, **kwargs) -> FixMessage:
    """
    docstring
    """
    returned = FixMessage(*args, **kwargs)
    returned.codec = codec
    return returned


data = (
    "8=FIX.4.2, 9=97, 35=6, 45=6, 49=ABC, 56=CAB, 34=14, 52=20201130-10:27:30"
    "23=115685,28=N,55=BLAH,54=2,44=2200.75,27=S,25=H,10=248")

fixMsg = FixMessage().load_fix(data, separator=",")

message = fixmsg().load_fix(data, separator=",")
message.codec = codec

print("Message Type {} ({})".format(fixMsg[35],
                                    spec.msg_types[fixMsg[35]].name))
check_tags = (55, 44, 27)
예제 #15
0
import os
from testplan.testing.multitest import testcase, testsuite, MultiTest
from pyfixmsg.fixmessage import FixMessage
from pyfixmsg.reference import FixSpec
from pyfixmsg.codecs.stringfix import Codec

from testplan.common.utils.context import context
from testplan.testing.multitest.driver.fix import FixServer, FixClient

CURRENT_PATH = os.getcwd()
SPEC_FILE = os.path.join(CURRENT_PATH, "spec", "FIX42.xml")
spec = FixSpec(SPEC_FILE)

codec = Codec(spec=spec)


def fixmsg(source: str) -> FixMessage:
    """
    docstring
    """
    msg = FixMessage(source)
    msg.codec = codec
    return msg


@testsuite
class FIXMultiClient:
    @testcase
    def send_and_receive_msgs(self, env, result):
        """
        docstring