From b3e97ee7ec8bc43b95f7e31e47d71318e02fb91c Mon Sep 17 00:00:00 2001 From: Vignesh Aigal Date: Fri, 15 Nov 2024 11:38:10 -0800 Subject: [PATCH] Add bounceban provider --- .../assets/images/bounceban-icon-light.png | Bin 0 -> 5348 bytes .../src/components/apps/ProviderIcon.jsx | 4 + .../providers/bounceban/__init__.py | 13 +++ .../processors/providers/bounceban/check.py | 97 +++++++++++++++++ .../processors/providers/bounceban/verify.py | 101 ++++++++++++++++++ .../providers/bounceban/verify_status.py | 96 +++++++++++++++++ 6 files changed, 311 insertions(+) create mode 100644 llmstack/client/src/assets/images/bounceban-icon-light.png create mode 100644 llmstack/processors/providers/bounceban/__init__.py create mode 100644 llmstack/processors/providers/bounceban/check.py create mode 100644 llmstack/processors/providers/bounceban/verify.py create mode 100644 llmstack/processors/providers/bounceban/verify_status.py diff --git a/llmstack/client/src/assets/images/bounceban-icon-light.png b/llmstack/client/src/assets/images/bounceban-icon-light.png new file mode 100644 index 0000000000000000000000000000000000000000..978f1db2aa8a6d17975ebdf8483bfb5e4a522be1 GIT binary patch literal 5348 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 { return isActive ? weaviateIcon_dark : weaviateIcon_light; case "singlestore": return isActive ? singlestoreIcon_dark : singlestoreIcon_light; + case "bounceban": + return isActive ? bouncbanIcon_dark : bouncbanIcon_light; default: return promptlyIcon_light; } diff --git a/llmstack/processors/providers/bounceban/__init__.py b/llmstack/processors/providers/bounceban/__init__.py new file mode 100644 index 00000000000..5a0cf4cc210 --- /dev/null +++ b/llmstack/processors/providers/bounceban/__init__.py @@ -0,0 +1,13 @@ +from pydantic import Field + +from llmstack.processors.providers.config import ProviderConfig + + +class BouncebanProviderConfig(ProviderConfig): + provider_slug: str = "bounceban" + api_key: str = Field( + title="API Key", + default="", + description="API Key for the BounceBan API", + json_schema_extra={"widget": "password", "advanced_parameter": False}, + ) diff --git a/llmstack/processors/providers/bounceban/check.py b/llmstack/processors/providers/bounceban/check.py new file mode 100644 index 00000000000..cada7b7347e --- /dev/null +++ b/llmstack/processors/providers/bounceban/check.py @@ -0,0 +1,97 @@ +import logging +from typing import Any, Dict, Optional + +from asgiref.sync import async_to_sync +from pydantic import Field + +from llmstack.apps.schemas import OutputTemplate +from llmstack.common.utils.prequests import get +from llmstack.processors.providers.api_processor_interface import ( + ApiProcessorInterface, + ApiProcessorSchema, +) +from llmstack.processors.providers.metrics import MetricType + +logger = logging.getLogger(__name__) + + +class CheckProcessorInput(ApiProcessorSchema): + email: Optional[str] = Field(description="The email to verify", default=None) + domain: Optional[str] = Field(description="The domain to verify", default=None) + + +class CheckProcessorOutput(ApiProcessorSchema): + response: str = Field(description="The response from the API call as a string", default="") + response_json: Optional[Dict[str, Any]] = Field( + description="The response from the API call as a JSON object", default={} + ) + response_objref: Optional[str] = Field(description="The reference to the response object", default=None) + headers: Optional[Dict[str, str]] = Field(description="The headers from the API call", default={}) + code: int = Field(description="The status code from the API call", default=200) + size: int = Field(description="The size of the response from the API call", default=0) + time: float = Field(description="The time it took to get the response from the API call", default=0.0) + + +class CheckProcessorConfiguration(ApiProcessorSchema): + pass + + +class EchoProcessor( + ApiProcessorInterface[CheckProcessorInput, CheckProcessorOutput, CheckProcessorConfiguration], +): + """ + Check basic information for an email or domain. + """ + + @staticmethod + def name() -> str: + return "Check domain or email" + + @staticmethod + def slug() -> str: + return "check" + + @staticmethod + def description() -> str: + return "Check basic information for an email or domain." + + @staticmethod + def provider_slug() -> str: + return "bounceban" + + @classmethod + def get_output_template(cls) -> OutputTemplate | None: + return OutputTemplate( + markdown="{{response}}", + jsonpath="$.response", + ) + + def process(self) -> dict: + provider_config = self.get_provider_config(provider_slug=self.provider_slug(), processor_slug="*") + deployment_config = self.get_provider_config(provider_slug=self.provider_slug(), processor_slug="*") + api_key = deployment_config.api_key + response = get( + url="https://api.bounceban.com/v1/check", + headers={"Authorization": f"{api_key}"}, + params=self._input.model_dump(), + ) + self._usage_data.append( + ( + f"{self.provider_slug()}/*/*/*", + MetricType.API_INVOCATION, + (provider_config.provider_config_source, 1), + ) + ) + async_to_sync(self._output_stream.write)( + CheckProcessorOutput( + response=response.text, + response_json=response.json(), + headers=response.headers, + code=response.status_code, + size=len(response.content), + time=response.elapsed.total_seconds(), + ) + ) + + output = self._output_stream.finalize() + return output diff --git a/llmstack/processors/providers/bounceban/verify.py b/llmstack/processors/providers/bounceban/verify.py new file mode 100644 index 00000000000..4bae33c12d0 --- /dev/null +++ b/llmstack/processors/providers/bounceban/verify.py @@ -0,0 +1,101 @@ +import logging +from typing import Any, Dict, Optional + +from asgiref.sync import async_to_sync +from pydantic import Field + +from llmstack.apps.schemas import OutputTemplate +from llmstack.common.utils.prequests import get +from llmstack.processors.providers.api_processor_interface import ( + ApiProcessorInterface, + ApiProcessorSchema, +) +from llmstack.processors.providers.metrics import MetricType + +logger = logging.getLogger(__name__) + + +class VerifyProcessorInput(ApiProcessorSchema): + email: str = Field(description="The email to verify") + mode: Optional[str] = Field(description="The mode to use for verification", default="regular") + url: Optional[str] = Field( + description="A webhook target URL specified to receive verification result event in real-time through an HTTP POST request.", + default=None, + ) + + +class VerifyProcessorOutput(ApiProcessorSchema): + response: str = Field(description="The response from the API call as a string", default="") + response_json: Optional[Dict[str, Any]] = Field( + description="The response from the API call as a JSON object", default={} + ) + response_objref: Optional[str] = Field(description="The reference to the response object", default=None) + headers: Optional[Dict[str, str]] = Field(description="The headers from the API call", default={}) + code: int = Field(description="The status code from the API call", default=200) + size: int = Field(description="The size of the response from the API call", default=0) + time: float = Field(description="The time it took to get the response from the API call", default=0.0) + + +class VerifyProcessorConfiguration(ApiProcessorSchema): + pass + + +class EchoProcessor( + ApiProcessorInterface[VerifyProcessorInput, VerifyProcessorOutput, VerifyProcessorConfiguration], +): + """ + Single email verification processor + """ + + @staticmethod + def name() -> str: + return "Email Verification" + + @staticmethod + def slug() -> str: + return "verify" + + @staticmethod + def description() -> str: + return "Submit email for verification." + + @staticmethod + def provider_slug() -> str: + return "bounceban" + + @classmethod + def get_output_template(cls) -> OutputTemplate | None: + return OutputTemplate( + markdown="{{response}}", + jsonpath="$.response", + ) + + def process(self) -> dict: + provider_config = self.get_provider_config(provider_slug=self.provider_slug(), processor_slug="*") + deployment_config = self.get_provider_config(provider_slug=self.provider_slug(), processor_slug="*") + api_key = deployment_config.api_key + response = get( + url="https://api.bounceban.com/v1/verify/single", + headers={"Authorization": f"{api_key}"}, + params=self._input.model_dump(), + ) + self._usage_data.append( + ( + f"{self.provider_slug()}/*/*/*", + MetricType.API_INVOCATION, + (provider_config.provider_config_source, 1), + ) + ) + async_to_sync(self._output_stream.write)( + VerifyProcessorOutput( + response=response.text, + response_json=response.json(), + headers=response.headers, + code=response.status_code, + size=len(response.content), + time=response.elapsed.total_seconds(), + ) + ) + + output = self._output_stream.finalize() + return output diff --git a/llmstack/processors/providers/bounceban/verify_status.py b/llmstack/processors/providers/bounceban/verify_status.py new file mode 100644 index 00000000000..5b6a7315f7d --- /dev/null +++ b/llmstack/processors/providers/bounceban/verify_status.py @@ -0,0 +1,96 @@ +import logging +from typing import Any, Dict, Optional + +from asgiref.sync import async_to_sync +from pydantic import Field + +from llmstack.apps.schemas import OutputTemplate +from llmstack.common.utils.prequests import get +from llmstack.processors.providers.api_processor_interface import ( + ApiProcessorInterface, + ApiProcessorSchema, +) +from llmstack.processors.providers.metrics import MetricType + +logger = logging.getLogger(__name__) + + +class VerifyStatusProcessorInput(ApiProcessorSchema): + id: str = Field(description="The unique ID for a single email verification task.") + + +class VerifyStatusProcessorOutput(ApiProcessorSchema): + response: str = Field(description="The response from the API call as a string", default="") + response_json: Optional[Dict[str, Any]] = Field( + description="The response from the API call as a JSON object", default={} + ) + response_objref: Optional[str] = Field(description="The reference to the response object", default=None) + headers: Optional[Dict[str, str]] = Field(description="The headers from the API call", default={}) + code: int = Field(description="The status code from the API call", default=200) + size: int = Field(description="The size of the response from the API call", default=0) + time: float = Field(description="The time it took to get the response from the API call", default=0.0) + + +class VerifyStatusProcessorConfiguration(ApiProcessorSchema): + pass + + +class EchoProcessor( + ApiProcessorInterface[VerifyStatusProcessorInput, VerifyStatusProcessorOutput, VerifyStatusProcessorConfiguration], +): + """ + Single email verification result processor + """ + + @staticmethod + def name() -> str: + return "Email Verification Status" + + @staticmethod + def slug() -> str: + return "verify_status" + + @staticmethod + def description() -> str: + return "Get the verification result for single email verification." + + @staticmethod + def provider_slug() -> str: + return "bounceban" + + @classmethod + def get_output_template(cls) -> OutputTemplate | None: + return OutputTemplate( + markdown="{{response}}", + jsonpath="$.response", + ) + + def process(self) -> dict: + provider_config = self.get_provider_config(provider_slug=self.provider_slug(), processor_slug="*") + deployment_config = self.get_provider_config(provider_slug=self.provider_slug(), processor_slug="*") + api_key = deployment_config.api_key + response = get( + url="https://api.bounceban.com/v1/verify/single/status", + headers={"Authorization": f"{api_key}"}, + params=self._input.model_dump(), + ) + self._usage_data.append( + ( + f"{self.provider_slug()}/*/*/*", + MetricType.API_INVOCATION, + (provider_config.provider_config_source, 1), + ) + ) + async_to_sync(self._output_stream.write)( + VerifyStatusProcessorOutput( + response=response.text, + response_json=response.json(), + headers=response.headers, + code=response.status_code, + size=len(response.content), + time=response.elapsed.total_seconds(), + ) + ) + + output = self._output_stream.finalize() + return output