pyln.proto.message: add to_py() operation.

This delivers the message contents in a much friendlier form for
manipulation: in particular, it makes it easy to compare two
messages without having to know all the message type internals.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2020-06-16 17:03:13 +09:30 committed by Christian Decker
parent ee76504e53
commit 02338a6b25
3 changed files with 55 additions and 2 deletions

View File

@ -1,5 +1,5 @@
from .fundamental_types import FieldType, IntegerType, split_field
from typing import List, Optional, Dict, Tuple, TYPE_CHECKING, Any
from typing import List, Optional, Dict, Tuple, TYPE_CHECKING, Any, Union
from io import BufferedIOBase
if TYPE_CHECKING:
from .message import SubtypeType, TlvStreamType
@ -41,6 +41,13 @@ wants an array of some type.
s = ','.join(self.elemtype.val_to_str(i, otherfields) for i in v)
return '[' + s + ']'
def val_to_py(self, v: Any, otherfields: Dict[str, Any]) -> Union[str, List[Any]]:
"""Convert to a python object: for arrays, this means a list (or hex, if bytes)"""
if self.elemtype.name == 'byte':
return bytes(v).hex()
return [self.elemtype.val_to_py(i, otherfields) for i in v]
def write(self, io_out: BufferedIOBase, v: List[Any], otherfields: Dict[str, Any]) -> None:
for i in v:
self.elemtype.write(io_out, i, otherfields)
@ -143,6 +150,11 @@ class LengthFieldType(FieldType):
return self.underlying_type.val_to_str(self.calc_value(otherfields),
otherfields)
def val_to_py(self, v: Any, otherfields: Dict[str, Any]) -> int:
"""Convert to a python object: for integer fields, this means an int"""
return self.underlying_type.val_to_py(self.calc_value(otherfields),
otherfields)
def name_and_val(self, name: str, v: int) -> str:
"""We don't print out length fields when printing out messages:
they're implied by the length of other fields"""

View File

@ -59,6 +59,10 @@ These are further specialized.
def val_to_str(self, v: Any, otherfields: Dict[str, Any]) -> str:
raise NotImplementedError()
def val_to_py(self, v: Any, otherfields: Dict[str, Any]) -> Any:
"""Convert to a python object: for simple fields, this means a string"""
return self.val_to_str(v, otherfields)
def __str__(self):
return self.name
@ -79,6 +83,10 @@ class IntegerType(FieldType):
a, b = split_field(s)
return int(a), b
def val_to_py(self, v: Any, otherfields: Dict[str, Any]) -> int:
"""Convert to a python object: for integer fields, this means an int"""
return int(v)
def write(self, io_out: BufferedIOBase, v: int, otherfields: Dict[str, Any]) -> None:
io_out.write(struct.pack(self.structfmt, v))
@ -107,6 +115,10 @@ basically a u64.
| (int(parts[1]) << 16)
| (int(parts[2]))), b
def val_to_py(self, v: Any, otherfields: Dict[str, Any]) -> str:
# Unlike a normal int, this returns a str.
return self.val_to_str(v, otherfields)
class TruncatedIntType(FieldType):
"""Truncated integer types"""
@ -128,6 +140,10 @@ class TruncatedIntType(FieldType):
.format(a, self.name))
return int(a), b
def val_to_py(self, v: Any, otherfields: Dict[str, Any]) -> int:
"""Convert to a python object: for integer fields, this means an int"""
return int(v)
def write(self, io_out: BufferedIOBase, v: int, otherfields: Dict[str, Any]) -> None:
binval = struct.pack('>Q', v)
while len(binval) != 0 and binval[0] == 0:
@ -219,6 +235,10 @@ class BigSizeType(FieldType):
def val_to_str(self, v: int, otherfields: Dict[str, Any]) -> str:
return "{}".format(int(v))
def val_to_py(self, v: Any, otherfields: Dict[str, Any]) -> int:
"""Convert to a python object: for integer fields, this means an int"""
return int(v)
def fundamental_types():
# From 01-messaging.md#fundamental-types:

View File

@ -4,7 +4,7 @@ from .fundamental_types import fundamental_types, BigSizeType, split_field, try_
from .array_types import (
SizedArrayType, DynamicArrayType, LengthFieldType, EllipsisArrayType
)
from typing import Dict, List, Optional, Tuple, Any, cast
from typing import Dict, List, Optional, Tuple, Any, Union, cast
class MessageNamespace(object):
@ -278,6 +278,12 @@ inherit from this too.
return '{' + s + '}'
def val_to_py(self, val: Dict[str, Any], otherfields: Dict[str, Any]) -> Dict[str, Any]:
ret: Dict[str, Any] = {}
for k, v in val.items():
ret[k] = self.find_field(k).fieldtype.val_to_py(v, val)
return ret
def write(self, io_out: BufferedIOBase, v: Dict[str, Any], otherfields: Dict[str, Any]) -> None:
self._raise_if_badvals(v)
for fname, val in v.items():
@ -471,6 +477,12 @@ tlvdata,reply_channel_range_tlvs,timestamps_tlv,encoding_type,u8,
return '{' + s + '}'
def val_to_py(self, val: Dict[str, Any], otherfields: Dict[str, Any]) -> Dict[str, Any]:
ret: Dict[str, Any] = {}
for k, v in val.items():
ret[k] = self.find_field(k).val_to_py(v, val)
return ret
def write(self, io_out: BufferedIOBase, v: Optional[Dict[str, Any]], otherfields: Dict[str, Any]) -> None:
# If they didn't specify this tlvstream, it's empty.
if v is None:
@ -662,3 +674,12 @@ Must not have missing fields.
if f.name in self.fields:
ret += f.fieldtype.name_and_val(f.name, self.fields[f.name])
return ret
def to_py(self) -> Dict[str, Any]:
"""Convert to a Python native object: dicts, lists, strings, ints"""
ret: Dict[str, Union[Dict[str, Any], List[Any], str, int]] = {}
for f, v in self.fields.items():
fieldtype = self.messagetype.find_field(f).fieldtype
ret[f] = fieldtype.val_to_py(v, self.fields)
return ret