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
20from proto.marshal.collections import repeated
21
22
23class ValueRule:
24 """A rule to marshal between google.protobuf.Value and Python values."""
25
26 def __init__(self, *, marshal):
27 self._marshal = marshal
28
29 def to_python(self, value, *, absent: bool = None):
30 """Coerce the given value to the appropriate Python type.
31
32 Note that both NullValue and absent fields return None.
33 In order to disambiguate between these two options,
34 use containment check,
35 E.g.
36 "value" in foo
37 which is True for NullValue and False for an absent value.
38 """
39 kind = value.WhichOneof("kind")
40 if kind == "null_value" or absent:
41 return None
42 if kind == "bool_value":
43 return bool(value.bool_value)
44 if kind == "number_value":
45 return float(value.number_value)
46 if kind == "string_value":
47 return str(value.string_value)
48 if kind == "struct_value":
49 return self._marshal.to_python(
50 struct_pb2.Struct,
51 value.struct_value,
52 absent=False,
53 )
54 if kind == "list_value":
55 return self._marshal.to_python(
56 struct_pb2.ListValue,
57 value.list_value,
58 absent=False,
59 )
60 # If more variants are ever added, we want to fail loudly
61 # instead of tacitly returning None.
62 raise ValueError("Unexpected kind: %s" % kind) # pragma: NO COVER
63
64 def to_proto(self, value) -> struct_pb2.Value:
65 """Return a protobuf Value object representing this value."""
66 if isinstance(value, struct_pb2.Value):
67 return value
68 if value is None:
69 return struct_pb2.Value(null_value=0)
70 if isinstance(value, bool):
71 return struct_pb2.Value(bool_value=value)
72 if isinstance(value, (int, float)):
73 return struct_pb2.Value(number_value=float(value))
74 if isinstance(value, str):
75 return struct_pb2.Value(string_value=value)
76 if isinstance(value, collections.abc.Sequence):
77 return struct_pb2.Value(
78 list_value=self._marshal.to_proto(struct_pb2.ListValue, value),
79 )
80 if isinstance(value, collections.abc.Mapping):
81 return struct_pb2.Value(
82 struct_value=self._marshal.to_proto(struct_pb2.Struct, value),
83 )
84 raise ValueError("Unable to coerce value: %r" % value)
85
86
87class ListValueRule:
88 """A rule translating google.protobuf.ListValue and list-like objects."""
89
90 def __init__(self, *, marshal):
91 self._marshal = marshal
92
93 def to_python(self, value, *, absent: bool = None):
94 """Coerce the given value to a Python sequence."""
95 return (
96 None
97 if absent
98 else repeated.RepeatedComposite(value.values, marshal=self._marshal)
99 )
100
101 def to_proto(self, value) -> struct_pb2.ListValue:
102 # We got a proto, or else something we sent originally.
103 # Preserve the instance we have.
104 if isinstance(value, struct_pb2.ListValue):
105 return value
106 if isinstance(value, repeated.RepeatedComposite):
107 return struct_pb2.ListValue(values=[v for v in value.pb])
108
109 # We got a list (or something list-like); convert it.
110 return struct_pb2.ListValue(
111 values=[self._marshal.to_proto(struct_pb2.Value, v) for v in value]
112 )
113
114
115class StructRule:
116 """A rule translating google.protobuf.Struct and dict-like objects."""
117
118 def __init__(self, *, marshal):
119 self._marshal = marshal
120
121 def to_python(self, value, *, absent: bool = None):
122 """Coerce the given value to a Python mapping."""
123 return (
124 None if absent else maps.MapComposite(value.fields, marshal=self._marshal)
125 )
126
127 def to_proto(self, value) -> struct_pb2.Struct:
128 # We got a proto, or else something we sent originally.
129 # Preserve the instance we have.
130 if isinstance(value, struct_pb2.Struct):
131 return value
132 if isinstance(value, maps.MapComposite):
133 return struct_pb2.Struct(
134 fields={k: v for k, v in value.pb.items()},
135 )
136
137 # We got a dict (or something dict-like); convert it.
138 answer = struct_pb2.Struct(
139 fields={
140 k: self._marshal.to_proto(struct_pb2.Value, v) for k, v in value.items()
141 }
142 )
143 return answer