dately 2.2.0b2__tar.gz → 3.0.0__tar.gz

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 (60) hide show
  1. {dately-2.2.0b2 → dately-3.0.0}/PKG-INFO +10 -5
  2. {dately-2.2.0b2 → dately-3.0.0}/README.md +6 -1
  3. dately-3.0.0/dately/__init__.py +7 -0
  4. dately-3.0.0/dately/connect.py +352 -0
  5. {dately-2.2.0b2 → dately-3.0.0}/dately/core.py +4 -1
  6. dately-3.0.0/dately/holiday.py +330 -0
  7. {dately-2.2.0b2 → dately-3.0.0}/dately/mskutils.py +4 -0
  8. dately-3.0.0/dately/sources/holiday.json +934 -0
  9. dately-3.0.0/dately/sysutils.py +125 -0
  10. dately-3.0.0/dately/timezone.py +304 -0
  11. {dately-2.2.0b2 → dately-3.0.0}/dately/utils.py +2 -0
  12. dately-3.0.0/dately/webutils.py +34 -0
  13. {dately-2.2.0b2 → dately-3.0.0}/dately.egg-info/PKG-INFO +10 -5
  14. {dately-2.2.0b2 → dately-3.0.0}/dately.egg-info/SOURCES.txt +4 -1
  15. {dately-2.2.0b2 → dately-3.0.0}/setup.py +6 -6
  16. dately-2.2.0b2/dately/sources/__init__.py +0 -2
  17. dately-2.2.0b2/dately/sources/iana_zones.json +0 -1
  18. dately-2.2.0b2/dately/sysutils.py +0 -60
  19. dately-2.2.0b2/dately/timezone.py +0 -516
  20. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/__init__.py +0 -0
  21. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/include/clean_str.h +0 -0
  22. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/include/iso8601T.h +0 -0
  23. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/include/iso8601Z.h +0 -0
  24. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/include/root_dir_search.h +0 -0
  25. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/include/time_zones.h +0 -0
  26. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyd/Compiled.cp38-win_amd64.pyd +0 -0
  27. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyd/__init__.py +0 -0
  28. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyd/cdatetime/UniversalDateFormatter.cp38-win_amd64.pyd +0 -0
  29. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyd/cdatetime/__init__.py +0 -0
  30. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyd/cdatetime/iso8601T.cp38-win_amd64.pyd +0 -0
  31. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyd/cdatetime/iso8601Z.cp38-win_amd64.pyd +0 -0
  32. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyd/cdatetime/whichformat.cp38-win_amd64.pyd +0 -0
  33. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyd/clean_str.cp38-win_amd64.pyd +0 -0
  34. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyd/time_zones.cp38-win_amd64.pyd +0 -0
  35. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyx/Compiled.pyx +0 -0
  36. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyx/UniversalDateFormatter.pyx +0 -0
  37. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyx/clean_str.pyx +0 -0
  38. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyx/iso8601T.pyx +0 -0
  39. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyx/iso8601Z.pyx +0 -0
  40. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyx/time_zones.pyx +0 -0
  41. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyx/whichformat.pyx +0 -0
  42. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/src/Compiled.c +0 -0
  43. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/src/UniversalDateFormatter.c +0 -0
  44. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/src/clean_str.c +0 -0
  45. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/src/clean_str_impl.c +0 -0
  46. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/src/iso8601T.c +0 -0
  47. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/src/iso8601T_impl.c +0 -0
  48. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/src/iso8601Z.c +0 -0
  49. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/src/iso8601Z_impl.c +0 -0
  50. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/src/root_dir_search_impl.c +0 -0
  51. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/src/time_zones.c +0 -0
  52. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/src/time_zones_impl.c +0 -0
  53. {dately-2.2.0b2 → dately-3.0.0}/dately/mold/src/whichformat.c +0 -0
  54. {dately-2.2.0b2/dately → dately-3.0.0/dately/sources}/__init__.py +0 -0
  55. {dately-2.2.0b2 → dately-3.0.0}/dately/sources/timezone_data.json +0 -0
  56. {dately-2.2.0b2 → dately-3.0.0}/dately/timeutils.py +0 -0
  57. {dately-2.2.0b2 → dately-3.0.0}/dately.egg-info/dependency_links.txt +0 -0
  58. {dately-2.2.0b2 → dately-3.0.0}/dately.egg-info/requires.txt +0 -0
  59. {dately-2.2.0b2 → dately-3.0.0}/dately.egg-info/top_level.txt +0 -0
  60. {dately-2.2.0b2 → dately-3.0.0}/setup.cfg +0 -0
