Squashed 'modules/libjuju/' content from commit c50c361
[osm/N2VC.git] / juju / client / overrides.py
1 from collections import namedtuple
2 import re
3
4 from .facade import ReturnMapping, Type, TypeEncoder
5 from .import _client
6 from .import _definitions
7
8
9 __all__ = [
10 'Delta',
11 'Number',
12 'Binary',
13 'ConfigValue',
14 ]
15
16 __patches__ = [
17 'ResourcesFacade',
18 'AllWatcherFacade',
19 ]
20
21
22 class Delta(Type):
23 """A single websocket delta.
24
25 :ivar entity: The entity name, e.g. 'unit', 'application'
26 :vartype entity: str
27
28 :ivar type: The delta type, e.g. 'add', 'change', 'remove'
29 :vartype type: str
30
31 :ivar data: The raw delta data
32 :vartype data: dict
33
34 NOTE: The 'data' variable above is being incorrectly cross-linked by a
35 Sphinx bug: https://github.com/sphinx-doc/sphinx/issues/2549
36
37 """
38 _toSchema = {'deltas': 'deltas'}
39 _toPy = {'deltas': 'deltas'}
40
41 def __init__(self, deltas=None):
42 """
43 :param deltas: [str, str, object]
44
45 """
46 self.deltas = deltas
47
48 Change = namedtuple('Change', 'entity type data')
49 change = Change(*self.deltas)
50
51 self.entity = change.entity
52 self.type = change.type
53 self.data = change.data
54
55 @classmethod
56 def from_json(cls, data):
57 return cls(deltas=data)
58
59
60 class ResourcesFacade(Type):
61 """Patch parts of ResourcesFacade to make it work.
62 """
63
64 @ReturnMapping(_client.AddPendingResourcesResult)
65 async def AddPendingResources(self, application_tag, charm_url, resources):
66 """Fix the calling signature of AddPendingResources.
67
68 The ResourcesFacade doesn't conform to the standard facade pattern in
69 the Juju source, which leads to the schemagened code not matching up
70 properly with the actual calling convention in the API. There is work
71 planned to fix this in Juju, but we have to work around it for now.
72
73 application_tag : str
74 charm_url : str
75 resources : typing.Sequence<+T_co>[~CharmResource]<~CharmResource>
76 Returns -> typing.Union[_ForwardRef('ErrorResult'),
77 typing.Sequence<+T_co>[str]]
78 """
79 # map input types to rpc msg
80 _params = dict()
81 msg = dict(type='Resources',
82 request='AddPendingResources',
83 version=1,
84 params=_params)
85 _params['tag'] = application_tag
86 _params['url'] = charm_url
87 _params['resources'] = resources
88 reply = await self.rpc(msg)
89 return reply
90
91
92 class AllWatcherFacade(Type):
93 """
94 Patch rpc method of allwatcher to add in 'id' stuff.
95
96 """
97 async def rpc(self, msg):
98 if not hasattr(self, 'Id'):
99 client = _client.ClientFacade.from_connection(self.connection)
100
101 result = await client.WatchAll()
102 self.Id = result.watcher_id
103
104 msg['Id'] = self.Id
105 result = await self.connection.rpc(msg, encoder=TypeEncoder)
106 return result
107
108
109 class Number(_definitions.Number):
110 """
111 This type represents a semver string.
112
113 Because it is not standard JSON, the typical from_json parsing fails and
114 the parsing must be handled specially.
115
116 See https://github.com/juju/version for more info.
117 """
118 numberPat = re.compile(r'^(\d{1,9})\.(\d{1,9})(?:\.|-([a-z]+))(\d{1,9})(\.\d{1,9})?$') # noqa
119
120 def __init__(self, major=None, minor=None, patch=None, tag=None,
121 build=None, **unknown_fields):
122 '''
123 major : int
124 minor : int
125 patch : int
126 tag : str
127 build : int
128 '''
129 self.major = int(major or '0')
130 self.minor = int(minor or '0')
131 self.patch = int(patch or '0')
132 self.tag = tag or ''
133 self.build = int(build or '0')
134
135 def __repr__(self):
136 return '<Number major={} minor={} patch={} tag={} build={}>'.format(
137 self.major, self.minor, self.patch, self.tag, self.build)
138
139 def __str__(self):
140 return self.serialize()
141
142 def __eq__(self, other):
143 return (
144 isinstance(other, type(self)) and
145 other.major == self.major and
146 other.minor == self.minor and
147 other.tag == self.tag and
148 other.patch == self.patch and
149 other.build == self.build)
150
151 @classmethod
152 def from_json(cls, data):
153 parsed = None
154 if isinstance(data, cls):
155 return data
156 elif data is None:
157 return cls()
158 elif isinstance(data, dict):
159 parsed = data
160 elif isinstance(data, str):
161 match = cls.numberPat.match(data)
162 if match:
163 parsed = {
164 'major': match.group(1),
165 'minor': match.group(2),
166 'tag': match.group(3),
167 'patch': match.group(4),
168 'build': (match.group(5)[1:] if match.group(5)
169 else 0),
170 }
171 if not parsed:
172 raise TypeError('Unable to parse Number version string: '
173 '{}'.format(data))
174 d = {}
175 for k, v in parsed.items():
176 d[cls._toPy.get(k, k)] = v
177
178 return cls(**d)
179
180 def serialize(self):
181 s = ""
182 if not self.tag:
183 s = "{}.{}.{}".format(self.major, self.minor, self.patch)
184 else:
185 s = "{}.{}-{}{}".format(self.major, self.minor, self.tag,
186 self.patch)
187 if self.build:
188 s = "{}.{}".format(s, self.build)
189 return s
190
191 def to_json(self):
192 return self.serialize()
193
194
195 class Binary(_definitions.Binary):
196 """
197 This type represents a semver string with additional series and arch info.
198
199 Because it is not standard JSON, the typical from_json parsing fails and
200 the parsing must be handled specially.
201
202 See https://github.com/juju/version for more info.
203 """
204 binaryPat = re.compile(r'^(\d{1,9})\.(\d{1,9})(?:\.|-([a-z]+))(\d{1,9})(\.\d{1,9})?-([^-]+)-([^-]+)$') # noqa
205
206 def __init__(self, number=None, series=None, arch=None, **unknown_fields):
207 '''
208 number : Number
209 series : str
210 arch : str
211 '''
212 self.number = Number.from_json(number)
213 self.series = series
214 self.arch = arch
215
216 def __repr__(self):
217 return '<Binary number={} series={} arch={}>'.format(
218 self.number, self.series, self.arch)
219
220 def __str__(self):
221 return self.serialize()
222
223 def __eq__(self, other):
224 return (
225 isinstance(other, type(self)) and
226 other.number == self.number and
227 other.series == self.series and
228 other.arch == self.arch)
229
230 @classmethod
231 def from_json(cls, data):
232 parsed = None
233 if isinstance(data, cls):
234 return data
235 elif data is None:
236 return cls()
237 elif isinstance(data, dict):
238 parsed = data
239 elif isinstance(data, str):
240 match = cls.binaryPat.match(data)
241 if match:
242 parsed = {
243 'number': {
244 'major': match.group(1),
245 'minor': match.group(2),
246 'tag': match.group(3),
247 'patch': match.group(4),
248 'build': (match.group(5)[1:] if match.group(5)
249 else 0),
250 },
251 'series': match.group(6),
252 'arch': match.group(7),
253 }
254 if parsed is None:
255 raise TypeError('Unable to parse Binary version string: '
256 '{}'.format(data))
257 d = {}
258 for k, v in parsed.items():
259 d[cls._toPy.get(k, k)] = v
260
261 return cls(**d)
262
263 def serialize(self):
264 return "{}-{}-{}".format(self.number.serialize(),
265 self.series, self.arch)
266
267 def to_json(self):
268 return self.serialize()
269
270
271 class ConfigValue(_definitions.ConfigValue):
272 def __repr__(self):
273 return '<{} source={} value={}>'.format(type(self).__name__,
274 repr(self.source),
275 repr(self.value))