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.
- {dately-2.2.0b2 → dately-3.0.0}/PKG-INFO +10 -5
- {dately-2.2.0b2 → dately-3.0.0}/README.md +6 -1
- dately-3.0.0/dately/__init__.py +7 -0
- dately-3.0.0/dately/connect.py +352 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/core.py +4 -1
- dately-3.0.0/dately/holiday.py +330 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mskutils.py +4 -0
- dately-3.0.0/dately/sources/holiday.json +934 -0
- dately-3.0.0/dately/sysutils.py +125 -0
- dately-3.0.0/dately/timezone.py +304 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/utils.py +2 -0
- dately-3.0.0/dately/webutils.py +34 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately.egg-info/PKG-INFO +10 -5
- {dately-2.2.0b2 → dately-3.0.0}/dately.egg-info/SOURCES.txt +4 -1
- {dately-2.2.0b2 → dately-3.0.0}/setup.py +6 -6
- dately-2.2.0b2/dately/sources/__init__.py +0 -2
- dately-2.2.0b2/dately/sources/iana_zones.json +0 -1
- dately-2.2.0b2/dately/sysutils.py +0 -60
- dately-2.2.0b2/dately/timezone.py +0 -516
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/__init__.py +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/include/clean_str.h +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/include/iso8601T.h +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/include/iso8601Z.h +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/include/root_dir_search.h +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/include/time_zones.h +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyd/Compiled.cp38-win_amd64.pyd +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyd/__init__.py +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyd/cdatetime/UniversalDateFormatter.cp38-win_amd64.pyd +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyd/cdatetime/__init__.py +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyd/cdatetime/iso8601T.cp38-win_amd64.pyd +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyd/cdatetime/iso8601Z.cp38-win_amd64.pyd +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyd/cdatetime/whichformat.cp38-win_amd64.pyd +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyd/clean_str.cp38-win_amd64.pyd +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyd/time_zones.cp38-win_amd64.pyd +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyx/Compiled.pyx +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyx/UniversalDateFormatter.pyx +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyx/clean_str.pyx +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyx/iso8601T.pyx +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyx/iso8601Z.pyx +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyx/time_zones.pyx +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/pyx/whichformat.pyx +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/src/Compiled.c +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/src/UniversalDateFormatter.c +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/src/clean_str.c +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/src/clean_str_impl.c +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/src/iso8601T.c +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/src/iso8601T_impl.c +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/src/iso8601Z.c +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/src/iso8601Z_impl.c +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/src/root_dir_search_impl.c +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/src/time_zones.c +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/src/time_zones_impl.c +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/mold/src/whichformat.c +0 -0
- {dately-2.2.0b2/dately → dately-3.0.0/dately/sources}/__init__.py +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/sources/timezone_data.json +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately/timeutils.py +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately.egg-info/dependency_links.txt +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately.egg-info/requires.txt +0 -0
- {dately-2.2.0b2 → dately-3.0.0}/dately.egg-info/top_level.txt +0 -0
- {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:
|
|
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.
|
|
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.
|
|
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 ::
|
|
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/
|
|
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
|
+
[](https://pepy.tech/project/dately)
|
|
30
|
+
[](https://pepy.tech/project/dately)
|
|
31
|
+
[](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/
|
|
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
|
+
[](https://pepy.tech/project/dately)
|
|
9
|
+
[](https://pepy.tech/project/dately)
|
|
10
|
+
[](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,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
|
]
|