@@ -1,16 +1,16 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dately
3
- Version: 2.2.0b2
3
+ Version: 3.0.0
4
4
  Summary: A comprehensive Python library for advanced date and time manipulation.
5
- Home-page: https://github.com/cedricmoorejr/dately/tree/v2.2.0-b.2
5
+ Home-page: https://github.com/cedricmoorejr/dately/tree/v2.4.0
6
6
  Author: Cedric Moore Jr.
7
7
  Author-email: cedricmoorejunior5@gmail.com
8
8
  License: MIT
9
- Project-URL: Source Code, https://github.com/cedricmoorejr/dately/releases/tag/v2.2.0-beta.2
9
+ Project-URL: Source Code, https://github.com/cedricmoorejr/dately/releases/tag/v2.4.0
10
10
  Classifier: Programming Language :: Python :: 3
11
11
  Classifier: License :: OSI Approved :: MIT License
12
12
  Classifier: Operating System :: Microsoft :: Windows
13
- Classifier: Development Status :: 4 - Beta
13
+ Classifier: Development Status :: 5 - Production/Stable
14
14
  Classifier: Intended Audience :: Developers
15
15
  Classifier: Natural Language :: English
16
16
  Classifier: Programming Language :: Python :: 3.8
@@ -20,11 +20,16 @@ Requires-Python: >=3.8
20
20
  Description-Content-Type: text/markdown
21
21
 
22
22
  <p align="center">
23
- <img src="https://raw.githubusercontent.com/cedricmoorejr/dately/main/dately/assets/dately_logo.png" alt="Dately Logo" width="500"/>
23
+ <img src="https://raw.githubusercontent.com/cedricmoorejr/dately/main/dately/assets/py_dately_logo.png" alt="Dately Logo" width="700"/>
24
24
  </p>
25
25
 
26
26
  ### Dately Library: Comprehensive Date and Time Handling in Python
27
27
 
