PK!4f3fast_rpc/__init__.pyfrom fast_rpc.serialization import encode, encode as dumps, decode, decode as loads __all__ = ['encode', 'dumps', 'decode', 'loads'] PK!fast_rpc/protocol/__init__.pyPK!?'A^^"fast_rpc/serialization/__init__.pyfrom fast_rpc.serialization.types import MethodCall, MethodResponse, FaultResponse from fast_rpc.serialization.encoders import encode, FastRPCEncodeError from fast_rpc.serialization.decoders import decode, FastRPCDecodeError __all__ = ['encode', 'FastRPCEncodeError', 'decode', 'FastRPCDecodeError', 'MethodCall', 'MethodResponse', 'FaultResponse'] PK!h%%#fast_rpc/serialization/constants.pyENDIAN = 'little' YEAR_OFFSET = 1600 PK!My//"fast_rpc/serialization/decoders.pyimport struct from datetime import datetime from typing import Any from fast_rpc.serialization.types import MethodCall, MethodResponse, FaultResponse from fast_rpc.serialization.constants import ENDIAN class FastRPCDecodeError(ValueError): pass class View: """ To iterate over data we use special 'iterator' view object with variable moving size """ def __init__(self, data: bytes): self._data = data self._offset = 0 def __iter__(self): return self def __next__(self, size: int=1) -> bytes: if self._offset >= len(self._data): raise StopIteration() old_offset = self._offset self._offset += size return self._data[old_offset:self._offset] def next(self, size: int) -> bytes: return self.__next__(size) def _get_data_type(i: bytes) -> int: return i[0] & 0b11111000 def _get_add_info(i: bytes) -> int: return i[0] & 0b00000111 class FastRPCDecoderV1: def __init__(self): self._decode_table = { 0x08: self._decode_int, 0x10: self._decode_bool, 0x18: self._decode_float, 0x20: self._decode_string, 0x28: self._decode_datetime, 0x30: self._decode_bytes, 0x50: self._decode_dict, 0x58: self._decode_array, 0x68: self._decode_method_call, 0x70: self._decode_method_response, 0x78: self._decode_fault_response, } def decode(self, view: View) -> Any: i = next(view) type_ = _get_data_type(i) add_info = _get_add_info(i) if type_ not in self._decode_table: raise FastRPCDecodeError('Unrecognized object type: 0x{:02x}'.format(type_)) return self._decode_table[type_](add_info, view) def _decode_int(self, add_info: int, view: View) -> int: """ Unsigned integer deserialization Bytes: | | | | | | value: 00001 xxx data (1-4 octets) field name: | Type| | Data stored in little endian | Number of data octets """ size = add_info + 1 if size > 4: raise FastRPCDecodeError('Integer can be max 4 bytes long') return int.from_bytes(bytes=view.next(size=size), byteorder=ENDIAN) def _decode_bool(self, add_info: int, view: View) -> bool: """ Boolean deserialization Bytes: | | value: 00010 00x field name: | Type| | Value is stored in least significant bit(x) """ return bool(add_info) def _decode_float(self, add_info: int, view: View) -> float: """ Float serialisation Bytes: | | | value: 00011 000 data (8 octets) field name: | Type| | Floating point number in double precision(IEEE 754) | """ return struct.unpack(' str: """ String serialisation Bytes: | | | | | | | value: 00100 xxx data-size (1-4 octets) data (data-size octets) field name: | Type| | specifies length of data | Utf-8, no null terminated, | Length of data-size field no escaping """ size = add_info + 1 data_size = int.from_bytes(view.next(size=size), byteorder=ENDIAN) if data_size == 0: return '' bytes_ = view.next(size=data_size) return bytes_.decode() def _decode_datetime(self, add_info: int, view: View) -> datetime: """ Datetime serialisation Bytes: | | | | | | | | | | | | value: 00101000 3b 6b 6b 5b 5b 4b 11b field name: | type | zone | timestamp | | sec | min | hour | day | | year | week day month """ zone = next(view) # TODO: implement timestamp = struct.unpack(' bytes: """ Serialize bytes(Binary) Bytes: | | | | | | | value: 00110 xxx data-size (1-4 octets) data (data-size octets) field name: | Type| | Number of data octets | No encoded data | Length of data-size field """ size = add_info + 1 data_size = int.from_bytes(view.next(size=size), byteorder=ENDIAN) return view.next(size=data_size) if data_size > 0 else b'' def _decode_dict(self, add_info: int, view: View) -> dict: """ Dict(Struct) serialisation Bytes: | | | | | | | | | value: 01010 xxx num-members (1-4 octets) (1-255 octets) DATATYPES field name: | Type| | number of struct members | | Utf-8 name | | Length of num-members field name length DATATYPES - serialized items | num-members * structure above """ size = add_info + 1 num_of_members = int.from_bytes(view.next(size=size), byteorder=ENDIAN) o = {} for _ in range(num_of_members): try: key_size = int.from_bytes(next(view), byteorder=ENDIAN) key = view.next(size=key_size).decode() value = self.decode(view) except StopIteration: raise FastRPCDecodeError('Uncomplete data for structure deserialization') o[key] = value return o def _decode_array(self, add_info: int, view: View) -> list: """ Deserialize Array Bytes: | | | | | | | value: 01011 xxx num-items (1-4 octets) DATATYPES field name: | Type| | Number of array items | | Length of num-items field | num-items * structure above DATATYPES - serialized items """ size = add_info + 1 num_of_items = int.from_bytes(view.next(size=size), byteorder=ENDIAN) o = [] for _ in range(num_of_items): item = self.decode(view) o.append(item) return o def _decode_method_call(self, add_info: int, view: View) -> MethodCall: """ Serialize method call non-data type object Bytes: | | | | | value: 01101000 (1-255 octets) PARAMETERS field name: | Type | | Utf-8 name | | Name size """ name_size = int.from_bytes(next(view), byteorder=ENDIAN) method_name = view.next(size=name_size).decode() params = [] try: while True: o = self.decode(view) params.append(o) except StopIteration: pass return MethodCall(method_name, *params) def _decode_method_response(self, add_info: int, view: View) -> MethodResponse: """ Deserialize method response non-data type object Bytes: | | | value: 01110000 VALUE field name: | Type | Return value | """ try: value = self.decode(view) except StopIteration: value = MethodResponse.Empty() return MethodResponse(value) def _decode_fault_response(self, add_info: int, view: View) -> FaultResponse: """ Serialize fault response non-data type object Bytes: | | | | value: 01111000 INTEGER STRING field name: | Type | Fault Num | Fault message | """ fault_number = self.decode(view) fault_message = self.decode(view) return FaultResponse(fault_number, fault_message) class FastRPCDecoderV2(FastRPCDecoderV1): def __init__(self): super(FastRPCDecoderV2, self).__init__() del self._decode_table[0x08] # remove unsupported v1 int self._decode_table.update({ 0x38: self._decode_positive_int, 0x40: self._decode_negative_int, }) def _decode_positive_int(self, add_info: int, view: View) -> int: """ Integer8 positive deserialization Bytes: | | | | | | | | | | value: 00111 xxx data (1-8 octets) field name: | Type| | Data stored in little endian | Number of data octets """ size = add_info + 1 if size > 8: raise FastRPCDecodeError('Integer can be max 8 bytes long') return int.from_bytes(bytes=view.next(size=size), byteorder=ENDIAN) def _decode_negative_int(self, add_info: int, view: View) -> int: """ Integer8 negative Bytes: | | | | | | | | | | value: 01000 xxx data (1-8 octets) field name: | Type| | Data stored in little endian | Number of data octets """ return -self._decode_positive_int(add_info, view) class FastRPCDecoderV3(FastRPCDecoderV2): def __init__(self): super(FastRPCDecoderV3, self).__init__() self._decode_table[0x08] = self._decode_int # return of one int type @staticmethod def _unzigzag(number: int) -> int: """ ZigZag encoding uses LSB for sign storage, the value of the signed integer is shifted left by one bit, and bitwise negated in case of negative integers. """ if number % 2: # negative numbers are zigzagged as odd number = ~number # don't forget to change sign ;) number >>= 1 return number def _decode_int(self, add_info: int, view: View) -> int: """ Unsigned integer serialisation Bytes: | | | | | | value: 00001 xxx data (1-4 octets) field name: | Type| | | Data stored as ZigZag encoded unsigned integer in little endian Number of data octets """ size = add_info + 1 if size > 8: raise FastRPCDecodeError('Integer can be max 8 bytes long') zigzagged_number = int.from_bytes(bytes=view.next(size=size), byteorder=ENDIAN) return self._unzigzag(zigzagged_number) def get_deserializer(version: int) -> FastRPCDecoderV1: """ Return deserializer instance for protocol version """ deserializers = { 1: FastRPCDecoderV1, 2: FastRPCDecoderV2, 3: FastRPCDecoderV3 } if version not in deserializers: raise ValueError('Unsupported protocol version') return deserializers[version]() def decode(data: bytes, *, version: int = 1) -> Any: """ Decode fastrpc encoded data to python obj """ view = View(data) if data.startswith(b'\xca\x11'): view.next(size=2) # TODO here we can get serializer version view.next(size=2) deserializer = get_deserializer(version) return deserializer.decode(view) PK!K_#@#@"fast_rpc/serialization/encoders.pyimport struct from collections.abc import Iterable from datetime import datetime from typing import Any, Dict, Callable from fast_rpc.serialization.types import MethodCall, MethodResponse, FaultResponse from fast_rpc.serialization.constants import ENDIAN, YEAR_OFFSET class FastRPCEncodeError(Exception): pass def get_mem_size_bytes(num: int) -> int: """ Compute memory size of bytes taken by integer """ return 1 if num == 0 else (num.bit_length() + 7) // 8 class FastRPCEncoderV1: _version = 1 @property def _magic(self) -> bytes: return b'\xca\x11\x01\x00' def __init__(self): self._encode_table: Dict[Any, Callable] = { int: self._encode_int, bool: self._encode_bool, float: self._encode_float, str: self._encode_str, datetime: self._encode_datetime, bytes: self._encode_bytes, dict: self._encode_struct, tuple: self._encode_array, list: self._encode_array, Iterable: self._encode_array, MethodCall: self._encode_method_call, MethodResponse: self._encode_method_response, FaultResponse: self._encode_fault_response } def encode(self, o: Any) -> bytes: """ Default function called for all unknown functions First byte of all fastrpc serialized objects is divided to two sections: +-----+---+ | 5b | 3b| +-----+---+ Type Add Type: code of serialized object type Add: additional info, usualy number of octets contain user data (for v1: 0-4 means 1-4 octets) """ type_ = type(o) method = self._encode_table.get(type_) # second chance to hit function if method is None: for type_ in self._encode_table: if isinstance(o, type_): method = self._encode_table[type_] break if method is None: raise FastRPCEncodeError("Object of type '{}' is not FastRPC serializable".format(type(o))) return method(o) @staticmethod def _encode_int(number: int) -> bytes: """ Unsigned integer serialisation Bytes: | | | | | | value: 00001 xxx data (1-4 octets) field name: | Type| | Data stored in little endian | Number of data octets """ type_ = 0b00001000 # 0x08 mem_size_bytes = get_mem_size_bytes(number) code_size_bytes = mem_size_bytes - 1 # in fast-rpc protocol 0b000 add mean 1 B of data type_add = type_ ^ code_size_bytes return type_add.to_bytes(length=1, byteorder=ENDIAN) + \ number.to_bytes(length=mem_size_bytes, byteorder=ENDIAN) @staticmethod def _encode_bool(boolean: bool) -> bytes: """ Boolean serialisation Bytes: | | value: 00010 00x field name: | Type| | Value is stored in least significant bit(x) """ type_ = 0b00010000 # 0x10 type_add = type_ | boolean return type_add.to_bytes(length=1, byteorder=ENDIAN) @staticmethod def _encode_float(number: float) -> bytes: """ Float serialisation Bytes: | | | value: 00011 000 data (8 octets) field name: | Type| | Floating point number in double precision(IEEE 754) | """ type_add = 0b00011000 # 0x18 return type_add.to_bytes(length=1, byteorder=ENDIAN) + struct.pack(' bytes: """ String serialisation Bytes: | | | | | | | value: 00100 xxx data-size (1-4 octets) data (data-size octets) field name: | Type| | specifies length of data | Utf-8, no null terminated, | Length of data-size field no escaping """ type_ = 0b00100000 # 0x20 encoded = s.encode() mem_size_bytes = get_mem_size_bytes(len(encoded)) code_size_bytes = mem_size_bytes - 1 # in fast-rpc protocol 0b000 add mean 1 B of data type_add = type_ ^ code_size_bytes return type_add.to_bytes(length=1, byteorder=ENDIAN) + \ len(encoded).to_bytes(length=mem_size_bytes, byteorder=ENDIAN) + \ encoded @staticmethod def _encode_datetime(d: datetime) -> bytes: """ Datetime serialisation Bytes: | | | | | | | | | | | | value: 00101000 3b 6b 6b 5b 5b 4b 11b field name: | type | zone | timestamp | | sec | min | hour | day | | year | week day month """ type_add = 0b00101000 # 0x28 zone = 0x00 # TODO implement timestamp = int(d.timestamp()) weekday = (d.weekday()) % 6 # fast-rpc week start with sunday, not monday ( WTF! ) encoded_date_time = weekday encoded_date_time |= (d.second << 3) encoded_date_time |= (d.minute << 9) encoded_date_time |= (d.hour << 15) encoded_date_time |= (d.day << 20) encoded_date_time |= (d.month << 25) encoded_date_time |= ((d.year - YEAR_OFFSET) << 29) # TODO not sure about zone endian, spec is not exact about this return type_add.to_bytes(length=1, byteorder=ENDIAN) + \ zone.to_bytes(length=1, byteorder=ENDIAN) + \ struct.pack(' bytes: """ Serialize bytes(Binary) Bytes: | | | | | | | value: 00110 xxx data-size (1-4 octets) data (data-size octets) field name: | Type| | Number of data octets | No encoded data | Length of data-size field """ type_ = 0b00110000 # 0x30 mem_size_bytes = get_mem_size_bytes(len(o)) code_size_bytes = mem_size_bytes - 1 type_add = type_ | code_size_bytes return type_add.to_bytes(length=1, byteorder=ENDIAN) + \ len(o).to_bytes(length=mem_size_bytes, byteorder=ENDIAN) + o def _encode_struct(self, o: Dict[Any, Any]) -> bytes: """ Dict(Struct) serialisation Bytes: | | | | | | | | | value: 01010 xxx num-members (1-4 octets) (1-255 octets) DATATYPES field name: | Type| | number of struct members | | Utf-8 name | | Length of num-members field name length DATATYPES - serialized items | num-members * structure above """ type_ = 0b01010000 # 0x50 # first we get struct size metainfo and meta_metainfo for storing metainfo ;D mem_size_bytes = get_mem_size_bytes(len(o)) code_size_bytes = mem_size_bytes - 1 type_add = type_ | code_size_bytes encoded = type_add.to_bytes(length=1, byteorder=ENDIAN) + \ len(o).to_bytes(length=mem_size_bytes, byteorder=ENDIAN) for key, value in o.items(): if key is None: raise TypeError("Key in Dictionary must be a string.") encoded_key = str(key).encode() if len(encoded_key) == 0: raise TypeError("Lenght of member name is 0 not in interval (1-255)") encoded += len(encoded_key).to_bytes(length=1, byteorder=ENDIAN) + encoded_key + self.encode(value) return encoded def _encode_array(self, o: Iterable) -> bytes: """ Serialize Iterable(Array) Bytes: | | | | | | | value: 01011 xxx num-items (1-4 octets) DATATYPES field name: | Type| | Number of array items | | Length of num-items field | num-items * structure above DATATYPES - serialized items """ type_ = 0b01011000 # 0x58 o = tuple(o) mem_size_bytes = get_mem_size_bytes(len(o)) code_size_bytes = mem_size_bytes - 1 type_add = type_ | code_size_bytes encoded = type_add.to_bytes(length=1, byteorder=ENDIAN) + \ len(o).to_bytes(length=mem_size_bytes, byteorder=ENDIAN) for i in o: encoded += self.encode(i) return encoded def _encode_method_call(self, o: MethodCall) -> bytes: """ Serialize method call non-data type object Bytes: | | | | | value: 01101000 (1-255 octets) PARAMETERS field name: | Type | | Utf-8 name | | Name size """ type_ = 0b01101000 # 0x68 encoded_method_name = str(o.method_name).encode() if len(encoded_method_name) > 255 or len(encoded_method_name) == 0: raise ValueError('Method name is not in interval (1-255)') encoded = self._magic + \ type_.to_bytes(length=1, byteorder=ENDIAN) + \ len(encoded_method_name).to_bytes(length=1, byteorder=ENDIAN) + \ encoded_method_name for param in o.params: encoded += self.encode(param) return encoded def _encode_method_response(self, o: MethodResponse) -> bytes: """ Serialize method response non-data type object Bytes: | | | value: 01110000 VALUE field name: | Type | Return value | """ type_ = 0b01110000 # 0x70 encoded = self._magic + type_.to_bytes(length=1, byteorder=ENDIAN) if not isinstance(o.value, MethodResponse.Empty): encoded += self.encode(o.value) return encoded def _encode_fault_response(self, o: FaultResponse) -> bytes: """ Serialize fault response non-data type object Bytes: | | | | value: 01111000 INTEGER STRING field name: | Type | Fault Num | Fault message | """ type_ = 0b01111000 # 0x78 encoded = self._magic + \ type_.to_bytes(length=1, byteorder=ENDIAN) + \ self.encode(o.fault_number) + \ self.encode(o.fault_message) return encoded class FastRPCEncoderV2(FastRPCEncoderV1): _version = 2 @property def _magic(self) -> bytes: return b'\xca\x11\x02\x00' def __init__(self): super(FastRPCEncoderV2, self).__init__() # update table with new type self._encode_table[type(None)] = self._encode_null @staticmethod def _encode_int(number: int) -> bytes: """ Integer serialisation Integer8 positive Bytes: | | | | | | | | | | value: 00111 xxx data (1-8 octets) field name: | Type| | Data stored in little endian | Number of data octets Integer8 negative Bytes: | | | | | | | | | | value: 01000 xxx data (1-8 octets) field name: | Type| | Data stored in little endian | Number of data octets """ type_ = 0b01000000 if number < 0 else 0b00111000 # 0x40 0x38 mem_size_bytes = get_mem_size_bytes(number) code_size_bytes = mem_size_bytes - 1 type_add = type_ ^ code_size_bytes return type_add.to_bytes(length=1, byteorder=ENDIAN) + \ abs(number).to_bytes(length=mem_size_bytes, byteorder=ENDIAN) @staticmethod def _encode_null(o: None) -> bytes: """ Serialize None object Bytes: | | value: 01100000 field name: | Type | """ return 0b01100000.to_bytes(length=1, byteorder=ENDIAN) class FastRPCEncoderV3(FastRPCEncoderV2): _version = 3 @property def _magic(self) -> bytes: return b'\xca\x11\x03\x00' def __init__(self): super(FastRPCEncoderV3, self).__init__() @staticmethod def _zigzag(number: int) -> int: """ ZigZag encoding uses LSB for sign storage, the value of the signed integer is shifted left by one bit, and bitwise negated in case of negative integers. """ number <<= 1 return ~number if number < 0 else number def _encode_int(self, number: int) -> bytes: """ Unsigned integer serialisation Bytes: | | | | | | value: 00001 xxx data (1-4 octets) field name: | Type| | | Data stored as ZigZag encoded unsigned integer in little endian Number of data octets """ type_ = 0b00001000 # 0x08 number = self._zigzag(number) mem_size_bytes = get_mem_size_bytes(number) code_size_bytes = mem_size_bytes - 1 # in fast-rpc protocol 0b000 add mean 1 B of data type_add = type_ ^ code_size_bytes return type_add.to_bytes(length=1, byteorder=ENDIAN) + \ number.to_bytes(length=mem_size_bytes, byteorder=ENDIAN) @staticmethod def _encode_datetime(d: datetime) -> bytes: """ Datetime serialisation Bytes: | | | | | | | | | | | | | | | | value: 00101000 3b 6b 6b 5b 5b 4b 11b field name: | type | zone | timestamp (64b) | | sec | min | hour | day | | year | week day month """ type_add = 0b00101000 # 0x28 zone = 0x00 # TODO implement timestamp = int(d.timestamp()) weekday = (d.weekday()) % 6 # fast-rpc week start with sunday, not monday ( WTF! ) encoded_date_time = weekday encoded_date_time |= (d.second << 3) encoded_date_time |= (d.minute << 9) encoded_date_time |= (d.hour << 15) encoded_date_time |= (d.day << 20) encoded_date_time |= (d.month << 25) encoded_date_time |= ((d.year - YEAR_OFFSET) << 29) # TODO not sure about zone endian, spec is not exact about this return type_add.to_bytes(length=1, byteorder=ENDIAN) + \ zone.to_bytes(length=1, byteorder=ENDIAN) + \ struct.pack(' FastRPCEncoderV1: """ Return serializer instance for protocol version """ serializers = { 1: FastRPCEncoderV1, 2: FastRPCEncoderV2, 3: FastRPCEncoderV3 } if version not in serializers: raise ValueError('Unsupported protocol version') return serializers[version]() def encode(obj: Any, *, version: int = 1) -> bytes: """ Encode obj to FastRPC binary format """ # we store used protocol version serializer = get_serializer(version) return serializer.encode(obj) PK! |!fast_rpc/serialization/types.pyfrom typing import Any class MethodCall: def __init__(self, method_name, *params): self.method_name = method_name self.params = params def __eq__(self, other): return self.method_name == other.method_name and self.params == other.params class MethodResponse: class Empty: pass @property def value(self): return self._value @value.setter def value(self, data: Any): if isinstance(data, (MethodCall, MethodResponse)): raise ValueError('Object {} can\'t be a response value'.format(type(data))) self._value = data def __init__(self, value: Any = Empty()): self._value = MethodResponse.Empty() self.value = value def __eq__(self, other): """ Two responses are equal if values are equal or both are empty """ return self.value == other.value or (isinstance(self.value, MethodResponse.Empty) and isinstance(other.value, MethodResponse.Empty)) class FaultResponse: def __init__(self, fault_number: int, fault_message: str): self.fault_number = fault_number self.fault_message = fault_message def __eq__(self, other): return self.fault_number == other.fault_number and self.fault_message == other.fault_message PK!2++ fast_rpc-0.1.0.dist-info/LICENSEMIT License Copyright (c) 2017 Ken Mijime Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!HڽTUfast_rpc-0.1.0.dist-info/WHEEL A н#Z;/"d&F[xzw@Zpy3Fv]\fi4WZ^EgM_-]#0(q7PK!H FU!fast_rpc-0.1.0.dist-info/METADATAN@ ~ U%D&Z~&q!oϦ$)(b'Qp!DM Z 1ʉZ?(O`zd"ƄmM.%ky s8jv)\Rl&PbH^ ƚNRFWNkm7 Zc`O!\2_LJvs~R3)56h!UqsPK!HMfast_rpc-0.1.0.dist-info/RECORDuͶjPy:؄8RR^b+gG<=kїAm}")AhQTw9a6yd77Ɉo`4>| ~bHUO(/jЩf4:O >d`ok>#;ܦaN!I'7 =v ҹ5qZyYѾ;W`Ѡ0+GUّ$7: Pjӎـe﹀"A5K( 1(?NJm|Мշ颠ԌYF)(~6 w^I=sںlE;Vf`d`](vɳMڣdkVW&ydV7 6t$ץ`q:-2Th CY.(ϥ#K/S~PK!4f3fast_rpc/__init__.pyPK!fast_rpc/protocol/__init__.pyPK!?'A^^"fast_rpc/serialization/__init__.pyPK!h%%#fast_rpc/serialization/constants.pyPK!My//"fast_rpc/serialization/decoders.pyPK!K_#@#@"3fast_rpc/serialization/encoders.pyPK! |!xsfast_rpc/serialization/types.pyPK!2++ xfast_rpc-0.1.0.dist-info/LICENSEPK!HڽTU!}fast_rpc-0.1.0.dist-info/WHEELPK!H FU!}fast_rpc-0.1.0.dist-info/METADATAPK!HM~fast_rpc-0.1.0.dist-info/RECORDPK Q5