Skip to content

Commit dfc8f76

Browse files
authored
Merge pull request #623 from zancas/feature.refactor_helpers_to_correct_locale_610
Feature.refactor helpers to correct locale 610
2 parents 8abb28b + c0f3fb2 commit dfc8f76

2 files changed

Lines changed: 99 additions & 89 deletions

File tree

f5/bigip/resource.py

Lines changed: 95 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@
2222
2323
Examples:
2424
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/
2727
28-
* Expression: bigip.ltm
28+
* Expression: bigip.tm.ltm
2929
* URI Returned: https://a/mgmt/tm/ltm/
3030
31-
* Expression: pools1 = bigip.ltm.pools
31+
* Expression: pools1 = bigip.tm.ltm.pools
3232
* URI Returned: https://a/mgmt/tm/ltm/pool
3333
3434
* Expression: pool_a = pools1.create(partition="Common", name="foo")
@@ -40,8 +40,8 @@
4040
We refer to a server-provided resource as a "service". Thus far all URI
4141
referenced resources are "services" in this sense.
4242
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.
4545
4646
Methods:
4747
@@ -53,16 +53,23 @@
5353
and sets the Resource attrs to the state the device reports
5454
* load -- uses HTTP GET, obtains the state of an existing resource on the
5555
device and sets the Resource attrs to that state
56+
* modify -- uses HTTP PATCH to selectively modify named resources submitted
57+
as keyword arguments
5658
* delete -- uses HTTP DELETE, removes the resource from the device, and sets
5759
self.__dict__ to {'deleted': True}
5860
5961
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.
6070
* ResourceBase -- only `refresh` is generally supported in all resource
6171
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.
6673
All ResourceBase objects (except BIG-IPs) have a container (BIG-IPs
6774
contain themselves). The container is the object the ResourceBase is an
6875
attribute of.
@@ -76,6 +83,10 @@
7683
post (via _create) they uniquely depend on 2 uri's, a uri that supports
7784
the creating post, and the returned uri of the newly created resource.
7885
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.
7990
"""
8091
import keyword
8192
import re
@@ -172,6 +183,21 @@ class AttemptedMutationOfReadOnly(F5SDKError):
172183
pass
173184

174185

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+
175201
class PathElement(LazyAttributeMixin):
176202
"""Base class to represent a URI path element that does not contain data.
177203
@@ -213,75 +239,18 @@ def _set_meta_data_uri(self):
213239
self._meta_data['container']._meta_data['uri'] + endpoint + '/'
214240
self._meta_data['uri'] = final_uri
215241

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-
228242
def _check_command_parameters(self, **kwargs):
229243
"""Params given to exec_cmd should satisfy required params.
230244
231245
:params: kwargs
232246
:raises: MissingRequiredCommandParameter
233247
"""
234248
rset = self._meta_data['required_command_parameters']
235-
check = self._missing_required_parameters(rset, **kwargs)
249+
check = _missing_required_parameters(rset, **kwargs)
236250
if check:
237251
error_message = 'Missing required params: %s' % check
238252
raise MissingRequiredCommandParameter(error_message)
239253

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-
285254
def _check_force_arg(self, force):
286255
if not isinstance(force, bool):
287256
raise InvalidForceType("force parameter must be type bool")
@@ -345,21 +314,6 @@ def _check_exclusive_parameters(self, **kwargs):
345314
'together: "%s".' % cset
346315
raise ExclusiveAttributesPresent(error)
347316

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-
363317
@property
364318
def raw(self):
365319
"""Display the attributes that the current object has and their values.
@@ -470,6 +424,51 @@ def _prepare_put_or_patch(self, kwargs):
470424
read_only = self._meta_data.get('read_only_attributes', [])
471425
return requests_params, update_uri, session, read_only
472426

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+
473472
def _update(self, **kwargs):
474473
"""wrapped with update, override that in a subclass to customize"""
475474

@@ -819,6 +818,18 @@ def _activate_URI(self, selfLinkuri):
819818
'creation_uri_frag': frag,
820819
'allowed_lazy_attributes': attrs})
821820

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+
822833
def _create(self, **kwargs):
823834
"""wrapped by `create` override that in subclasses to customize"""
824835
if 'uri' in self._meta_data:
@@ -881,7 +892,7 @@ def _check_load_parameters(self, **kwargs):
881892
:raises: MissingRequiredReadParameter
882893
"""
883894
rset = self._meta_data['required_load_parameters']
884-
check = self._missing_required_parameters(rset, **kwargs)
895+
check = _missing_required_parameters(rset, **kwargs)
885896
if check:
886897
error_message = 'Missing required params: %s' % check
887898
raise MissingRequiredReadParameter(error_message)

f5/bigip/test/test_resource.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import pytest
1717
import requests
1818

19+
from f5.bigip.resource import _missing_required_parameters
1920
from f5.bigip.resource import AttemptedMutationOfReadOnly
2021
from f5.bigip.resource import BooleansToReduceHaveSameValue
2122
from f5.bigip.resource import Collection
@@ -669,18 +670,16 @@ def test_collection_s():
669670
class TestPathElement(object):
670671

671672
def test_missing_req_param_true(self):
672-
p = PathElement(mock.MagicMock())
673673
rqset = set(['FOOPAR1', 'FOOPAR2'])
674674
fakearg = {'FOOPAR1': 'FOOVAL'}
675-
mrq = p._missing_required_parameters(rqset, **fakearg)
675+
mrq = _missing_required_parameters(rqset, **fakearg)
676676
assert mrq
677677
assert mrq == ['FOOPAR2']
678678

679679
def test_missing_req_param_false(self):
680-
p = PathElement(mock.MagicMock())
681680
rqset = set(['FOOPAR1'])
682681
fakearg = {'FOOPAR1': 'FOOVAL'}
683-
mrq = p._missing_required_parameters(rqset, **fakearg)
682+
mrq = _missing_required_parameters(rqset, **fakearg)
684683
assert not mrq
685684

686685
def test_check_load_parameters_fail(self):
@@ -691,7 +690,7 @@ def test_check_load_parameters_fail(self):
691690
assert "['FAKELOAD']" in RLPEIO.value.message
692691

693692
def test_check_create_parameters_fail(self):
694-
p = PathElement(mock.MagicMock())
693+
p = Resource(mock.MagicMock())
695694
p._meta_data['required_creation_parameters'] = set(['FAKECREATE'])
696695
with pytest.raises(MissingRequiredCreationParameter) as RCPEIO:
697696
p._check_create_parameters(FOOCREATE='FOOVAL')

0 commit comments

Comments
 (0)