celitech-sdk 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. celitech/__init__.py +2 -0
  2. celitech/hooks/__init__.py +0 -0
  3. celitech/hooks/hook.py +95 -0
  4. celitech/models/__init__.py +14 -0
  5. celitech/models/base.py +251 -0
  6. celitech/models/create_purchase_ok_response.py +85 -0
  7. celitech/models/create_purchase_request.py +54 -0
  8. celitech/models/edit_purchase_ok_response.py +41 -0
  9. celitech/models/edit_purchase_request.py +41 -0
  10. celitech/models/get_esim_device_ok_response.py +41 -0
  11. celitech/models/get_esim_history_ok_response.py +50 -0
  12. celitech/models/get_esim_mac_ok_response.py +39 -0
  13. celitech/models/get_esim_ok_response.py +43 -0
  14. celitech/models/get_purchase_consumption_ok_response.py +17 -0
  15. celitech/models/list_destinations_ok_response.py +38 -0
  16. celitech/models/list_packages_ok_response.py +61 -0
  17. celitech/models/list_purchases_ok_response.py +129 -0
  18. celitech/models/top_up_esim_ok_response.py +82 -0
  19. celitech/models/top_up_esim_request.py +49 -0
  20. celitech/models/utils/cast_models.py +81 -0
  21. celitech/models/utils/json_map.py +80 -0
  22. celitech/net/__init__.py +0 -0
  23. celitech/net/environment/__init__.py +1 -0
  24. celitech/net/environment/environment.py +11 -0
  25. celitech/net/headers/__init__.py +0 -0
  26. celitech/net/headers/base_header.py +9 -0
  27. celitech/net/request_chain/__init__.py +0 -0
  28. celitech/net/request_chain/handlers/__init__.py +0 -0
  29. celitech/net/request_chain/handlers/base_handler.py +39 -0
  30. celitech/net/request_chain/handlers/hook_handler.py +47 -0
  31. celitech/net/request_chain/handlers/http_handler.py +80 -0
  32. celitech/net/request_chain/handlers/retry_handler.py +65 -0
  33. celitech/net/request_chain/request_chain.py +57 -0
  34. celitech/net/transport/__init__.py +0 -0
  35. celitech/net/transport/request.py +89 -0
  36. celitech/net/transport/request_error.py +53 -0
  37. celitech/net/transport/response.py +57 -0
  38. celitech/net/transport/serializer.py +249 -0
  39. celitech/net/transport/utils.py +28 -0
  40. celitech/sdk.py +30 -0
  41. celitech/services/__init__.py +0 -0
  42. celitech/services/destinations.py +29 -0
  43. celitech/services/e_sim.py +121 -0
  44. celitech/services/packages.py +72 -0
  45. celitech/services/purchases.py +189 -0
  46. celitech/services/utils/base_service.py +63 -0
  47. celitech/services/utils/default_headers.py +57 -0
  48. celitech/services/utils/validator.py +170 -0
  49. celitech_sdk-1.0.0.dist-info/LICENSE +21 -0
  50. celitech_sdk-1.0.0.dist-info/METADATA +482 -0
  51. celitech_sdk-1.0.0.dist-info/RECORD +53 -0
  52. celitech_sdk-1.0.0.dist-info/WHEEL +5 -0
  53. celitech_sdk-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,39 @@
