1# Copyright 2018 Google LLC
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import collections.abc
16
17from google.protobuf import struct_pb2
18
19from proto.marshal.collections import maps, repeated
20
21
22class ValueRule:
23 """A rule to marshal between google.protobuf.Value and Python values."""
24
25 def __init__(self, *, marshal):
26 self._marshal = marshal
27
28 def to_python(self, value, *, absent: bool = None):
29 """Coerce the given value to the appropriate Python type.
30
31 Note that both NullValue and absent fields return None.
32 In order to disambiguate between these two options,
33 use containment check,
34 E.g.
35 "value" in foo
36 which is True for NullValue and False for an absent value.
37 """
38 kind = value.WhichOneof("kind")
39 if kind == "null_value" or absent:
40 return None
41 if kind == "bool_value":
42 return bool(value.bool_value)
43 if kind == "number_value":
44 return float(value.number_value)
45 if kind == "string_value":
46 return str(value.string_value)
47 if kind == "struct_value":
48 return self._marshal.to_python(
49 struct_pb2.Struct,
50 value.struct_value,
51 absent=False,
52 )
53 if kind == "list_value":
54 return self._marshal.to_python(
55 struct_pb2.ListValue,
56 value.list_value,
57 absent=False,
58 )
59 # If more variants are ever added, we want to fail loudly
60 # instead of tacitly returning None.
61 raise ValueError("Unexpected kind: %s" % kind) # pragma: NO COVER
62
63 def to_proto(self, value) -> struct_pb2.Value:
64 """Return a protobuf Value object representing this value."""
65 if isinstance(value, struct_pb2.Value):
66 return value
67 if value is None:
68 return struct_pb2.Value(null_value=0)
69 if isinstance(value, bool):
70 return struct_pb2.Value(bool_value=value)
71 if isinstance(value, (int, float)):
72 return struct_pb2.Value(number_value=float(value))
73 if isinstance(value, str):
74 return struct_pb2.Value(string_value=value)
75 if isinstance(value, collections.abc.Sequence):
76 return struct_pb2.Value(
77 list_value=self._marshal.to_proto(struct_pb2.ListValue, value),
78 )
79 if isinstance(value, collections.abc.Mapping):
80 return struct_pb2.Value(
81 struct_value=self._marshal.to_proto(struct_pb2.Struct, value),
82 )
83 raise ValueError("Unable to coerce value: %r" % value)
84
85
86class ListValueRule:
87 """A rule translating google.protobuf.ListValue and list-like objects."""
88
89 def __init__(self, *, marshal):
90 self._marshal = marshal
91
92 def to_python(self, value, *, absent: bool = None):
93 """Coerce the given value to a Python sequence."""
94 return (
95 None
96 if absent
97 else repeated.RepeatedComposite(value.values, marshal=self._marshal)
98 )
99
100 def to_proto(self, value) -> struct_pb2.ListValue:
101 # We got a proto, or else something we sent originally.
102 # Preserve the instance we have.
103 if isinstance(value, struct_pb2.ListValue):
104 return value
105 if isinstance(value, repeated.RepeatedComposite):
106 return struct_pb2.ListValue(values=[v for v in value.pb])
107
108 # We got a list (or something list-like); convert it.
109 return struct_pb2.ListValue(
110 values=[self._marshal.to_proto(struct_pb2.Value, v) for v in value]
111 )
112
113
114class StructRule:
115 """A rule translating google.protobuf.Struct and dict-like objects."""
116
117 def __init__(self, *, marshal):
118 self._marshal = marshal
119
120 def to_python(self, value, *, absent: bool = None):
121 """Coerce the given value to a Python mapping."""
122 return (
123 None if absent else maps.MapComposite(value.fields, marshal=self._marshal)
124 )
125
126 def to_proto(self, value) -> struct_pb2.Struct:
127 # We got a proto, or else something we sent originally.
128 # Preserve the instance we have.
129 if isinstance(value, struct_pb2.Struct):
130 return value
131 if isinstance(value, maps.MapComposite):
132 return struct_pb2.Struct(
133 fields={k: v for k, v in value.pb.items()},
134 )
135
136 # We got a dict (or something dict-like); convert it.
137 answer = struct_pb2.Struct(
138 fields={
139 k: self._marshal.to_proto(struct_pb2.Value, v) for k, v in value.items()
140 }
141 )
142 return answer