apirlpy 0.0.4__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.
- apirlpy-0.0.4/LICENSE.txt +8 -0
- apirlpy-0.0.4/PKG-INFO +55 -0
- apirlpy-0.0.4/README.md +40 -0
- apirlpy-0.0.4/pyproject.toml +26 -0
- apirlpy-0.0.4/setup.cfg +4 -0
- apirlpy-0.0.4/src/apirlpy/ARL_json.py +245 -0
- apirlpy-0.0.4/src/apirlpy/ARL_nostorage.py +142 -0
- apirlpy-0.0.4/src/apirlpy/ARL_sql.py +258 -0
- apirlpy-0.0.4/src/apirlpy/__init__.py +3 -0
- apirlpy-0.0.4/src/apirlpy/ratelimiter.py +38 -0
- apirlpy-0.0.4/src/apirlpy.egg-info/PKG-INFO +55 -0
- apirlpy-0.0.4/src/apirlpy.egg-info/SOURCES.txt +12 -0
- apirlpy-0.0.4/src/apirlpy.egg-info/dependency_links.txt +1 -0
- apirlpy-0.0.4/src/apirlpy.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Copyright 2026 Shaik Naseer John Ahmed
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
8
|
+
|
apirlpy-0.0.4/PKG-INFO
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: apirlpy
|
|
3
|
+
Version: 0.0.4
|
|
4
|
+
Summary: A Python Based API RateLimiter
|
|
5
|
+
Author-email: Shaik Naseer John Ahmed <sknaseer.fez@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Naseer-fez/Apirl-py
|
|
8
|
+
Project-URL: Issues, https://github.com/Naseer-fez/Apirl-py/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.4
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE.txt
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
## How to Run
|
|
17
|
+
```python
|
|
18
|
+
from apirlpy import ratelimiter as APIRL
|
|
19
|
+
#Import the Function
|
|
20
|
+
|
|
21
|
+
# Checking rate limit for a specific IP
|
|
22
|
+
CoolDown=APIRL(IP_Adrs="127.0.0.1", #IP address ofthe Client
|
|
23
|
+
#_______________________________________________________________
|
|
24
|
+
Cleaning=False, #To Enable the Ip Address Cleaning
|
|
25
|
+
CleaningFreq=80,# The Frequency at which the cleaning shoudl occur
|
|
26
|
+
AutoUpdate=False, #If you want to update the Data only after a certain time to reduce I/O operations
|
|
27
|
+
UpdateFreq=70, #(value in sec) of auto update
|
|
28
|
+
CooldownTime=20, #Amount of time differnce the Clients last vist shoudld
|
|
29
|
+
# exced for the IP address cleaner to remove the data
|
|
30
|
+
# *** FOR JSON AND SQL Format only ***
|
|
31
|
+
#If passed to "no stograge" option then this arguments will not be passed to the ratelimiter
|
|
32
|
+
#_______________________________________________________________
|
|
33
|
+
AllowedFreq=8, #The Number of Fair attempts the Client can send the request to the server
|
|
34
|
+
ResetTime=8, # After the Client is denied the access , how much time
|
|
35
|
+
#they need to wait to get the access again
|
|
36
|
+
Filename="Test", # The file name to store the data
|
|
37
|
+
Format="sql", # The File extenstion type which the ratelimiter is going to use to store the data
|
|
38
|
+
# Extensions available are : sql to use sqllite , json to use json format and "None" for No storage option
|
|
39
|
+
# If nothing is mentioned then the data will not be stored
|
|
40
|
+
FolderPath=None # The path at which u want to store this results
|
|
41
|
+
|
|
42
|
+
)
|
|
43
|
+
if CoolDown == 1: #The Client can access the API
|
|
44
|
+
print( "You are a Valid user")
|
|
45
|
+
else: # the Request is denied
|
|
46
|
+
print( f"You need to wait {CoolDown}")
|
|
47
|
+
```
|
|
48
|
+
**This is a test code **
|
|
49
|
+
### The Code Return
|
|
50
|
+
- 1 : Means the Client Can Access the API
|
|
51
|
+
- other than 1: Means the Client Need to wait for the access of the API
|
|
52
|
+
___
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
apirlpy-0.0.4/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
## How to Run
|
|
2
|
+
```python
|
|
3
|
+
from apirlpy import ratelimiter as APIRL
|
|
4
|
+
#Import the Function
|
|
5
|
+
|
|
6
|
+
# Checking rate limit for a specific IP
|
|
7
|
+
CoolDown=APIRL(IP_Adrs="127.0.0.1", #IP address ofthe Client
|
|
8
|
+
#_______________________________________________________________
|
|
9
|
+
Cleaning=False, #To Enable the Ip Address Cleaning
|
|
10
|
+
CleaningFreq=80,# The Frequency at which the cleaning shoudl occur
|
|
11
|
+
AutoUpdate=False, #If you want to update the Data only after a certain time to reduce I/O operations
|
|
12
|
+
UpdateFreq=70, #(value in sec) of auto update
|
|
13
|
+
CooldownTime=20, #Amount of time differnce the Clients last vist shoudld
|
|
14
|
+
# exced for the IP address cleaner to remove the data
|
|
15
|
+
# *** FOR JSON AND SQL Format only ***
|
|
16
|
+
#If passed to "no stograge" option then this arguments will not be passed to the ratelimiter
|
|
17
|
+
#_______________________________________________________________
|
|
18
|
+
AllowedFreq=8, #The Number of Fair attempts the Client can send the request to the server
|
|
19
|
+
ResetTime=8, # After the Client is denied the access , how much time
|
|
20
|
+
#they need to wait to get the access again
|
|
21
|
+
Filename="Test", # The file name to store the data
|
|
22
|
+
Format="sql", # The File extenstion type which the ratelimiter is going to use to store the data
|
|
23
|
+
# Extensions available are : sql to use sqllite , json to use json format and "None" for No storage option
|
|
24
|
+
# If nothing is mentioned then the data will not be stored
|
|
25
|
+
FolderPath=None # The path at which u want to store this results
|
|
26
|
+
|
|
27
|
+
)
|
|
28
|
+
if CoolDown == 1: #The Client can access the API
|
|
29
|
+
print( "You are a Valid user")
|
|
30
|
+
else: # the Request is denied
|
|
31
|
+
print( f"You need to wait {CoolDown}")
|
|
32
|
+
```
|
|
33
|
+
**This is a test code **
|
|
34
|
+
### The Code Return
|
|
35
|
+
- 1 : Means the Client Can Access the API
|
|
36
|
+
- other than 1: Means the Client Need to wait for the access of the API
|
|
37
|
+
___
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools >= 77.0.3"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
[project]
|
|
8
|
+
name = "apirlpy"
|
|
9
|
+
# name="Api_RateLimiter"
|
|
10
|
+
version = "0.0.4"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name="Shaik Naseer John Ahmed",email="sknaseer.fez@gmail.com" },
|
|
13
|
+
]
|
|
14
|
+
description = "A Python Based API RateLimiter "
|
|
15
|
+
readme = "README.md"
|
|
16
|
+
requires-python = ">=3.4"
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
]
|
|
21
|
+
license = "MIT"
|
|
22
|
+
license-files = ["LICEN[CS]E*"]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://github.com/Naseer-fez/Apirl-py"
|
|
26
|
+
Issues = "https://github.com/Naseer-fez/Apirl-py/issues"
|
apirlpy-0.0.4/setup.cfg
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import json
|
|
5
|
+
import time
|
|
6
|
+
import threading
|
|
7
|
+
import copy
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class __RateLimiter:
|
|
12
|
+
__isfileopen :bool = False
|
|
13
|
+
__samplefile = Path(sys.modules['__main__'].__file__).stem
|
|
14
|
+
def __init__(self,filename=__samplefile,filetype="json",folder=None):
|
|
15
|
+
self.thread_running = False
|
|
16
|
+
self.filename=filename
|
|
17
|
+
self.filetype=filetype
|
|
18
|
+
self.Data=dict()
|
|
19
|
+
self.CurrentFile=None
|
|
20
|
+
self.lock = threading.RLock()
|
|
21
|
+
self.stop_event = threading.Event()
|
|
22
|
+
self.fullpath=os.path.join(folder or "", f"{filename}.{filetype}")
|
|
23
|
+
self.Validopen=self.__Fileopener()
|
|
24
|
+
|
|
25
|
+
pass
|
|
26
|
+
def API_RL(self,IP_Adrs:str,Cleaning=False,AutoUpdate=False,
|
|
27
|
+
CooldownTime=20,AllowedFreq=8,CleaningFreq=80,ResetTime=8,
|
|
28
|
+
UpdateFreq=80)->int:
|
|
29
|
+
# if isinstance(ipaddress,str):
|
|
30
|
+
# self.ip=int(ipaddress.ip_address(IP_Adrs))
|
|
31
|
+
# else:print("Here")
|
|
32
|
+
# self.ip=IP_Adrs
|
|
33
|
+
self.AutoUpdate=AutoUpdate
|
|
34
|
+
self.Metrics={
|
|
35
|
+
"CooldownTime":CooldownTime,
|
|
36
|
+
"AllowedFreq":AllowedFreq,
|
|
37
|
+
|
|
38
|
+
}
|
|
39
|
+
# background_thread = threading.Thread(target=self.hehe, args=(start_val,), daemon=True)
|
|
40
|
+
if (Cleaning or (AutoUpdate)) and not self.thread_running:
|
|
41
|
+
self.thread_running=True
|
|
42
|
+
BackgroundThread=threading.Thread(
|
|
43
|
+
|
|
44
|
+
target=self.__BackgroundWorker,
|
|
45
|
+
args=(CleaningFreq,ResetTime,UpdateFreq,Cleaning,),
|
|
46
|
+
daemon=False)
|
|
47
|
+
BackgroundThread.start()
|
|
48
|
+
|
|
49
|
+
return self.__Validator(ip=IP_Adrs)
|
|
50
|
+
def __Fileopener(self)->int:
|
|
51
|
+
if getattr(self, "__isfileopen", False):
|
|
52
|
+
return 0
|
|
53
|
+
data=dict()
|
|
54
|
+
fullpath=self.fullpath
|
|
55
|
+
try:
|
|
56
|
+
with open (fullpath,'r') as File:
|
|
57
|
+
try:
|
|
58
|
+
self.CurrentFile = open(fullpath, 'r+')
|
|
59
|
+
self.Data=json.load(File)
|
|
60
|
+
self.__isfileopen=True
|
|
61
|
+
return 1
|
|
62
|
+
except json.JSONDecodeError as error:
|
|
63
|
+
return 0
|
|
64
|
+
|
|
65
|
+
except FileNotFoundError as Fnf:
|
|
66
|
+
|
|
67
|
+
return self.__Filedumper(operation=0)
|
|
68
|
+
except FileNotFoundError as Fnf:
|
|
69
|
+
self.CurrentFile=open(fullpath, 'w')
|
|
70
|
+
# print("HAHHA")
|
|
71
|
+
# json.dump({},self.CurrentFile,indent=4)
|
|
72
|
+
return 1
|
|
73
|
+
# return self.__Filedumper(operation=0)
|
|
74
|
+
|
|
75
|
+
except FileExistsError as FEE:
|
|
76
|
+
|
|
77
|
+
return 0
|
|
78
|
+
except Exception as error:
|
|
79
|
+
|
|
80
|
+
print(error)
|
|
81
|
+
return 0
|
|
82
|
+
def __Filedumper(self,operation=0,Data=None,update=1)->int:
|
|
83
|
+
# print(Data)
|
|
84
|
+
# if msg is not None: print(msg[0])
|
|
85
|
+
if (self.AutoUpdate and update) :
|
|
86
|
+
return 1
|
|
87
|
+
try:
|
|
88
|
+
with self.lock:
|
|
89
|
+
# if msg is not None: print(msg[1])
|
|
90
|
+
fullpath=self.fullpath
|
|
91
|
+
flag=0
|
|
92
|
+
|
|
93
|
+
if Data is None:
|
|
94
|
+
Data={}
|
|
95
|
+
if self.CurrentFile is None:
|
|
96
|
+
self.CurrentFile = open(fullpath, 'w')
|
|
97
|
+
try:
|
|
98
|
+
self.CurrentFile.seek(0)
|
|
99
|
+
json.dump(Data,self.CurrentFile,indent=4)
|
|
100
|
+
self.CurrentFile.truncate()
|
|
101
|
+
self.CurrentFile.flush()
|
|
102
|
+
flag=1
|
|
103
|
+
|
|
104
|
+
except Exception as e:
|
|
105
|
+
try :
|
|
106
|
+
with open("LOg..txt",'a') as file:
|
|
107
|
+
record=f"{time.time()}:Error is {e}\n"
|
|
108
|
+
file.write(record)
|
|
109
|
+
|
|
110
|
+
flag=0
|
|
111
|
+
except Exception as err:
|
|
112
|
+
flag=0
|
|
113
|
+
|
|
114
|
+
if operation==1:
|
|
115
|
+
self.CurrentFile.close()
|
|
116
|
+
self.__isfileopen=False
|
|
117
|
+
return 1
|
|
118
|
+
else:
|
|
119
|
+
self.__isfileopen=True
|
|
120
|
+
return flag
|
|
121
|
+
except Exception as e:
|
|
122
|
+
print("HEHEH")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
pass
|
|
127
|
+
def __Validator(self,ip:int)->int:
|
|
128
|
+
|
|
129
|
+
currenttime=int(time.time())
|
|
130
|
+
error=False
|
|
131
|
+
flag=1
|
|
132
|
+
with self.lock:
|
|
133
|
+
try:
|
|
134
|
+
lastseen=currenttime-self.Data[ip]["LastSeenTime"]
|
|
135
|
+
except KeyError:
|
|
136
|
+
|
|
137
|
+
self.Data[ip]={
|
|
138
|
+
# "WaitTime":0,
|
|
139
|
+
"WaitStamp":0,
|
|
140
|
+
"LastSeenTime":currenttime,
|
|
141
|
+
"Visits":1
|
|
142
|
+
}
|
|
143
|
+
error=True
|
|
144
|
+
self.Data[ip]["LastSeenTime"]=currenttime
|
|
145
|
+
if error is False:
|
|
146
|
+
if lastseen>self.Metrics["CooldownTime"]:
|
|
147
|
+
# self.Data[ip]["WaitTime"]=0
|
|
148
|
+
self.Data[ip]["WaitStamp"]=0
|
|
149
|
+
self.Data[ip]["Visits"]=1
|
|
150
|
+
flag= 1
|
|
151
|
+
else:
|
|
152
|
+
Datacopy=copy.deepcopy(self.Data)
|
|
153
|
+
self.Data[ip],flag= self.__RecentVists(Data=Datacopy[ip],CrnTime=currenttime)
|
|
154
|
+
|
|
155
|
+
self.__Filedumper(Data=self.Data,update=0)
|
|
156
|
+
return (flag or error)
|
|
157
|
+
def __RecentVists(self,Data,CrnTime)->tuple: #Solution For DeadLock
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
if Data["Visits"]<=self.Metrics["AllowedFreq"]:
|
|
161
|
+
Data["WaitStamp"]=0
|
|
162
|
+
Data["Visits"]+=1
|
|
163
|
+
return (Data,1)
|
|
164
|
+
# timetowait=0
|
|
165
|
+
if Data["WaitStamp"]==0:
|
|
166
|
+
Data["WaitStamp"]=CrnTime+self.Metrics["CooldownTime"]
|
|
167
|
+
Data["Visits"]+=1
|
|
168
|
+
return (Data,self.Metrics["CooldownTime"])
|
|
169
|
+
else:
|
|
170
|
+
timetosend=Data["WaitStamp"]-CrnTime
|
|
171
|
+
Data["Visits"]+=1
|
|
172
|
+
if timetosend<=0:
|
|
173
|
+
Data["WaitStamp"]=0
|
|
174
|
+
Data["Visits"]=0
|
|
175
|
+
timetosend=1
|
|
176
|
+
|
|
177
|
+
return (Data,timetosend)
|
|
178
|
+
def __BackgroundWorker(self,ClnFrq=100,restlimit=80,UpdateFreq=80,Cleaning=False):
|
|
179
|
+
|
|
180
|
+
self.thread_running=True
|
|
181
|
+
CleanData={}
|
|
182
|
+
Slptime=self.__Timecalculator(ClnFrq,UpdateFreq)
|
|
183
|
+
while not self.stop_event.is_set():
|
|
184
|
+
# print("Inside the Loop")
|
|
185
|
+
# self.stop_event.wait(ClnFrq)
|
|
186
|
+
if self.stop_event.is_set():
|
|
187
|
+
break
|
|
188
|
+
if Cleaning:
|
|
189
|
+
with self.lock:
|
|
190
|
+
# print("Inside the Lock")
|
|
191
|
+
currenttime=int(time.time())
|
|
192
|
+
# Keystodelete=[]
|
|
193
|
+
changes=False
|
|
194
|
+
CleanData=self.Data.copy()
|
|
195
|
+
keys=list(CleanData.keys())
|
|
196
|
+
for ip in keys:
|
|
197
|
+
# print("Checking IPs")
|
|
198
|
+
# print("Key here")
|
|
199
|
+
if(currenttime-CleanData[ip]["LastSeenTime"]>restlimit):
|
|
200
|
+
# print("BYEEE")
|
|
201
|
+
# Keystodelete.append(ip)
|
|
202
|
+
# print("Found IP")
|
|
203
|
+
changes=True
|
|
204
|
+
try:
|
|
205
|
+
del CleanData[ip]
|
|
206
|
+
except Exception as e:
|
|
207
|
+
print(e)
|
|
208
|
+
if changes is True:
|
|
209
|
+
# print("Bye IPPPP")
|
|
210
|
+
self.Data=CleanData
|
|
211
|
+
changes=False
|
|
212
|
+
|
|
213
|
+
self.__Filedumper(Data=self.Data)
|
|
214
|
+
# print("Sleep")
|
|
215
|
+
|
|
216
|
+
# print("Loop End")
|
|
217
|
+
time.sleep(Slptime)
|
|
218
|
+
self.thread_running = False
|
|
219
|
+
def __Timecalculator(self,CleaningFrq,UpdateFreq)->int:
|
|
220
|
+
return UpdateFreq
|
|
221
|
+
|
|
222
|
+
__Rl=__RateLimiter()
|
|
223
|
+
def Ratelimiter(IP_Adrs:str,Cleaning=False,AutoUpdate=False,
|
|
224
|
+
CooldownTime=20,AllowedFreq=8,CleaningFreq=80,ResetTime=8,UpdateFreq=8,Filename=None,FolderPath=None)->int:
|
|
225
|
+
global __Rl
|
|
226
|
+
if (Filename is not None) and (FolderPath is not None):
|
|
227
|
+
__Rl = __RateLimiter(filename=Filename, folder=FolderPath)
|
|
228
|
+
|
|
229
|
+
return (__Rl.API_RL(IP_Adrs=IP_Adrs,Cleaning=Cleaning,AutoUpdate=AutoUpdate,
|
|
230
|
+
CooldownTime=CooldownTime,AllowedFreq=AllowedFreq,CleaningFreq=CleaningFreq,ResetTime=ResetTime,UpdateFreq=UpdateFreq))
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
if __name__=="__main__":
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
v=Ratelimiter("127.0.0.2",Cleaning=0,CleaningFreq=1,CooldownTime=7)
|
|
238
|
+
print(v)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import threading
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class __RateLimiter:
|
|
7
|
+
|
|
8
|
+
def __init__(self):
|
|
9
|
+
self.thread_running = False
|
|
10
|
+
self.Data=dict()
|
|
11
|
+
self.stop_event = threading.Event()
|
|
12
|
+
self.lock = threading.RLock()
|
|
13
|
+
|
|
14
|
+
pass
|
|
15
|
+
def API_RL(self,IP_Adrs:str,Cleaning=False,CleaningFreq=80,CooldownTime=80,AllowedFreq=10,ResetTime=100)->int:
|
|
16
|
+
# if isinstance(ipaddress,str):
|
|
17
|
+
# self.ip=int(ipaddress.ip_address(IP_Adrs))
|
|
18
|
+
# else:print("Here")
|
|
19
|
+
self.Metrics={
|
|
20
|
+
"CooldownTime":CooldownTime,
|
|
21
|
+
"AllowedFreq":AllowedFreq}
|
|
22
|
+
if (Cleaning ) and not self.thread_running:
|
|
23
|
+
self.thread_running=True
|
|
24
|
+
BackgroundThread=threading.Thread(
|
|
25
|
+
|
|
26
|
+
target=self.__BackgroundWorker,
|
|
27
|
+
args=(CleaningFreq,ResetTime,),
|
|
28
|
+
daemon=True)
|
|
29
|
+
BackgroundThread.start()
|
|
30
|
+
|
|
31
|
+
return self.__Validator(ip=IP_Adrs)
|
|
32
|
+
def __Validator(self,ip:int)->int:
|
|
33
|
+
|
|
34
|
+
currenttime=int(time.time())
|
|
35
|
+
error=False
|
|
36
|
+
flag=1
|
|
37
|
+
with self.lock:
|
|
38
|
+
try:
|
|
39
|
+
lastseen=currenttime-self.Data[ip]["LastSeenTime"]
|
|
40
|
+
except KeyError:
|
|
41
|
+
|
|
42
|
+
self.Data[ip]={
|
|
43
|
+
# "WaitTime":0,
|
|
44
|
+
"WaitStamp":0,
|
|
45
|
+
"LastSeenTime":currenttime,
|
|
46
|
+
"Visits":1
|
|
47
|
+
}
|
|
48
|
+
error=True
|
|
49
|
+
self.Data[ip]["LastSeenTime"]=currenttime
|
|
50
|
+
if error is False:
|
|
51
|
+
if lastseen>self.Metrics["CooldownTime"]:
|
|
52
|
+
# self.Data[ip]["WaitTime"]=0
|
|
53
|
+
self.Data[ip]["WaitStamp"]=0
|
|
54
|
+
self.Data[ip]["Visits"]=1
|
|
55
|
+
flag= 1
|
|
56
|
+
else:
|
|
57
|
+
self.Data[ip],flag= self.__RecentVists(Data=self.Data[ip],CrnTime=currenttime)
|
|
58
|
+
|
|
59
|
+
return (flag or error)
|
|
60
|
+
def __RecentVists(self,Data,CrnTime)->tuple: #Solution For DeadLock
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if Data["Visits"]<=self.Metrics["AllowedFreq"]:
|
|
64
|
+
Data["WaitStamp"]=0
|
|
65
|
+
Data["Visits"]+=1
|
|
66
|
+
return (Data,1)
|
|
67
|
+
# timetowait=0
|
|
68
|
+
if Data["WaitStamp"]==0:
|
|
69
|
+
Data["WaitStamp"]=CrnTime+self.Metrics["CooldownTime"]
|
|
70
|
+
Data["Visits"]+=1
|
|
71
|
+
return (Data,self.Metrics["CooldownTime"])
|
|
72
|
+
else:
|
|
73
|
+
timetosend=Data["WaitStamp"]-CrnTime
|
|
74
|
+
Data["Visits"]+=1
|
|
75
|
+
if timetosend<=0:
|
|
76
|
+
Data["WaitStamp"]=0
|
|
77
|
+
Data["Visits"]=0
|
|
78
|
+
timetosend=1
|
|
79
|
+
|
|
80
|
+
return (Data,timetosend)
|
|
81
|
+
def __BackgroundWorker(self,ClnFrq=100,restlimit=100):
|
|
82
|
+
|
|
83
|
+
self.thread_running=True
|
|
84
|
+
self.Data={}
|
|
85
|
+
|
|
86
|
+
while not self.stop_event.is_set():
|
|
87
|
+
# print("Inside the Loop")
|
|
88
|
+
# self.stop_event.wait(ClnFrq)
|
|
89
|
+
if self.stop_event.is_set():
|
|
90
|
+
break
|
|
91
|
+
|
|
92
|
+
with self.lock:
|
|
93
|
+
# print("Inside the Lock")
|
|
94
|
+
currenttime=int(time.time())
|
|
95
|
+
# Keystodelete=[]
|
|
96
|
+
changes=False
|
|
97
|
+
keys=list(self.Data.keys())
|
|
98
|
+
for ip in keys:
|
|
99
|
+
# print("Checking IPs")
|
|
100
|
+
# print("Key here")
|
|
101
|
+
if(currenttime-self.Data[ip]["LastSeenTime"]>restlimit):
|
|
102
|
+
# print("BYEEE")
|
|
103
|
+
# Keystodelete.append(ip)
|
|
104
|
+
# print("Found IP")
|
|
105
|
+
changes=True
|
|
106
|
+
try:
|
|
107
|
+
del self.Data[ip]
|
|
108
|
+
except Exception as e:
|
|
109
|
+
print(e)
|
|
110
|
+
if changes is True:
|
|
111
|
+
# print("Bye IPPPP")
|
|
112
|
+
self.Data=self.Data
|
|
113
|
+
changes=False
|
|
114
|
+
|
|
115
|
+
# print("Sleep")
|
|
116
|
+
|
|
117
|
+
# print("Loop End")
|
|
118
|
+
time.sleep(ClnFrq)
|
|
119
|
+
self.thread_running = False
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
__Rl=__RateLimiter()
|
|
123
|
+
def Ratelimiter(IP_Adrs:str,Cleaning=False,CleaningFreq=80,AllowedFreq=10,ResetTime=100)->int:
|
|
124
|
+
global __Rl
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
return (__Rl.API_RL(IP_Adrs=IP_Adrs,Cleaning=Cleaning,CleaningFreq=CleaningFreq,AllowedFreq=AllowedFreq,ResetTime=ResetTime))
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
if __name__=="__main__":
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
v=Ratelimiter("127.0.0.2",Cleaning=0,CleaningFreq=1)
|
|
135
|
+
print(v)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import json
|
|
5
|
+
import time
|
|
6
|
+
import threading
|
|
7
|
+
import copy
|
|
8
|
+
import sqlite3
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class __RateLimiter:
|
|
12
|
+
__isfileopen :bool = False
|
|
13
|
+
__samplefile = Path(sys.modules['__main__'].__file__).stem
|
|
14
|
+
def __init__(self,filename=__samplefile,filetype="db",folder=None):
|
|
15
|
+
self.thread_running = False
|
|
16
|
+
self.filename=filename
|
|
17
|
+
self.filetype=filetype
|
|
18
|
+
self.Data=dict()
|
|
19
|
+
self.CurrentFile=None
|
|
20
|
+
self.lock = threading.RLock()
|
|
21
|
+
self.stop_event = threading.Event()
|
|
22
|
+
self.fullpath=os.path.join(folder or "", f"{filename}.{filetype}")
|
|
23
|
+
self.cursor=None
|
|
24
|
+
self.connection=None
|
|
25
|
+
self.Validopen=self.__Fileopener()
|
|
26
|
+
self.AutoUpdate=False
|
|
27
|
+
self.Changes=set()
|
|
28
|
+
pass
|
|
29
|
+
def API_RL(self,IP_Adrs:str,Cleaning=False,AutoUpdate=False,
|
|
30
|
+
CooldownTime=20,AllowedFreq=8,CleaningFreq=80,ResetTime=8,
|
|
31
|
+
UpdateFreq=80)->int:
|
|
32
|
+
# if isinstance(ipaddress,str):
|
|
33
|
+
# self.ip=int(ipaddress.ip_address(IP_Adrs))
|
|
34
|
+
# else:print("Here")
|
|
35
|
+
# self.ip=IP_Adrs
|
|
36
|
+
self.AutoUpdate=AutoUpdate
|
|
37
|
+
self.Metrics={
|
|
38
|
+
"CooldownTime":CooldownTime,
|
|
39
|
+
"AllowedFreq":AllowedFreq,
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
# background_thread = threading.Thread(target=self.hehe, args=(start_val,), daemon=True)
|
|
43
|
+
if (Cleaning or (AutoUpdate)) and not self.thread_running:
|
|
44
|
+
try:
|
|
45
|
+
self.thread_running=True
|
|
46
|
+
BackgroundThread=threading.Thread(
|
|
47
|
+
|
|
48
|
+
target=self.__BackgroundWorker,
|
|
49
|
+
args=(CleaningFreq,ResetTime,UpdateFreq,Cleaning,),
|
|
50
|
+
daemon=False)
|
|
51
|
+
BackgroundThread.start()
|
|
52
|
+
except Exception as e:
|
|
53
|
+
print("Erro reached")
|
|
54
|
+
Cleaning=False
|
|
55
|
+
AutoUpdate=False
|
|
56
|
+
return self.__Validator(ip=IP_Adrs)
|
|
57
|
+
def __Fileopener(self)->int:
|
|
58
|
+
if getattr(self, "__isfileopen", False):
|
|
59
|
+
return 0
|
|
60
|
+
data=dict()
|
|
61
|
+
fullpath=self.fullpath
|
|
62
|
+
##Sql implementation here now
|
|
63
|
+
try:
|
|
64
|
+
self.CurrentFile=f"{self.fullpath}"
|
|
65
|
+
self.__isfileopen=True
|
|
66
|
+
self.connection=sqlite3.connect(self.CurrentFile,check_same_thread=False,
|
|
67
|
+
timeout=10.0)
|
|
68
|
+
self.cursor=self.connection.cursor()
|
|
69
|
+
self.cursor.execute('''
|
|
70
|
+
create table if not exists UserIps(
|
|
71
|
+
IP Text primary key,
|
|
72
|
+
Data TEXT
|
|
73
|
+
)''') #Table is created
|
|
74
|
+
self.cursor.execute("SELECT IP, Data FROM UserIps")#Get the data
|
|
75
|
+
self.Data={row[0]: json.loads(row[1]) for row in self.cursor.fetchall()}
|
|
76
|
+
self.__isfileopen=False
|
|
77
|
+
self.connection.commit()
|
|
78
|
+
|
|
79
|
+
except Exception as e:
|
|
80
|
+
print(e)
|
|
81
|
+
return 0
|
|
82
|
+
return 1
|
|
83
|
+
def __Filedumper(self,operation=0,Data=None,update=1)->int:
|
|
84
|
+
# print(Data)
|
|
85
|
+
# if msg is not None: print(msg[0])
|
|
86
|
+
if (update & self.AutoUpdate) :
|
|
87
|
+
return 1
|
|
88
|
+
try:
|
|
89
|
+
with self.lock:
|
|
90
|
+
# if msg is not None: print(msg[1])
|
|
91
|
+
fullpath=self.fullpath
|
|
92
|
+
flag=0
|
|
93
|
+
|
|
94
|
+
if Data is None:
|
|
95
|
+
Data={}
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
#Write the data
|
|
99
|
+
toupdate=list()
|
|
100
|
+
while self.Changes:
|
|
101
|
+
values=self.Changes.pop()
|
|
102
|
+
datatodump=json.dumps(self.Data[values])
|
|
103
|
+
toupdate.append((values,datatodump))
|
|
104
|
+
if toupdate:
|
|
105
|
+
self.cursor.executemany("INSERT OR REPLACE INTO UserIps (IP, Data) VALUES (?, ?)", toupdate)
|
|
106
|
+
self.connection.commit()
|
|
107
|
+
flag=1
|
|
108
|
+
|
|
109
|
+
except Exception as e:
|
|
110
|
+
try :
|
|
111
|
+
with open("Log.txt",'a') as file:
|
|
112
|
+
record=f"{time.time()}:Error is {e}\n"
|
|
113
|
+
file.write(record)
|
|
114
|
+
|
|
115
|
+
flag=0
|
|
116
|
+
except Exception as err:
|
|
117
|
+
flag=0
|
|
118
|
+
|
|
119
|
+
if operation==1:
|
|
120
|
+
self.CurrentFile.close()
|
|
121
|
+
self.__isfileopen=False
|
|
122
|
+
return 1
|
|
123
|
+
else:
|
|
124
|
+
self.__isfileopen=True
|
|
125
|
+
return flag
|
|
126
|
+
except Exception as e:
|
|
127
|
+
with open("Log.txt",'a') as file:
|
|
128
|
+
record=f"{time.time()}:Error is {e}\n"
|
|
129
|
+
file.write(record)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
pass
|
|
134
|
+
def __Validator(self,ip:int)->int:
|
|
135
|
+
|
|
136
|
+
currenttime=int(time.time())
|
|
137
|
+
error=False
|
|
138
|
+
flag=1
|
|
139
|
+
with self.lock:
|
|
140
|
+
try:
|
|
141
|
+
lastseen=currenttime-self.Data[ip]["LastSeenTime"]
|
|
142
|
+
except KeyError:
|
|
143
|
+
|
|
144
|
+
self.Data[ip]={
|
|
145
|
+
# "WaitTime":0,
|
|
146
|
+
"WaitStamp":0,
|
|
147
|
+
"LastSeenTime":currenttime,
|
|
148
|
+
"Visits":1
|
|
149
|
+
}
|
|
150
|
+
error=True
|
|
151
|
+
self.Data[ip]["LastSeenTime"]=currenttime
|
|
152
|
+
if error is False:
|
|
153
|
+
if lastseen>self.Metrics["CooldownTime"]:
|
|
154
|
+
# self.Data[ip]["WaitTime"]=0
|
|
155
|
+
self.Data[ip]["WaitStamp"]=0
|
|
156
|
+
self.Data[ip]["Visits"]=1
|
|
157
|
+
flag= 1
|
|
158
|
+
else:
|
|
159
|
+
Datacopy=copy.deepcopy(self.Data)
|
|
160
|
+
self.Data[ip],flag= self.__RecentVists(Data=Datacopy[ip],CrnTime=currenttime)
|
|
161
|
+
|
|
162
|
+
self.__Filedumper(Data=self.Data)
|
|
163
|
+
self.Changes.add(ip)
|
|
164
|
+
return (flag or error)
|
|
165
|
+
def __RecentVists(self,Data,CrnTime)->tuple: #Solution For DeadLock
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
if Data["Visits"]<=self.Metrics["AllowedFreq"]:
|
|
169
|
+
Data["WaitStamp"]=0
|
|
170
|
+
Data["Visits"]+=1
|
|
171
|
+
return (Data,1)
|
|
172
|
+
# timetowait=0
|
|
173
|
+
if Data["WaitStamp"]==0:
|
|
174
|
+
Data["WaitStamp"]=CrnTime+self.Metrics["CooldownTime"]
|
|
175
|
+
Data["Visits"]+=1
|
|
176
|
+
return (Data,self.Metrics["CooldownTime"])
|
|
177
|
+
else:
|
|
178
|
+
timetosend=Data["WaitStamp"]-CrnTime
|
|
179
|
+
Data["Visits"]+=1
|
|
180
|
+
if timetosend<=0:
|
|
181
|
+
Data["WaitStamp"]=0
|
|
182
|
+
Data["Visits"]=0
|
|
183
|
+
timetosend=1
|
|
184
|
+
|
|
185
|
+
return (Data,timetosend)
|
|
186
|
+
def __BackgroundWorker(self,ClnFrq=100,restlimit=80,UpdateFreq=80,Cleaning=False):
|
|
187
|
+
|
|
188
|
+
self.thread_running=True
|
|
189
|
+
CleanData={}
|
|
190
|
+
Slptime=self.__Timecalculator(ClnFrq,UpdateFreq)
|
|
191
|
+
while not self.stop_event.is_set():
|
|
192
|
+
# print("Inside the Loop")
|
|
193
|
+
# self.stop_event.wait(ClnFrq)
|
|
194
|
+
if self.stop_event.is_set():
|
|
195
|
+
break
|
|
196
|
+
#here write about the clenaing freq
|
|
197
|
+
self.__Filedumper(Data=self.Data,update=0)
|
|
198
|
+
if Cleaning:
|
|
199
|
+
|
|
200
|
+
currenttime=int(time.time())
|
|
201
|
+
try:
|
|
202
|
+
query="""
|
|
203
|
+
DELETE FROM UserIps
|
|
204
|
+
WHERE ? - json_extract(Data, '$.LastSeenTime') > ?
|
|
205
|
+
RETURNING IP
|
|
206
|
+
"""
|
|
207
|
+
self.cursor.execute(query,(currenttime,restlimit))
|
|
208
|
+
deleted_rows = self.cursor.fetchall()
|
|
209
|
+
if deleted_rows:
|
|
210
|
+
self.connection.commit()
|
|
211
|
+
with self.lock:
|
|
212
|
+
for row in deleted_rows:
|
|
213
|
+
deleted_ip=row[0]
|
|
214
|
+
self.Data.pop(deleted_ip,None)
|
|
215
|
+
# except sqlite3.OperationalError as e:
|
|
216
|
+
# #"""THis is the no coloum found error wont be that expsenive and also it will spoil the log.txt so
|
|
217
|
+
# # it is better now to log this error
|
|
218
|
+
# pass
|
|
219
|
+
except Exception as e:
|
|
220
|
+
with open("Log.txt",'a') as file:
|
|
221
|
+
record=f"{time.time()}:Error is {e}\n"
|
|
222
|
+
file.write(record)
|
|
223
|
+
# print("Sleep")
|
|
224
|
+
|
|
225
|
+
# print("Loop End")
|
|
226
|
+
time.sleep(Slptime)
|
|
227
|
+
self.thread_running = False
|
|
228
|
+
def __Timecalculator(self,CleaningFrq,UpdateFreq)->int:
|
|
229
|
+
#Need to create a proper time calcuator here
|
|
230
|
+
return CleaningFrq
|
|
231
|
+
|
|
232
|
+
__Rl=__RateLimiter()
|
|
233
|
+
def Ratelimiter(IP_Adrs:str,Cleaning=False,AutoUpdate=False,
|
|
234
|
+
CooldownTime=20,AllowedFreq=8,CleaningFreq=80,ResetTime=8,UpdateFreq=8,Filename=None,FolderPath=None)->int:
|
|
235
|
+
global __Rl
|
|
236
|
+
if (Filename is not None) and (FolderPath is not None):
|
|
237
|
+
__Rl = __RateLimiter(filename=Filename, folder=FolderPath)
|
|
238
|
+
|
|
239
|
+
# def API_RL(self,IP_Adrs:str,Cleaning=False,AutoUpdate=False,
|
|
240
|
+
# CooldownTime=20,AllowedFreq=8,CleaningFreq=80,ResetTime=8,
|
|
241
|
+
# UpdateFreq=80)
|
|
242
|
+
|
|
243
|
+
return (__Rl.API_RL(IP_Adrs=IP_Adrs,Cleaning=Cleaning,AutoUpdate=AutoUpdate,
|
|
244
|
+
CooldownTime=CooldownTime,AllowedFreq=AllowedFreq,CleaningFreq=CleaningFreq,ResetTime=ResetTime,UpdateFreq=UpdateFreq))
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
if __name__=="__main__":
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
v=Ratelimiter("127.0.0.2",Cleaning=0,CleaningFreq=1,CooldownTime=7)
|
|
251
|
+
print(v)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import sys
|
|
3
|
+
__isfileopen :bool = False
|
|
4
|
+
___samplefile = Path(sys.modules['__main__'].__file__).stem
|
|
5
|
+
def ratelimiter(IP_Adrs:str,Cleaning=False,AutoUpdate=False,
|
|
6
|
+
CooldownTime=20,AllowedFreq=8,CleaningFreq=80,ResetTime=8,UpdateFreq=8,Filename=___samplefile,FolderPath=None,Format=None)->int:
|
|
7
|
+
if Format is None:
|
|
8
|
+
from .ARL_nostorage import Ratelimiter as Rl
|
|
9
|
+
return Rl(IP_Adrs=IP_Adrs,Cleaning=Cleaning,CleaningFreq=CleaningFreq,AllowedFreq=AllowedFreq,ResetTime=ResetTime)
|
|
10
|
+
if (isinstance(Format,str) is False):
|
|
11
|
+
raise TypeError("Only String Option is Availabe for Format Option")
|
|
12
|
+
elif (Format.lower())=="json":
|
|
13
|
+
from .ARL_json import Ratelimiter as Rl
|
|
14
|
+
return Rl(
|
|
15
|
+
IP_Adrs=IP_Adrs,Cleaning=Cleaning,AutoUpdate=AutoUpdate,
|
|
16
|
+
CooldownTime=CooldownTime,AllowedFreq=AllowedFreq,CleaningFreq=CleaningFreq,ResetTime=ResetTime,
|
|
17
|
+
UpdateFreq=UpdateFreq,Filename=Filename,FolderPath=FolderPath)
|
|
18
|
+
elif Format.lower()=="sql" :
|
|
19
|
+
from .ARL_sql import Ratelimiter as Rl
|
|
20
|
+
return Rl(
|
|
21
|
+
IP_Adrs=IP_Adrs,Cleaning=Cleaning,AutoUpdate=AutoUpdate,
|
|
22
|
+
CooldownTime=CooldownTime,AllowedFreq=AllowedFreq,CleaningFreq=CleaningFreq,ResetTime=ResetTime,
|
|
23
|
+
UpdateFreq=UpdateFreq,Filename=Filename,FolderPath=FolderPath)
|
|
24
|
+
else :
|
|
25
|
+
raise TypeError("""
|
|
26
|
+
Format Extensions available here
|
|
27
|
+
is not valid please provide a valid extension,
|
|
28
|
+
Availabe : Sql,json,Nostorage
|
|
29
|
+
Format : json,sql ,(Python (None))
|
|
30
|
+
""")
|
|
31
|
+
|
|
32
|
+
if __name__=="__main__":
|
|
33
|
+
options=[None,"json","sql"]
|
|
34
|
+
ip="111"
|
|
35
|
+
|
|
36
|
+
for i in options:
|
|
37
|
+
ARL(Format=i,IP_Adrs=ip)
|
|
38
|
+
print(f"Format:{i}")
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: apirlpy
|
|
3
|
+
Version: 0.0.4
|
|
4
|
+
Summary: A Python Based API RateLimiter
|
|
5
|
+
Author-email: Shaik Naseer John Ahmed <sknaseer.fez@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Naseer-fez/Apirl-py
|
|
8
|
+
Project-URL: Issues, https://github.com/Naseer-fez/Apirl-py/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.4
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE.txt
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
## How to Run
|
|
17
|
+
```python
|
|
18
|
+
from apirlpy import ratelimiter as APIRL
|
|
19
|
+
#Import the Function
|
|
20
|
+
|
|
21
|
+
# Checking rate limit for a specific IP
|
|
22
|
+
CoolDown=APIRL(IP_Adrs="127.0.0.1", #IP address ofthe Client
|
|
23
|
+
#_______________________________________________________________
|
|
24
|
+
Cleaning=False, #To Enable the Ip Address Cleaning
|
|
25
|
+
CleaningFreq=80,# The Frequency at which the cleaning shoudl occur
|
|
26
|
+
AutoUpdate=False, #If you want to update the Data only after a certain time to reduce I/O operations
|
|
27
|
+
UpdateFreq=70, #(value in sec) of auto update
|
|
28
|
+
CooldownTime=20, #Amount of time differnce the Clients last vist shoudld
|
|
29
|
+
# exced for the IP address cleaner to remove the data
|
|
30
|
+
# *** FOR JSON AND SQL Format only ***
|
|
31
|
+
#If passed to "no stograge" option then this arguments will not be passed to the ratelimiter
|
|
32
|
+
#_______________________________________________________________
|
|
33
|
+
AllowedFreq=8, #The Number of Fair attempts the Client can send the request to the server
|
|
34
|
+
ResetTime=8, # After the Client is denied the access , how much time
|
|
35
|
+
#they need to wait to get the access again
|
|
36
|
+
Filename="Test", # The file name to store the data
|
|
37
|
+
Format="sql", # The File extenstion type which the ratelimiter is going to use to store the data
|
|
38
|
+
# Extensions available are : sql to use sqllite , json to use json format and "None" for No storage option
|
|
39
|
+
# If nothing is mentioned then the data will not be stored
|
|
40
|
+
FolderPath=None # The path at which u want to store this results
|
|
41
|
+
|
|
42
|
+
)
|
|
43
|
+
if CoolDown == 1: #The Client can access the API
|
|
44
|
+
print( "You are a Valid user")
|
|
45
|
+
else: # the Request is denied
|
|
46
|
+
print( f"You need to wait {CoolDown}")
|
|
47
|
+
```
|
|
48
|
+
**This is a test code **
|
|
49
|
+
### The Code Return
|
|
50
|
+
- 1 : Means the Client Can Access the API
|
|
51
|
+
- other than 1: Means the Client Need to wait for the access of the API
|
|
52
|
+
___
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
LICENSE.txt
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/apirlpy/ARL_json.py
|
|
5
|
+
src/apirlpy/ARL_nostorage.py
|
|
6
|
+
src/apirlpy/ARL_sql.py
|
|
7
|
+
src/apirlpy/__init__.py
|
|
8
|
+
src/apirlpy/ratelimiter.py
|
|
9
|
+
src/apirlpy.egg-info/PKG-INFO
|
|
10
|
+
src/apirlpy.egg-info/SOURCES.txt
|
|
11
|
+
src/apirlpy.egg-info/dependency_links.txt
|
|
12
|
+
src/apirlpy.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
apirlpy
|