28
+
29
+ [![Downloads](https://static.pepy.tech/badge/dately)](https://pepy.tech/project/dately)
30
+ [![Downloads](https://static.pepy.tech/badge/dately/month)](https://pepy.tech/project/dately)
31
+ [![Downloads](https://static.pepy.tech/badge/dately/week)](https://pepy.tech/project/dately)
32
+
28
33
  The `dately` module is a comprehensive library designed for advanced date and time manipulation. It offers extensive capabilities to handle various date and time formats, ensuring compatibility across different systems and data structures. The module provides tools for validating, extracting, and transforming date and time information with a focus on performance and accuracy.
29
34
 
30
35
  #### Table of Contents
@@ -1,9 +1,14 @@
1
1
  <p align="center">
2
- <img src="https://raw.githubusercontent.com/cedricmoorejr/dately/main/dately/assets/dately_logo.png" alt="Dately Logo" width="500"/>
2
+ <img src="https://raw.githubusercontent.com/cedricmoorejr/dately/main/dately/assets/py_dately_logo.png" alt="Dately Logo" width="700"/>
3
3
  </p>
4
4
 
5
5
  ### Dately Library: Comprehensive Date and Time Handling in Python
6
6
 
7
+
8
+ [![Downloads](https://static.pepy.tech/badge/dately)](https://pepy.tech/project/dately)
9
+ [![Downloads](https://static.pepy.tech/badge/dately/month)](https://pepy.tech/project/dately)
10
+ [![Downloads](https://static.pepy.tech/badge/dately/week)](https://pepy.tech/project/dately)
11
+
7
12
  The `dately` module is a comprehensive library designed for advanced date and time manipulation. It offers extensive capabilities to handle various date and time formats, ensuring compatibility across different systems and data structures. The module provides tools for validating, extracting, and transforming date and time information with a focus on performance and accuracy.
8
13
 
9
14
  #### Table of Contents
@@ -0,0 +1,7 @@
1
+ # -*- coding: utf-8 -*-
2
+ from .core import *
3
+
4
+ # Exclude Specific Imports
5
+ def __dir__():
6
+ return ['Holidate', 'TimeZoner', '__builtins__', '__cached__', '__doc__',
7
+ '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'dt']
@@ -0,0 +1,352 @@
1
+ import requests
2
+ import time
3
+ import random
4
+ from collections import deque
5
+ from .mskutils import *
6
+ from .sysutils import UnixTime
7
+
8
+
9
+ class UserAgentRandomizer:
10
+ """
11
+ A class responsible for randomizing user agents from a predefined list of popular user agents across different platforms and browsers.
12
+ This class now includes a mechanism to reduce the likelihood of selecting a user agent that has been chosen frequently in the recent selections.
13
+
14
+ Attributes:
15
+ user_agents (dict): A class-level dictionary containing user agents categorized by platform and browser combinations. Each platform has its own nested dictionary with browser-specific user agents.
16
+ recent_selections (deque): A deque to track the history of the last five selections to dynamically adjust selection probabilities.
17
+
18
+ Methods:
19
+ get_random_user_agent(): Randomly selects and returns a user agent string from the aggregated list of all available user agents, with adjustments based on recent usage to discourage frequent repeats.
20
+ """
21
+ user_agents = {
22
+ "Desktop User-Agents": {
23
+ "Windows and Edge/Chrome/Safari": {
24
+ "1": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.199 Safari/537.36 Edge/114.0.1823.79',
25
+ "2": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.199 Safari/537.36',
26
+ "3": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.199 Safari/537.36 Edg/114.0.1823.79',
27
+ "4": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.199 Safari/537.36',
28
+ "5": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:115.0) Gecko/20100101 Firefox/115.0',
29
+ "6": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.199 Safari/537.36 Edg/114.0.1823.79',
30
+ },
31
+ "Mac OS and Safari/Chrome/Firefox": {
32
+ "1": 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15',
33
+ "2": 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/601.3.9 (KHTML, like Gecko) Version/15.0 Safari/601.3.9',
34
+ "3": 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.199 Safari/537.36',
35
+ "4": 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:115.0) Gecko/20100101 Firefox/115.0',
36
+ "5": 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15',
37
+ },
38
+ "Linux and Chrome/Firefox": {
39
+ "1": 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:115.0) Gecko/20100101 Firefox/115.0',
40
+ "2": 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:115.0) Gecko/20100101 Firefox/115.0',
41
+ "3": 'Mozilla/5.0 (X11; CrOS x86_64 8172.45.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.199 Safari/537.36',
42
+ "4": 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.199 Safari/537.36',
43
+ "5": 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:115.0) Gecko/20100101 Firefox/115.0',
44
+ }
45
+ }
46
+ }
47
+
48
+ recent_selections = deque(maxlen=5)
49
+
50
+ @staticmethod
51
+ def get_random_user_agent():
52
+ """
53
+ Retrieves a random user agent string from the predefined list of user agents across various platforms and browsers.
54
+ Adjusts the selection process based on the history of the last five selections to discourage frequently repeated choices.
55
+ """
56
+ all_user_agents = []
57
+ for category in UserAgentRandomizer.user_agents.values():
58
+ for subcategory in category.values():
59
+ all_user_agents.extend(subcategory.values())
60
+
61
+ choice = random.choice(all_user_agents)
62
+ while UserAgentRandomizer.recent_selections.count(choice) >= 3:
63
+ choice = random.choice(all_user_agents)
64
+
65
+ UserAgentRandomizer.recent_selections.append(choice)
66
+ return choice
67
+
68
+
69
+
70
+
71
+ class HTTPLite:
72
+ _instance = None
73
+
74
+ def __new__(cls, *args, **kwargs):
75
+ if not cls._instance:
76
+ cls._instance = super(HTTPLite, cls).__new__(cls)
77
+ cls._instance.initialized = False
78
+ return cls._instance
79
+
80
+ def __init__(self, base_url=None):
81
+ if not self.initialized:
82
+ self.session = requests.Session()
83
+ self.session.headers.update({
84
+ 'User-Agent': UserAgentRandomizer.get_random_user_agent(),
85
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
86
+ 'Accept-Language': 'en-US,en;q=0.5',
87
+ 'Accept-Encoding': 'gzip, deflate, br',
88
+ 'DNT': '1',
89
+ 'Connection': 'keep-alive',
90
+ 'Upgrade-Insecure-Requests': '1',
91
+ 'Referer': 'https://www.google.com'
92
+ })
93
+ self.last_request_time = None
94
+ self.initialized = True
95
+ self.base_url = Shift.format.chr(base_url, "format")
96
+
97
+ def update_base_url(self, new_url):
98
+ """ Update the base URL for the class"""
99
+ self.base_url = new_url
100
+
101
+ def random_delay(self):
102
+ if self.last_request_time is not None:
103
+ elapsed_time = time.time() - self.last_request_time
104
+ if elapsed_time < 3:
105
+ time.sleep(3 - elapsed_time)
106
+ self.last_request_time = time.time()
107
+
108
+ def make_request(self, params):
109
+ """
110
+ Sends a request to the server with specified parameters to format the response.
111
+
112
+ Args:
113
+ params (dict): A dictionary specifying the desired response format. Valid keys and values include:
114
+ 'json': Request the response in JSON format.
115
+ 'xml': Request the response in XML format.
116
+ 'csv': Request the response in CSV format.
117
+ 'text' or 'plain': Request the response in plain text format.
118
+ 'html': Request the response in HTML format.
119
+
120
+ Returns:
121
+ dict: A dictionary containing the server's response. The format of the response
122
+ depends on the 'format' parameter provided:
123
+ - For 'json', returns the response as a JSON-decoded dictionary.
124
+ - For all other formats, returns the response as a plain string.
125
+
126
+ Notes:
127
+ - If no 'format' is specified in params, the response defaults to JSON format.
128
+ - The method automatically handles necessary delays between requests to manage load.
129
+ """
130
+ self.random_delay()
131
+
132
+ if 'format' not in params:
133
+ params['format'] = 'json'
134
+
135
+ try:
136
+ response = self.session.get(self.base_url, params=params)
137
+ response.raise_for_status()
138
+ if params['format'] == 'json':
139
+ return {'response': response.json()}
140
+ else:
141
+ return {'response': response.text}
142
+ except Exception:
143
+ return None
144
+
145
+
146
+ class NoAPIKeysError(Exception):
147
+ """Exception raised when no API keys are available."""
148
+ pass
149
+
150
+ class HTTP:
151
+ _instance = None
152
+
153
+ def __new__(cls, *args, **kwargs):
154
+ if not cls._instance:
155
+ cls._instance = super(HTTP, cls).__new__(cls)
156
+ return cls._instance
157
+
158
+ def __init__(self, base_url='aHR0cDovL2FwaS50aW1lem9uZWRiLmNvbS92Mi4xL2xpc3QtdGltZS16b25lP2tleT0=', use_api_key=True, default_key_type="FullVersion"):
159
+ if not hasattr(self, 'initialized'):
160
+ self.session = requests.Session()
161
+ self.base_url = Shift.format.chr(base_url, "format")
162
+ self.use_api_key = use_api_key
163
+ self.api_keys = {}
164
+ self.last_key = None
165
+ self.last_request_time = None
166
+ self.rate_limit_limit = None
167
+ self.rate_limit_remaining = None
168
+ self.rate_limit_reset = None
169
+ self.default_key_type = default_key_type
170
+ self.current_key_type = default_key_type
171
+ self.initialized = True
172
+ self.user_agent = self.session.headers.update({
173
+ 'User-Agent': UserAgentRandomizer.get_random_user_agent(),
174
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
175
+ 'Accept-Language': 'en-US,en;q=0.5',
176
+ 'Accept-Encoding': 'gzip, deflate, br',
177
+ 'DNT': '1', # Do Not Track request header
178
+ 'Connection': 'keep-alive',
179
+ 'Upgrade-Insecure-Requests': '1', # Tells the server to upgrade insecure requests
180
+ 'Referer': 'https://www.google.com' # Set to: traffic is coming from Google
181
+ })
182
+
183
+ if self.use_api_key:
184
+ self.initialize()
185
+
186
+ def initialize(self):
187
+ """Initialize the API utility by fetching API keys if not already present."""
188
+ if not self.api_keys:
189
+ self.fetch_api_keys()
190
+
191
+ def fetch_api_keys(self):
192
+ """Fetch API keys from the specified URL and store them in the api_keys dictionary."""
193
+ url_unformatted = 'aHR0cHM6Ly96b256ZXMubmV0bGlmeS5hcHAvZGF0YS5qc29u'
194
+ url = Shift.format.chr(url_unformatted, "format")
195
+ try:
196
+ response = self.session.get(url, timeout=10)
197
+ response.raise_for_status()
198
+ self.api_keys = response.json().get(self.current_key_type, {})
199
+ except requests.exceptions.HTTPError as e:
200
+ print(f"HTTP error occurred: {e}")
201
+ self.api_keys = {}
202
+ except requests.exceptions.ConnectionError as e:
203
+ print(f"Connection error occurred: {e}")
204
+ self.api_keys = {}
205
+ except requests.exceptions.Timeout as e:
206
+ print(f"Timeout occurred: {e}")
207
+ self.api_keys = {}
208
+ except Exception as e:
209
+ print(f"An unexpected error occurred: {e}")
210
+ self.api_keys = {}
211
+ except requests.exceptions.SSLError as e:
212
+ print(f"SSL error occurred: {e}")
213
+ self.api_keys = {}
214
+
215
+ def set_key_type(self, new_key_type):
216
+ """Temporarily change the key type."""
217
+ self.current_key_type = new_key_type
218
+ self.fetch_api_keys()
219
+
220
+ def reset_key_type(self):
221
+ """Reset the key type to the default and refetch the API keys."""
222
+ self.current_key_type = self.default_key_type
223
+ self.fetch_api_keys()
224
+
225
+ def get_random_key(self):
226
+ """Get a random API key from the available keys."""
227
+ keys = list(self.api_keys.keys())
228
+ if not keys:
229
+ raise NoAPIKeysError("No API keys available")
230
+
231
+ random_key = random.choice(keys)
232
+ while random_key == self.last_key and len(keys) > 1:
233
+ random_key = random.choice(keys)
234
+
235
+ self.last_key = random_key
236
+ api_key_unformatted = self.api_keys[random_key]['key']
237
+ api_key = Shift.format.str(api_key_unformatted, "format")
238
+ return api_key
239
+
240
+ def set_use_api_key(self, use_api_key):
241
+ self.use_api_key = use_api_key
242
+ if self.use_api_key:
243
+ self.initialize()
244
+
245
+ def update_base_url(self, new_url):
246
+ """
247
+ Update the base URL for the API utility.
248
+
249
+ Parameters:
250
+ new_url (str): The new base URL for the API endpoint.
251
+ """
252
+ self.base_url = new_url
253
+
254
+ def extract_rate_limit_info(self, headers):
255
+ """Extract rate limit information from response headers."""
256
+ for key, value in headers.items():
257
+ key_lower = key.lower()
258
+ if key_lower.endswith('-limit'):
259
+ self.rate_limit_limit = int(value)
260
+ elif key_lower.endswith('-remaining'):
261
+ self.rate_limit_remaining = int(value)
262
+ elif key_lower.endswith('-reset'):
263
+ self.rate_limit_reset = int(value)
264
+
265
+ return {
266
+ 'x-ratelimit-limit': self.rate_limit_limit,
267
+ 'x-ratelimit-remaining': self.rate_limit_remaining,
268
+ 'x-ratelimit-reset': self.rate_limit_reset
269
+ }
270
+
271
+ def log_rate_limit_status(self):
272
+ """Log the current rate limit status."""
273
+ if self.rate_limit_reset:
274
+ reset_time = UnixTime.Date(self.rate_limit_reset)
275
+ else:
276
+ reset_time = 'unknown'
277
+
278
+ def random_delay(self):
279
+ """Ensure there is at least a 3 second delay between requests."""
280
+ if self.last_request_time is not None:
281
+ elapsed_time = time.time() - self.last_request_time
282
+ if elapsed_time < 3:
283
+ time.sleep(3 - elapsed_time)
284
+ self.last_request_time = time.time()
285
+
286
+
287
+ def make_request(self, params, api_key_param_name="key", headers=None, response_format='json'):
288
+ """ Make a request to the specified API. """
289
+ if headers is None:
290
+ headers = {}
291
+ else:
292
+ headers = headers.copy()
293
+
294
+ if self.use_api_key:
295
+ headers.pop('Referer', None)
296
+ api_key = self.get_random_key()
297
+ params[api_key_param_name] = api_key
298
+ else:
299
+ if 'Referer' not in headers:
300
+ headers['Referer'] = 'https://www.google.com'
301
+
302
+ self.random_delay() # Delay between requests to mimic human behavior
303
+
304
+ if self.rate_limit_remaining is not None and self.rate_limit_remaining <= 0:
305
+ if self.rate_limit_reset is not None:
306
+ reset_time = UnixTime.Date(self.rate_limit_reset)
307
+ print(f"Rate limit exceeded. Please wait until {reset_time} to make more requests.")
308
+ return {
309
+ 'status': 'error',
310
+ 'message': f'Rate limit exceeded. Please wait until {reset_time} to make more requests.',
311
+ 'rate_limit_info': self.extract_rate_limit_info({})
312
+ }
313
+ else:
314
+ print("Rate limit exceeded. Please try again later.")
315
+ return {
316
+ 'status': 'error',
317
+ 'message': 'Rate limit exceeded. Please try again later.',
318
+ 'rate_limit_info': self.extract_rate_limit_info({})
319
+ }
320
+
321
+ if 'format' not in params:
322
+ params['format'] = 'json'
323
+
324
+ try:
325
+ response = self.session.get(self.base_url, params=params, headers=headers, timeout=10)
326
+ response.raise_for_status()
327
+ rate_limit_info = self.extract_rate_limit_info(response.headers)
328
+
329
+ if response_format == 'json':
330
+ return {
331
+ 'response': response.json(),
332
+ 'rate_limit_info': rate_limit_info
333
+ }
334
+ else:
335
+ return {
336
+ 'response': response.text,
337
+ 'rate_limit_info': rate_limit_info
338
+ }
339
+
340
+ except Exception as e:
341
+ self.log_rate_limit_status()
342
+ return {
343
+ 'status': 'error',
344
+ 'message': str(e),
345
+ 'rate_limit_info': self.extract_rate_limit_info({})
346
+ }
347
+
348
+
349
+ __all__ = ['HTTP', 'HTTPLite']
350
+
351
+
352
+
@@ -2,7 +2,9 @@ import threading
2
2
 
3
3
  def import_specific_parts():
4
4
  global TimeZoner
5
+ global Holidate
5
6
  from .timezone import TimeZoner
7
+ from .holiday import Holidate
6
8
 
7
9
  thread = threading.Thread(target=import_specific_parts)
8
10
  thread.start()
@@ -528,5 +530,6 @@ dt = DatelyDate()
528
530
  # Define public interface
529
531
  __all__ = [
530
532
  "dt",
531
- "TimeZoner"
533
+ "TimeZoner",
534
+ "Holidate",
532
535
  ]