2 from collections
import namedtuple
4 from . import _client
, _definitions
5 from .facade
import ReturnMapping
, Type
, TypeEncoder
23 """A single websocket delta.
25 :ivar entity: The entity name, e.g. 'unit', 'application'
28 :ivar type: The delta type, e.g. 'add', 'change', 'remove'
31 :ivar data: The raw delta data
34 NOTE: The 'data' variable above is being incorrectly cross-linked by a
35 Sphinx bug: https://github.com/sphinx-doc/sphinx/issues/2549
38 _toSchema
= {'deltas': 'deltas'}
39 _toPy
= {'deltas': 'deltas'}
41 def __init__(self
, deltas
=None):
43 :param deltas: [str, str, object]
48 Change
= namedtuple('Change', 'entity type data')
49 change
= Change(*self
.deltas
)
51 self
.entity
= change
.entity
52 self
.type = change
.type
53 self
.data
= change
.data
56 def from_json(cls
, data
):
57 return cls(deltas
=data
)
60 class ResourcesFacade(Type
):
61 """Patch parts of ResourcesFacade to make it work.
64 @ReturnMapping(_client
.AddPendingResourcesResult
)
65 async def AddPendingResources(self
, application_tag
, charm_url
, resources
):
66 """Fix the calling signature of AddPendingResources.
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.
75 resources : typing.Sequence<+T_co>[~CharmResource]<~CharmResource>
76 Returns -> typing.Union[_ForwardRef('ErrorResult'),
77 typing.Sequence<+T_co>[str]]
79 # map input types to rpc msg
81 msg
= dict(type='Resources',
82 request
='AddPendingResources',
85 _params
['tag'] = application_tag
86 _params
['url'] = charm_url
87 _params
['resources'] = resources
88 reply
= await self
.rpc(msg
)
92 class AllWatcherFacade(Type
):
94 Patch rpc method of allwatcher to add in 'id' stuff.
97 async def rpc(self
, msg
):
98 if not hasattr(self
, 'Id'):
99 client
= _client
.ClientFacade
.from_connection(self
.connection
)
101 result
= await client
.WatchAll()
102 self
.Id
= result
.watcher_id
105 result
= await self
.connection
.rpc(msg
, encoder
=TypeEncoder
)
109 class ActionFacade(Type
):
111 class _FindTagsResults(Type
):
112 _toSchema
= {'matches': 'matches'}
113 _toPy
= {'matches': 'matches'}
115 def __init__(self
, matches
=None, **unknown_fields
):
117 FindTagsResults wraps the mapping between the requested prefix and the
118 matching tags for each requested prefix.
120 Matches map[string][]Entity `json:"matches"`
123 matches
= matches
or {}
124 for prefix
, tags
in matches
.items():
125 self
.matches
[prefix
] = [_definitions
.Entity
.from_json(r
)
128 @ReturnMapping(_FindTagsResults
)
129 async def FindActionTagsByPrefix(self
, prefixes
):
131 prefixes : typing.Sequence[str]
132 Returns -> typing.Sequence[~Entity]
134 # map input types to rpc msg
136 msg
= dict(type='Action',
137 request
='FindActionTagsByPrefix',
140 _params
['prefixes'] = prefixes
141 reply
= await self
.rpc(msg
)
145 class Number(_definitions
.Number
):
147 This type represents a semver string.
149 Because it is not standard JSON, the typical from_json parsing fails and
150 the parsing must be handled specially.
152 See https://github.com/juju/version for more info.
154 numberPat
= re
.compile(r
'^(\d{1,9})\.(\d{1,9})(?:\.|-([a-z]+))(\d{1,9})(\.\d{1,9})?$') # noqa
156 def __init__(self
, major
=None, minor
=None, patch
=None, tag
=None,
157 build
=None, **unknown_fields
):
165 self
.major
= int(major
or '0')
166 self
.minor
= int(minor
or '0')
167 self
.patch
= int(patch
or '0')
169 self
.build
= int(build
or '0')
172 return '<Number major={} minor={} patch={} tag={} build={}>'.format(
173 self
.major
, self
.minor
, self
.patch
, self
.tag
, self
.build
)
176 return self
.serialize()
180 return (self
.major
, self
.minor
, self
.tag
, self
.patch
, self
.build
)
182 def __eq__(self
, other
):
183 return isinstance(other
, type(self
)) and self
._cmp
== other
._cmp
185 def __lt__(self
, other
):
186 return self
._cmp
< other
._cmp
188 def __le__(self
, other
):
189 return self
._cmp
<= other
._cmp
191 def __gt__(self
, other
):
192 return self
._cmp
> other
._cmp
194 def __ge__(self
, other
):
195 return self
._cmp
>= other
._cmp
198 def from_json(cls
, data
):
200 if isinstance(data
, cls
):
204 elif isinstance(data
, dict):
206 elif isinstance(data
, str):
207 match
= cls
.numberPat
.match(data
)
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)
218 raise TypeError('Unable to parse Number version string: '
221 for k
, v
in parsed
.items():
222 d
[cls
._toPy
.get(k
, k
)] = v
229 s
= "{}.{}.{}".format(self
.major
, self
.minor
, self
.patch
)
231 s
= "{}.{}-{}{}".format(self
.major
, self
.minor
, self
.tag
,
234 s
= "{}.{}".format(s
, self
.build
)
238 return self
.serialize()
241 class Binary(_definitions
.Binary
):
243 This type represents a semver string with additional series and arch info.
245 Because it is not standard JSON, the typical from_json parsing fails and
246 the parsing must be handled specially.
248 See https://github.com/juju/version for more info.
250 binaryPat
= re
.compile(r
'^(\d{1,9})\.(\d{1,9})(?:\.|-([a-z]+))(\d{1,9})(\.\d{1,9})?-([^-]+)-([^-]+)$') # noqa
252 def __init__(self
, number
=None, series
=None, arch
=None, **unknown_fields
):
258 self
.number
= Number
.from_json(number
)
263 return '<Binary number={} series={} arch={}>'.format(
264 self
.number
, self
.series
, self
.arch
)
267 return self
.serialize()
269 def __eq__(self
, other
):
271 isinstance(other
, type(self
)) and
272 other
.number
== self
.number
and
273 other
.series
== self
.series
and
274 other
.arch
== self
.arch
)
277 def from_json(cls
, data
):
279 if isinstance(data
, cls
):
283 elif isinstance(data
, dict):
285 elif isinstance(data
, str):
286 match
= cls
.binaryPat
.match(data
)
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)
297 'series': match
.group(6),
298 'arch': match
.group(7),
301 raise TypeError('Unable to parse Binary version string: '
304 for k
, v
in parsed
.items():
305 d
[cls
._toPy
.get(k
, k
)] = v
310 return "{}-{}-{}".format(self
.number
.serialize(),
311 self
.series
, self
.arch
)
314 return self
.serialize()
317 class ConfigValue(_definitions
.ConfigValue
):
319 return '<{} source={} value={}>'.format(type(self
).__name
__,
324 class Resource(Type
):
325 _toSchema
= {'application': 'application',
326 'charmresource': 'CharmResource',
328 'pending_id': 'pending-id',
329 'timestamp': 'timestamp',
330 'username': 'username',
333 _toPy
= {'CharmResource': 'charmresource',
334 'application': 'application',
336 'pending-id': 'pending_id',
337 'timestamp': 'timestamp',
338 'username': 'username',
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
):
346 charmresource : CharmResource
356 self
.charmresource
= _client
.CharmResource
.from_json(charmresource
)
358 self
.charmresource
= None
359 self
.application
= application
361 self
.pending_id
= pending_id
362 self
.timestamp
= timestamp
363 self
.username
= username