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