You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@buildstream.apache.org by tv...@apache.org on 2021/01/25 02:17:11 UTC
[buildstream] 04/06: node.pyx, node.pxd,
node.pyi: Type checking in MappingNode.get_sequence()
This is an automated email from the ASF dual-hosted git repository.
tvb pushed a commit to branch tristan/yaml-fixes
in repository https://gitbox.apache.org/repos/asf/buildstream.git
commit 111e4c02225e63c07f85d1cd9a21daa1582ae3c5
Author: Tristan van Berkom <tr...@codethink.co.uk>
AuthorDate: Sun Jan 24 13:39:02 2021 +0900
node.pyx, node.pxd, node.pyi: Type checking in MappingNode.get_sequence()
The MappingNode.get_sequence() accessor now has an allowed_types parameter
allowing the caller to obtain a squence with prevalidated element types.
---
src/buildstream/node.pxd | 2 +-
src/buildstream/node.pyi | 26 +++++++++++++------
src/buildstream/node.pyx | 67 ++++++++++++++++++++++++++++++++----------------
3 files changed, 64 insertions(+), 31 deletions(-)
diff --git a/src/buildstream/node.pxd b/src/buildstream/node.pxd
index c26d78a..e18b26f 100644
--- a/src/buildstream/node.pxd
+++ b/src/buildstream/node.pxd
@@ -51,7 +51,7 @@ cdef class MappingNode(Node):
cpdef MappingNode get_mapping(self, str key, default=*)
cpdef Node get_node(self, str key, list allowed_types=*, bint allow_none=*)
cpdef ScalarNode get_scalar(self, str key, default=*)
- cpdef SequenceNode get_sequence(self, str key, object default=*)
+ cpdef SequenceNode get_sequence(self, str key, object default=*, list allowed_types=*)
cpdef str get_str(self, str key, object default=*)
cpdef list get_str_list(self, str key, object default=*)
cpdef object items(self)
diff --git a/src/buildstream/node.pyi b/src/buildstream/node.pyi
index 34dc363..a3b0ed9 100644
--- a/src/buildstream/node.pyi
+++ b/src/buildstream/node.pyi
@@ -12,6 +12,14 @@ class Node:
def get_provenance(self) -> ProvenanceInformation: ...
def strip_node_info(self) -> Dict[str, Any]: ...
+class ScalarNode(Node):
+ def as_str(self) -> str: ...
+ def clone(self) -> "ScalarNode": ...
+
+class SequenceNode(Node, Generic[TNode]):
+ def as_str_list(self) -> List[str]: ...
+ def clone(self) -> "SequenceNode[TNode]": ...
+
class MappingNode(Node, Generic[TNode]):
def __init__(self, file_index: int, line: int, column: int, value: Mapping[str, TValidNodeValue]) -> None: ...
def clone(self) -> MappingNode[TNode]: ...
@@ -46,20 +54,22 @@ class MappingNode(Node, Generic[TNode]):
@overload
def get_mapping(self, key: str, default: Optional["MappingNode"]) -> Optional["MappingNode"]: ...
@overload
+ def get_sequence(self, key: str, *, allowed_types: Optional[List[Type[Node]]]) -> SequenceNode: ...
+ @overload
+ def get_sequence(
+ self, key: str, default: List[Any], *, allowed_types: Optional[List[Type[Node]]]
+ ) -> SequenceNode: ...
+ @overload
+ def get_sequence(
+ self, key: str, default: Optional[List[Any]], *, allowed_types: Optional[List[Type[Node]]]
+ ) -> Optional[SequenceNode]: ...
+ @overload
def get_node(self, key: str) -> Node: ...
@overload
def get_node(self, key: str, allowed_types: Optional[List[Type[Node]]]) -> Node: ...
@overload
def get_node(self, key: str, allowed_types: Optional[List[Type[Node]]], allow_none: bool) -> Optional[Node]: ...
-class ScalarNode(Node):
- def as_str(self) -> str: ...
- def clone(self) -> "ScalarNode": ...
-
-class SequenceNode(Node, Generic[TNode]):
- def as_str_list(self) -> List[str]: ...
- def clone(self) -> "SequenceNode[TNode]": ...
-
def _assert_symbol_name(
symbol_name: str, purpose: str, *, ref_node: Optional[Node], allow_dashes: bool = True
) -> None: ...
diff --git a/src/buildstream/node.pyx b/src/buildstream/node.pyx
index 7b46809..48f0739 100644
--- a/src/buildstream/node.pyx
+++ b/src/buildstream/node.pyx
@@ -683,18 +683,7 @@ cdef class MappingNode(Node):
raise LoadError("{}: Dictionary did not contain expected key '{}'".format(provenance, key),
LoadErrorReason.INVALID_DATA)
- if allowed_types and type(value) not in allowed_types:
- provenance = self.get_provenance()
- human_types = []
- if MappingNode in allowed_types:
- human_types.append("dict")
- if SequenceNode in allowed_types:
- human_types.append('list')
- if ScalarNode in allowed_types:
- human_types.append('scalar')
-
- raise LoadError("{}: Value of '{}' is not one of the following: {}.".format(
- provenance, key, ", ".join(human_types)), LoadErrorReason.INVALID_DATA)
+ __validate_node_type(value, allowed_types, key)
return value
@@ -728,7 +717,7 @@ cdef class MappingNode(Node):
return value
- cpdef SequenceNode get_sequence(self, str key, object default=_sentinel):
+ cpdef SequenceNode get_sequence(self, str key, object default=_sentinel, list allowed_types = None):
"""get_sequence(key, default=sentinel)
Get the value of the node for `key` as a :class:`.SequenceNode`.
@@ -737,6 +726,7 @@ cdef class MappingNode(Node):
key (str): key for which to get the value
default (list): default value to return if `key` is not in the mapping. It will be converted
to a :class:`.SequenceNode` before being returned
+ allowed_types (list): list of valid subtypes of :class:`.Node` that are valid for nodes in the sequence.
Raises:
:class:`buildstream._exceptions.LoadError`: if the value at `key` is not a
@@ -745,14 +735,19 @@ cdef class MappingNode(Node):
Returns:
:class:`.SequenceNode`: the value at `key` or the default
"""
- value = self._get(key, default, SequenceNode)
+ cdef Node value = self._get(key, default, SequenceNode)
+ cdef Node node
if type(value) is not SequenceNode and value is not None:
provenance = value.get_provenance()
raise LoadError("{}: Value of '{}' is not of the expected type 'list'"
.format(provenance, key), LoadErrorReason.INVALID_DATA)
- return value
+ if allowed_types:
+ for node in value:
+ __validate_node_type(node, allowed_types)
+
+ return <SequenceNode> value
cpdef str get_str(self, str key, object default=_sentinel):
"""get_str(key, default=sentinel)
@@ -1319,13 +1314,7 @@ cdef class SequenceNode(Node):
:class:`.Node`: the value at `index`
"""
cdef value = self.value[index]
-
- if allowed_types and type(value) not in allowed_types:
- provenance = self.get_provenance()
- raise LoadError("{}: Value of '{}' is not one of the following: {}.".format(
- provenance, index, ", ".join(allowed_types)),
- LoadErrorReason.INVALID_DATA)
-
+ __validate_node_type(value, allowed_types, str(index))
return value
cpdef ScalarNode scalar_at(self, int index):
@@ -1715,3 +1704,37 @@ cdef Node __new_node_from_list(list inlist, Node ref_node):
ret.value.append(__create_node_recursive(v, ref_node))
return ret
+
+
+# __validate_node_type(node, allowed_types, key)
+#
+# Validates that this node is of the expected node type,
+# and raises a user facing LoadError if not.
+#
+# Args:
+# allowed_types (list): list of valid subtypes of Node, or None
+# key (str): A key, in case the validated node is a value for a key
+#
+# Raises:
+# (LoadError): If this node is not of the expected type
+#
+cdef void __validate_node_type(Node node, list allowed_types = None, str key = None) except *:
+ cdef ProvenanceInformation provenance
+ cdef list human_types
+ cdef str message
+
+ if allowed_types and type(node) not in allowed_types:
+ provenance = node.get_provenance()
+ human_types = []
+ if MappingNode in allowed_types:
+ human_types.append("dict")
+ if SequenceNode in allowed_types:
+ human_types.append('list')
+ if ScalarNode in allowed_types:
+ human_types.append('scalar')
+
+ message = "{}: Value ".format(provenance)
+ if key:
+ message += "of '{}' ".format(key)
+ message += "is not one of the following: {}.".format(", ".join(human_types))
+ raise LoadError(message, LoadErrorReason.INVALID_DATA)