Skip to content

Commit 7f1e39b

Browse files
committed
✨ resilience system v1
1 parent 14b3cb2 commit 7f1e39b

5 files changed

Lines changed: 1045 additions & 38 deletions

File tree

dymoapi/dymoapi.py

Lines changed: 99 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,29 @@
33
from .config import set_base_url, get_base_url
44
import dymoapi.response_models as response_models
55
from .services.autoupload import check_for_updates
6+
from .resilience import ResilienceManager, FallbackDataGenerator
7+
from requests import Session
68

79
logging.basicConfig(level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s')
810

911
class DymoAPI:
1012
def __init__(self, config={}):
1113
"""
12-
This is the main class to interact with the Dymo API. It should be
13-
instantiated with the root API key and the API key. The root API key is
14-
used to fetch the tokens and the API key is used to authenticate the
14+
This is the main class to interact with Dymo API. It should be
15+
instantiated with root API key and API key. The root API key is
16+
used to fetch the tokens and the API key is used to authenticate
1517
requests.
1618
1719
Args:
1820
- options (dict, optional): Options to create the DymoAPI instance.
1921
- options["root_api_key"] (str, optional): The root API key. Defaults to None.
2022
- options["api_key"] (str, optional): The API key. Defaults to None.
2123
- options["base_url"] (str, optional): Whether to use a local server instead of
22-
the cloud server. Defaults to False.
24+
cloud server. Defaults to False.
2325
- options["server_email_config"] (dict, optional):
2426
The server email config. Defaults to None.
2527
- options["rules"] (dict, optional): The rules config. Defaults to None.
28+
- options["resilience"] (dict, optional): The resilience config. Defaults to None.
2629
2730
Example:
2831
dymo_api = DymoAPI({
@@ -44,6 +47,29 @@ def __init__(self, config={}):
4447

4548
set_base_url(self.base_url)
4649
self.base_url = get_base_url()
50+
51+
# Initialize resilience system
52+
resilience = config.get("resilience", {})
53+
client_id = self.api_key or self.root_api_key or "anonymous"
54+
self.resilience = ResilienceManager(client_id=client_id)
55+
if resilience:
56+
self.resilience.config.fallback_enabled = resilience.get("fallback_enabled", False)
57+
self.resilience.config.retry_attempts = resilience.get("retry_attempts", 2)
58+
self.resilience.config.retry_delay = resilience.get("retry_delay", 1000)
59+
60+
# Initialize requests session
61+
self.session = Session()
62+
self.session.headers.update({
63+
"User-Agent": "DymoAPISDK/1.0.0",
64+
"X-Dymo-SDK-Env": "Python",
65+
"X-Dymo-SDK-Version": "0.0.62"
66+
})
67+
68+
if self.root_api_key or self.api_key:
69+
self.session.headers.update({
70+
"Authorization": f"Bearer {self.root_api_key or self.api_key}"
71+
})
72+
4773
check_for_updates()
4874

4975
def _get_function(self, module_name, function_name="main"):
@@ -54,9 +80,9 @@ def _get_function(self, module_name, function_name="main"):
5480

5581
def is_valid_data(self, data: response_models.Validator) -> response_models.DataVerifierResponse:
5682
"""
57-
Validates the given data against the configured validation settings.
83+
Validates given data against the configured validation settings.
5884
59-
This method requires either the root API key or the API key to be set.
85+
This method requires either root API key or the API key to be set.
6086
If neither is set, it will throw an error.
6187
6288
Args:
@@ -79,31 +105,46 @@ def is_valid_data(self, data: response_models.Validator) -> response_models.Data
79105
80106
[Documentation](https://docs.tpeoficial.com/docs/dymo-api/private/data-verifier)
81107
"""
82-
response = self._get_function("private", "is_valid_data")(data)
83-
if response.get("ip",{}).get("as"):
84-
response["ip"]["_as"] = response["ip"]["as"]
85-
response["ip"]["_class"] = response["ip"]["class"]
86-
response["ip"].pop("as")
87-
response["ip"].pop("class")
88-
return response_models.DataVerifierResponse(**response)
108+
fallback_data = FallbackDataGenerator.generate_fallback_data("isValidData", data.dict())
109+
110+
try:
111+
response = self.resilience.execute_with_resilience(
112+
session=self.session,
113+
method="POST",
114+
url=f"{self.base_url}/v1/private/secure/verify",
115+
json=data.dict() if hasattr(data, 'dict') else data,
116+
fallback_data=fallback_data if self.resilience.config.fallback_enabled else None
117+
)
118+
119+
if response.get("ip",{}).get("as"):
120+
response["ip"]["_as"] = response["ip"]["as"]
121+
response["ip"]["_class"] = response["ip"]["class"]
122+
response["ip"].pop("as")
123+
response["ip"].pop("class")
124+
125+
return response_models.DataVerifierResponse(**response)
126+
except Exception as e:
127+
if self.resilience.config.fallback_enabled:
128+
return response_models.DataVerifierResponse(**fallback_data)
129+
raise e
89130

90131
def is_valid_email(self, email: str, rules: dict | None = None) -> bool:
91132
"""
92-
Wrapper for the private email validation function.
133+
Wrapper for private email validation function.
93134
94-
Calls the internal `is_valid_email` function with the provided email and deny rules,
95-
returning True or False according to the validation result.
135+
Calls internal `is_valid_email` function with provided email and deny rules,
136+
returning True or False according to validation result.
96137
97138
Args:
98139
email (str): The email address to validate.
99140
rules (dict, optional): Validation rules object with key "deny" (list of deny rules).
100141
⚠️ Some deny rules are PREMIUM: "NO_MX_RECORDS", "HIGH_RISK_SCORE", "NO_REACHABLE".
101142
102143
Returns:
103-
bool: True if the email passes validation, False otherwise.
144+
bool: True if email passes validation, False otherwise.
104145
105146
Raises:
106-
APIError: If the underlying validation function fails or the API key is missing.
147+
APIError: If underlying validation function fails or API key is missing.
107148
108149
Example:
109150
>>> valid = dymoClient.is_valid_email(
@@ -115,13 +156,20 @@ def is_valid_email(self, email: str, rules: dict | None = None) -> bool:
115156
https://docs.tpeoficial.com/docs/dymo-api/private/email-validation
116157
"""
117158
rules_to_use = rules or self.rules.get("email")
118-
return self._get_function("private", "is_valid_email")(email, rules_to_use)
159+
fallback_data = FallbackDataGenerator.generate_fallback_data("isValidEmail", email)
160+
161+
try:
162+
return self._get_function("private", "is_valid_email")(email, rules_to_use)
163+
except Exception as e:
164+
if self.resilience.config.fallback_enabled:
165+
return fallback_data.get("allow", False)
166+
raise e
119167

120168
def is_valid_ip(self, ip: str, rules: dict | None = None) -> bool:
121169
"""
122-
Wrapper for the private IP validation function.
170+
Wrapper for private IP validation function.
123171
124-
Calls the internal `is_valid_ip` function with the provided IP and deny rules,
172+
Calls internal `is_valid_ip` function with the provided IP and deny rules,
125173
returning True or False according to the validation result.
126174
127175
Args:
@@ -130,10 +178,10 @@ def is_valid_ip(self, ip: str, rules: dict | None = None) -> bool:
130178
⚠️ Some deny rules are PREMIUM: "TOR_NETWORK", "HIGH_RISK_SCORE".
131179
132180
Returns:
133-
bool: True if the IP passes validation, False otherwise.
181+
bool: True if IP passes validation, False otherwise.
134182
135183
Raises:
136-
APIError: If the underlying validation function fails or the API key is missing.
184+
APIError: If the underlying validation function fails or API key is missing.
137185
138186
Example:
139187
>>> valid = dymoClient.is_valid_ip(
@@ -145,13 +193,20 @@ def is_valid_ip(self, ip: str, rules: dict | None = None) -> bool:
145193
https://docs.tpeoficial.com/docs/dymo-api/private/ip-validation
146194
"""
147195
rules_to_use = rules or self.rules.get("ip")
148-
return self._get_function("private", "is_valid_ip")(ip, rules_to_use)
196+
fallback_data = FallbackDataGenerator.generate_fallback_data("isValidIP", ip)
197+
198+
try:
199+
return self._get_function("private", "is_valid_ip")(ip, rules_to_use)
200+
except Exception as e:
201+
if self.resilience.config.fallback_enabled:
202+
return fallback_data.get("allow", False)
203+
raise e
149204

150205
def is_valid_phone(self, phone: str, rules: dict | None = None) -> bool:
151206
"""
152-
Wrapper for the private phone validation function.
207+
Wrapper for private phone validation function.
153208
154-
Calls the internal `is_valid_phone` function with the provided phone and deny rules,
209+
Calls internal `is_valid_phone` function with the provided phone and deny rules,
155210
returning True or False according to the validation result.
156211
157212
Args:
@@ -160,10 +215,10 @@ def is_valid_phone(self, phone: str, rules: dict | None = None) -> bool:
160215
⚠️ Some deny rules are PREMIUM: "HIGH_RISK_SCORE".
161216
162217
Returns:
163-
bool: True if the phone passes validation, False otherwise.
218+
bool: True if phone passes validation, False otherwise.
164219
165220
Raises:
166-
APIError: If the underlying validation function fails or the API key is missing.
221+
APIError: If the underlying validation function fails or API key is missing.
167222
168223
Example:
169224
>>> valid = dymoClient.is_valid_phone(
@@ -175,8 +230,15 @@ def is_valid_phone(self, phone: str, rules: dict | None = None) -> bool:
175230
https://docs.tpeoficial.com/docs/dymo-api/private/phone-validation
176231
"""
177232
rules_to_use = rules or self.rules.get("phone")
178-
return self._get_function("private", "is_valid_phone")(phone, rules_to_use)
179-
233+
fallback_data = FallbackDataGenerator.generate_fallback_data("isValidPhone", phone)
234+
235+
try:
236+
return self._get_function("private", "is_valid_phone")(phone, rules_to_use)
237+
except Exception as e:
238+
if self.resilience.config.fallback_enabled:
239+
return fallback_data.get("allow", False)
240+
raise e
241+
180242
def send_email(self, data: response_models.EmailStatus) -> response_models.SendEmailResponse:
181243
"""
182244
Sends an email using the configured email client settings.
@@ -190,7 +252,7 @@ def send_email(self, data: response_models.EmailStatus) -> response_models.SendE
190252
- data["to"] (str): The email address to which the email will be sent.
191253
- data["subject"] (str): The subject of the email.
192254
- data["html"] (str, optional): The HTML content of the email.
193-
- data["react"] (Object, optional): The React component to be rendered as the email content.
255+
- data["react"] (Object, optional): The React component to be rendered as email content.
194256
- data["options"] (dict, optional): Content configuration options.
195257
- data["options"]["priority"] (str, optional): Email priority (default: "normal"). Allowed values: "high", "normal", "low".
196258
- data["options"]["waitToResponse"] (bool, optional): Wait until the email is sent (default: True).
@@ -223,22 +285,22 @@ def get_random(self, data: response_models.SRNG) -> response_models.SRNGResponse
223285
- data (response_models.SRNG): The data for random number generation.
224286
- data["min"] (int/float): The minimum value of the range.
225287
- data["max"] (int/float): The maximum value of the range.
226-
- data["quantity"] (int, optional): The number of random values to generate. Defaults to 1.
288+
- data["quantity"] (int, optional): The number of random values to generate. Defaults to1.
227289
228290
Returns:
229291
Promise[response_models.SRNGResponse]: A promise that resolves to the response from the server.
230292
231293
Raises:
232294
Exception: An error will be thrown if there is an issue with the random number
233295
generation process.
234-
296+
235297
[Documentation](https://docs.tpeoficial.com/docs/dymo-api/private/secure-random-number-generator)
236298
"""
237299
return response_models.DataVerifierResponse(**self._get_function("private", "get_random")({**data}))
238300

239301
def extract_with_textly(self, data: response_models.Textly) -> response_models.TextlyResponse:
240302
"""
241-
Extracts structured data from a given text using the Textly endpoint.
303+
Extracts structured data from a given text using Textly endpoint.
242304
243305
This method requires a valid private API token to authenticate the request.
244306
The input must include both the text to process and a format schema describing
@@ -310,7 +372,7 @@ def is_valid_pwd(self, data: response_models.IsValidPwdData) -> response_models.
310372
"""
311373
Validates a password based on the given parameters.
312374
313-
This method requires the password to be provided in the data object.
375+
This method requires a password to be provided in the data object.
314376
If the password is not provided, an error will be thrown. The method
315377
will validate the password against the following rules:
316378
- The password must be at least `data["min"]` characters long (default 8).
@@ -323,8 +385,8 @@ def is_valid_pwd(self, data: response_models.IsValidPwdData) -> response_models.
323385
324386
Args:
325387
- data (response_models.IsValidPwdData): The data for password validation.
326-
- data["min"] (int, optional): Minimum length of the password. Defaults to 8.
327-
- data["max"] (int, optional): Maximum length of the password. Defaults to 32.
388+
- data["min"] (int, optional): Minimum length of password. Defaults to 8.
389+
- data["max"] (int, optional): Maximum length of password. Defaults to 32.
328390
- data["email"] (str, optional): Optional email associated with the password.
329391
- data["password"] (str): The password to be validated.
330392
- data["bannedWords"] (str or list[str], optional): The list of banned words that the password must not contain.

0 commit comments

Comments
 (0)