mirror of
https://github.com/checktheroads/hyperglass
synced 2024-05-11 05:55:08 +00:00
Improve external http client typing and add tests
This commit is contained in:
156
hyperglass/external/_base.py
vendored
156
hyperglass/external/_base.py
vendored
@@ -1,4 +1,4 @@
|
|||||||
"""Session handler for RIPEStat Data API."""
|
"""Session handler for external http data sources."""
|
||||||
|
|
||||||
# Standard Library
|
# Standard Library
|
||||||
import re
|
import re
|
||||||
@@ -6,7 +6,6 @@ import json as _json
|
|||||||
import socket
|
import socket
|
||||||
import typing as t
|
import typing as t
|
||||||
from json import JSONDecodeError
|
from json import JSONDecodeError
|
||||||
from types import TracebackType
|
|
||||||
from socket import gaierror
|
from socket import gaierror
|
||||||
|
|
||||||
# Third Party
|
# Third Party
|
||||||
@@ -16,10 +15,21 @@ import httpx
|
|||||||
from hyperglass.log import log
|
from hyperglass.log import log
|
||||||
from hyperglass.util import make_repr, parse_exception
|
from hyperglass.util import make_repr, parse_exception
|
||||||
from hyperglass.constants import __version__
|
from hyperglass.constants import __version__
|
||||||
|
from hyperglass.models.fields import JsonValue, HttpMethod, Primitives
|
||||||
from hyperglass.exceptions.private import ExternalError
|
from hyperglass.exceptions.private import ExternalError
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
# Standard Library
|
||||||
|
from types import TracebackType
|
||||||
|
|
||||||
def _prepare_dict(_dict):
|
# Project
|
||||||
|
from hyperglass.exceptions._common import ErrorLevel
|
||||||
|
from hyperglass.models.config.logging import Http
|
||||||
|
|
||||||
|
D = t.TypeVar("D", bound=t.Dict)
|
||||||
|
|
||||||
|
|
||||||
|
def _prepare_dict(_dict: D) -> D:
|
||||||
return _json.loads(_json.dumps(_dict, default=str))
|
return _json.loads(_json.dumps(_dict, default=str))
|
||||||
|
|
||||||
|
|
||||||
@@ -28,16 +38,17 @@ class BaseExternal:
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
base_url,
|
base_url: str,
|
||||||
config=None,
|
config: t.Optional["Http"] = None,
|
||||||
uri_prefix="",
|
uri_prefix: str = "",
|
||||||
uri_suffix="",
|
uri_suffix: str = "",
|
||||||
verify_ssl=True,
|
verify_ssl: bool = True,
|
||||||
timeout=10,
|
timeout: int = 10,
|
||||||
parse=True,
|
parse: bool = True,
|
||||||
):
|
) -> None:
|
||||||
"""Initialize connection instance."""
|
"""Initialize connection instance."""
|
||||||
self.__name__ = getattr(self, "name", "BaseExternal")
|
self.__name__ = getattr(self, "name", "BaseExternal")
|
||||||
|
self.name = self.__name__
|
||||||
self.config = config
|
self.config = config
|
||||||
self.base_url = base_url.strip("/")
|
self.base_url = base_url.strip("/")
|
||||||
self.uri_prefix = uri_prefix.strip("/")
|
self.uri_prefix = uri_prefix.strip("/")
|
||||||
@@ -55,61 +66,80 @@ class BaseExternal:
|
|||||||
self._asession = httpx.AsyncClient(**session_args)
|
self._asession = httpx.AsyncClient(**session_args)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def __init_subclass__(cls, name=None, **kwargs):
|
def __init_subclass__(
|
||||||
|
cls: "BaseExternal", name: t.Optional[str] = None, **kwargs: t.Any
|
||||||
|
) -> None:
|
||||||
"""Set correct subclass name."""
|
"""Set correct subclass name."""
|
||||||
super().__init_subclass__(**kwargs)
|
super().__init_subclass__(**kwargs)
|
||||||
cls.name = name or cls.__name__
|
cls.name = name or cls.__name__
|
||||||
|
|
||||||
async def __aenter__(self):
|
async def __aenter__(self: "BaseExternal") -> "BaseExternal":
|
||||||
"""Test connection on entry."""
|
"""Test connection on entry."""
|
||||||
available = await self._atest()
|
available = await self._atest()
|
||||||
|
|
||||||
if available:
|
if available:
|
||||||
log.debug("Initialized session with {}", self.base_url)
|
log.debug("Initialized session with {}", self.base_url)
|
||||||
return self
|
return self
|
||||||
else:
|
raise self._exception(f"Unable to create session to {self.name}")
|
||||||
raise self._exception(f"Unable to create session to {self.name}")
|
|
||||||
|
|
||||||
async def __aexit__(self, exc_type=None, exc_value=None, traceback=None):
|
async def __aexit__(
|
||||||
|
self: "BaseExternal",
|
||||||
|
exc_type: t.Optional[t.Type[BaseException]] = None,
|
||||||
|
exc_value: t.Optional[BaseException] = None,
|
||||||
|
traceback: t.Optional["TracebackType"] = None,
|
||||||
|
) -> True:
|
||||||
"""Close connection on exit."""
|
"""Close connection on exit."""
|
||||||
log.debug("Closing session with {}", self.base_url)
|
log.debug("Closing session with {}", self.base_url)
|
||||||
|
|
||||||
|
if exc_type is not None:
|
||||||
|
log.error(str(exc_value))
|
||||||
|
|
||||||
await self._asession.aclose()
|
await self._asession.aclose()
|
||||||
|
if exc_value is not None:
|
||||||
|
raise exc_value
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self: "BaseExternal") -> "BaseExternal":
|
||||||
"""Test connection on entry."""
|
"""Test connection on entry."""
|
||||||
available = self._test()
|
available = self._test()
|
||||||
|
|
||||||
if available:
|
if available:
|
||||||
log.debug("Initialized session with {}", self.base_url)
|
log.debug("Initialized session with {}", self.base_url)
|
||||||
return self
|
return self
|
||||||
else:
|
raise self._exception(f"Unable to create session to {self.name}")
|
||||||
raise self._exception(f"Unable to create session to {self.name}")
|
|
||||||
|
|
||||||
def __exit__(
|
def __exit__(
|
||||||
self,
|
self: "BaseExternal",
|
||||||
exc_type: t.Optional[t.Type[BaseException]] = None,
|
exc_type: t.Optional[t.Type[BaseException]] = None,
|
||||||
exc_value: t.Optional[BaseException] = None,
|
exc_value: t.Optional[BaseException] = None,
|
||||||
exc_traceback: t.Optional[TracebackType] = None,
|
exc_traceback: t.Optional["TracebackType"] = None,
|
||||||
):
|
) -> bool:
|
||||||
"""Close connection on exit."""
|
"""Close connection on exit."""
|
||||||
if exc_type is not None:
|
if exc_type is not None:
|
||||||
log.error(str(exc_value))
|
log.error(str(exc_value))
|
||||||
self._session.close()
|
self._session.close()
|
||||||
|
if exc_value is not None:
|
||||||
|
raise exc_value
|
||||||
|
return True
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self: "BaseExternal") -> str:
|
||||||
"""Return user friendly representation of instance."""
|
"""Return user friendly representation of instance."""
|
||||||
return make_repr(self)
|
return make_repr(self)
|
||||||
|
|
||||||
def _exception(self, message, exc=None, level="warning", **kwargs):
|
def _exception(
|
||||||
|
self: "BaseExternal",
|
||||||
|
message: str,
|
||||||
|
exc: t.Optional[BaseException] = None,
|
||||||
|
level: "ErrorLevel" = "warning",
|
||||||
|
**kwargs: t.Any,
|
||||||
|
) -> ExternalError:
|
||||||
"""Add stringified exception to message if passed."""
|
"""Add stringified exception to message if passed."""
|
||||||
if exc is not None:
|
if exc is not None:
|
||||||
message = f"{str(message)}: {str(exc)}"
|
message = f"{message!s}: {exc!s}"
|
||||||
|
|
||||||
return ExternalError(message=message, level=level, **kwargs)
|
return ExternalError(message=message, level=level, **kwargs)
|
||||||
|
|
||||||
def _parse_response(self, response):
|
def _parse_response(self: "BaseExternal", response: httpx.Response) -> t.Any:
|
||||||
if self.parse:
|
if self.parse:
|
||||||
parsed = {}
|
parsed = {}
|
||||||
try:
|
try:
|
||||||
@@ -124,7 +154,7 @@ class BaseExternal:
|
|||||||
parsed = response
|
parsed = response
|
||||||
return parsed
|
return parsed
|
||||||
|
|
||||||
def _test(self):
|
def _test(self: "BaseExternal") -> bool:
|
||||||
"""Open a low-level connection to the base URL to ensure its port is open."""
|
"""Open a low-level connection to the base URL to ensure its port is open."""
|
||||||
log.debug("Testing connection to {}", self.base_url)
|
log.debug("Testing connection to {}", self.base_url)
|
||||||
|
|
||||||
@@ -146,15 +176,17 @@ class BaseExternal:
|
|||||||
|
|
||||||
except gaierror as err:
|
except gaierror as err:
|
||||||
# Raised if the target isn't listening on the port
|
# Raised if the target isn't listening on the port
|
||||||
raise self._exception(f"{self.name} appears to be unreachable", err) from None
|
raise self._exception(
|
||||||
|
f"{self.name!r} appears to be unreachable at {self.base_url!r}", err
|
||||||
|
) from None
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def _atest(self):
|
async def _atest(self: "BaseExternal") -> bool:
|
||||||
"""Open a low-level connection to the base URL to ensure its port is open."""
|
"""Open a low-level connection to the base URL to ensure its port is open."""
|
||||||
return self._test()
|
return self._test()
|
||||||
|
|
||||||
def _build_request(self, **kwargs):
|
def _build_request(self: "BaseExternal", **kwargs: t.Any) -> t.Dict[str, t.Any]:
|
||||||
"""Process requests parameters into structure usable by http library."""
|
"""Process requests parameters into structure usable by http library."""
|
||||||
# Standard Library
|
# Standard Library
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
@@ -212,16 +244,16 @@ class BaseExternal:
|
|||||||
return request
|
return request
|
||||||
|
|
||||||
async def _arequest( # noqa: C901
|
async def _arequest( # noqa: C901
|
||||||
self,
|
self: "BaseExternal",
|
||||||
method,
|
method: HttpMethod,
|
||||||
endpoint,
|
endpoint: str,
|
||||||
item=None,
|
item: t.Union[str, int, None] = None,
|
||||||
headers=None,
|
headers: t.Dict[str, str] = None,
|
||||||
params=None,
|
params: t.Dict[str, JsonValue[Primitives]] = None,
|
||||||
data=None,
|
data: t.Optional[t.Any] = None,
|
||||||
timeout=None,
|
timeout: t.Optional[int] = None,
|
||||||
response_required=False,
|
response_required: bool = False,
|
||||||
):
|
) -> t.Any:
|
||||||
"""Run HTTP POST operation."""
|
"""Run HTTP POST operation."""
|
||||||
request = self._build_request(
|
request = self._build_request(
|
||||||
method=method,
|
method=method,
|
||||||
@@ -249,35 +281,35 @@ class BaseExternal:
|
|||||||
|
|
||||||
return self._parse_response(response)
|
return self._parse_response(response)
|
||||||
|
|
||||||
async def _aget(self, endpoint, **kwargs):
|
async def _aget(self: "BaseExternal", endpoint: str, **kwargs: t.Any) -> t.Any:
|
||||||
return await self._arequest(method="GET", endpoint=endpoint, **kwargs)
|
return await self._arequest(method="GET", endpoint=endpoint, **kwargs)
|
||||||
|
|
||||||
async def _apost(self, endpoint, **kwargs):
|
async def _apost(self: "BaseExternal", endpoint: str, **kwargs: t.Any) -> t.Any:
|
||||||
return await self._arequest(method="POST", endpoint=endpoint, **kwargs)
|
return await self._arequest(method="POST", endpoint=endpoint, **kwargs)
|
||||||
|
|
||||||
async def _aput(self, endpoint, **kwargs):
|
async def _aput(self: "BaseExternal", endpoint: str, **kwargs: t.Any) -> t.Any:
|
||||||
return await self._arequest(method="PUT", endpoint=endpoint, **kwargs)
|
return await self._arequest(method="PUT", endpoint=endpoint, **kwargs)
|
||||||
|
|
||||||
async def _adelete(self, endpoint, **kwargs):
|
async def _adelete(self: "BaseExternal", endpoint: str, **kwargs: t.Any) -> t.Any:
|
||||||
return await self._arequest(method="DELETE", endpoint=endpoint, **kwargs)
|
return await self._arequest(method="DELETE", endpoint=endpoint, **kwargs)
|
||||||
|
|
||||||
async def _apatch(self, endpoint, **kwargs):
|
async def _apatch(self: "BaseExternal", endpoint: str, **kwargs: t.Any) -> t.Any:
|
||||||
return await self._arequest(method="PATCH", endpoint=endpoint, **kwargs)
|
return await self._arequest(method="PATCH", endpoint=endpoint, **kwargs)
|
||||||
|
|
||||||
async def _ahead(self, endpoint, **kwargs):
|
async def _ahead(self: "BaseExternal", endpoint: str, **kwargs: t.Any) -> t.Any:
|
||||||
return await self._arequest(method="HEAD", endpoint=endpoint, **kwargs)
|
return await self._arequest(method="HEAD", endpoint=endpoint, **kwargs)
|
||||||
|
|
||||||
def _request( # noqa: C901
|
def _request( # noqa: C901
|
||||||
self,
|
self: "BaseExternal",
|
||||||
method,
|
method: HttpMethod,
|
||||||
endpoint,
|
endpoint: str,
|
||||||
item=None,
|
item: t.Union[str, int, None] = None,
|
||||||
headers=None,
|
headers: t.Dict[str, str] = None,
|
||||||
params=None,
|
params: t.Dict[str, JsonValue[Primitives]] = None,
|
||||||
data=None,
|
data: t.Optional[t.Any] = None,
|
||||||
timeout=None,
|
timeout: t.Optional[int] = None,
|
||||||
response_required=False,
|
response_required: bool = False,
|
||||||
):
|
) -> t.Any:
|
||||||
"""Run HTTP POST operation."""
|
"""Run HTTP POST operation."""
|
||||||
request = self._build_request(
|
request = self._build_request(
|
||||||
method=method,
|
method=method,
|
||||||
@@ -305,20 +337,20 @@ class BaseExternal:
|
|||||||
|
|
||||||
return self._parse_response(response)
|
return self._parse_response(response)
|
||||||
|
|
||||||
def _get(self, endpoint, **kwargs):
|
def _get(self: "BaseExternal", endpoint: str, **kwargs: t.Any) -> t.Any:
|
||||||
return self._request(method="GET", endpoint=endpoint, **kwargs)
|
return self._request(method="GET", endpoint=endpoint, **kwargs)
|
||||||
|
|
||||||
def _post(self, endpoint, **kwargs):
|
def _post(self: "BaseExternal", endpoint: str, **kwargs: t.Any) -> t.Any:
|
||||||
return self._request(method="POST", endpoint=endpoint, **kwargs)
|
return self._request(method="POST", endpoint=endpoint, **kwargs)
|
||||||
|
|
||||||
def _put(self, endpoint, **kwargs):
|
def _put(self: "BaseExternal", endpoint: str, **kwargs: t.Any) -> t.Any:
|
||||||
return self._request(method="PUT", endpoint=endpoint, **kwargs)
|
return self._request(method="PUT", endpoint=endpoint, **kwargs)
|
||||||
|
|
||||||
def _delete(self, endpoint, **kwargs):
|
def _delete(self: "BaseExternal", endpoint: str, **kwargs: t.Any) -> t.Any:
|
||||||
return self._request(method="DELETE", endpoint=endpoint, **kwargs)
|
return self._request(method="DELETE", endpoint=endpoint, **kwargs)
|
||||||
|
|
||||||
def _patch(self, endpoint, **kwargs):
|
def _patch(self: "BaseExternal", endpoint: str, **kwargs: t.Any) -> t.Any:
|
||||||
return self._request(method="PATCH", endpoint=endpoint, **kwargs)
|
return self._request(method="PATCH", endpoint=endpoint, **kwargs)
|
||||||
|
|
||||||
def _head(self, endpoint, **kwargs):
|
def _head(self: "BaseExternal", endpoint: str, **kwargs: t.Any) -> t.Any:
|
||||||
return self._request(method="HEAD", endpoint=endpoint, **kwargs)
|
return self._request(method="HEAD", endpoint=endpoint, **kwargs)
|
||||||
|
|||||||
15
hyperglass/external/generic.py
vendored
15
hyperglass/external/generic.py
vendored
@@ -1,20 +1,29 @@
|
|||||||
"""Session handler for Generic HTTP API endpoint."""
|
"""Session handler for Generic HTTP API endpoint."""
|
||||||
|
|
||||||
|
# Standard Library
|
||||||
|
import typing as t
|
||||||
|
|
||||||
# Project
|
# Project
|
||||||
from hyperglass.log import log
|
from hyperglass.log import log
|
||||||
from hyperglass.external._base import BaseExternal
|
|
||||||
from hyperglass.models.webhook import Webhook
|
from hyperglass.models.webhook import Webhook
|
||||||
|
|
||||||
|
# Local
|
||||||
|
from ._base import BaseExternal
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
# Project
|
||||||
|
from hyperglass.models.config.logging import Http
|
||||||
|
|
||||||
|
|
||||||
class GenericHook(BaseExternal, name="Generic"):
|
class GenericHook(BaseExternal, name="Generic"):
|
||||||
"""Slack session handler."""
|
"""Slack session handler."""
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self: "GenericHook", config: "Http") -> None:
|
||||||
"""Initialize external base class with http connection details."""
|
"""Initialize external base class with http connection details."""
|
||||||
|
|
||||||
super().__init__(base_url=f"{config.host.scheme}://{config.host.host}", config=config)
|
super().__init__(base_url=f"{config.host.scheme}://{config.host.host}", config=config)
|
||||||
|
|
||||||
async def send(self, query):
|
async def send(self: "GenericHook", query: t.Dict[str, t.Any]):
|
||||||
"""Send an incoming webhook to http endpoint."""
|
"""Send an incoming webhook to http endpoint."""
|
||||||
|
|
||||||
payload = Webhook(**query)
|
payload = Webhook(**query)
|
||||||
|
|||||||
9
hyperglass/external/msteams.py
vendored
9
hyperglass/external/msteams.py
vendored
@@ -1,20 +1,25 @@
|
|||||||
"""Session handler for Microsoft Teams API."""
|
"""Session handler for Microsoft Teams API."""
|
||||||
|
|
||||||
|
import typing as t
|
||||||
|
|
||||||
# Project
|
# Project
|
||||||
from hyperglass.log import log
|
from hyperglass.log import log
|
||||||
from hyperglass.external._base import BaseExternal
|
from hyperglass.external._base import BaseExternal
|
||||||
from hyperglass.models.webhook import Webhook
|
from hyperglass.models.webhook import Webhook
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
from hyperglass.models.config.logging import Http
|
||||||
|
|
||||||
|
|
||||||
class MSTeams(BaseExternal, name="MSTeams"):
|
class MSTeams(BaseExternal, name="MSTeams"):
|
||||||
"""Microsoft Teams session handler."""
|
"""Microsoft Teams session handler."""
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self: "MSTeams", config: "Http") -> None:
|
||||||
"""Initialize external base class with Microsoft Teams connection details."""
|
"""Initialize external base class with Microsoft Teams connection details."""
|
||||||
|
|
||||||
super().__init__(base_url="https://outlook.office.com", config=config, parse=False)
|
super().__init__(base_url="https://outlook.office.com", config=config, parse=False)
|
||||||
|
|
||||||
async def send(self, query):
|
async def send(self: "MSTeams", query: t.Dict[str, t.Any]):
|
||||||
"""Send an incoming webhook to Microsoft Teams."""
|
"""Send an incoming webhook to Microsoft Teams."""
|
||||||
|
|
||||||
payload = Webhook(**query)
|
payload = Webhook(**query)
|
||||||
|
|||||||
11
hyperglass/external/slack.py
vendored
11
hyperglass/external/slack.py
vendored
@@ -1,20 +1,27 @@
|
|||||||
"""Session handler for Slack API."""
|
"""Session handler for Slack API."""
|
||||||
|
|
||||||
|
# Standard Library
|
||||||
|
import typing as t
|
||||||
|
|
||||||
# Project
|
# Project
|
||||||
from hyperglass.log import log
|
from hyperglass.log import log
|
||||||
from hyperglass.external._base import BaseExternal
|
from hyperglass.external._base import BaseExternal
|
||||||
from hyperglass.models.webhook import Webhook
|
from hyperglass.models.webhook import Webhook
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
# Project
|
||||||
|
from hyperglass.models.config.logging import Http
|
||||||
|
|
||||||
|
|
||||||
class SlackHook(BaseExternal, name="Slack"):
|
class SlackHook(BaseExternal, name="Slack"):
|
||||||
"""Slack session handler."""
|
"""Slack session handler."""
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self: "SlackHook", config: "Http") -> None:
|
||||||
"""Initialize external base class with Slack connection details."""
|
"""Initialize external base class with Slack connection details."""
|
||||||
|
|
||||||
super().__init__(base_url="https://hooks.slack.com", config=config, parse=False)
|
super().__init__(base_url="https://hooks.slack.com", config=config, parse=False)
|
||||||
|
|
||||||
async def send(self, query):
|
async def send(self: "SlackHook", query: t.Dict[str, t.Any]):
|
||||||
"""Send an incoming webhook to Slack."""
|
"""Send an incoming webhook to Slack."""
|
||||||
|
|
||||||
payload = Webhook(**query)
|
payload = Webhook(**query)
|
||||||
|
|||||||
49
hyperglass/external/tests/test_base.py
vendored
Normal file
49
hyperglass/external/tests/test_base.py
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
"""Test external http client."""
|
||||||
|
# Standard Library
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
# Third Party
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
# Project
|
||||||
|
from hyperglass.exceptions.private import ExternalError
|
||||||
|
from hyperglass.models.config.logging import Http
|
||||||
|
|
||||||
|
# Local
|
||||||
|
from .._base import BaseExternal
|
||||||
|
|
||||||
|
config = Http(provider="generic", host="https://httpbin.org")
|
||||||
|
|
||||||
|
|
||||||
|
def test_base_external_sync():
|
||||||
|
with BaseExternal(base_url="https://httpbin.org", config=config) as client:
|
||||||
|
res1 = client._get("/get")
|
||||||
|
res2 = client._get("/get", params={"key": "value"})
|
||||||
|
res3 = client._post("/post", data={"strkey": "value", "intkey": 1})
|
||||||
|
assert res1["url"] == "https://httpbin.org/get"
|
||||||
|
assert res2["args"].get("key") == "value"
|
||||||
|
assert res3["json"].get("strkey") == "value"
|
||||||
|
assert res3["json"].get("intkey") == 1
|
||||||
|
|
||||||
|
with pytest.raises(ExternalError):
|
||||||
|
with BaseExternal(base_url="https://httpbin.org", config=config, timeout=2) as client:
|
||||||
|
client._get("/delay/4")
|
||||||
|
|
||||||
|
|
||||||
|
async def _run_test_base_external_async():
|
||||||
|
async with BaseExternal(base_url="https://httpbin.org", config=config) as client:
|
||||||
|
res1 = await client._aget("/get")
|
||||||
|
res2 = await client._aget("/get", params={"key": "value"})
|
||||||
|
res3 = await client._apost("/post", data={"strkey": "value", "intkey": 1})
|
||||||
|
assert res1["url"] == "https://httpbin.org/get"
|
||||||
|
assert res2["args"].get("key") == "value"
|
||||||
|
assert res3["json"].get("strkey") == "value"
|
||||||
|
assert res3["json"].get("intkey") == 1
|
||||||
|
|
||||||
|
with pytest.raises(ExternalError):
|
||||||
|
async with BaseExternal(base_url="https://httpbin.org", config=config, timeout=2) as client:
|
||||||
|
await client._get("/delay/4")
|
||||||
|
|
||||||
|
|
||||||
|
def test_base_external_async():
|
||||||
|
asyncio.run(_run_test_base_external_async())
|
||||||
19
hyperglass/external/webhooks.py
vendored
19
hyperglass/external/webhooks.py
vendored
@@ -1,12 +1,21 @@
|
|||||||
"""Convenience functions for webhooks."""
|
"""Convenience functions for webhooks."""
|
||||||
|
|
||||||
|
# Standard Library
|
||||||
|
import typing as t
|
||||||
|
|
||||||
# Project
|
# Project
|
||||||
from hyperglass.external._base import BaseExternal
|
|
||||||
from hyperglass.external.slack import SlackHook
|
|
||||||
from hyperglass.external.generic import GenericHook
|
|
||||||
from hyperglass.external.msteams import MSTeams
|
|
||||||
from hyperglass.exceptions.private import UnsupportedError
|
from hyperglass.exceptions.private import UnsupportedError
|
||||||
|
|
||||||
|
# Local
|
||||||
|
from ._base import BaseExternal
|
||||||
|
from .slack import SlackHook
|
||||||
|
from .generic import GenericHook
|
||||||
|
from .msteams import MSTeams
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
# Project
|
||||||
|
from hyperglass.models.config.logging import Http
|
||||||
|
|
||||||
PROVIDER_MAP = {
|
PROVIDER_MAP = {
|
||||||
"generic": GenericHook,
|
"generic": GenericHook,
|
||||||
"msteams": MSTeams,
|
"msteams": MSTeams,
|
||||||
@@ -17,7 +26,7 @@ PROVIDER_MAP = {
|
|||||||
class Webhook(BaseExternal):
|
class Webhook(BaseExternal):
|
||||||
"""Get webhook for provider name."""
|
"""Get webhook for provider name."""
|
||||||
|
|
||||||
def __new__(cls, config):
|
def __new__(cls: "Webhook", config: "Http") -> "BaseExternal":
|
||||||
"""Return instance for correct provider handler."""
|
"""Return instance for correct provider handler."""
|
||||||
try:
|
try:
|
||||||
provider_class = PROVIDER_MAP[config.provider]
|
provider_class = PROVIDER_MAP[config.provider]
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ import typing as t
|
|||||||
from pydantic import StrictInt, StrictFloat
|
from pydantic import StrictInt, StrictFloat
|
||||||
|
|
||||||
IntFloat = t.TypeVar("IntFloat", StrictInt, StrictFloat)
|
IntFloat = t.TypeVar("IntFloat", StrictInt, StrictFloat)
|
||||||
|
J = t.TypeVar("J")
|
||||||
|
|
||||||
SupportedDriver = t.Literal["netmiko", "hyperglass_agent"]
|
SupportedDriver = t.Literal["netmiko", "hyperglass_agent"]
|
||||||
HttpAuthMode = t.Literal["basic", "api_key"]
|
HttpAuthMode = t.Literal["basic", "api_key"]
|
||||||
HttpProvider = t.Literal["msteams", "slack", "generic"]
|
HttpProvider = t.Literal["msteams", "slack", "generic"]
|
||||||
LogFormat = t.Literal["text", "json"]
|
LogFormat = t.Literal["text", "json"]
|
||||||
Primitives = t.Union[None, float, int, bool, str]
|
Primitives = t.Union[None, float, int, bool, str]
|
||||||
|
JsonValue = t.Union[J, t.Sequence[J], t.Dict[str, J]]
|
||||||
|
|
||||||
|
|
||||||
class AnyUri(str):
|
class AnyUri(str):
|
||||||
|
|||||||
Reference in New Issue
Block a user