Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New pyln modules. #3733

Merged
merged 18 commits into from
Jun 12, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ca71845
pyln: add Makefile
rustyrussell May 18, 2020
31fa55e
pyln: add pyln.proto.message.
rustyrussell May 28, 2020
16297af
patch message-export-types.patch
rustyrussell Jun 3, 2020
1bff330
pyln.proto.message: use BufferedIOBase instead of bytes for binary ops.
rustyrussell Jun 4, 2020
46151ed
pyln.proto.message: expose fundamental MessageTypes as variables.
rustyrussell Jun 4, 2020
e274226
pyln: add (undocumented) u8 fundamental type.
rustyrussell Jun 4, 2020
59e2064
message: support option fields.
rustyrussell Jun 4, 2020
784d138
pyln.proto.message: separate fundamental types from other subtypes.
rustyrussell Jun 4, 2020
a4eb933
pyln: new module pyln.proto.message.bolts
rustyrussell Jun 4, 2020
42fc48f
new modules: pyln.proto.message.{bolt1,bolt2,bolt4,bolt7}
rustyrussell Jun 4, 2020
3ed6831
pyln.proto.message: support adding two namespaces.
rustyrussell Jun 4, 2020
360bcaf
pyln.proto.message: python-fluency feedback from @darosior
rustyrussell Jun 4, 2020
4a336db
pyln.proto.message: export more.
rustyrussell Jun 4, 2020
efd38a4
pyln.proto.message: allow fields with options to be missing.
rustyrussell Jun 4, 2020
bef68d3
pyln.proto.message.*: Add Makefile to do mypy checks.
rustyrussell Jun 4, 2020
851122d
pyln.proto.message.*: add type annotations.
rustyrussell Jun 12, 2020
e0d3174
pyln.proto.message: expose array types, add set_field for Message class.
rustyrussell Jun 12, 2020
5dbec8f
pyln.proto.message: fix handling of missing optional fields.
rustyrussell Jun 12, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*.po
*.pyc
.cppcheck-suppress
.mypy_cache
TAGS
tags
ccan/tools/configurator/configurator
Expand Down
60 changes: 32 additions & 28 deletions contrib/pyln-proto/pyln/proto/message/array_types.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
from .fundamental_types import FieldType, IntegerType, split_field
from typing import List, Optional, Dict, Tuple, TYPE_CHECKING, Any
from io import BufferedIOBase
if TYPE_CHECKING:
from .message import SubtypeType, TlvStreamType


class ArrayType(FieldType):
Expand All @@ -8,11 +12,11 @@ class ArrayType(FieldType):
wants an array of some type.

