class TestClass(object): __metaclass__ = RestClientMetaClass some_resource = RestResource( "some_resource", "/{resource_one_id}/subresources/{resource_two_id}") extra_views_resource = RestResource( "books", "/{resource_one_id}/books/{bookid}", extra_views=["unavailable", {"name": "get_all_books_status", "path": "status"}]) detail_only_resource = RestResource( "auction", "/users/{user_id}/auctions/{auction_id}", methods=["detail"]) extra_object_view_resource = RestResource( "tools", "/sheds/{shed_id}/tools/{tool_id}", extra_views=[ {"name": "get_tool_users", "path": "users", "scope": "object"}] ) custom_names_resource = RestResource( "stupidname", "/place/{objid}", method_names={ "list": "get_birds", "object": "get_bird", "create": "create_bird", "update": "update_bird", "delete": "delete_bird" })
def test_resource_path_calculated_correctly(self): # Catch a bug where the url /accounts/{account_id}/phone_numbers/{phone_number} # was parsing incorrectly resource = RestResource("phone_numbers", "/accounts/{account_id}/phone_numbers/{phone_number}") self.assertEqual(resource.path, "/accounts/{account_id}/phone_numbers")
def test_custom_resource_names(self): method_names={"list":"get_books", "object": "get_book", "update": "update_book", "create": "create_book", "delete": "delete_book"} resource = RestResource("someresource", "/someplace/{resource_id}", method_names=method_names) self.assertEqual(resource.method_names, method_names)
def test_resource_names_for_default_method_names(self): resource = RestResource("someresource", "/someplace/{resource_id}") method_names = resource.method_names expected_names = {"list": "get_someresources", "object": "get_someresource", "update": "update_someresource", "create": "create_someresource", "delete": "delete_someresource"} self.assertEqual(expected_names, method_names)
def test_custom_resource_names_merged_with_default(self): method_names = {"list": "get_books", "create": "create_book"} resource = RestResource("someresource", "/someplace/{resource_id}", method_names=method_names) expected_names = dict(method_names) expected_names.update({ "object": "get_someresource", "update": "update_someresource", "delete": "delete_someresource" }) self.assertEqual(expected_names, resource.method_names)
def setUp(self): self.resource = RestResource( "somresource", "/{id1}/somesubresource/{id2}", extra_views=[ {"path": "status", "name": "all_devices_status"}, "missing", {"path": "children", "name": "subresource_children", "scope": "object"}, {"path": "ingredients", "name": "ingredients", "scope": "object", "method": "put"}] )
class Client(object): """The interface to the Kazoo API This class should be initialized either with a username, password and account name combination, or with an API key. Once you have initialized the client you will need to call :meth:`authenticate()` before you can begin making API calls. :: >>>import kazoo >>>client = kazoo.Client(api_key="sdfasdfas") >>>client.authenticate() You can also initialize with a username and password combination: :: >>>client = kazoo.Client(username="******", password="******", account_name="my_account_name") >>>client.authenticate() API calls which require data take it in the form of a required argument called 'data' which is the last argument to the method. For example :: >>>client.update_account(acct_id, {"name": "somename", "realm":"superfunrealm"}) Dictionaries and lists will automatically be converted to their appropriate representation so you can do things like: :: >>>client.update_callflow(acct_id, callflow_id, {"flow":{"module":"somemodule"}}) Invalid data will result in an exception explaining the problem. The server response is returned from each method as a python dictionary of the returned JSON object, for example: :: >>>client.get_account(acct_id) {u'auth_token': u'abc437d000007d0454cc984f6f09daf3', u'data': {u'billing_mode': u'normal', u'caller_id': {}, u'caller_id_options': {}, u'id': u'c4f64412ad0057222c0009a3e7da011', u'media': {u'bypass_media': u'auto'}, u'music_on_hold': {}, u'name': u'test3', u'notifications': {}, u'realm': u'4c8050.sip.2600hz.com', u'superduper_admin': False, u'timezone': u'America/Los_Angeles', u'wnm_allow_additions': False}, u'request_id': u'ea6441422fb85000ad21db4f1e2326c1', u'revision': u'3-c16dd0a629fe1da0000e1e7b3e5fb35a', u'status': u'success'} For each resource exposed by the kazoo api there are corresponding methods on the client. For example, for the 'callflows' resource the correspondence is as follows. :: GET /accounts/{account_id}/callflows -> client.get_callflows(acct_id) GET /accounts/{account_id}/callflows/{callflow_id} -> client.get_callflow(acct_id, callflow_id) PUT /accounts/{account_id}/callflows/ -> client.create_callflow(acct_id, data) POST /account/{account_id}/callflows/{callflow_id} -> client.update_callflow(acct_id, data) DELETE /account/{account_id}/callflows/{callflow_id} -> client.delete_callflow(acct_id, callflow_id) Some resources do not have all methods available, in which case they are not present on the client. There are also some resources which don't quite fit this paradigm, they are: :: GET /accounts/{account_id}/media -> client.get_all_media(acct_id) GET /accounts/{account_id}/children -> client.get_account_children(acct_id) GET /accounts/{account_id}/descendants -> client.get_account_descendants(acct_id) GET /accounts/{account_id}/devices/status -> client.get_all_devices_status(acct_id) GET /accounts/{account_id}/servers/{server_id}/deployment -> client.get_deployment(acct_id, server_id) GET /accounts/{account_id}/users/hotdesk -> client.get_hotdesk(acct_id) """ __metaclass__ = RestClientMetaClass BASE_URL = "http://api.2600hz.com:8000/v1" _accounts_resource = RestResource( "account", "/accounts/{account_id}", exclude_methods=["list", "delete", "create"], extra_views=[{ "name": "get_account_children", "path": "children", "scope": "object" }, { "name": "get_account_descendants", "path": "descendants", "scope": "object" }]) _callflow_resource = RestResource( "callflow", "/accounts/{account_id}/callflows/{callflow_id}") _conference_resource = RestResource( "conference", "/accounts/{account_id}/conferences/{conference_id}") _device_resource = RestResource( "device", "/accounts/{account_id}/devices/{device_id}", extra_views=[{ "name": "get_all_devices_status", "path": "status" }]) _directories_resource = RestResource( "directory", "/accounts/{account_id}/directories/{directory_id}", plural_name="directories") _global_resources = RestResource( "global_resource", "/accounts/{account_id}/global_resources/{resource_id}") _limits_resource = RestResource("limit", "/accounts/{account_id}/limits/{ignored}", methods=["list"]) _local_resources_resource = RestResource( "local_resource", "/accounts/{account_id}/local_resources/{resource_id}") _media_resource = RestResource("media", "/accounts/{account_id}/media/{media_id}", plural_name="media", method_names={"list": "get_all_media"}) _menus_resource = RestResource("menu", "/accounts/{account_id}/menus/{menu_id}") _phone_number_resource = RestResource( "phone_number", "/accounts/{account_id}/phone_numbers/{phone_number}", methods=["list", "update", "delete"], extra_views=[{ "name": "activate_phone_number", "path": "activate", "scope": "object", "method": "put" }, { "name": "reserve_phone_number", "path": "reserve", "scope": "object", "method": "put" }, { "name": "add_port_in_number", "path": "port", "scope": "object", "method": "put" }]) _queues_resource = RestResource( "queue", "/accounts/{account_id}/queues/{queue_id}") _server_resource = RestResource( "server", "/accounts/{account_id}/servers/{server_id}", methods=["list"], extra_views=[{ "name": "get_deployment", "path": "deployment", "scope": "object" }, { "name": "create_deployment", "path": "deployment", "scope": "object", "method": "put" }, { "name": "get_server_log", "path": "log" }]) _temporal_rules_resource = RestResource( "temporal_rule", "/accounts/{account_id}/temporal_rules/{rule_id}") _users_resource = RestResource("user", "/accounts/{account_id}/users/{user_id}", extra_views=[{ "name": "get_hotdesk", "path": "hotdesks" }]) _vmbox_resource = RestResource("voicemail_box", "/accounts/{account_id}/vmboxes/{vmbox_id}", plural_name="voicemail_boxes") _phone_number_docs_resource = RestResource( "phone_number_doc", "/accounts/{account_id}/phone_numbers/{phone_number}/docs/{filename}", methods=["delete"], ) def __init__(self, api_key=None, password=None, account_name=None, username=None): if not api_key and not password: raise RuntimeError("You must pass either an api_key or an " "account name/password pair") if password or account_name or username: if not (password and account_name and username): raise RuntimeError("If using account name/password " "authentication then you must specify " "password, userame and account_name " "arguments") self.auth_request = UsernamePasswordAuthRequest( username, password, account_name) else: self.auth_request = ApiKeyAuthRequest(api_key) self.api_key = api_key self._authenticated = False self.auth_token = None def authenticate(self): """Call this before making other api calls to fetch an auth token which will be automatically used for all further requests """ if not self._authenticated: self.auth_data = self.auth_request.execute(self.BASE_URL) self.auth_token = self.auth_data["auth_token"] self._authenticated = True return self.auth_token def _execute_request(self, request, **kwargs): if request.auth_required: kwargs["token"] = self.auth_token return request.execute(self.BASE_URL, **kwargs) def search_phone_numbers(self, prefix, quantity=10): request = KazooRequest("/phone_numbers", get_params={ "prefix": prefix, "quantity": quantity }) return self._execute_request(request) def create_phone_number(self, acct_id, phone_number): request = KazooRequest( "/accounts/{account_id}/phone_numbers/{phone_number}", method="put") return self._execute_request(request, account_id=acct_id, phone_number=phone_number) def upload_phone_number_file(self, acct_id, phone_number, filename, file_obj): """Uploads a file like object as part of a phone numbers documents""" request = KazooRequest( "/accounts/{account_id}/phone_numbers/{phone_number}", method="post") return self._execute_request(request, files={filename: file_obj})
def setUp(self): self.path = "/{argument1}/subresource/{argument2}" self.resource = RestResource( "subresource", "/{argument1}/subresource/{argument2}")
def test_resource_with_no_required_args_correct(self): resource = RestResource("somename", "/sompath/{someobj_id}") self.assertEqual(resource.required_args, []) self.assertEqual(resource.object_arg, "someobj_id")
def test_resource_with_no_args_generates_error(self): with self.assertRaises(ValueError): resource = RestResource("somename", "/blahblah/blah")
def test_excludes_resource(self): resource = RestResource("subresource", "/{oneid}/someplace", exclude_methods=["list", "detail"]) self.assertEqual(resource.methods, ["create", "update", "delete"])
def test_if_plural_name_set_in_constructor_then_use_that(self): resource = RestResource("subresource", "/{oneid}/someotherplace", plural_name="subresourcae") self.assertEqual(resource.plural_name, "subresourcae")
def test_resource_plural_name(self): resource = RestResource("subresource", "/{oneid}/someotherplace") self.assertEqual(resource.plural_name, "subresources")