Native charm support
[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 'ActionFacade',
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 ActionFacade(Type):
110
111 class _FindTagsResults(Type):
112 _toSchema = {'matches': 'matches'}
113 _toPy = {'matches': 'matches'}
114
115 def __init__(self, matches=None, **unknown_fields):
116 '''
117 FindTagsResults wraps the mapping between the requested prefix and the
118 matching tags for each requested prefix.
119
120 Matches map[string][]Entity `json:"matches"`
121 '''
122 self.matches = {}
123 matches = matches or {}
124 for prefix, tags in matches.items():
125 self.matches[prefix] = [_definitions.Entity.from_json(r)
126 for r in tags]
127
128 @ReturnMapping(_FindTagsResults)
129 async def FindActionTagsByPrefix(self, prefixes):
130 '''
131 prefixes : typing.Sequence[str]
132 Returns -> typing.Sequence[~Entity]
133 '''
134 # map input types to rpc msg
135 _params = dict()
136 msg = dict(type='Action',
137 request='FindActionTagsByPrefix',
138 version=2,
139 params=_params)
140 _params['prefixes'] = prefixes
141 reply = await self.rpc(msg)
142 return reply
143
144
145 class Number(_definitions.Number):
146 """
147 This type represents a semver string.
148
149 Because it is not standard JSON, the typical from_json parsing fails and
150 the parsing must be handled specially.
151
152 See https://github.com/juju/version for more info.
153 """
154 numberPat = re.compile(r'^(\d{1,9})\.(\d{1,9})(?:\.|-([a-z]+))(\d{1,9})(\.\d{1,9})?$') # noqa
155
156 def __init__(self, major=None, minor=None, patch=None, tag=None,
157 build=None, **unknown_fields):
158 '''
159 major : int
160 minor : int
161 patch : int
162 tag : str
163 build : int
164 '''
165 self.major = int(major or '0')
166 self.minor = int(minor or '0')
167 self.patch = int(patch or '0')
168 self.tag = tag or ''
169 self.build = int(build or '0')
170
171 def __repr__(self):
172 return '<Number major={} minor={} patch={} tag={} build={}>'.format(
173 self.major, self.minor, self.patch, self.tag, self.build)
174
175 def __str__(self):
176 return self.serialize()
177
178 @property
179 def _cmp(self):
180 return (self.major, self.minor, self.tag, self.patch, self.build)
181
182 def __eq__(self, other):
183 return isinstance(other, type(self)) and self._cmp == other._cmp
184
185 def __lt__(self, other):
186 return self._cmp < other._cmp
187
188 def __le__(self, other):
189 return self._cmp <= other._cmp
190
191 def __gt__(self, other):
192 return self._cmp > other._cmp
193
194 def __ge__(self, other):
195 return self._cmp >= other._cmp
196
197 @classmethod
198 def from_json(cls, data):
199 parsed = None
200 if isinstance(data, cls):
201 return data
202 elif data is None:
203 return cls()
204 elif isinstance(data, dict):
205 parsed = data
206 elif isinstance(data, str):
207 match = cls.numberPat.match(data)
208 if match:
209 parsed = {
210 'major': match.group(1),
211 'minor': match.group(2),
212 'tag': match.group(3),
213 'patch': match.group(4),
214 'build': (match.group(5)[1:] if match.group(5)
215 else 0),
216 }
217 if not parsed:
218 raise TypeError('Unable to parse Number version string: '
219 '{}'.format(data))
220 d = {}
221 for k, v in parsed.items():
222 d[cls._toPy.get(k, k)] = v
223
224 return cls(**d)
225
226 def serialize(self):
227 s = ""
228 if not self.tag:
229 s = "{}.{}.{}".format(self.major, self.minor, self.patch)
230 else:
231 s = "{}.{}-{}{}".format(self.major, self.minor, self.tag,
232 self.patch)
233 if self.build:
234 s = "{}.{}".format(s, self.build)
235 return s
236
237 def to_json(self):
238 return self.serialize()
239
240
241 class Binary(_definitions.Binary):
242 """
243 This type represents a semver string with additional series and arch info.
244
245 Because it is not standard JSON, the typical from_json parsing fails and
246 the parsing must be handled specially.
247
248 See https://github.com/juju/version for more info.
249 """
250 binaryPat = re.compile(r'^(\d{1,9})\.(\d{1,9})(?:\.|-([a-z]+))(\d{1,9})(\.\d{1,9})?-([^-]+)-([^-]+)$') # noqa
251
252 def __init__(self, number=None, series=None, arch=None, **unknown_fields):
253 '''
254 number : Number
255 series : str
256 arch : str
257 '''
258 self.number = Number.from_json(number)
259 self.series = series
260 self.arch = arch
261
262 def __repr__(self):
263 return '<Binary number={} series={} arch={}>'.format(
264 self.number, self.series, self.arch)
265
266 def __str__(self):
267 return self.serialize()
268
269 def __eq__(self, other):
270 return (
271 isinstance(other, type(self)) and
272 other.number == self.number and
273 other.series == self.series and
274 other.arch == self.arch)
275
276 @classmethod
277 def from_json(cls, data):
278 parsed = None
279 if isinstance(data, cls):
280 return data
281 elif data is None:
282 return cls()
283 elif isinstance(data, dict):
284 parsed = data
285 elif isinstance(data, str):
286 match = cls.binaryPat.match(data)
287 if match:
288 parsed = {
289 'number': {
290 'major': match.group(1),
291 'minor': match.group(2),
292 'tag': match.group(3),
293 'patch': match.group(4),
294 'build': (match.group(5)[1:] if match.group(5)
295 else 0),
296 },
297 'series': match.group(6),
298 'arch': match.group(7),
299 }
300 if parsed is None:
301 raise TypeError('Unable to parse Binary version string: '
302 '{}'.format(data))
303 d = {}
304 for k, v in parsed.items():
305 d[cls._toPy.get(k, k)] = v
306
307 return cls(**d)
308
309 def serialize(self):
310 return "{}-{}-{}".format(self.number.serialize(),
311 self.series, self.arch)
312
313 def to_json(self):
314 return self.serialize()
315
316
317 class ConfigValue(_definitions.ConfigValue):
318 def __repr__(self):
319 return '<{} source={} value={}>'.format(type(self).__name__,
320 repr(self.source),
321 repr(self.value))
322
323
324 class Resource(Type):
325 _toSchema = {'application': 'application',
326 'charmresource': 'CharmResource',
327 'id_': 'id',
328 'pending_id': 'pending-id',
329 'timestamp': 'timestamp',
330 'username': 'username',
331 'name': 'name',
332 'origin': 'origin'}
333 _toPy = {'CharmResource': 'charmresource',
334 'application': 'application',
335 'id': 'id_',
336 'pending-id': 'pending_id',
337 'timestamp': 'timestamp',
338 'username': 'username',
339 'name': 'name',
340 'origin': 'origin'}
341
342 def __init__(self, charmresource=None, application=None, id_=None,
343 pending_id=None, timestamp=None, username=None, name=None,
344 origin=None, **unknown_fields):
345 '''
346 charmresource : CharmResource
347 application : str
348 id_ : str
349 pending_id : str
350 timestamp : str
351 username : str
352 name: str
353 origin : str
354 '''
355 if charmresource:
356 self.charmresource = _client.CharmResource.from_json(charmresource)
357 else:
358 self.charmresource = None
359 self.application = application
360 self.id_ = id_
361 self.pending_id = pending_id
362 self.timestamp = timestamp
363 self.username = username
364 self.name = name
365 self.origin = origin