├── .gitignore ├── LICENSE ├── README.md ├── piapi.py ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | piapi AKA Prime Infrastructure API 2 | ================================== 3 | 4 | The piapi library ease the interaction with the Cisco Prime Infrastructure REST API with python. 5 | piapi implements a unique class known as **PIAPI** has the unique entry point for all requests made against the API. 6 | 7 | **[All credit goes to maximumG for piapi](https://github.com/maximumG/piapi)**, any changes after v0.1.4 were implemented by tyler-guy. 8 | 9 | Version 10 | ------- 11 | 12 | ### 0.1.5 13 | - Add support for PRIME v3 API. 14 | - Conversion to python 3. 15 | 16 | 17 | ### 0.1.4 18 | 19 | - Fix issue with PUT request and json parameters. 20 | 21 | ### 0.1.3 22 | 23 | - Add support of Cisco Prime Infrastructure virtual domain. 24 | - Skip the *grequests* library for concurrent requests and use *threading* instead (grequests doesn't work with 25 | multiprocessing) 26 | 27 | ### 0.1.2 28 | 29 | - Major fixes with the request methods. 30 | 31 | ### 0.1.0 32 | 33 | - Initial release. 34 | 35 | Installation 36 | ------------ 37 | 38 | ```shell 39 | pip install https://github.com/tyler-guy/piapi/archive/python3.zip 40 | ``` 41 | 42 | Cisco Prime Infrastructure REST API 43 | =================================== 44 | 45 | Prime Infrastructure Network Monitoring/Configuration solution exposes a classic REST API to get access to several *resource*. 46 | The resources are of 2 types: 47 | 48 | - **Data** resources: exposes several statistics/metric of the network and often requested using HTTP GET (e.g. client summary, alarms,…) 49 | - **Service** resources: exposes several services to modify the configuration of the NMS/network and often used with HTTP POST, PUT and DELETE 50 | 51 | Check the Cisco Prime Infrastructure REST API documentation available at 52 | 53 | 54 | Note that piapi library is only interacting with the REST API using JSON structure. 55 | 56 | How does piapi works ? 57 | ====================== 58 | 59 | Basic Usage 60 | ----------- 61 | 62 | The following code resumes all functionalities of the *PIAPI* class. 63 | 64 | ```python 65 | from piapi import PIAPI 66 | 67 | api = PIAPI("https://pi-server/", "username" , "password") 68 | 69 | api.resources 70 | api.data_resources 71 | api.service_resources 72 | 73 | # Request a Data resource from the API 74 | api.request("Clients", params={"connectionType": "LIGHTWEIGHTWIRELESS"}) 75 | 76 | # Request a Action resource from the API 77 | to_delete = {"deviceDeleteCandidates": {"ipAddresses": {"ipAddress": "1.1.1.1"}}} 78 | api.request("deleteDevices", params=to_delete) 79 | ``` 80 | 81 | We can request several properties from the class such as *resources*, *data\_resources*, *action\_resources*. 82 | These properties are list of available resources that are exposed from the REST API. 83 | The resources can used after when calling the API with the *request* method. 84 | 85 | The *request* method is the generic entry point to interact with the REST API. 86 | It needs to be called using the resource’s name as required argument and some *params*. 87 | All requests will return the response as JSON structure. 88 | 89 | Also note that the requests for data resources always returns a detailed JSON structure and not the summary one. 90 | 91 | Rate Limiting 92 | ------------- 93 | 94 | The Cisco Prime API is using rate limiting features to protect the server from request’s overloading. 95 | This means that the API is restricting requests from a user inside a window of time. 96 | 97 | To ‘bypass’ and embrace this rate limiting feature, the *request* method can be tuned using optional parameters: 98 | 99 | - **paging\_size**: the maximum result that can be present in a page (default : 1000) 100 | - **concurrent\_requests**: the number of requests that can be sent in parallel (default : 5) 101 | - **hold**: time in second to wait between each chunk of concurrent requests (default : 1) 102 | 103 | Check the Cisco Prime Infrastructure REST API documentation to known more about the rate limiting feature and how it can be tuned internally. 104 | 105 | PIAPI Caching feature 106 | --------------------- 107 | 108 | Some REST API call can be extensively long, depending on what you want to retrieve. 109 | For that matter, the PIAPI class implements a caching mechanism for data resources only. When calling the request method for 110 | the same couple resource+parameters more than once, the method will return the already stored result instead of running the request again. 111 | 112 | ```python 113 | api = PIAPI("https://pi-server/", "username" , "password") 114 | 115 | # This request will be run against the REST API (could take a long time) 116 | api.request("Clients", params={"connectionType": "LIGHTWEIGHTWIRELESS"}) 117 | 118 | # This request will comes directly from the cache PIAPI class (faster return of data) 119 | api.request("Clients", params={"connectionType": "LIGHTWEIGHTWIRELESS"}) 120 | ``` 121 | 122 | You can explicitly avoid using the cache by setting the *check\_cache* argument to *False* in the request method. 123 | 124 | ```python 125 | api.request("Clients", params={"connectionType": "LIGHTWEIGHTWIRELESS"}, check_cache=False) 126 | ``` 127 | 128 | API SSL feature 129 | --------------- 130 | 131 | The Cisco Prime Infrastructure API is only accessible trough HTTP over SSL (HTTPS) connections. 132 | By default the PIAPI class verifies the server’s SSL certificate. 133 | You can disable this behaviour by setting the *verify* argument of the PIAPI constructor to False. 134 | 135 | ```python 136 | api = PIAPI("https://pi-server/", "username" , "password", verify=False) 137 | ``` 138 | 139 | API Timeout handling 140 | -------------------- 141 | 142 | The Cisco Prime Infrastructure API can be really slow. The default request timeout is set to 300 seconds (5min); 143 | this is usefull for some REST Call for long job reporting. To reduce this timeout simply use the 144 | *timeout* parameters of the request method (seconds as metric). 145 | 146 | ```python 147 | api = PIAPI("https://pi-server/", "username" , "password", verify=False) 148 | api.request("MyNotSoLongAction", timeout=20) 149 | ``` 150 | 151 | Virtual Domain Support 152 | ---------------------- 153 | 154 | A Virtual Domain consists of a set of devices and/or maps and restricts a user view to information relevant 155 | to these managed objects. PIAPI is "virtual domain" aware and it is possible to specify the virtual domain name either 156 | during the creation of the PIAPI or during each call to the request method. 157 | 158 | ```python 159 | api = PIAPI("https://pi-server/", "username" , "password", virtual_domain="root-domain") 160 | # retrieve clients from the 'root-domain' 161 | api.request("Clients", params={"connectionType": "LIGHTWEIGHTWIRELESS"}) 162 | # retrieve clients from the 'sub-domain' 163 | api.request("Clients", params={"connectionType": "LIGHTWEIGHTWIRELESS"}, virtual_domain="sub-domain") 164 | ``` 165 | 166 | Also note that when changing the virtual domain for one request, the virtual domain is persistent for all next requests. 167 | -------------------------------------------------------------------------------- /piapi.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 maximumG 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | piapi module stands for (Cisco) Prime Infrastructure API. 16 | The module implements the PIAPI class which helps interacting with the Cisco Prime Infrastructure REST API using 17 | simple methods that can either request data or request an action. 18 | 19 | The Cisco Prime Infrastructure API is a REST API which exposes several resources that can be of 2 types: 20 | * Data resources: expose some data collected by the software which can be retrieved (e.g: client summary). 21 | * Service resources: expose some services that can modify the configuration of the software (e.g: modify/update an Access Point) 22 | 23 | The REST API is applying request rate limiting to avoid server's overloading. To bypass this limitation, especially 24 | when requesting data resources, the PIAPI uses multithreading requests (grequests library) with an hold time between 25 | chunk of requests. Please check the documentation to knowns more about rate limiting. 26 | 27 | Also note that the piapi module only works with the JSON structure exposed by the REST API. The module doesn't support 28 | the default XML structure. 29 | 30 | Please check your Cisco Prime REST API available at http://{server-name}/webacs/api/v1/ 31 | """ 32 | 33 | from __future__ import absolute_import 34 | import six.moves.urllib.parse 35 | import time 36 | import copy 37 | import hashlib 38 | import threading 39 | import six.moves.queue 40 | import json 41 | 42 | import requests 43 | import requests.auth 44 | from six.moves import range 45 | 46 | #import grequests 47 | 48 | """ 49 | Default number of concurrent requests (check *Rate Limiting* of the API) 50 | """ 51 | DEFAULT_CONCURRENT_REQUEST = 5 52 | """ 53 | Default number of results per page (check *Rate Limiting* of the API) 54 | """ 55 | DEFAULT_PAGE_SIZE = 1000 56 | """ 57 | Default hold time in second to wait between group of concurrent request to avoid rate timiting (check *Rate Limiting* of the API) 58 | """ 59 | DEFAULT_HOLD_TIME = 1 60 | """ 61 | Default time in second to wait for a response fomr the REST API 62 | """ 63 | DEFAULT_REQUEST_TIMEOUT = 300 64 | """ 65 | Default base URI of the Prime API 66 | """ 67 | DEFAULT_API_URI = "/webacs/api/v3/" 68 | 69 | 70 | class PIAPIError(Exception): 71 | """ 72 | Generic error raised by the piapi module. 73 | """ 74 | 75 | 76 | class PIAPIRequestError(PIAPIError): 77 | """ 78 | Error raised by the piapi module when HTTP error code occurred. 79 | """ 80 | 81 | 82 | class PIAPICountError(PIAPIError): 83 | """ 84 | Error raised by the piapi module when no result can be found for an API request. 85 | """ 86 | 87 | 88 | class PIAPIResourceNotFound(PIAPIError): 89 | """ 90 | Error raised by the piapi module when a requested resource is not available in the API. 91 | """ 92 | 93 | 94 | class PIAPI(object): 95 | """ 96 | Interface with the Cisco Prime Infrastructure REST API. 97 | 98 | Attributes 99 | ---------- 100 | base_url : str 101 | The base URL to get access to the API (e.g. https://{server}/webacs/v1/api/). 102 | verify : bool 103 | Whether or not to verify the server's SSL certificate. 104 | cache : dict 105 | Cache for all data requests already performed. 106 | session : requests.Session 107 | HTTP session that will be used as base for all interaction with the REST API. 108 | 109 | Parameters 110 | ---------- 111 | url : str 112 | The base URL to get access to Cisco Prime Infrastructure (without the URI of the REST API!). 113 | username : str 114 | Username to be used for authentication. 115 | password : str 116 | Password to be used for authentication. 117 | verify : bool (optional) 118 | Whether or not to verify the server's SSL certificate (default: True). 119 | virtual_domain : str (optional) 120 | The virtual domain used by all the request. Virtual domain are used as a filter (default: None). 121 | """ 122 | 123 | def __init__(self, url, username, password, verify=True, virtual_domain=None): 124 | """ 125 | Constructor of the PIAPI class. 126 | """ 127 | self.base_url = six.moves.urllib.parse.urljoin(url, DEFAULT_API_URI) 128 | self.verify = verify 129 | self.virtual_domain = virtual_domain 130 | self.cache = {} # Caching is used for data resource with keys as checksum of resource's name+params from the request 131 | 132 | # Service resources holds all possible service resources with keys as service name 133 | # and hold the HTTP method + full url to request the service. 134 | self._service_resources = {} 135 | # Data resources holds all possible data resources with key as service name and value as full url access. 136 | self._data_resources = {} 137 | 138 | self.session = requests.Session() 139 | self.session.auth = requests.auth.HTTPBasicAuth(username, password) 140 | 141 | # Disable HTTP keep_alive as advised by the API documentation 142 | self.session.headers['connection'] = 'close' 143 | 144 | # Don't print warning message from request if not wanted 145 | if not self.verify: 146 | import warnings 147 | warnings.filterwarnings("ignore") 148 | 149 | def _parse(self, response): 150 | """ 151 | Parse a requests.Response object to check for potential errors using the HTTP status code. 152 | Please check your Cisco Prime Infrastructure REST API documentation for errors and return code. 153 | 154 | Parameters 155 | ---------- 156 | response : requests.Response 157 | HTTP response from an HTTP requests. 158 | 159 | Returns 160 | ------- 161 | response_json : JSON structure 162 | The JSON structure from the response. 163 | """ 164 | if response.status_code == 200: 165 | response_json = response.json() 166 | return response_json 167 | elif response.status_code == 302: 168 | raise PIAPIRequestError("Incorrect credentials provided") 169 | elif response.status_code == 400: 170 | response_json = response.json() 171 | raise PIAPIRequestError("Invalid request: %s" % response_json["errorDocument"]["message"]) 172 | elif response.status_code == 401: 173 | raise PIAPIRequestError("Unauthorized access") 174 | elif response.status_code == 403: 175 | raise PIAPIRequestError("Forbidden access to the REST API") 176 | elif response.status_code == 404: 177 | raise PIAPIRequestError("URL not found %s" % response.url) 178 | elif response.status_code == 406: 179 | raise PIAPIRequestError("The Accept header sent in the request does not match a supported type") 180 | elif response.status_code == 415: 181 | raise PIAPIRequestError("The Content-Type header sent in the request does not match a supported type") 182 | elif response.status_code == 500: 183 | raise PIAPIRequestError("An error has occured during the API invocation") 184 | elif response.status_code == 502: 185 | raise PIAPIRequestError("The server is down or being upgraded") 186 | elif response.status_code == 503: 187 | raise PIAPIRequestError("The servers are up, but overloaded with requests. Try again later (rate limiting)") 188 | else: 189 | raise PIAPIRequestError("Unknown Request Error, return code is %s" % response.status_code) 190 | 191 | def _request_wrapper(self, queue, url, params, timeout): 192 | """ 193 | Wrapper to requests used by each thread. 194 | 195 | Parameters 196 | ---------- 197 | queue : Queue.Queue 198 | The Queue to write the response from the request in. 199 | url : str 200 | The URL to be queried. 201 | params : dict 202 | A dictionary of parameters to pass to the request. 203 | timeout : int 204 | Timeout to wait for a response to the request. 205 | """ 206 | response = self.session.get(url, params=params, verify=self.verify, timeout=timeout) 207 | queue.put(response) 208 | 209 | @property 210 | def resources(self): 211 | """ 212 | List of all available resources to be requested. This includes actions and data resources. 213 | """ 214 | return self.data_resources + self.service_resources 215 | 216 | @property 217 | def data_resources(self): 218 | """ 219 | List of all available data resources, meaning resources that return data. 220 | """ 221 | if self._data_resources: 222 | return list(self._data_resources.keys()) 223 | 224 | data_resources_url = six.moves.urllib.parse.urljoin(self.base_url, "data.json") 225 | response = self.session.get(data_resources_url, verify=self.verify) 226 | response_json = self._parse(response) 227 | for entry in response_json["queryResponse"]["entityType"]: 228 | self._data_resources[entry["$"]] = "%s.json" % entry["@url"] 229 | 230 | return list(self._data_resources.keys()) 231 | 232 | @property 233 | def service_resources(self): 234 | """ 235 | List of all available service resources, meaning resources that modify the NMS. 236 | """ 237 | if self._service_resources: 238 | return list(self._service_resources.keys()) 239 | 240 | service_resources_url = six.moves.urllib.parse.urljoin(self.base_url, "op.json") 241 | response = self.session.get(service_resources_url, verify=self.verify) 242 | response_json = self._parse(response) 243 | for entry in response_json["queryResponse"]["operation"]: 244 | self._service_resources[entry["$"]] = {"method": entry["@httpMethod"], "url": six.moves.urllib.parse.urljoin(self.base_url, "op/%s.json" % entry["@path"])} 245 | 246 | return list(self._service_resources.keys()) 247 | 248 | def request_data(self, resource_name, params={}, check_cache=True, timeout=DEFAULT_REQUEST_TIMEOUT, paging_size=DEFAULT_PAGE_SIZE, concurrent_requests=DEFAULT_CONCURRENT_REQUEST, hold=DEFAULT_HOLD_TIME): 249 | """ 250 | Request a 'resource_name' resource from the REST API. The request can be tuned with filtering, sorting options. 251 | Check the REST API documentation for available filters by resource. 252 | 253 | To bypass rate limiting feature of the API you can tune paging_size, concurrent_requests and hold_time parameters. 254 | 'X' concurrent requests will be sent as chunk and we will wait the hold time before sending the next chunk until 255 | all resource_name have been retrieved. 256 | 257 | Parameters 258 | ---------- 259 | resource_name : str 260 | Data resource name to be requested. 261 | params : dict (optional) 262 | Additional parameters to be sent along the query for filtering, sorting,... (default : empty dict). 263 | check_cache : bool (optional) 264 | Whether or not to check the cache instead of performing a call against the REST API. 265 | timeout : int (optional) 266 | Time to wait for a response from the REST API (default : piapi.DEFAULT_REQUEST_TIMEOUT) 267 | paging_size : int (optional) 268 | Number of entries to include per page (default : piapi.DEFAULT_PAGE_SIZE). 269 | concurrent_requests : int (optional) 270 | Number of parallel requests to make (default : piapi.DEFAULT_CONCURRENT_REQUEST). 271 | hold : int (optional) 272 | Hold time in second to wait between chunk of concurrent requests to avoid rate limiting (default : piapi.DEFAULT_HOLD_TIME). 273 | 274 | Returns 275 | ------- 276 | results : JSON structure 277 | Data results from the requested resources. 278 | """ 279 | if resource_name not in self.data_resources: 280 | raise PIAPIResourceNotFound("Data Resource '%s' not found in the API, check 'data_resources' property " 281 | "for a list of available resource_name" % resource_name) 282 | 283 | # Check the cache to see if the couple (resource + parameters) already exists (using SHA256 hash of resource_name and params) 284 | # hash_cache = hashlib.sha256(b"%s%s" % (resource_name, params)).hexdigest() 285 | # if check_cache and hash_cache in self.cache: 286 | # return self.cache[hash_cache] 287 | 288 | # Get total number of entries for the request 289 | response = self.session.get(self._data_resources[resource_name], params=params, timeout=timeout) 290 | self._parse(response) 291 | count_entry = int(response.json()["queryResponse"]["@count"]) 292 | if count_entry <= 0: 293 | raise PIAPICountError("No result found for the query %s with params %s" % (response.url, params)) 294 | 295 | # Create the necessary requests with paging to avoid rate limiting 296 | paging_requests = [] 297 | queue = six.moves.queue.Queue() 298 | for first_result in range(0, count_entry, paging_size): 299 | params_copy = copy.deepcopy(params) 300 | params_copy.update({".full": "true", ".firstResult": first_result, ".maxResults": paging_size}) 301 | #paging_requests.append(grequests.get(self._data_resources[resource_name], session=self.session, params=params_copy, verify=self.verify, timeout=timeout)) 302 | paging_requests.append(threading.Thread(None, self._request_wrapper, args=(queue, 303 | self._data_resources[resource_name], 304 | params_copy, 305 | timeout))) 306 | 307 | # Create chunks from the previous list of requests to avoid rate limiting (we hold between each chunk) 308 | chunk_requests = [paging_requests[x:x+concurrent_requests] for x in range(0, len(paging_requests), concurrent_requests)] 309 | 310 | # Bulk query the chunk pages by waiting between each chunk to avoid rate limiting 311 | responses = [] 312 | for chunk_request in chunk_requests: 313 | #responses += grequests.map(chunk_request) 314 | for request in chunk_request: 315 | request.start() 316 | for request in chunk_request: 317 | request.join() 318 | responses.append(queue.get()) 319 | time.sleep(hold) 320 | 321 | # Parse the results of the previous queries 322 | results = [] 323 | for response in responses: 324 | response_json = self._parse(response) 325 | results += response_json["queryResponse"]["entity"] 326 | # self.cache[hash_cache] = results 327 | return results 328 | 329 | def request_service(self, resource_name, params=None, timeout=DEFAULT_REQUEST_TIMEOUT): 330 | """ 331 | Request a service resource from the REST API. 332 | 333 | Parameters 334 | ---------- 335 | resource_name : str 336 | Action resource to be requested 337 | params : dict (optional) 338 | JSON parameters to be sent along the resource_name request (default : empty dict) 339 | timeout : int (optional) 340 | Time to wait for a response from the REST API (default : piapi.DEFAULT_REQUEST_TIMEOUT) 341 | 342 | Returns 343 | ------- 344 | results : JSON structure 345 | Data results from the requested resources. 346 | """ 347 | if resource_name not in self.service_resources: 348 | raise PIAPIResourceNotFound("Service Resource '%s' not found in the API, check 'service_resources' property " 349 | "for a list of available actions" % resource_name) 350 | 351 | method = self._service_resources[resource_name]["method"] 352 | url = self._service_resources[resource_name]["url"] 353 | headers = {'Content-Type':'application/json'} 354 | # if the HTTP method is 'GET', use the params args of request, otherwise use data (POST, DELETE, PUT) 355 | if method == "GET": 356 | response = self.session.request(method, url, params=params, verify=self.verify, timeout=timeout) 357 | elif method == "PUT" or method == "POST": 358 | response = self.session.request(method, url, data=json.dumps(params), headers=headers, verify=self.verify, timeout=timeout) 359 | else: 360 | response = self.session.request(method, url, data=params, verify=self.verify, timeout=timeout) 361 | return self._parse(response) 362 | 363 | def request(self, resource, params={}, virtual_domain=None, check_cache=True, timeout=DEFAULT_REQUEST_TIMEOUT, paging_size=DEFAULT_PAGE_SIZE, 364 | concurrent_requests=DEFAULT_CONCURRENT_REQUEST, hold=DEFAULT_HOLD_TIME): 365 | """ 366 | Generic request for either data or services resources. The parameters correspond to the ones from 367 | *PIAPI.request_data* or *PIAPI.request_action*. 368 | 369 | Parameters 370 | ---------- 371 | resource : str 372 | Action resource to be requested 373 | params : dict (optional) 374 | JSON parameters to be sent along the resource_name request (default : empty dict). 375 | virtual_domain : str (optional) 376 | Name of the virtual domain to send the request for (default : None). 377 | check_cache : bool (optional) 378 | Whether or not to check the cache instead of performing a call against the REST API. 379 | timeout : int (optional) 380 | Time to wait for a response from the REST API (default : piapi.DEFAULT_REQUEST_TIMEOUT) 381 | paging_size : int (optional) 382 | Number of entries to include per page (default : piapi.DEFAULT_PAGE_SIZE). 383 | concurrent_requests : int (optional) 384 | Number of parallel requests to make (default : piapi.DEFAULT_CONCURRENT_REQUEST). 385 | hold : int (optional) 386 | Hold time in second to wait between chunk of concurrent requests to avoid rate limiting (default : piapi.DEFAULT_HOLD_TIME). 387 | 388 | Returns 389 | ------- 390 | results : JSON structure 391 | Data results from the requested resources. 392 | """ 393 | virtual_domain = virtual_domain or self.virtual_domain 394 | if virtual_domain: 395 | params["_ctx.domain"] = virtual_domain 396 | 397 | if resource in self.data_resources: 398 | return self.request_data(resource, params, check_cache, timeout, paging_size, concurrent_requests, hold) 399 | elif resource in self.service_resources: 400 | return self.request_service(resource, params, timeout) 401 | 402 | def __getattr__(self, item): 403 | """ 404 | Magic method used to render all resources as class attribute 405 | 406 | item : str 407 | Name of the resource to be found 408 | """ 409 | if item in self.resources: 410 | return self.request(item) 411 | raise AttributeError("'%s' resource not found in the REST API" % item) 412 | 413 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | piapi module stands for (Cisco) Prime Infrastructure API. 3 | The module implements the PIAPI class which helps interacting with the Cisco Prime Infrastructure REST API using 4 | simple methods that can either request data or request an action. 5 | 6 | The Cisco Prime Infrastructure API is a REST API which exposes several resources that can be of 2 types: 7 | * Data resources: expose some data collected by the software which can be retrieved (e.g: client summary). 8 | * Service resources: expose some service that can modify the configuration of the software (e.g: modify/update an Access Point) 9 | 10 | The REST API is applying request rate limiting to avoid server's overloading. To bypass this limitation, especially 11 | when requesting data resources, the PIAPI uses multithreading requests (grequests library) with an hold time between 12 | chunk of requests. Please check the documentation to knowns more about rate limiting. 13 | 14 | Also note that the piapi module only works with the JSON structure exposed by the REST API. The module doesn't support 15 | the default XML structure. 16 | 17 | Please check your Cisco Prime REST API available at https://{server-name}/webacs/api/v1/ 18 | 19 | test modification 20 | """ 21 | from __future__ import absolute_import 22 | from setuptools import setup 23 | 24 | setup( 25 | name='piapi', 26 | version='0.1.5', 27 | py_modules=['piapi'], 28 | platforms='any', 29 | url='https://github.com/HSRNetwork/piapi', 30 | download_url='https://github.com/HSRNetwork/piapi/archive/0.1.5.tar.gz', 31 | license='Apache', 32 | author='maximumG', 33 | author_email='mgerges@stubbynet.com', 34 | description='Cisco Prime Infrastructure REST API for python', 35 | long_description=__doc__, 36 | keywords=["Cisco", "Prime", "API", "REST", "request"], 37 | classifiers=[ 38 | "Environment :: Web Environment", 39 | "Intended Audience :: Developers", 40 | "License :: OSI Approved :: Apache Software License", 41 | "Programming Language :: Python :: 3", 42 | "Topic :: Internet :: WWW/HTTP :: Dynamic Content", 43 | "Topic :: Software Development :: Libraries :: Python Modules" 44 | ], 45 | install_requires=[ 46 | 'requests', 'six' 47 | ], 48 | ) 49 | --------------------------------------------------------------------------------