"""
def __init__(self, outer, name, elemtype):
def __init__(self, outer: 'SubtypeType', name: str, elemtype: FieldType):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems you were far quicker than me finding out about recursive type definitions being possible via string annotations 😉

I got quite lost when trying to figure out how to annotate something with a type that'd be defined later...

super().__init__("{}.{}".format(outer.name, name))
self.elemtype = elemtype

def val_from_str(self, s):
def val_from_str(self, s: str) -> Tuple[List[Any], str]:
# Simple arrays of bytes don't need commas
if self.elemtype.name == 'byte':
a, b = split_field(s)
Expand All @@ -30,20 +34,20 @@ def val_from_str(self, s):
s = s[1:]
return ret, s[1:]

def val_to_str(self, v, otherfields):
def val_to_str(self, v: List[Any], otherfields: Dict[str, Any]) -> str:
if self.elemtype.name == 'byte':
return bytes(v).hex()

s = ','.join(self.elemtype.val_to_str(i, otherfields) for i in v)
return '[' + s + ']'

def write(self, io_out, v, otherfields):
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)

def read_arr(self, io_in, otherfields, arraysize):
def read_arr(self, io_in: BufferedIOBase, otherfields: Dict[str, Any], arraysize: Optional[int]) -> List[Any]:
"""arraysize None means take rest of io entirely and exactly"""
vals = []
vals: List[Any] = []
while arraysize is None or len(vals) < arraysize:
# Throws an exception on partial read, so None means completely empty.
val = self.elemtype.read(io_in, otherfields)
Expand All @@ -60,73 +64,73 @@ def read_arr(self, io_in, otherfields, arraysize):

class SizedArrayType(ArrayType):
"""A fixed-size array"""
def __init__(self, outer, name, elemtype, arraysize):
def __init__(self, outer: 'SubtypeType', name: str, elemtype: FieldType, arraysize: int):
super().__init__(outer, name, elemtype)
self.arraysize = arraysize

def val_to_str(self, v, otherfields):
def val_to_str(self, v: List[Any], otherfields: Dict[str, Any]) -> str:
if len(v) != self.arraysize:
raise ValueError("Length of {} != {}", v, self.arraysize)
return super().val_to_str(v, otherfields)

def val_from_str(self, s):
def val_from_str(self, s: str) -> Tuple[List[Any], str]:
a, b = super().val_from_str(s)
if len(a) != self.arraysize:
raise ValueError("Length of {} != {}", s, self.arraysize)
return a, b

def write(self, io_out, v, otherfields):
def write(self, io_out: BufferedIOBase, v: List[Any], otherfields: Dict[str, Any]) -> None:
if len(v) != self.arraysize:
raise ValueError("Length of {} != {}", v, self.arraysize)
return super().write(io_out, v, otherfields)

def read(self, io_in, otherfields):
def read(self, io_in: BufferedIOBase, otherfields: Dict[str, Any]) -> List[Any]:
return super().read_arr(io_in, otherfields, self.arraysize)


class EllipsisArrayType(ArrayType):
"""This is used for ... fields at the end of a tlv: the array ends
when the tlv ends"""
def __init__(self, tlv, name, elemtype):
def __init__(self, tlv: 'TlvStreamType', name: str, elemtype: FieldType):
super().__init__(tlv, name, elemtype)

def read(self, io_in, otherfields):
def read(self, io_in: BufferedIOBase, otherfields: Dict[str, Any]) -> List[Any]:
"""Takes rest of bytestream"""
return super().read_arr(io_in, otherfields, None)

def only_at_tlv_end(self):
def only_at_tlv_end(self) -> bool:
"""These only make sense at the end of a TLV"""
return True


class LengthFieldType(FieldType):
"""Special type to indicate this serves as a length field for others"""
def __init__(self, inttype):
def __init__(self, inttype: IntegerType):
if type(inttype) is not IntegerType:
raise ValueError("{} cannot be a length; not an integer!"
.format(self.name))
super().__init__(inttype.name)
self.underlying_type = inttype
# You can be length for more than one field!
self.len_for = []
self.len_for: List[DynamicArrayType] = []

def is_optional(self):
def is_optional(self) -> bool:
"""This field value is always implies, never specified directly"""
return True

def add_length_for(self, field):
def add_length_for(self, field: 'DynamicArrayType') -> None:
assert isinstance(field.fieldtype, DynamicArrayType)
self.len_for.append(field)

def calc_value(self, otherfields):
def calc_value(self, otherfields: Dict[str, Any]) -> int:
"""Calculate length value from field(s) themselves"""
if self.len_fields_bad('', otherfields):
raise ValueError("Lengths of fields {} not equal!"
.format(self.len_for))

return len(otherfields[self.len_for[0].name])

def _maybe_calc_value(self, fieldname, otherfields):
def _maybe_calc_value(self, fieldname: str, otherfields: Dict[str, Any]) -> int:
# Perhaps we're just demarshalling from binary now, so we actually
# stored it. Remove, and we'll calc from now on.
if fieldname in otherfields:
Expand All @@ -135,27 +139,27 @@ def _maybe_calc_value(self, fieldname, otherfields):
return v
return self.calc_value(otherfields)

def val_to_str(self, _, otherfields):
def val_to_str(self, _, otherfields: Dict[str, Any]) -> str:
return self.underlying_type.val_to_str(self.calc_value(otherfields),
otherfields)

def name_and_val(self, name, v):
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"""
return ''

def read(self, io_in, otherfields):
def read(self, io_in: BufferedIOBase, otherfields: Dict[str, Any]) -> None:
"""We store this, but it'll be removed from the fields as soon as it's used (i.e. by DynamicArrayType's val_from_bin)"""
return self.underlying_type.read(io_in, otherfields)

def write(self, io_out, _, otherfields):
def write(self, io_out: BufferedIOBase, _, otherfields: Dict[str, Any]) -> None:
self.underlying_type.write(io_out, self.calc_value(otherfields),
otherfields)

def val_from_str(self, s):
def val_from_str(self, s: str):
raise ValueError('{} is implied, cannot be specified'.format(self))

def len_fields_bad(self, fieldname, otherfields):
def len_fields_bad(self, fieldname: str, otherfields: Dict[str, Any]) -> List[str]:
"""fieldname is the name to return if this length is bad"""
mylen = None
for lens in self.len_for:
Expand All @@ -170,11 +174,11 @@ def len_fields_bad(self, fieldname, otherfields):

class DynamicArrayType(ArrayType):
"""This is used for arrays where another field controls the size"""
def __init__(self, outer, name, elemtype, lenfield):
def __init__(self, outer: 'SubtypeType', name: str, elemtype: FieldType, lenfield: LengthFieldType):
super().__init__(outer, name, elemtype)
assert type(lenfield.fieldtype) is LengthFieldType
self.lenfield = lenfield

def read(self, io_in, otherfields):
def read(self, io_in: BufferedIOBase, otherfields: Dict[str, Any]) -> List[Any]:
return super().read_arr(io_in, otherfields,
self.lenfield.fieldtype._maybe_calc_value(self.lenfield.name, otherfields))
Loading