1
+ from typing import Optional, Tuple
2
+ from ...transport.request import Request
3
+ from ...transport.response import Response
4
+ from ...transport.request_error import RequestError
5
+
6
+
7
+ class BaseHandler:
8
+ """
9
+ A class for sending the request through the chain of handlers.
10
+
11
+ :ivar BaseHandler _next_handler: The next handler in the chain.
12
+ """
13
+
14
+ def __init__(self):
15
+ """
16
+ Initialize a new instance of BaseHandler.
17
+ """
18
+ self._next_handler = None
19
+
20
+ def handle(
21
+ self, request: Request
22
+ ) -> Tuple[Optional[Response], Optional[RequestError]]:
23
+ """
24
+ Process the given request and return a response or an error.
25
+ This method must be implemented by all subclasses.
26
+
27
+ :param Request request: The request to handle.
28
+ :return: The response and any error that occurred.
29
+ :rtype: Tuple[Optional[Response], Optional[RequestError]]
30
+ """
31
+ raise NotImplementedError()
32
+
33
+ def set_next(self, handler: "BaseHandler"):
34
+ """
35
+ Set the next handler in the chain.
36
+
37
+ :param BaseHandler handler: The next handler.
38
+ """
39
+ self._next_handler = handler
@@ -0,0 +1,47 @@
1
+ from typing import Optional, Tuple
2
+
3
+
4
+ from .base_handler import BaseHandler
5
+ from ....hooks.hook import CustomHook
6
+ from ...transport.request import Request
7
+ from ...transport.response import Response
8
+ from ...transport.request_error import RequestError
9
+
10
+
11
+ class HookHandler(BaseHandler):
12
+ """
13
+ Handler for calling hooks.
14
+
15
+ :ivar Hook _hook: The hook to be called. This is a placeholder and should be replaced with an instance of the actual hook class.
16
+ """
17
+
18
+ def __init__(self):
19
+ """
20
+ Initialize a new instance of HookHandler.
21
+ """
22
+ super().__init__()
23
+ self._hook = CustomHook()
24
+
25
+ def handle(
26
+ self, request: Request
27
+ ) -> Tuple[Optional[Response], Optional[RequestError]]:
28
+ """
29
+ Call the beforeRequest hook before passing the request to the next handler in the chain.
30
+ Call the afterResponse hook after receiving a response from the next handler in the chain.
31
+ Call the onError hook if an error occurs in the next handler in the chain.
32
+
33
+ :param Request request: The request to handle.
34
+ :return: The response and any error that occurred.
35
+ :rtype: Tuple[Optional[Response], Optional[RequestError]]
36
+ """
37
+ if self._next_handler is None:
38
+ raise RequestError("Handler chain is incomplete")
39
+
40
+ self._hook.before_request(request)
41
+ response, error = self._next_handler.handle(request)
42
+ if error is not None and error.is_http_error:
43
+ self._hook.on_error(error, request, error.response)
44
+ else:
45
+ self._hook.after_response(request, response)
46
+
47
+ return response, error
@@ -0,0 +1,80 @@
1
+ import requests
2
+
3
+ from requests.exceptions import Timeout
4
+ from typing import Optional, Tuple
5
+ from .base_handler import BaseHandler
6
+ from ...transport.request import Request
7
+ from ...transport.response import Response
8
+ from ...transport.request_error import RequestError
9
+
10
+
11
+ class HttpHandler(BaseHandler):
12
+ """
13
+ Handler for making HTTP requests.
14
+ This handler sends the request to the specified URL and returns the response.
15
+
16
+ :ivar int _timeout_in_seconds: The timeout for the HTTP request in seconds.
17
+ """
18
+
19
+ def __init__(self):
20
+ """
21
+ Initialize a new instance of HttpHandler.
22
+ """
23
+ super().__init__()
24
+ self._timeout_in_seconds = 60
25
+
26
+ def handle(
27
+ self, request: Request
28
+ ) -> Tuple[Optional[Response], Optional[RequestError]]:
29
+ """
30
+ Send the request to the specified URL and return the response.
31
+
32
+ :param Request request: The request to send.
33
+ :return: The response and any error that occurred.
34
+ :rtype: Tuple[Optional[Response], Optional[RequestError]]
35
+ """
36
+ try:
37
+ request_args = self._get_request_data(request)
38
+
39
+ result = requests.request(
40
+ request.method,
41
+ request.url,
42
+ headers=request.headers,
43
+ timeout=self._timeout_in_seconds,
44
+ **request_args,
45
+ )
46
+ response = Response(result)
47
+
48
+ if result.status_code >= 400:
49
+ return None, RequestError(
50
+ message=f"{result.status_code} error in request to: {request.url}",
51
+ status_code=result.status_code,
52
+ response=response,
53
+ )
54
+
55
+ return response, None
56
+ except Timeout:
57
+ return None, RequestError("Request timed out")
58
+
59
+ def _get_request_data(self, request: Request) -> dict:
60
+ """
61
+ Get the request arguments based on the request headers and data.
62
+
63
+ :param Request request: The request object.
64
+ :return: The request arguments.
65
+ :rtype: dict
66
+ """
67
+ headers = request.headers or {}
68
+ data = request.body or {}
69
+ content_type = headers.get("Content-Type", "application/json")
70
+
71
+ if request.method == "GET" and not data:
72
+ return {}
73
+
74
+ if content_type.startswith("application/") and "json" in content_type:
75
+ return {"json": data}
76
+
77
+ if "multipart/form-data" in content_type:
78
+ return {"files": data}
79
+
80
+ return {"data": data}
@@ -0,0 +1,65 @@
1
+ import random
2
+
3
+ from typing import Optional, Tuple
4
+ from time import sleep
5
+ from .base_handler import BaseHandler
6
+ from ...transport.request import Request
7
+ from ...transport.response import Response
8
+ from ...transport.request_error import RequestError
9
+
10
+
11
+ class RetryHandler(BaseHandler):
12
+ """
13
+ Handler for retrying requests.
14
+ Retries the request if the previous handler in the chain returned an error or a response with a status code of 500 or higher.
15
+
16
+ :ivar int _max_attempts: The maximum number of retry attempts.
17
+ :ivar int _delay_in_milliseconds: The delay between retry attempts in milliseconds.
18
+ """
19
+
20
+ def __init__(self):
21
+ """
22
+ Initialize a new instance of RetryHandler.
23
+ """
24
+ super().__init__()
25
+ self._max_attempts = 3
26
+ self._delay_in_milliseconds = 150
27
+
28
+ def handle(
29
+ self, request: Request
30
+ ) -> Tuple[Optional[Response], Optional[RequestError]]:
31
+ """
32
+ Retry the request if the previous handler in the chain returned an error or a response with a status code of 500 or higher.
33
+
34
+ :param Request request: The request to retry.
35
+ :return: The response and any error that occurred.
36
+ :rtype: Tuple[Optional[Response], Optional[RequestError]]
37
+ :raises RequestError: If the handler chain is incomplete.
38
+ """
39
+ if self._next_handler is None:
40
+ raise RequestError("Handler chain is incomplete")
41
+
42
+ response, error = self._next_handler.handle(request)
43
+
44
+ try_count = 0
45
+ while try_count < self._max_attempts and self._should_retry(error):
46
+ jitter = random.uniform(0.5, 1.5)
47
+ delay = self._delay_in_milliseconds * (2**try_count) * jitter / 1000
48
+ sleep(delay)
49
+ response, error = self._next_handler.handle(request)
50
+ try_count += 1
51
+
52
+ return response, error
53
+
54
+ def _should_retry(self, error: Optional[RequestError]) -> bool:
55
+ """
56
+ Determine whether the request should be retried.
57
+
58
+ :param Optional[Response] response: The response from the previous handler.
59
+ :param Optional[RequestError] error: The error from the previous handler.
60
+ :return: True if the request should be retried, False otherwise.
61
+ :rtype: bool
62
+ """
63
+ if not error:
64
+ return False
65
+ return error.status_code == 408 or error.status_code >= 500
@@ -0,0 +1,57 @@
1
+ from typing import Optional
2
+ from .handlers.base_handler import BaseHandler
3
+ from ..transport.request import Request
4
+ from ..transport.response import Response
5
+
6
+
7
+ class RequestChain:
8
+ """
9
+ Class representing a chain of request handlers.
10
+ Handlers are added to the chain and the request is passed through each handler in the order they were added.
11
+
12
+ :ivar Optional[BaseHandler] _head: The first handler in the chain.
13
+ :ivar Optional[BaseHandler] _tail: The last handler in the chain.
14
+ """
15
+
16
+ def __init__(self):
17
+ """
18
+ Initialize a new instance of RequestChain.
19
+ """
20
+ self._head: Optional[BaseHandler] = None
21
+ self._tail: Optional[BaseHandler] = None
22
+
23
+ def add_handler(self, handler: BaseHandler) -> "RequestChain":
24
+ """
25
+ Add a handler to the chain.
26
+
27
+ :param BaseHandler handler: The handler to add.
28
+ :return: The current instance of RequestChain to allow for method chaining.
29
+ :rtype: RequestChain
30
+ """
31
+ if self._head is None:
32
+ self._head = handler
33
+ self._tail = handler
34
+ else:
35
+ self._tail.set_next(handler)
36
+ self._tail = handler
37
+
38
+ return self
39
+
40
+ def send(self, request: Request) -> Response:
41
+ """
42
+ Send the request through the chain of handlers.
43
+
44
+ :param Request request: The request to send.
45
+ :return: The response from the request.
46
+ :rtype: Response
47
+ :raises RuntimeError: If the RequestChain is empty.
48
+ """
49
+ if self._head is not None:
50
+ response, error = self._head.handle(request)
51
+
52
+ if error is not None:
53
+ raise error
54
+
55
+ return response
56
+ else:
57
+ raise RuntimeError("RequestChain is empty")
File without changes
@@ -0,0 +1,89 @@
1
+ from typing import Any
2
+ from .utils import extract_original_data
3
+
4
+
5
+ class Request:
6
+ """
7
+ A simple HTTP request builder class using the requests library.
8
+
9
+ Example Usage:
10
+ ```python
11
+ # Create a Request object
12
+ request = Request()
13
+
14
+ # Set request parameters
15
+ request.set_url('https://yourendpoint.com/') \
16
+ .set_method('GET') \
17
+ .set_headers({'Content-Type': 'application/json'}) \
18
+ .set_body(None) # For GET requests, the body should be None
19
+
20
+ # Send the HTTP request
21
+ response = request.send()
22
+ ```
23
+
24
+ :ivar str url: The URL of the API endpoint.
25
+ :ivar str method: The HTTP method for the request.
26
+ :ivar dict headers: Dictionary of headers to include in the request.
27
+ :ivar Any body: Request body.
28
+ """
29
+
30
+ def __init__(self):
31
+ self.url = None
32
+ self.method = None
33
+ self.headers = None
34
+ self.body = None
35
+
36
+ def set_url(self, url: str) -> "Request":
37
+ """
38
+ Set the URL of the API endpoint.
39
+
40
+ :param str url: The URL of the API endpoint.
41
+ :return: The updated Request object.
42
+ :rtype: Request
43
+ """
44
+ self.url = url
45
+ return self
46
+
47
+ def set_headers(self, headers: dict) -> "Request":
48
+ """
49
+ Set the headers for the HTTP request.
50
+
51
+ :param dict headers: Dictionary of headers to include in the request.
52
+ :return: The updated Request object.
53
+ :rtype: Request
54
+ """
55
+ self.headers = headers
56
+ return self
57
+
58
+ def set_method(self, method: str) -> "Request":
59
+ """
60
+ Set the HTTP method for the request.
61
+
62
+ :param str method: The HTTP method (e.g., 'GET', 'POST', 'PUT', 'DELETE', etc.).
63
+ :return: The updated Request object.
64
+ :rtype: Request
65
+ """
66
+ self.method = method
67
+ return self
68
+
69
+ def set_body(self, body: Any, content_type: str = "application/json") -> "Request":
70
+ """
71
+ Set the request body (e.g., JSON payload).
72
+
73
+ :param Any body: Request body.
74
+ :param str content_type: The content type of the request body. Default is "application/json".
75
+ :return: The updated Request object.
76
+ :rtype: Request
77
+ """
78
+ self.body = extract_original_data(body)
79
+ self.headers["Content-Type"] = content_type
80
+ return self
81
+
82
+ def __str__(self) -> str:
83
+ """
84
+ Return a string representation of the Request object.
85
+
86
+ :return: A string representation of the Request object.
87
+ :rtype: str
88
+ """
89
+ return f"Request(url={self.url}, method={self.method}, headers={self.headers}, body={self.body})"
@@ -0,0 +1,53 @@
1
+ from .response import Response
2
+ from typing import Optional
3
+
4
+
5
+ class RequestError(IOError):
6
+ """
7
+ Class representing a Request Error.
8
+
9
+ :ivar bool is_http_error: Indicates if the error is an HTTP error.
10
+ :ivar int status_code: The status code of the HTTP error.
11
+ :ivar Optional[Response] response: The response associated with the error.
12
+ """
13
+
14
+ def __init__(
15
+ self,
16
+ message: str,
17
+ status_code: Optional[int] = None,
18
+ response: Optional[Response] = None,
19
+ stack: Optional["RequestError"] = None,
20
+ ):
21
+ """
22
+ Initialize a new instance of RequestError.
23
+
24
+ :param str message: The error message.
25
+ :param Optional[int] status_code: The status code of the HTTP error.
26
+ :param Optional[Response] response: The response associated with the error.
27
+ """
28
+ super().__init__(message)
29
+ self.response = response
30
+ self.stack = stack
31
+
32
+ if status_code is not None:
33
+ self.status_code = status_code
34
+ self.is_http_error = True
35
+ else:
36
+ self.status_code = -1
37
+ self.is_http_error = False
38
+
39
+ def __str__(self):
40
+ """
41
+ Get the string representation of the error.
42
+
43
+ :return: The string representation of the error.
44
+ :rtype: str
45
+ """
46
+ error_stack = []
47
+ current_error = self
48
+ while current_error is not None:
49
+ error_stack.append(
50
+ f"Error: {super().__str__()}, Status Code: {current_error.status_code}"
51
+ )
52
+ current_error = current_error.stack
53
+ return "\n".join(error_stack)
@@ -0,0 +1,57 @@
1
+ from requests import Response as RequestsResponse
2
+ from urllib.parse import parse_qs
3
+
4
+
5
+ class Response:
6
+ """
7
+ A simple HTTP response wrapper class using the requests library.
8
+
9
+ :ivar int status_code: The status code of the HTTP response.
10
+ :ivar dict headers: The headers of the HTTP response.
11
+ :ivar str body: The body of the HTTP response.
12
+ """
13
+
14
+ def __init__(self, response: RequestsResponse):
15
+ """
16
+ Initializes a Response object.
17
+
18
+ :param RequestsResponse response: The requests.Response object.
19
+ """
20
+ self.status_code = response.status_code
21
+ self.headers = response.headers
22
+ self.body = self._get_response_body(response)
23
+
24
+ def __str__(self) -> str:
25
+ """
26
+ Return a string representation of the Response object.
27
+
28
+ :return: A string representation of the Response object.
29
+ :rtype: str
30
+ """
31
+ return f"Response(status_code={self.status_code}, headers={self.headers}, body={self.body})"
32
+
33
+ def _get_response_body(self, response: RequestsResponse) -> str:
34
+ """
35
+ Extracts the response body from a given HTTP response.
36
+
37
+ This method attempts to parse the response body based on its content type.
38
+ If the content type is JSON, it tries to parse the body as JSON.
39
+ If the content type is text or XML, it returns the raw text.
40
+ If the content type is 'application/x-www-form-urlencoded', it parses the body as a query string.
41
+ For all other content types, it returns the raw binary content.
42
+
43
+ :param RequestsResponse response: The HTTP response received from a request.
44
+ :return: The parsed response body.
45
+ :rtype: str or dict or bytes
46
+ """
47
+ try:
48
+ return response.json()
49
+ except ValueError:
50
+ content_type = response.headers.get("Content-Type", "").lower()
51
+ if "text/" in content_type or content_type == "application/xml":
52
+ return response.text
53
+ elif content_type == "application/x-www-form-urlencoded":
54
+ parsed_response = parse_qs(response.text)
55
+ return {k: v[0] for k, v in parsed_response.items()}
56
+ else:
57
+ return response.content