|
22 | 22 |
|
23 | 23 | Examples: |
24 | 24 |
|
25 | | - * Expression: bigip = BigIP('a', 'b', 'c') |
26 | | - * URI Returned: https://a/mgmt/tm/ |
| 25 | + * Expression: bigip = ManagementRoot('a', 'b', 'c') |
| 26 | + * URI Returned: https://a/mgmt/ |
27 | 27 |
|
28 | | - * Expression: bigip.ltm |
| 28 | + * Expression: bigip.tm.ltm |
29 | 29 | * URI Returned: https://a/mgmt/tm/ltm/ |
30 | 30 |
|
31 | | - * Expression: pools1 = bigip.ltm.pools |
| 31 | + * Expression: pools1 = bigip.tm.ltm.pools |
32 | 32 | * URI Returned: https://a/mgmt/tm/ltm/pool |
33 | 33 |
|
34 | 34 | * Expression: pool_a = pools1.create(partition="Common", name="foo") |
|
40 | 40 | We refer to a server-provided resource as a "service". Thus far all URI |
41 | 41 | referenced resources are "services" in this sense. |
42 | 42 |
|
43 | | -We use methods named Create, Refresh, Update, Load, and Delete to manipulate |
44 | | -BIG-IP® device services. |
| 43 | +We use methods named Create, Refresh, Update, Load, Modify, and Delete to |
| 44 | +manipulate BIG-IP® device services. |
45 | 45 |
|
46 | 46 | Methods: |
47 | 47 |
|
|
53 | 53 | and sets the Resource attrs to the state the device reports |
54 | 54 | * load -- uses HTTP GET, obtains the state of an existing resource on the |
55 | 55 | device and sets the Resource attrs to that state |
| 56 | + * modify -- uses HTTP PATCH to selectively modify named resources submitted |
| 57 | + as keyword arguments |
56 | 58 | * delete -- uses HTTP DELETE, removes the resource from the device, and sets |
57 | 59 | self.__dict__ to {'deleted': True} |
58 | 60 |
|
59 | 61 | Available Classes: |
| 62 | + * PathElement -- the most fundamental class it represent URI elements that |
| 63 | + serve only as place-holders. All other Resources inherit from |
| 64 | + PathElement, though the inheritance may be indirect. PathElement provides |
| 65 | + a constructor to match its call in LazyAttributeMixin.__getattr__. The |
| 66 | + expected behavior is that all resource subclasses depend on this |
| 67 | + constructor to correctly set their self._meta_data['uri']. See |
| 68 | + _set_meta_data_uri for the logic underlying self._meta_data['uri'] |
| 69 | + construction. |
60 | 70 | * ResourceBase -- only `refresh` is generally supported in all resource |
61 | 71 | types, this class provides `refresh`. ResourceBase objects are usually |
62 | | - instantiated via setting lazy attributes. ResourceBase provides a |
63 | | - constructor to match its call in LazyAttributeMixin.__getattr__. The |
64 | | - expected behavior is that all resource subclasses depend on this |
65 | | - constructor to correctly set their self._meta_data['uri']. |
| 72 | + instantiated via setting lazy attributes. |
66 | 73 | All ResourceBase objects (except BIG-IPs) have a container (BIG-IPs |
67 | 74 | contain themselves). The container is the object the ResourceBase is an |
68 | 75 | attribute of. |
|
76 | 83 | post (via _create) they uniquely depend on 2 uri's, a uri that supports |
77 | 84 | the creating post, and the returned uri of the newly created resource. |
78 | 85 | Example URI_path: /mgmt/tm/ltm/nat/~Common~testnat1 |
| 86 | + * UnnamedResource -- Some resources correspond to URIs that do not have |
| 87 | + unique names, therefore the class does _not_ support create-or-delete, |
| 88 | + and supports a customized 'load' that doesn't require name/partition |
| 89 | + parameters. |
79 | 90 | """ |
80 | 91 | import keyword |
81 | 92 | import re |
@@ -172,6 +183,21 @@ class AttemptedMutationOfReadOnly(F5SDKError): |
172 | 183 | pass |
173 | 184 |
|
174 | 185 |
|
| 186 | +def _missing_required_parameters(rqset, **kwargs): |
| 187 | + """Helper function to do operation on sets. |
| 188 | +
|
| 189 | + Checks for any missing required parameters. |
| 190 | + Returns non-empty or empty list. With empty |
| 191 | + list being False. |
| 192 | +
|
| 193 | + ::returns list |
| 194 | + """ |
| 195 | + key_set = set(kwargs.keys()) |
| 196 | + required_minus_received = rqset - key_set |
| 197 | + if required_minus_received != set(): |
| 198 | + return list(required_minus_received) |
| 199 | + |
| 200 | + |
175 | 201 | class PathElement(LazyAttributeMixin): |
176 | 202 | """Base class to represent a URI path element that does not contain data. |
177 | 203 |
|
@@ -213,75 +239,18 @@ def _set_meta_data_uri(self): |
213 | 239 | self._meta_data['container']._meta_data['uri'] + endpoint + '/' |
214 | 240 | self._meta_data['uri'] = final_uri |
215 | 241 |
|
216 | | - def _check_create_parameters(self, **kwargs): |
217 | | - """Params given to create should satisfy required params. |
218 | | -
|
219 | | - :params: kwargs |
220 | | - :raises: MissingRequiredCreateParameter |
221 | | - """ |
222 | | - rset = self._meta_data['required_creation_parameters'] |
223 | | - check = self._missing_required_parameters(rset, **kwargs) |
224 | | - if check: |
225 | | - error_message = 'Missing required params: %s' % check |
226 | | - raise MissingRequiredCreationParameter(error_message) |
227 | | - |
228 | 242 | def _check_command_parameters(self, **kwargs): |
229 | 243 | """Params given to exec_cmd should satisfy required params. |
230 | 244 |
|
231 | 245 | :params: kwargs |
232 | 246 | :raises: MissingRequiredCommandParameter |
233 | 247 | """ |
234 | 248 | rset = self._meta_data['required_command_parameters'] |
235 | | - check = self._missing_required_parameters(rset, **kwargs) |
| 249 | + check = _missing_required_parameters(rset, **kwargs) |
236 | 250 | if check: |
237 | 251 | error_message = 'Missing required params: %s' % check |
238 | 252 | raise MissingRequiredCommandParameter(error_message) |
239 | 253 |
|
240 | | - def _local_update(self, rdict): |
241 | | - """Call this with a response dictionary to update instance attrs. |
242 | | -
|
243 | | - If the response has only valid keys, stash meta_data, replace __dict__, |
244 | | - and reassign meta_data. |
245 | | -
|
246 | | - :param rdict: response attributes derived from server JSON |
247 | | - """ |
248 | | - sanitized = self._check_keys(rdict) |
249 | | - temp_meta = self._meta_data |
250 | | - self.__dict__ = sanitized |
251 | | - self._meta_data = temp_meta |
252 | | - |
253 | | - def _check_keys(self, rdict): |
254 | | - """Call this from _local_update to validate response keys |
255 | | -
|
256 | | - disallowed server-response json keys: |
257 | | - 1. The string-literal '_meta_data' |
258 | | - 2. strings that are not valid Python 2.7 identifiers |
259 | | - 3. strings that are Python keywords |
260 | | - 4. strings beginning with '__'. |
261 | | -
|
262 | | - :param rdict: from response.json() |
263 | | - :raises: DeviceProvidesIncompatibleKey |
264 | | - :returns: checked response rdict |
265 | | - """ |
266 | | - if '_meta_data' in rdict: |
267 | | - error_message = "Response contains key '_meta_data' which is "\ |
268 | | - "incompatible with this API!!\n Response json: %r" % rdict |
269 | | - raise DeviceProvidesIncompatibleKey(error_message) |
270 | | - for x in rdict: |
271 | | - if not re.match(tokenize.Name, x): |
272 | | - error_message = "Device provided %r which is disallowed"\ |
273 | | - " because it's not a valid Python 2.7 identifier." % x |
274 | | - raise DeviceProvidesIncompatibleKey(error_message) |
275 | | - elif keyword.iskeyword(x): |
276 | | - error_message = "Device provided %r which is disallowed"\ |
277 | | - " because it's a Python keyword." % x |
278 | | - raise DeviceProvidesIncompatibleKey(error_message) |
279 | | - elif x.startswith('__'): |
280 | | - error_message = "Device provided %r which is disallowed"\ |
281 | | - ", it mangles into a Python non-public attribute." % x |
282 | | - raise DeviceProvidesIncompatibleKey(error_message) |
283 | | - return rdict |
284 | | - |
285 | 254 | def _check_force_arg(self, force): |
286 | 255 | if not isinstance(force, bool): |
287 | 256 | raise InvalidForceType("force parameter must be type bool") |
@@ -345,21 +314,6 @@ def _check_exclusive_parameters(self, **kwargs): |
345 | 314 | 'together: "%s".' % cset |
346 | 315 | raise ExclusiveAttributesPresent(error) |
347 | 316 |
|
348 | | - @staticmethod |
349 | | - def _missing_required_parameters(rqset, **kwargs): |
350 | | - """Helper function to do operation on sets. |
351 | | -
|
352 | | - Checks for any missing required parameters. |
353 | | - Returns non-empty or empty list. With empty |
354 | | - list being False. |
355 | | -
|
356 | | - ::returns list |
357 | | - """ |
358 | | - key_set = set(kwargs.keys()) |
359 | | - required_minus_received = rqset - key_set |
360 | | - if required_minus_received != set(): |
361 | | - return list(required_minus_received) |
362 | | - |
363 | 317 | @property |
364 | 318 | def raw(self): |
365 | 319 | """Display the attributes that the current object has and their values. |
@@ -470,6 +424,51 @@ def _prepare_put_or_patch(self, kwargs): |
470 | 424 | read_only = self._meta_data.get('read_only_attributes', []) |
471 | 425 | return requests_params, update_uri, session, read_only |
472 | 426 |
|
| 427 | + def _check_keys(self, rdict): |
| 428 | + """Call this from _local_update to validate response keys |
| 429 | +
|
| 430 | + disallowed server-response json keys: |
| 431 | + 1. The string-literal '_meta_data' |
| 432 | + 2. strings that are not valid Python 2.7 identifiers |
| 433 | + 3. strings that are Python keywords |
| 434 | + 4. strings beginning with '__'. |
| 435 | +
|
| 436 | + :param rdict: from response.json() |
| 437 | + :raises: DeviceProvidesIncompatibleKey |
| 438 | + :returns: checked response rdict |
| 439 | + """ |
| 440 | + if '_meta_data' in rdict: |
| 441 | + error_message = "Response contains key '_meta_data' which is "\ |
| 442 | + "incompatible with this API!!\n Response json: %r" % rdict |
| 443 | + raise DeviceProvidesIncompatibleKey(error_message) |
| 444 | + for x in rdict: |
| 445 | + if not re.match(tokenize.Name, x): |
| 446 | + error_message = "Device provided %r which is disallowed"\ |
| 447 | + " because it's not a valid Python 2.7 identifier." % x |
| 448 | + raise DeviceProvidesIncompatibleKey(error_message) |
| 449 | + elif keyword.iskeyword(x): |
| 450 | + error_message = "Device provided %r which is disallowed"\ |
| 451 | + " because it's a Python keyword." % x |
| 452 | + raise DeviceProvidesIncompatibleKey(error_message) |
| 453 | + elif x.startswith('__'): |
| 454 | + error_message = "Device provided %r which is disallowed"\ |
| 455 | + ", it mangles into a Python non-public attribute." % x |
| 456 | + raise DeviceProvidesIncompatibleKey(error_message) |
| 457 | + return rdict |
| 458 | + |
| 459 | + def _local_update(self, rdict): |
| 460 | + """Call this with a response dictionary to update instance attrs. |
| 461 | +
|
| 462 | + If the response has only valid keys, stash meta_data, replace __dict__, |
| 463 | + and reassign meta_data. |
| 464 | +
|
| 465 | + :param rdict: response attributes derived from server JSON |
| 466 | + """ |
| 467 | + sanitized = self._check_keys(rdict) |
| 468 | + temp_meta = self._meta_data |
| 469 | + self.__dict__ = sanitized |
| 470 | + self._meta_data = temp_meta |
| 471 | + |
473 | 472 | def _update(self, **kwargs): |
474 | 473 | """wrapped with update, override that in a subclass to customize""" |
475 | 474 |
|
@@ -819,6 +818,18 @@ def _activate_URI(self, selfLinkuri): |
819 | 818 | 'creation_uri_frag': frag, |
820 | 819 | 'allowed_lazy_attributes': attrs}) |
821 | 820 |
|
| 821 | + def _check_create_parameters(self, **kwargs): |
| 822 | + """Params given to create should satisfy required params. |
| 823 | +
|
| 824 | + :params: kwargs |
| 825 | + :raises: MissingRequiredCreateParameter |
| 826 | + """ |
| 827 | + rset = self._meta_data['required_creation_parameters'] |
| 828 | + check = _missing_required_parameters(rset, **kwargs) |
| 829 | + if check: |
| 830 | + error_message = 'Missing required params: %s' % check |
| 831 | + raise MissingRequiredCreationParameter(error_message) |
| 832 | + |
822 | 833 | def _create(self, **kwargs): |
823 | 834 | """wrapped by `create` override that in subclasses to customize""" |
824 | 835 | if 'uri' in self._meta_data: |
@@ -881,7 +892,7 @@ def _check_load_parameters(self, **kwargs): |
881 | 892 | :raises: MissingRequiredReadParameter |
882 | 893 | """ |
883 | 894 | rset = self._meta_data['required_load_parameters'] |
884 | | - check = self._missing_required_parameters(rset, **kwargs) |
| 895 | + check = _missing_required_parameters(rset, **kwargs) |
885 | 896 | if check: |
886 | 897 | error_message = 'Missing required params: %s' % check |
887 | 898 | raise MissingRequiredReadParameter(error_message) |
|
0 commit comments