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