Skip to content

Commit 81c0da8

Browse files
committed
Adds Sphinx Documentation
* Adds documentation for Client and managers. The CLI code isn't covered yet. * Consolidated iter logic into Client.call instead of Client.__call__. It's much easier to document that way. * Added iter_call to SoftLayer.Service * Added some missing doc blocks and fixed some incorrectly formatted blocks. * Adds fabric script to build docs and push them up to github via gh-pages. Viewable here: http://softlayer.github.com/softlayer-api-python-client.
1 parent 8278e19 commit 81c0da8

18 files changed

Lines changed: 924 additions & 65 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Thumbs.db
77
.coverage
88
cover/*
99
.tox
10+
docs/_build/*
1011
build/*
1112
dist/*
1213
*.egg-info

SoftLayer/API.py

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
66
:copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved.
77
:license: BSD, see LICENSE for more details.
8-
:website: http://sldn.softlayer.com/article/Python
9-
108
"""
119
from SoftLayer.consts import API_PUBLIC_ENDPOINT, API_PRIVATE_ENDPOINT, \
1210
USER_AGENT
@@ -109,7 +107,7 @@ def set_authentication(self, username, api_key):
109107
Use this method if you wish to bypass the API_USER and API_KEY class
110108
constants and set custom authentication per API call.
111109
112-
See U{https://manage.softlayer.com/Administrative/apiKeychain} for more
110+
See https://manage.softlayer.com/Administrative/apiKeychain for more
113111
information.
114112
115113
:param username: the username to authenticate with
@@ -132,7 +130,7 @@ def set_init_parameter(self, id):
132130
of 1234 in the SoftLayer_Hardware_Server Service instructs the API to
133131
act on server record 1234 in your method calls.
134132
135-
See U{http://sldn.softlayer.com/article/Using-Initialization-Parameters-SoftLayer-API} # NOQA
133+
See http://sldn.softlayer.com/article/Using-Initialization-Parameters-SoftLayer-API # NOQA
136134
for more information.
137135
138136
:param id: the ID of the SoftLayer API object to instantiate
@@ -151,7 +149,7 @@ def set_object_mask(self, mask):
151149
Object masks are skeleton objects, or strings that define nested
152150
relational properties to retrieve along with an object's local
153151
properties. See
154-
U{http://sldn.softlayer.com/article/Using-Object-Masks-SoftLayer-API}
152+
http://sldn.softlayer.com/article/Using-Object-Masks-SoftLayer-API
155153
for more information.
156154
157155
:param mask: the object mask you wish to define
@@ -200,15 +198,12 @@ def __call__(self, *args, **kwargs):
200198
201199
:param service: the name of the SoftLayer API service
202200
:param method: the method to call on the service
203-
:param *args: same optional arguments that ``Client.call`` takes
204-
:param **kwargs: same optional keyword arguments that ``Client.call``
205-
takes
201+
:param \*args: same optional arguments that ``Client.call`` takes
202+
:param \*\*kwargs: same optional keyword arguments that ``Client.call``
203+
takes
206204
207205
"""
208-
if kwargs.get('iter'):
209-
return self.iter_call(*args, **kwargs)
210-
else:
211-
return self.call(*args, **kwargs)
206+
return self.call(*args, **kwargs)
212207

213208
def call(self, service, method, *args, **kwargs):
214209
""" Make a SoftLayer API call
@@ -223,12 +218,17 @@ def call(self, service, method, *args, **kwargs):
223218
:param dict raw_headers: (optional) HTTP transport headers
224219
:param int limit: (optional) return at most this many results
225220
:param int offset: (optional) offset results by this many
221+
:param boolean iter: (optional) if True, returns a generator with the
222+
results
226223
227224
Usage:
228225
>>> client['Account'].getVirtualGuests(mask="id", limit=10)
229226
[...]
230227
231228
"""
229+
if kwargs.get('iter'):
230+
return self.iter_call(service, method, *args, **kwargs)
231+
232232
objectid = kwargs.get('id')
233233
objectmask = kwargs.get('mask')
234234
objectfilter = kwargs.get('filter')
@@ -276,13 +276,24 @@ def call(self, service, method, *args, **kwargs):
276276

277277
def iter_call(self, service, method,
278278
chunk=100, limit=None, offset=0, *args, **kwargs):
279+
""" A generator that deals with paginating through results.
280+
281+
:param service: the name of the SoftLayer API service
282+
:param method: the method to call on the service
283+
:param integer chunk: result size for each API call
284+
:param \*args: same optional arguments that ``Client.call`` takes
285+
:param \*\*kwargs: same optional keyword arguments that ``Client.call``
286+
takes
287+
288+
"""
279289
if chunk <= 0:
280290
raise AttributeError("Chunk size should be greater than zero.")
281291

282292
if limit:
283293
chunk = min(chunk, limit)
284294

285295
result_count = 0
296+
kwargs['iter'] = False
286297
while True:
287298
if limit:
288299
# We've reached the end of the results
@@ -361,7 +372,7 @@ def call_handler(*args, **kwargs):
361372
raise SoftLayerError(
362373
"Service is not set on Client instance.")
363374
kwargs['headers'] = self._headers
364-
return self(self._service_name, name, *args, **kwargs)
375+
return self.call(self._service_name, name, *args, **kwargs)
365376
return call_handler
366377

367378
def __repr__(self):
@@ -389,7 +400,27 @@ def call(self, name, *args, **kwargs):
389400
[...]
390401
391402
"""
392-
return self.client(self.name, name, *args, **kwargs)
403+
return self.client.call(self.name, name, *args, **kwargs)
404+
405+
def iter_call(self, name, *args, **kwargs):
406+
""" A generator that deals with paginating through results.
407+
408+
:param method: the method to call on the service
409+
:param integer chunk: result size for each API call
410+
:param \*args: same optional arguments that ``Client.call`` takes
411+
:param \*\*kwargs: same optional keyword arguments that ``Client.call``
412+
takes
413+
414+
Usage:
415+
>>> gen = client['Account'].getVirtualGuests(iter=True)
416+
>>> for virtual_guest in gen:
417+
... virtual_guest['id']
418+
...
419+
1234
420+
4321
421+
422+
"""
423+
return self.client.iter_call(self.name, name, *args, **kwargs)
393424

394425
__call__ = call
395426

SoftLayer/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313
1414
:copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved.
1515
:license: BSD, see LICENSE for more details.
16-
:website: http://sldn.softlayer.com/article/Python
17-
1816
"""
1917
from SoftLayer.consts import VERSION
2018

SoftLayer/metadata.py

Lines changed: 58 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,30 +17,46 @@
1717

1818
__all__ = ["MetadataManager"]
1919

20+
METADATA_MAPPING = {
21+
'backend_mac': {'call': 'BackendMacAddresses'},
22+
'datacenter': {'call': 'Datacenter'},
23+
'datacenter_id': {'call': 'DatacenterId'},
24+
'domain': {'call': 'Domain'},
25+
'frontend_mac': {'call': 'FrontendMacAddresses'},
26+
'fqdn': {'call': 'FullyQualifiedDomainName'},
27+
'hostname': {'call': 'Hostname'},
28+
'id': {'call': 'Id'},
29+
'primary_backend_ip': {'call': 'PrimaryBackendIpAddress'},
30+
'primary_ip': {'call': 'PrimaryIpAddress'},
31+
'primary_frontend_ip': {'call': 'PrimaryIpAddress'},
32+
'provision_state': {'call': 'ProvisionState'},
33+
'router': {'call': 'Router', 'param_req': True},
34+
'tags': {'call': 'Tags'},
35+
'user_data': {'call': 'UserMetadata'},
36+
'user_metadata': {'call': 'UserMetadata'},
37+
'vlan_ids': {'call': 'VlanIds', 'param_req': True},
38+
'vlans': {'call': 'Vlans', 'param_req': True},
39+
}
40+
METADATA_ATTRIBUTES = METADATA_MAPPING.keys()
41+
2042

2143
class MetadataManager(object):
22-
""" Manages metadata. """
23-
24-
attribs = {
25-
'backend_mac': {'call': 'BackendMacAddresses'},
26-
'datacenter': {'call': 'Datacenter'},
27-
'datacenter_id': {'call': 'DatacenterId'},
28-
'domain': {'call': 'Domain'},
29-
'frontend_mac': {'call': 'FrontendMacAddresses'},
30-
'fqdn': {'call': 'FullyQualifiedDomainName'},
31-
'hostname': {'call': 'Hostname'},
32-
'id': {'call': 'Id'},
33-
'primary_backend_ip': {'call': 'PrimaryBackendIpAddress'},
34-
'primary_ip': {'call': 'PrimaryIpAddress'},
35-
'primary_frontend_ip': {'call': 'PrimaryIpAddress'},
36-
'provision_state': {'call': 'ProvisionState'},
37-
'router': {'call': 'Router', 'param_req': True},
38-
'tags': {'call': 'Tags'},
39-
'user_data': {'call': 'UserMetadata'},
40-
'user_metadata': {'call': 'UserMetadata'},
41-
'vlan_ids': {'call': 'VlanIds', 'param_req': True},
42-
'vlans': {'call': 'Vlans', 'param_req': True},
43-
}
44+
""" Provides an interface for the metadata service. This provides metadata
45+
about the resourse it is called from. See `METADATA_ATTRIBUTES` for
46+
full list of attributes.
47+
48+
Usage:
49+
50+
>>> from SoftLayer.metadata import MetadataManager
51+
>>> meta = MetadataManager(client)
52+
>>> meta.get('datacenter')
53+
'dal05'
54+
>>> meta.get('fqdn')
55+
'test.example.com'
56+
57+
"""
58+
59+
attribs = METADATA_MAPPING
4460

4561
def __init__(self, client=None, timeout=5):
4662
self.url = API_PRIVATE_ENDPOINT_REST.rstrip('/')
@@ -63,6 +79,12 @@ def make_request(self, path):
6379
return resp.read()
6480

6581
def get(self, name, param=None):
82+
""" Retreive a metadata attribute
83+
84+
:param name: name of the attribute to retrieve. See `attribs`
85+
:param param: Required parameter for some attributes
86+
87+
"""
6688
if name not in self.attribs:
6789
raise SoftLayerError('Unknown metadata attribute.')
6890

@@ -100,7 +122,21 @@ def _get_network(self, kind, router=True, vlans=True, vlan_ids=True):
100122
return network
101123

102124
def public_network(self, **kwargs):
125+
""" Returns details about the public network
126+
127+
:param boolean router: True to return router details
128+
:param boolean vlans: True to return vlan details
129+
:param boolean vlan_ids: True to return vlan_ids
130+
131+
"""
103132
return self._get_network('frontend', **kwargs)
104133

105134
def private_network(self, **kwargs):
135+
""" Returns details about the private network
136+
137+
:param boolean router: True to return router details
138+
:param boolean vlans: True to return vlan details
139+
:param boolean vlan_ids: True to return vlan_ids
140+
141+
"""
106142
return self._get_network('backend', **kwargs)

SoftLayer/tests/API/client_tests.py

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -270,68 +270,72 @@ def test_mask_call_invalid_mask(self, make_api_call):
270270
else:
271271
self.fail('No exception raised')
272272

273+
@patch('SoftLayer.API.Client.iter_call')
274+
def test_iterate(self, _iter_call):
275+
self.client['SERVICE'].METHOD(iter=True)
276+
_iter_call.assert_called_with('SoftLayer_SERVICE', 'METHOD', iter=True)
277+
278+
@patch('SoftLayer.API.Client.iter_call')
279+
def test_service_iter_call(self, _iter_call):
280+
self.client['SERVICE'].iter_call('METHOD')
281+
_iter_call.assert_called_with('SoftLayer_SERVICE', 'METHOD')
282+
273283
@patch('SoftLayer.API.Client.call')
274-
def test_iterate(self, _call):
284+
def test_iter_call(self, _call):
275285
# chunk=100, no limit
276286
_call.side_effect = [range(100), range(100, 125)]
277-
result = list(self.client['SERVICE'].METHOD(iter=True))
287+
result = list(self.client.iter_call('SERVICE', 'METHOD', iter=True))
278288

279289
self.assertEquals(range(125), result)
280290
_call.assert_has_calls([
281-
call('SoftLayer_SERVICE', 'METHOD',
282-
limit=100, iter=True, offset=0),
283-
call('SoftLayer_SERVICE', 'METHOD',
284-
limit=100, iter=True, offset=100),
291+
call('SERVICE', 'METHOD', limit=100, iter=False, offset=0),
292+
call('SERVICE', 'METHOD', limit=100, iter=False, offset=100),
285293
])
286294
_call.reset_mock()
287295

288296
# chunk=100, no limit. Requires one extra request.
289297
_call.side_effect = [range(100), range(100, 200), []]
290-
result = list(self.client['SERVICE'].METHOD(iter=True))
298+
result = list(self.client.iter_call('SERVICE', 'METHOD', iter=True))
291299
self.assertEquals(range(200), result)
292300
_call.assert_has_calls([
293-
call('SoftLayer_SERVICE', 'METHOD',
294-
limit=100, iter=True, offset=0),
295-
call('SoftLayer_SERVICE', 'METHOD',
296-
limit=100, iter=True, offset=100),
297-
call('SoftLayer_SERVICE', 'METHOD',
298-
limit=100, iter=True, offset=200),
301+
call('SERVICE', 'METHOD', limit=100, iter=False, offset=0),
302+
call('SERVICE', 'METHOD', limit=100, iter=False, offset=100),
303+
call('SERVICE', 'METHOD', limit=100, iter=False, offset=200),
299304
])
300305
_call.reset_mock()
301306

302307
# chunk=25, limit=30
303308
_call.side_effect = [range(0, 25), range(25, 30)]
304-
result = list(
305-
self.client['SERVICE'].METHOD(iter=True, limit=30, chunk=25))
309+
result = list(self.client.iter_call(
310+
'SERVICE', 'METHOD', iter=True, limit=30, chunk=25))
306311
self.assertEquals(range(30), result)
307312
_call.assert_has_calls([
308-
call('SoftLayer_SERVICE', 'METHOD', iter=True, limit=25, offset=0),
309-
call('SoftLayer_SERVICE', 'METHOD', iter=True, limit=5, offset=25),
313+
call('SERVICE', 'METHOD', iter=False, limit=25, offset=0),
314+
call('SERVICE', 'METHOD', iter=False, limit=5, offset=25),
310315
])
311316
_call.reset_mock()
312317

313318
# A non-list was returned
314319
_call.side_effect = ["test"]
315-
result = list(self.client['SERVICE'].METHOD(iter=True))
320+
result = list(self.client.iter_call('SERVICE', 'METHOD', iter=True))
316321
self.assertEquals(["test"], result)
317322
_call.assert_has_calls([
318-
call('SoftLayer_SERVICE', 'METHOD',
319-
iter=True, limit=100, offset=0),
323+
call('SERVICE', 'METHOD', iter=False, limit=100, offset=0),
320324
])
321325
_call.reset_mock()
322326

323327
# chunk=25, limit=30, offset=12
324328
_call.side_effect = [range(0, 25), range(25, 30)]
325-
result = list(self.client['SERVICE'].METHOD(
326-
iter=True, limit=30, chunk=25, offset=12))
329+
result = list(self.client.iter_call(
330+
'SERVICE', 'METHOD', iter=True, limit=30, chunk=25, offset=12))
327331
self.assertEquals(range(30), result)
328332
_call.assert_has_calls([
329-
call('SoftLayer_SERVICE', 'METHOD',
330-
iter=True, limit=25, offset=12),
331-
call('SoftLayer_SERVICE', 'METHOD', iter=True, limit=5, offset=37),
333+
call('SERVICE', 'METHOD', iter=False, limit=25, offset=12),
334+
call('SERVICE', 'METHOD', iter=False, limit=5, offset=37),
332335
])
333336

334337
# Chunk size of 0 is invalid
335338
self.assertRaises(
336339
AttributeError,
337-
lambda: list(self.client['SERVICE'].METHOD(iter=True, chunk=0)))
340+
lambda: list(self.client.iter_call(
341+
'SERVICE', 'METHOD', iter=True, chunk=0)))

SoftLayer/utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
"""
2+
SoftLayer.utils
3+
~~~~~~~~~~~~~~~
4+
Utility function/classes
5+
6+
:copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved.
7+
:license: BSD, see LICENSE for more details.
8+
"""
9+
110
KNOWN_OPERATIONS = ['<=', '>=', '<', '>', '~', '*=', '^=', '$=', '_=', '!~']
211

312

0 commit comments

Comments
 (0)