Beispiel #1
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]]
Beispiel #2
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='=')
Beispiel #3
0
 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;'
Beispiel #4
0
 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)
Beispiel #5
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)
Beispiel #6
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)
Beispiel #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]
Beispiel #8
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;'
Beispiel #9
0
 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)
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
Beispiel #11
0
    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
Beispiel #12
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
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)
Beispiel #14
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