mirror of
https://github.com/outbackdingo/patroni.git
synced 2026-08-25 14:53:37 +00:00
Add docstrings and type annotations to patroni/request.py (#2635)
References: PAT-73
This commit is contained in:
+103
-8
@@ -1,34 +1,97 @@
|
||||
"""Facilities for handling communication with Patroni's REST API."""
|
||||
import json
|
||||
import urllib3
|
||||
|
||||
from typing import Any, Dict, Optional, Union
|
||||
|
||||
from urllib.parse import urlparse, urlunparse
|
||||
|
||||
from .config import Config
|
||||
from .dcs import Member
|
||||
from .utils import USER_AGENT
|
||||
|
||||
|
||||
class PatroniRequest(object):
|
||||
"""Wrapper for performing requests to Patroni's REST API.
|
||||
|
||||
def __init__(self, config, insecure=None):
|
||||
Prepares the request manager with the configured settings before performing the request.
|
||||
"""
|
||||
|
||||
def __init__(self, config: Union[Config, Dict[str, Any]], insecure: Optional[bool] = None) -> None:
|
||||
"""Create a new :class:`PatroniRequest` instance with given *config*.
|
||||
|
||||
:param config: Patroni YAML configuration.
|
||||
:param insecure: how to deal with SSL certs verification
|
||||
* If ``True`` it will perform REST API requests without verifying SSL certs; or
|
||||
* If ``False`` it will perform REST API requests and verify SSL certs; or
|
||||
* If ``None`` it will behave according to the value of ``ctl -> insecure`` configuration; or
|
||||
* If none of the above applies, then it falls back to ``False``.
|
||||
"""
|
||||
self._insecure = insecure
|
||||
self._pool = urllib3.PoolManager(num_pools=10, maxsize=10)
|
||||
self.reload_config(config)
|
||||
|
||||
@staticmethod
|
||||
def _get_cfg_value(config, name):
|
||||
def _get_cfg_value(config: Union[Config, Dict[str, Any]], name: str) -> Union[Any, None]:
|
||||
"""Get value of *name* setting in *config*.
|
||||
|
||||
.. note::
|
||||
*name* key will be searched only under ``ctl`` and ``restapi`` sections, in that order.
|
||||
|
||||
:param config: Patroni YAML configuration.
|
||||
:param name: name of the setting value to be retrieved.
|
||||
|
||||
:returns: value of ``ctl -> *name*`` or ``restapi -> *name*``, if either is present, ``None`` otherwise.
|
||||
"""
|
||||
return config.get('ctl', {}).get(name) or config.get('restapi', {}).get(name)
|
||||
|
||||
def _apply_pool_param(self, param, value):
|
||||
def _apply_pool_param(self, param: str, value: Any) -> None:
|
||||
"""Configure *param* as *value* in the request manager.
|
||||
|
||||
:param param: name of the setting to be changed.
|
||||
:param value: new value for *param*. If ``None``, ``0``, ``False``, and similar values, then explicit *param*
|
||||
declaration is removed, in which case it takes its default value, if any.
|
||||
"""
|
||||
if value:
|
||||
self._pool.connection_pool_kw[param] = value
|
||||
else:
|
||||
self._pool.connection_pool_kw.pop(param, None)
|
||||
|
||||
def _apply_ssl_file_param(self, config, name):
|
||||
def _apply_ssl_file_param(self, config: Union[Config, Dict[str, Any]], name: str) -> Union[str, None]:
|
||||
"""Apply a given SSL related param to the request manager.
|
||||
|
||||
:param config: Patroni YAML configuration.
|
||||
:param name: prefix of the Patroni SSL related setting name. Currently, supports these:
|
||||
* ``cert``: gets translated to ``certfile``
|
||||
* ``key``: gets translated to ``keyfile``
|
||||
|
||||
Will attempt to fetch the requested key first from ``ctl`` section, and fall back to ``restapi`` section
|
||||
if the former is missing.
|
||||
|
||||
:returns: value of ``ctl -> *name*file`` or ``restapi -> *name*file`` if either is present, ``None`` otherwise.
|
||||
"""
|
||||
value = self._get_cfg_value(config, name + 'file')
|
||||
self._apply_pool_param(name + '_file', value)
|
||||
return value
|
||||
|
||||
def reload_config(self, config):
|
||||
def reload_config(self, config: Union[Config, Dict[str, Any]]) -> None:
|
||||
"""Apply *config* to request manager.
|
||||
|
||||
Configure these HTTP headers for requests:
|
||||
|
||||
* ``authorization``: based on Patroni' REST API authentication config;
|
||||
* ``user-agent``: based on `patroni.utils.USER_AGENT`.
|
||||
|
||||
Also configure SSL related settings for requests:
|
||||
|
||||
* ``ca_certs`` is configured if ``ctl -> cacert`` or ``restapi -> cafile`` is available;
|
||||
* ``cert``, ``key`` and ``key_password`` are configured if ``ctl -> certile`` or ``restapi -> certfile`` is
|
||||
available.
|
||||
|
||||
:param config: Patroni YAML configuration.
|
||||
"""
|
||||
# ``restapi -> auth`` is equivalent to ``restapi -> authentication -> username`` + ``:`` +
|
||||
# ``restapi -> authentication -> password``
|
||||
self._pool.headers = urllib3.make_headers(basic_auth=self._get_cfg_value(config, 'auth'), user_agent=USER_AGENT)
|
||||
|
||||
insecure = self._insecure if isinstance(self._insecure, bool) else config.get('ctl', {}).get('insecure', False)
|
||||
@@ -49,12 +112,34 @@ class PatroniRequest(object):
|
||||
cacert = config.get('ctl', {}).get('cacert') or config.get('restapi', {}).get('cafile')
|
||||
self._apply_pool_param('ca_certs', cacert)
|
||||
|
||||
def request(self, method, url, body=None, **kwargs):
|
||||
def request(self, method: str, url: str, body: Optional[Any] = None,
|
||||
**kwargs: Any) -> urllib3.response.HTTPResponse:
|
||||
"""Perform an HTTP request.
|
||||
|
||||
:param method: the HTTP method to be used, e.g. ``GET``.
|
||||
:param url: the URL to be requested.
|
||||
:param body: anything to be used as the request body.
|
||||
:param kwargs: keyword arguments to be passed to :func:`urllib3.PoolManager.request`.
|
||||
|
||||
:returns: the response returned upon request.
|
||||
"""
|
||||
if body is not None and not isinstance(body, str):
|
||||
body = json.dumps(body)
|
||||
return self._pool.request(method.upper(), url, body=body, **kwargs)
|
||||
|
||||
def __call__(self, member, method='GET', endpoint=None, data=None, **kwargs):
|
||||
def __call__(self, member: Member, method: str = 'GET', endpoint: Optional[str] = None,
|
||||
data: Optional[Any] = None, **kwargs: Any) -> urllib3.response.HTTPResponse:
|
||||
"""Turn :class:`PatroniRequest` into a callable object.
|
||||
|
||||
When called, perform a request through the manager.
|
||||
|
||||
:param member: DCS member so we can fetch from it the configured base URL for the REST API.
|
||||
:param method: HTTP method to be used, e.g. ``GET``.
|
||||
:param endpoint: URL path of this request, e.g. ``switchover``.
|
||||
:param data: anything to be used as the request body.
|
||||
|
||||
:returns: the response returned upon request.
|
||||
"""
|
||||
url = member.api_url
|
||||
if endpoint:
|
||||
scheme, netloc, _, _, _, _ = urlparse(url)
|
||||
@@ -62,6 +147,16 @@ class PatroniRequest(object):
|
||||
return self.request(method, url, data, **kwargs)
|
||||
|
||||
|
||||
def get(url, verify=True, **kwargs):
|
||||
def get(url: str, verify: Optional[bool] = True, **kwargs: Any) -> urllib3.response.HTTPResponse:
|
||||
"""Perform an HTTP GET request.
|
||||
|
||||
.. note::
|
||||
It uses :class:`PatroniRequest` so all relevant configuration is applied before processing the request.
|
||||
|
||||
:param url: full URL for this GET request.
|
||||
:param verify: if it should verify SSL certificates when processing the request.
|
||||
|
||||
:returns: the response returned from the request.
|
||||
"""
|
||||
http = PatroniRequest({}, not verify)
|
||||
return http.request('GET', url, **kwargs)
|
||||
|
||||
Reference in New Issue
Block a user