caudal/programa/cartera/msgpack_min.py
esetheyers 7e18576f86 Caudal 0.2.0
Taxes for the whole of Spain (common territory, Navarre, Álava, Bizkaia and Gipuzkoa); flats, garages and land valued on their own; Treasury bills, bonds and deposits priced on BME; broker accounts; a guided start; a page per investment with its chart and the tax of selling today; net worth at the top; undo when deleting; Woodpecker CI for the tests and the Linux build.
2026-10-15 16:49:15 -05:00

138 lines
4.4 KiB
Python

"""msgpack_min.py — MessagePack mínimo, lo justo para el canal de precios de BME (sin dependencias)."""
import datetime as dt
import struct
def empaquetar(o) -> bytes:
if o is None:
return b"\xc0"
if o is True:
return b"\xc3"
if o is False:
return b"\xc2"
if isinstance(o, int):
if 0 <= o < 128:
return bytes([o])
if -32 <= o < 0:
return struct.pack("b", o)
return b"\xd3" + struct.pack(">q", o)
if isinstance(o, float):
return b"\xcb" + struct.pack(">d", o)
if isinstance(o, str):
b = o.encode("utf-8")
n = len(b)
cab = bytes([0xa0 | n]) if n < 32 else b"\xd9" + bytes([n]) if n < 256 else b"\xda" + struct.pack(">H", n)
return cab + b
if isinstance(o, (list, tuple)):
n = len(o)
cab = bytes([0x90 | n]) if n < 16 else b"\xdc" + struct.pack(">H", n)
return cab + b"".join(empaquetar(x) for x in o)
if isinstance(o, dict):
n = len(o)
cab = bytes([0x80 | n]) if n < 16 else b"\xde" + struct.pack(">H", n)
return cab + b"".join(empaquetar(k) + empaquetar(v) for k, v in o.items())
raise TypeError(type(o))
def desempaquetar(b: bytes, i: int = 0):
t = b[i]
i += 1
if t < 0x80:
return t, i
if t >= 0xe0:
return t - 256, i
if 0x80 <= t <= 0x8f:
return _mapa(b, i, t & 0x0f)
if 0x90 <= t <= 0x9f:
return _lista(b, i, t & 0x0f)
if 0xa0 <= t <= 0xbf:
n = t & 0x1f
return b[i:i + n].decode("utf-8"), i + n
fijos = {0xc0: None, 0xc2: False, 0xc3: True}
if t in fijos:
return fijos[t], i
tam = {0xcc: ">B", 0xcd: ">H", 0xce: ">I", 0xcf: ">Q", 0xd0: ">b", 0xd1: ">h", 0xd2: ">i", 0xd3: ">q", 0xca: ">f", 0xcb: ">d"}
if t in tam:
f = tam[t]
n = struct.calcsize(f)
return struct.unpack(f, b[i:i + n])[0], i + n
if t in (0xd9, 0xda, 0xdb, 0xc4, 0xc5, 0xc6):
f = {0xd9: ">B", 0xda: ">H", 0xdb: ">I", 0xc4: ">B", 0xc5: ">H", 0xc6: ">I"}[t]
k = struct.calcsize(f)
n = struct.unpack(f, b[i:i + k])[0]
i += k
crudo = b[i:i + n]
return (crudo.decode("utf-8") if t in (0xd9, 0xda, 0xdb) else crudo), i + n
if t in (0xdc, 0xdd):
f = ">H" if t == 0xdc else ">I"
k = struct.calcsize(f)
return _lista(b, i + k, struct.unpack(f, b[i:i + k])[0])
if t in (0xde, 0xdf):
f = ">H" if t == 0xde else ">I"
k = struct.calcsize(f)
return _mapa(b, i + k, struct.unpack(f, b[i:i + k])[0])
if t in (0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xc7, 0xc8, 0xc9): # ext: la fecha (-1) se convierte
if t in (0xc7, 0xc8, 0xc9):
f = {0xc7: ">B", 0xc8: ">H", 0xc9: ">I"}[t]
k = struct.calcsize(f)
n = struct.unpack(f, b[i:i + k])[0]
i += k
else:
n = {0xd4: 1, 0xd5: 2, 0xd6: 4, 0xd7: 8, 0xd8: 16}[t]
tipo = struct.unpack("b", b[i:i + 1])[0]
datos = b[i + 1:i + 1 + n]
i += 1 + n
if tipo == -1:
if n == 4:
seg, ns = struct.unpack(">I", datos)[0], 0
elif n == 8:
v = struct.unpack(">Q", datos)[0]
seg, ns = v & 0x3ffffffff, v >> 34
else:
ns, seg = struct.unpack(">Iq", datos)
return dt.datetime.fromtimestamp(seg + ns / 1e9, dt.timezone.utc), i
return datos, i
raise ValueError(f"tipo MessagePack {t:#x} no soportado")
def _lista(b, i, n):
salida = []
for _ in range(n):
x, i = desempaquetar(b, i)
salida.append(x)
return salida, i
def _mapa(b, i, n):
salida = {}
for _ in range(n):
k, i = desempaquetar(b, i)
v, i = desempaquetar(b, i)
salida[k] = v
return salida, i
def marco(msg: bytes) -> bytes:
"""Longitud en varint delante, como pide el protocolo MessagePack de SignalR."""
n, cab = len(msg), bytearray()
while True:
byte = n & 0x7f
n >>= 7
cab.append(byte | (0x80 if n else 0))
if not n:
return bytes(cab) + msg
def marcos(b: bytes):
i = 0
while i < len(b):
n = desplaza = 0
while True:
byte = b[i]
i += 1
n |= (byte & 0x7f) << desplaza
desplaza += 7
if not byte & 0x80:
break
yield desempaquetar(b[i:i + n])[0]
i += n