1
0
Fork 0
mirror of https://github.com/ansible-collections/hetzner.hcloud.git synced 2026-02-03 23:51:48 +00:00
hetzner.hcloud/plugins/module_utils/_client.py
Jonas L. cfa0d181f7
refactor: mark module_utils modules as private (#782)
##### SUMMARY

All `module_utils` are now marked as **private**. None of the modules
were intended for public use.

Similar to
https://togithub.com/ansible-collections/community.general/issues/11312
2026-01-06 08:43:46 +01:00

111 lines
3.3 KiB
Python

# Copyright: (c) 2023, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# Note that this module util is **PRIVATE** to the collection. It can have breaking changes at any time.
# Do not use this from other collections or standalone plugins/modules!
from __future__ import annotations
from contextlib import contextmanager
from ansible.module_utils.basic import missing_required_lib
from ._vendor.hcloud import APIException, Client as ClientBase
HAS_REQUESTS = True
HAS_DATEUTIL = True
try:
import requests # pylint: disable=unused-import
except ImportError:
HAS_REQUESTS = False
try:
import dateutil # pylint: disable=unused-import
except ImportError:
HAS_DATEUTIL = False
class ClientException(Exception):
"""An error related to the client occurred."""
def client_check_required_lib():
if not HAS_REQUESTS:
raise ClientException(missing_required_lib("requests"))
if not HAS_DATEUTIL:
raise ClientException(missing_required_lib("python-dateutil"))
def client_resource_not_found(resource: str, param: str | int):
return ClientException(f"resource ({resource}) does not exist: {param}")
def client_get_by_name_or_id(client: Client, resource: str, param: str | int):
"""
Get a resource by name, and if not found by its ID.
:param client: Client to use to make the call
:param resource: Name of the resource client that implements both `get_by_name` and `get_by_id` methods
:param param: Name or ID of the resource to query
"""
resource_client = getattr(client, resource)
result = resource_client.get_by_name(param)
if result is not None:
return result
# If the param is not a valid ID, prevent an unnecessary call to the API.
try:
int(param)
except ValueError as exception:
raise client_resource_not_found(resource.rstrip("s"), param) from exception
try:
return resource_client.get_by_id(param)
except APIException as exception:
if exception.code == "not_found":
raise client_resource_not_found(resource.rstrip("s"), param) from exception
raise exception
if HAS_REQUESTS:
class CachedSession(requests.Session):
cache: dict[str, requests.Response]
def __init__(self) -> None:
super().__init__()
self.cache = {}
def send(self, request: requests.PreparedRequest, **kwargs) -> requests.Response: # type: ignore[no-untyped-def]
"""
Send a given PreparedRequest.
"""
if request.method != "GET" or request.url is None:
return super().send(request, **kwargs)
if request.url in self.cache:
return self.cache[request.url]
response = super().send(request, **kwargs)
if response.ok:
self.cache[request.url] = response
return response
class Client(ClientBase):
@contextmanager
def cached_session(self):
"""
Swap the client session during the scope of the context. The session will cache
all GET requests.
Cached response will not expire, therefore the cached client must not be used
for long living scopes.
"""
self._client._session = CachedSession()
try:
yield
finally:
self._client._session = requests.Session()