| # -*- coding: utf-8 -*- |
| |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| |
| """ |
| Module to help retrieve/create/update/delete data from/to any RESTful API (Base Class). |
| |
| Requires Python Version >= 2.7 or >= 3.6 |
| """ |
| |
| # Core Modules |
| import json |
| import logging |
| import functools |
| from builtins import str |
| |
| # Third-party Modules |
| import munch |
| import requests |
| import requests.adapters as ra |
| |
| # Python 2 to Python 3 Compatibility |
| import requests.compat as compat |
| # Local Modules |
| from .utils import log_with_debug_info |
| |
| |
| try: |
| from future.utils import iteritems |
| except ImportError: |
| iteritems = lambda x: x.items() |
| |
| __all__ = ['LoginError', 'OperationError', 'InvalidJSONError', 'api_request', 'RestApiSession', |
| 'DEFAULT_HEADERS'] |
| |
| LOGGER = logging.getLogger(__name__) |
| |
| # Exception Classes |
| class LoginError(OSError): |
| """ |
| This represents an error that occurred during server login. |
| """ |
| def __init__(self, *args): |
| OSError.__init__(self, *args) |
| |
| |
| class OperationError(IOError): |
| """ |
| This class represents a generic error, indicating something went wrong with the request or on |
| the server. |
| """ |
| #: Contains the response object that generated the error |
| resp = None |
| def __init__(self, *args, resp=None): |
| IOError.__init__(self, *args) |
| self.resp = resp |
| |
| |
| class InvalidJSONError(ValueError): |
| """ |
| An error that occurs when an invalid JSON payload is passed to an endpoint. |
| """ |
| #: Contains the response object that generated the error |
| resp = None |
| def __init__(self, *args, resp=None): |
| ValueError.__init__(self, *args) |
| self.resp = resp |
| |
| # Miscellaneous Constants and/or Variables |
| DEFAULT_HEADERS = {u'Content-Type': u'application/json; charset=UTF-8'} |
| |
| |
| # Helper Functions/Decorators |
| def api_request(method_name, api_path, supported_versions): |
| """ |
| This wrapper returns a decorator that routes the calls to the appropriate utility function that |
| generates the RESTful API endpoint, performs the appropriate call to the endpoint and returns |
| the data to the user. |
| |
| :param method_name: A method name defined on the Class, this decorator is decorating, that will be called to perform the operation. E.g. 'GET', 'POST', 'PUT', 'DELETE', etc. The method_name chosen must have the signature of ``<method>(self, api_path, **kwargs)`` e.g. ``def get(self, api_path, **kwargs): ...`` |
| :type method_name: str |
| :param api_path: The path to the API end-point that you want to call which does not include the base url e.g. ``user/login``, ``servers``, etc. This string can contain substitution parameters as denoted by a valid field_name replacement field specification as per :meth:`str.format` e.g. ``cachegroups/{id}`` or ``cachegroups/{id:d}`` |
| :type api_path: str |
| :param supported_versions: A tuple of API versions that this route supports |
| :type supported_versions: Tuple[str] |
| :return: rtype int: A new function that replaces the original function with a boilerplate execution process. |
| :rtype: Callable[str, Dict[str, Any]] |
| """ |
| |
| def outer(func): |
| @functools.wraps(func) |
| def method_wrapper(self, *args, **kwargs): |
| # Positional arguments, e.g. *args, are not being used. Keyword arguments are the |
| # preferred way to pass the parameters needed by the helper functions |
| if (self.api_version is None) or (self.api_version in supported_versions): |
| msg = (u'Calling method [{0}] with keyword arguments [{1}] ' |
| u'via API endpoint method [{2}]') |
| log_with_debug_info(logging.DEBUG, msg.format(method_name, kwargs, func.__name__)) |
| |
| return getattr(self, method_name)(api_path, **kwargs) |
| |
| # Client API version is not supported by the method being called |
| msg = (u"Method [{0}] is not supported by this client's API version [{1}]; " |
| u'Supported versions: {2}') # type: Text |
| msg = msg.format(func.__name__, self.api_version, supported_versions) |
| log_with_debug_info(logging.DEBUG, msg) |
| raise OperationError(msg) |
| |
| return method_wrapper |
| return outer |
| |
| |
| class RestApiSession(object): |
| """ |
| This class represents a login session with a generic REST API server. It provides base |
| functionality inherited by :class:`TOSession`. |
| """ |
| def __init__(self, host_ip, api_version=None, api_base_path=u'api/', host_port=443, ssl=True, |
| headers=None, verify_cert=True, create_session=False, max_retries=5): |
| """ |
| The class initializer. |
| |
| :param host_ip: The dns name or ip address of the RESTful API host to use to talk to the API |
| :type host_ip: str |
| :param host_port: The port to use when contacting the RESTful API |
| :type host_port: int |
| :param api_version: The version of the API to make calls against. If supplied, endpoint version validation will be performed. If supplied as None, no version validation will be performed. :const:`None` is allowed so that non-versioned REST APIs can be implemented. |
| :type api_version: Union[str, None] |
| :param api_base_path: The part of the url that is the base path, from the web server root (which may include an API version), for all API endpoints without the server url portion e.g. 'api/', 'api/latest/' |
| |
| .. note:: To specify the base path with the passed ``api_version`` you can specify |
| ``api_base_path`` as ``api/{api_version}/`` and the API version will be substituted. |
| If ``api_version`` is :const:`None` and '{api_version}' is specified in the |
| ``api_base_path`` string then an exception will be thrown. |
| e.g. api_version=u'latest' -> 'api/{api_version}/' -> 'api/latest/' |
| api_version=None -> 'api/{api_version}/' -> Throws Exception |
| |
| :type api_base_path: str |
| :param ssl: Should SSL be used? (http vs. https) |
| :type ssl: bool |
| :param headers: The HTTP headers to use when contacting the RESTful API |
| :type headers: Dict[str, str] |
| :param verify_cert: Should the SSL certificates be verified when contacting the RESTful API. You may want to set this to :const:`False` for systems with self-signed certificates. |
| :type verify_cert: bool |
| :param create_session: Should a session be created automatically? |
| :type create_session: bool |
| """ |
| if headers is None: |
| headers = DEFAULT_HEADERS |
| |
| self._session = None |
| self._host_ip = host_ip |
| self._host_port = host_port |
| self._api_version = api_version |
| self._api_base_path = api_base_path |
| self._ssl = ssl |
| self._headers = headers |
| self._verify_cert = verify_cert |
| self._create_session = create_session |
| self._max_retries = max_retries |
| |
| # Setup API End-point Version validation, if enabled |
| self.__api_version_format_name = u'api_version' |
| self.__api_version_format_value = u'{{{0}}}'.format(self.__api_version_format_name) |
| |
| if self._api_version: |
| # if api_base_path is supplied as 'api/{api_version}/' or some string |
| # containing '{api_version}' then try to substitute the api_version supplied |
| # by the user. |
| |
| version_params = { |
| self.__api_version_format_name: self._api_version |
| } |
| self._api_base_path = self._api_base_path.format(**version_params) |
| |
| if not self._api_version and self.__api_version_format_value in self._api_base_path: |
| msg = (u'{0} was specified in the API Base Path [{1}] ' |
| u'but the replacement did not occur because the API Version ' |
| u'was not supplied.') |
| msg = msg.format(self.__api_version_format_value, self._api_base_path) |
| log_with_debug_info(logging.ERROR, msg) |
| raise OperationError(msg) |
| |
| # Setup some common URLs |
| self._server_url = u'{0}://{1}{2}/'.format(u'https' if ssl else u'http', |
| host_ip, |
| u':{0}'.format(host_port) if host_port else u'') |
| self._api_base_url = compat.urljoin(self._server_url, self._api_base_path) |
| self._api_base_url = self._api_base_url.rstrip(u'/') + u'/' |
| |
| |
| if not self._verify_cert: |
| # Not verifying certs so let's disable the warning |
| #pylint: disable=E1101 |
| requests.packages.urllib3.disable_warnings(\ |
| requests.packages.urllib3.exceptions.InsecureRequestWarning) |
| #pylint: enable=E1101 |
| log_with_debug_info(logging.WARNING, u'Certificate verification warnings are disabled.') |
| |
| msg = u'RestApiSession instance {0:#0x} initialized: Details: {1}' |
| log_with_debug_info(logging.DEBUG, msg.format(id(self), self.__dict__)) |
| |
| if self._create_session: |
| self.create() |
| |
| @property |
| def is_open(self): |
| """ |
| Is the session open to the RESTful API? (Read-only Property) |
| |
| :return: :const:`True` if yes, otherwise, :const:`False` |
| :rtype: bool |
| """ |
| return self._session is not None |
| |
| @property |
| def session(self): |
| """ |
| The RESTful API session (Read-only Property) |
| |
| :return: The requests session |
| :rtype: :class:`requests.Session` |
| """ |
| return self._session |
| |
| def create(self): |
| """ |
| Create the requests.Session to communicate with the RESTful API. |
| |
| :return: :const:`None` |
| :rtype: NoneType |
| """ |
| if self._session: |
| self.close() |
| |
| if not self._session: |
| self._session = requests.Session() |
| self._session.mount('http://', ra.HTTPAdapter(max_retries=self._max_retries)) |
| self._session.mount('https://', ra.HTTPAdapter(max_retries=self._max_retries)) |
| |
| msg = u'Created internal requests Session instance {0:#0x}' |
| log_with_debug_info(logging.DEBUG, msg.format(id(self._session))) |
| |
| def close(self): |
| """ |
| Close and cleanup the requests Session object. |
| |
| :return: :const:`None` |
| :rtype: NoneType |
| """ |
| |
| if self._session: |
| sid = id(self._session) |
| self._session.close() |
| del self._session |
| self._session = None |
| |
| msg = u'Internal requests Session instance 0x{0:x} closed and cleaned up' |
| log_with_debug_info(logging.DEBUG, msg.format(sid)) |
| |
| @property |
| def server_url(self): |
| """ |
| The URL without the api portion. (read-only) |
| |
| :return: The URL should match '[\\w\\+\\-\\.]+://[\\w\\+\\-\\.]+(:\\d+)?' e.g. 'https://to.somedomain.net' or 'https://to.somedomain.net:443' |
| :rtype: str |
| """ |
| |
| return self._server_url |
| |
| @property |
| def api_version(self): |
| """ |
| Returns the api version. (read-only) |
| |
| :return: The api version from which this instance will request endpoints. |
| :rtype: str |
| """ |
| |
| return self._api_version |
| |
| @property |
| def api_base_url(self): |
| """ |
| Returns the base URL. (read-only) |
| |
| :return: The base URL should match '[\\w\\+\\-\\.]+://[\\w\\+\\-\\.]+(:\\d+)?' e.g. 'https://to.somedomain.net/api/0.1/' |
| :rtype: str |
| """ |
| |
| return self._api_base_url |
| |
| def _build_endpoint(self, api_path, params=None, query_params=None): |
| """ |
| Helper function to form API URL. The base URL is |
| '<protocol>://<hostname>[:<port>]/<api base url>' e.g. 'https://to.somedomain.net/api/0.1/' |
| |
| :param api_path: The path to the API end-point that you want to call which does not include the base URL e.g. ``user/login``, ``servers``, etc. This string can contain substitution parameters as denoted by a valid field_name replacement field specification as per :meth:`str.format` e.g. ``cachegroups/{id}`` or ``cachegroups/{id:d}`` |
| :type api_path: str |
| :param params: If :meth:`str.format` field_name replacement field specifications exists in the ``api_path`` use this dictionary to perform replacements of the specifications with the value(s) in the dictionary that match the parameter name(s) e.g. ``{param_id}`` or ``{param_id:d}`` in ``api_string`` is replaced by value in ``params['param_id']``. |
| :type params: Union[Dict[str, Any], None] |
| :param query_params: URL query params to provide to the end-point e.g. ``{ 'sort': 'asc', 'maxresults': 200 }`` which translates to something like ``?sort=asc&maxresults=200`` which is appended to the request URL |
| :type query_params: Union[Dict[str, Any], None] |
| :return: The base url plus the passed and possibly substituted ``api_path`` to form a complete URL to the API resource to request |
| :rtype: str |
| :raises: ValueError |
| """ |
| |
| new_api_path = api_path |
| |
| # Replace all parameters in the new_api_path path, if required |
| try: |
| # Make the parameters values safe for adding to URLs |
| url_params = {k: compat.quote(str(v)) if isinstance(v, str)\ |
| else v for k, v in iteritems(params)} |
| |
| log_with_debug_info(logging.DEBUG, u'URL parameters are: [{0}]'.format(url_params)) |
| |
| qparams = u'' |
| if query_params: |
| # Process the URL query parameters |
| qparams = u'?{0}'.format(compat.urlencode(query_params)) |
| log_with_debug_info(logging.DEBUG, |
| u'URL query parameters are: [{0}]'.format(qparams)) |
| |
| new_api_path = api_path.format(**url_params) + qparams |
| except KeyError as e: |
| msg = (u'Expecting a value for keyword argument [{0}] for format field ' |
| u'specification [{1!r}]') |
| msg = msg.format(e, api_path) |
| log_with_debug_info(logging.ERROR, msg) |
| raise ValueError(msg) |
| except ValueError as e: |
| msg = (u'One or more values do not match the format field specification ' |
| u'[{0!r}]; Supplied values: {1!r} ') |
| msg = msg.format(api_path, params) |
| log_with_debug_info(logging.ERROR, msg) |
| raise ValueError(msg) |
| |
| retval = compat.urljoin(self.api_base_url, new_api_path) |
| |
| log_with_debug_info(logging.DEBUG, u'Built end-point to return: {0}'.format(retval)) |
| |
| return retval |
| |
| def _do_operation(self, |
| operation, api_path, query_params=None, munchify=True, debug_response=False, |
| expected_status_codes=range(200, 300), *unused_args, **kwargs): |
| """ |
| Helper method to perform HTTP operation requests - This is a boilerplate process for HTTP |
| operations. |
| |
| :param operation: Name of method to call on the :attr:`self._session` object to perform the HTTP request |
| :type operation: str |
| :param api_path: The path to the API end-point that you want to call which does not include the URL e.g. ``user/login``, ``servers``, etc. This string can contain substitution parameters as denoted by a valid field_name replacement field specification as per :meth:`str.format` e.g. ``cachegroups/{id}`` or ``cachegroups/{id:d}`` |
| :type api_path: str |
| :param: query_params: URL query parameters to provide to the endpoint e.g. ``{ 'sort': 'asc', 'maxresults': 200 }`` which translates to something like ``?sort=asc&maxresults=200`` which is appended to the request URL |
| :type query_params: Union[Dict[str, Any], None] |
| :param: munchify: If :const:`True` encapsulate data to be returned in a :class:`munch.Munch` object which allows keys in a Python dictionary to additionally have attribute access e.g. ``a_dict['a_key']`` with :mod:`munch` becomes ``a_dict['a_key']`` or ``a_dict.a_key`` |
| :type munchify: bool |
| :param kwargs: Passed Keyword Parameters. If you need to send JSON data to the endpoint pass the keyword parameter ``data`` with the Python data structure e.g. a ``dict``. This method will convert it to JSON before sending it to the API endpoint. |
| :type kwargs: Dict[str, Any] |
| :param debug_response: If :const:`True`, the actual response data text will be added to the log if a JSON decoding exception is encountered. |
| :type debug_response: bool |
| :type expected_status_codes: Tuple[int] |
| :param: expected_status_codes: expected success HTTP status codes. If the user needs to override the defaults this parameter can be passed e.g. ``(200, 204,)`` |
| :type munchify: bool |
| :return: Python data structure distilled from JSON from the API request. |
| :rtype: Tuple[Union[Dict[Text, Any], List[Dict[Text, Any]], munch.Munch, List[munch.Munch]], requests.Response] |
| :raises: miscellaneous.exceptions.OperationError |
| """ |
| |
| if not self._session: |
| msg = u'No session has been created for the API. Have you called create() yet?' |
| log_with_debug_info(logging.ERROR, msg) |
| raise OperationError(msg) |
| |
| response = None |
| retdata = None |
| |
| endpoint = self._build_endpoint(api_path, params=kwargs, query_params=query_params) |
| |
| params = {u'headers': self._headers, u'verify': self._verify_cert} |
| |
| if u'data' in kwargs: |
| params[u'data'] = json.dumps(kwargs[u'data']) |
| |
| log_with_debug_info(logging.DEBUG, u'Call parameters: {0}'.format(params)) |
| |
| # Call the API endpoint |
| response = getattr(self._session, operation)(endpoint, **params) |
| |
| log_with_debug_info(logging.DEBUG, u'Response status: {0} {1}'.format(response.status_code, |
| response.reason)) |
| |
| if response.status_code not in expected_status_codes: |
| try: |
| retdata = response.json() |
| except Exception as e: |
| # Invalid JSON payload. |
| msg = (u'HTTP Status Code: [{0}]; API response data for end-point [{1}] does not ' |
| u'appear to be valid JSON. Cause: {2}.') |
| msg = msg.format(response.status_code, endpoint, e) |
| if debug_response: |
| log_with_debug_info(logging.ERROR, msg + u' Data: [' + str(response.text) + u']') |
| raise InvalidJSONError(msg, resp=response) |
| msg = u'{0} request to RESTful API at [{1}] expected status(s) {2}; failed: {3} {4};'\ |
| u' Response: {5}' |
| msg = msg.format(operation.upper(), endpoint, expected_status_codes, |
| response.status_code, response.reason, retdata) |
| log_with_debug_info(logging.ERROR, msg) |
| raise OperationError(msg, resp=response) |
| |
| try: |
| if response.status_code in ('204',): |
| # "204 No Content" |
| retdata = {} |
| else: |
| # Decode the expected JSON |
| retdata = response.json() |
| except Exception as e: |
| # Invalid JSON payload. |
| msg = (u'HTTP Status Code: [{0}]; API response data for end-point [{1}] does not ' |
| u'appear to be valid JSON. Cause: {2}.') |
| msg = msg.format(response.status_code, endpoint, e) |
| if debug_response: |
| log_with_debug_info(logging.ERROR, msg + u' Data: [' + str(response.text) + u']') |
| raise InvalidJSONError(msg, resp=response) |
| retdata = munch.munchify(retdata) if munchify else retdata |
| return (retdata[u'response'] if u'response' in retdata else retdata), response |
| |
| def get(self, api_path, *args, **kwargs): |
| """ |
| Perform http get requests |
| |
| :param api_path: The path to the API end-point that you want to call which does not include the base url e.g. ``user/login``, ``servers``, etc. This string can contain substitution parameters as denoted by a valid field_name replacement field specification as per :meth:`str.format` e.g. ``cachegroups/{id}`` or ``cachegroups/{id:d}`` |
| :type api_path: str |
| :param kwargs: Passed Keyword Parameters. If you need to send JSON data to the endpoint pass the keyword parameter ``data`` with the Python data structure. This method will convert it to JSON before sending it to the API endpoint. Use ``query_params`` to pass a dictionary of query parameters |
| :type kwargs: Dict[str, Any] |
| :return: Python data structure distilled from JSON from the API request. |
| :rtype: Tuple[Union[Dict[str, Any], List[Dict[str, Any]], munch.Munch, List[munch.Munch]], requests.Response] |
| :raises: Union[LoginError, OperationError] |
| """ |
| |
| return self._do_operation(u'get', api_path, *args, **kwargs) |
| |
| def post(self, api_path, *args, **kwargs): |
| """ |
| Perform http post requests |
| |
| :param api_path: The path to the API end-point that you want to call which does not include the base URL e.g. ``user/login``, ``servers``, etc. This string can contain substitution parameters as denoted by a valid field_name replacement field specification as per :meth:`str.format` e.g. ``cachegroups/{id}`` or ``cachegroups/{id:d}`` |
| :type api_path: str |
| :param kwargs: Passed Keyword Parameters. If you need to send JSON data to the endpoint pass the keyword parameter ``data`` with the Python data structure. This method will convert it to JSON before sending it to the API endpoint. Use ``query_params`` to pass a dictionary of query parameters |
| :type kwargs: Dict[str, Any] |
| :return: Python data structure distilled from JSON from the API request. |
| :rtype: Tuple[Union[Dict[str, Any], List[Dict[str, Any]], munch.Munch, List[munch.Munch]], requests.Response] |
| :raises: Union[LoginError, OperationError] |
| """ |
| |
| return self._do_operation(u'post', api_path, *args, **kwargs) |
| |
| def put(self, api_path, *args, **kwargs): |
| """ |
| Perform http put requests |
| |
| :param api_path: The path to the API end-point that you want to call which does not include the base URL e.g. ``user/login``, ``servers``, etc. This string can contain substitution parameters as denoted by a valid field_name replacement field specification as per :meth:`str.format` e.g. ``cachegroups/{id}`` or ``cachegroups/{id:d}`` |
| :type api_path: str |
| :param kwargs: Passed Keyword Parameters. If you need to send JSON data to the endpoint pass the keyword parameter ``data`` with the Python data structure. This method will convert it to JSON before sending it to the API endpoint. Use ``query_params`` to pass a dictionary of query parameters |
| :type kwargs: Dict[str, Any] |
| :return: Python data structure distilled from JSON from the API request. |
| :rtype: Tuple[Union[Dict[str, Any], List[Dict[str, Any]], munch.Munch, List[munch.Munch]], requests.Response] |
| :raises: Union[LoginError, OperationError] |
| """ |
| |
| return self._do_operation(u'put', api_path, *args, **kwargs) |
| |
| def delete(self, api_path, *args, **kwargs): |
| """ |
| Perform HTTP DELETE requests |
| |
| :param api_path: The path to the API end-point that you want to call which does not include the base URL e.g. ``user/login``, ``servers``, etc. This string can contain substitution parameters as denoted by a valid field_name replacement field specification as per :meth:`str.format` e.g. ``cachegroups/{id}`` or ``cachegroups/{id:d}`` |
| :type api_path: str |
| :param kwargs: Passed Keyword Parameters. If you need to send JSON data to the endpoint pass the keyword parameter ``data`` with the Python data structure. This method will convert it to JSON before sending it to the API endpoint. Use ``query_params`` to pass a dictionary of query parameters |
| :type kwargs: Dict[str, Any] |
| :return: Python data structure distilled from JSON from the API request. |
| :rtype: Tuple[Union[Dict[str, Any], List[Dict[str, Any]], munch.Munch, List[munch.Munch]], requests.Response] |
| :raises: Union[LoginError, OperationError] |
| """ |
| |
| return self._do_operation(u'delete', api_path, *args, **kwargs) |
| |
| def head(self, api_path, *args, **kwargs): |
| """ |
| Perform HTTP HEAD requests |
| :param api_path: The path to the API end-point that you want to call which does not include the base URL e.g. ``user/login``, ``servers``, etc. This string can contain substitution parameters as denoted by a valid field_name replacement field specification as per :meth:`str.format` e.g. ``cachegroups/{id}`` or ``cachegroups/{id:d}`` |
| :type api_path: str |
| :param kwargs: Passed Keyword Parameters. If you need to send JSON data to the endpoint pass the keyword parameter ``data`` with the Python data structure. This method will convert it to JSON before sending it to the API endpoint. Use ``query_params`` to pass a dictionary of query parameters |
| :type kwargs: Dict[str, Any] |
| :return: Python data structure distilled from JSON from the API request. |
| :rtype: Tuple[Union[Dict[str, Any], List[Dict[str, Any]], munch.Munch, List[munch.Munch]], requests.Response] |
| :raises: Union[LoginError, OperationError] |
| """ |
| |
| return self._do_operation(u'head', api_path, *args, **kwargs) |
| |
| def options(self, api_path, *args, **kwargs): |
| """ |
| Perform HTTP OPTIONS requests |
| :param api_path: The path to the API end-point that you want to call which does not include the base URL e.g. ``user/login``, ``servers``, etc. This string can contain substitution parameters as denoted by a valid field_name replacement field specification as per :meth:`str.format` e.g. ``cachegroups/{id}`` or ``cachegroups/{id:d}`` |
| :type api_path: str |
| :param kwargs: Passed Keyword Parameters. If you need to send JSON data to the endpoint pass the keyword parameter ``data`` with the Python data structure. This method will convert it to JSON before sending it to the API endpoint. Use ``query_params`` to pass a dictionary of query parameters |
| :type kwargs: Dict[str, Any] |
| :return: Python data structure distilled from JSON from the API request. |
| :rtype: Tuple[Union[Dict[str, Any], List[Dict[str, Any]], munch.Munch, List[munch.Munch]], requests.Response] |
| :raises: Union[LoginError, OperationError] |
| """ |
| |
| return self._do_operation(u'options', api_path, *args, **kwargs) |
| |
| def patch(self, api_path, *args, **kwargs): |
| """ |
| Perform HTTP PATCH requests |
| :param api_path: The path to the API end-point that you want to call which does not include the base URL e.g. ``user/login``, ``servers``, etc. This string can contain substitution parameters as denoted by a valid field_name replacement field specification as per :meth:`str.format` e.g. ``cachegroups/{id}`` or ``cachegroups/{id:d}`` |
| :type api_path: str |
| :param kwargs: Passed Keyword Parameters. If you need to send JSON data to the endpoint pass the keyword parameter ``data`` with the Python data structure. This method will convert it to JSON before sending it to the API endpoint. Use ``query_params`` to pass a dictionary of query parameters |
| :type kwargs: Dict[str, Any] |
| :return: Python data structure distilled from JSON from the API request. |
| :rtype: Tuple[Union[Dict[str, Any], List[Dict[str, Any]], munch.Munch, List[munch.Munch]], requests.Response] |
| :raises: Union[LoginError, OperationError] |
| """ |
| |
| return self._do_operation(u'patch', api_path, *args, **kwargs) |