abstract-solana 0.0.2.7__py3-none-any.whl → 0.0.2.9__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.

Potentially problematic release.


This version of abstract-solana might be problematic. Click here for more details.

@@ -1,4 +1,4 @@
1
1
  from .get_body import *
2
2
  from .db_templates import *
3
- from .solana_rpc_client import get_rpc_dict,abstract_solana_rate_limited_call
4
- from .rate_limit import *
3
+ from .solana_rpc_client import get_rpc_dict,abstract_solana_rate_limited_call,make_call
4
+ from .rate_limiter import *
@@ -1,6 +1,10 @@
1
- import time,os,json
1
+ import time
2
+ import os
3
+ import json
4
+ from datetime import datetime
5
+
2
6
  from abstract_utilities import *
3
- from abstract_security import *
7
+ from abstract_security import get_env_value
4
8
  def getAbsFile():
5
9
  return os.path.abspath(__file__)
6
10
  def getAbsDir():
@@ -9,14 +13,13 @@ def getAbsPath(path):
9
13
  return os.path.join(getAbsDir(),path)
10
14
  def getSaveStatePath():
11
15
  return getAbsPath('rate_limiter_state.json')
12
- def readSaveState():
13
- path= getSaveStatePath()
16
+ def readSaveState(url1,url2,path=None):
17
+ path= path or getSaveStatePath()
14
18
  if not os.path.isfile(path):
15
- state = {'last_method':None,'rate_limit': [],'last_mb': {},'cooldown_time': False,'last_url':None}
16
- safe_dump_to_file(file_path=path,data=state)
17
- return safe_read_from_json(getSaveStatePath())
19
+ state = {'last_method':None,'rate_limits': {url1: [], url2: []},'last_mb': {url1: {}, url2: {}},'cooldown_times': {url1: {}, url2: {}},'last_url':url1}
20
+ safe_dump_to_file(data=state,file_path=path)
21
+ return safe_read_from_json(path)
18
22
  def is_time_interval(time_obj, interval):
19
- print([time.time() - time_obj,interval-1])
20
23
  return (time.time() - time_obj) < interval-1
21
24
 
22
25
  def get_mb(sum_list, limit, last_mb):
@@ -24,118 +27,152 @@ def get_mb(sum_list, limit, last_mb):
24
27
 
25
28
  def datasize(data):
26
29
  if isinstance(data, str):
27
- return len(data.encode('utf-8'))
30
+ size = len(data.encode('utf-8'))
28
31
  elif isinstance(data, (bytes, bytearray)):
29
- return len(data)
32
+ size = len(data)
30
33
  elif isinstance(data, list) or isinstance(data, dict):
31
- return len(json.dumps(data).encode('utf-8'))
34
+ size = len(json.dumps(data).encode('utf-8'))
32
35
  else:
33
- return len(str(data).encode('utf-8'))
34
-
36
+ size = len(str(data).encode('utf-8'))
37
+ return size/1000
35
38
  class RateLimiter(metaclass=SingletonMeta):
36
- def __init__(self,rpc_url = None,fallback_rpc_url=None,env_directory=None):
39
+ def __init__(self, rpc_url=None, fallback_rpc_url=None, env_directory=None,save_state_path = None):
37
40
  if not hasattr(self, 'initialized'): # Prevent reinitialization
38
- self.initialized = True
39
- self.rpc_url = rpc_url or get_env_value(key="solana_primary_rpc_url",path=env_directory) or "https://api.mainnet-beta.solana.com"
40
- self.fallback_rpc_url = fallback_rpc_url or get_env_value(key="solana_fallback_rpc_url",path=env_directory)
41
- self.initialized = True
42
- self.rate_limit = []
43
- self.last_mb = {}
44
- self.cooldown_time = False
45
- self.url1 = self.rpc_url
46
- self.url2 = self.fallback_rpc_url
47
- self.state_file = getSaveStatePath()
48
- self.last_url = None
49
- self.last_method=None
50
- self.load_state()
51
- def get_url_2(self):
52
- return self.url2
41
+ self.initialized = True
42
+ self.rpc_url = rpc_url or get_env_value(key="solana_primary_rpc_url", path=env_directory) or "http://api.mainnet-beta.solana.com"
43
+ self.fallback_rpc_url = fallback_rpc_url or get_env_value(key="solana_fallback_rpc_url", path=env_directory)
44
+ self.state_file = save_state_path or getSaveStatePath()
45
+ self.url1 = self.rpc_url
46
+ self.url2 = self.fallback_rpc_url
47
+ self.rate_limits = {self.url1: [], self.url2: []} # Separate rate limits for each URL
48
+ self.last_mb = {self.url1: {}, self.url2: {}}
49
+ self.cooldown_times = {self.url1: {}, self.url2: {}} # Separate cooldowns for each URL
50
+ self.last_url = None
51
+ self.last_method = None
52
+ self.load_state()
53
53
 
54
54
  def save_state(self):
55
55
  state = {
56
- 'last_method':self.last_method,
57
- 'rate_limit': self.rate_limit,
56
+ 'last_method': self.last_method,
57
+ 'rate_limits': self.rate_limits,
58
58
  'last_mb': self.last_mb,
59
- 'cooldown_time': self.cooldown_time,
59
+ 'cooldown_times': self.cooldown_times,
60
60
  'last_url': self.last_url
61
61
  }
62
- safe_dump_to_file(self.state_file, state)
62
+ safe_dump_to_file(data=state, file_path=self.state_file)
63
63
 
64
64
  def load_state(self):
65
- state = readSaveState()
65
+ state = readSaveState(self.url1,self.url2)
66
66
  self.last_method = state.get('last_method')
67
- self.rate_limit = state.get('rate_limit', [])
68
- self.last_mb = state.get('last_mb', {})
69
- self.last_url = state.get('last_url')
70
- self.cooldown_time = state.get('cooldown_time', False)
71
-
72
- def set_cooldown(self, add=False):
73
- if add:
74
- self.cooldown_time = time.time() + add
75
- if self.cooldown_time and (time.time() > self.cooldown_time):
76
- self.cooldown_time = False
77
- return bool(self.cooldown_time)
78
-
79
- def get_last_rate_limit(self):
80
- if self.rate_limit:
81
- return self.rate_limit[-1]
67
+ self.rate_limits = state.get('rate_limits', {self.url1: [], self.url2: []})
68
+ self.last_mb = state.get('last_mb', {self.url1: {}, self.url2: {}})
69
+ self.cooldown_times = state.get('cooldown_times', {self.url1: {}, self.url2: {}})
70
+
71
+ def set_cooldown(self, url, method=None, add=False):
72
+ if method:
73
+ if add:
74
+ self.cooldown_times[url][method] = time.time() + add
75
+ if method in self.cooldown_times[url] and time.time() > self.cooldown_times[url][method]:
76
+ del self.cooldown_times[url][method]
77
+ return method in self.cooldown_times[url]
78
+ return False
79
+
80
+ def get_last_rate_limit(self, url):
81
+ if self.rate_limits[url]:
82
+ return self.rate_limits[url][-1]
82
83
  return {}
83
84
 
84
- def is_all_limit(self, method):
85
- if method not in self.last_mb:
85
+ def is_all_limit(self, url, method):
86
+ if url == self.url1:
87
+ if method not in self.last_mb:
86
88
  self.last_mb[method] = 0
87
89
 
88
- if self.set_cooldown():
90
+ if self.set_cooldown(url, method):
91
+ print(f'set_cooldown for method {method} in {url} hit')
89
92
  return True
90
93
 
91
- self.rate_limit = [query for query in self.rate_limit if is_time_interval(query.get('time') or 0, 30)]
92
- last_rate_limit = self.get_last_rate_limit()
93
-
94
- # Check if data size exceeds limit
95
- if get_mb(sum(query.get('data', 0) for query in self.rate_limit), 100, self.last_mb[method]):
94
+ # Clean up expired queries for the current URL
95
+ self.rate_limits[url] = [
96
+ query for query in self.rate_limits[url] if is_time_interval(query.get('time') or 0, 30)
97
+ ]
98
+ last_rate_limit = self.get_last_rate_limit(url)
99
+
100
+ # Check data size limits
101
+ total_mb = sum(query.get('data', 0) for query in self.rate_limits[url])
102
+ mb = get_mb(total_mb, 100, self.last_mb[url][method])
103
+ if mb:
104
+ print(f'mb {total_mb} of limit 100 hit')
96
105
  return True
97
106
 
98
107
  # Check if the last request for the same method was within 10 seconds
99
- if self.last_method == method and is_time_interval(last_rate_limit.get('time') or 0, 10):
100
- return True
101
-
102
- # Check if more than 100 requests in the last 10 seconds
103
- time_rate = [query for query in self.rate_limit if is_time_interval(query.get('time') or 0, 10)]
108
+ time_rate = [
109
+ query for query in self.rate_limits[url] if is_time_interval(query.get('time') or 0, 10)
110
+ ]
104
111
  if len(time_rate) > 100:
112
+ print(f'time_rate {time_rate} of timerate limit 100 hit')
105
113
  return True
106
114
 
107
- # Check if more than 40 requests for the same method in the last 10 seconds
108
- method_specific_time_rate = [query for query in time_rate if query['method'] == method]
115
+ method_specific_time_rate = [
116
+ query for query in time_rate if query['method'] == method
117
+ ]
109
118
  if len(method_specific_time_rate) > 40:
119
+ print(f'method_specific_time_rate {len(method_specific_time_rate)} of method_specific_time_rate limit 40 hit')
110
120
  return True
111
121
 
112
122
  return False
113
123
 
114
- def log_response(self, method=None, response_data=None):
124
+ def log_response(self, method=None, response=None, retry_after=None):
115
125
  method = method or 'default_method'
116
- response_data = response_data or {}
117
- data_size = datasize(response_data)
118
- self.last_mb[method] = data_size
119
-
120
- if self.last_url == self.url1:
121
- self.rate_limit.append({'method': method, 'data': data_size, 'time': time.time()})
122
-
123
- self.rate_limit = [query for query in self.rate_limit if is_time_interval(query['time'], 30)]
126
+ response = response or {}
127
+ data_size = datasize(response)
128
+ active_url = self.last_url
129
+
130
+ # Handle Retry-After logic
131
+ if retry_after:
132
+ try:
133
+ wait_time = int(retry_after)
134
+ except ValueError:
135
+ retry_after_date = datetime.strptime(retry_after, '%a, %d %b %Y %H:%M:%S GMT')
136
+ wait_time = (retry_after_date - datetime.utcnow()).total_seconds()
137
+ self.set_cooldown(active_url, method, add=max(wait_time, 0))
138
+
139
+ if active_url == self.url1:
140
+ self.rate_limits[active_url].append({'method': method, 'data': data_size, 'time': time.time()})
141
+
142
+ # Clean up expired entries for the current URL
143
+ self.rate_limits[active_url] = [
144
+ query for query in self.rate_limits[active_url] if is_time_interval(query['time'], 30)
145
+ ]
124
146
  self.save_state()
125
-
147
+ def get_cooldown_for_method(self,url,method):
148
+ wait_time = 0
149
+ if self.set_cooldown(url,method):
150
+ wait_time = int(self.cooldown_times[url][method]) - time.time()
151
+ if wait_time <= 0:
152
+ del self.cooldown_times[url][method]
153
+
154
+ else:
155
+ return wait_time
156
+ return False
126
157
  def get_url(self, method=None):
127
158
  method = method or 'default_method'
128
- if self.url2 and method == 'get_url_2':
129
- self.last_url = self.url2
130
- return self.url2
159
+ wait_time = self.get_cooldown_for_method(self.url1,method)
131
160
 
132
- if not self.is_all_limit(method):
161
+ if wait_time:
162
+ wait_time = int(self.cooldown_times[self.url1][method]) - time.time()
163
+ if wait_time > 0:
164
+ self.last_url = self.url2
165
+ #retry_after_date = datetime.strptime(str(int(self.cooldown_times[method])), '%a, %d %b %Y %H:%M:%S GMT')
166
+ print(f"{method} is on cooldown for {wait_time} more seconds")
167
+ if method == 'get_url2':
168
+ self.last_url = self.url2
169
+ return self.last_url
170
+ # If fallback URL is selected, skip all limits
171
+
172
+ is_limit = self.is_all_limit(self.url1, method)
173
+ if not is_limit:
133
174
  self.last_method = method
134
-
135
175
  self.last_url = self.url1
136
- elif self.url2:
137
- self.last_url = self.url2
138
- else:
139
- return {"rate_limited":"limit has been reached"}
176
+ print([is_limit,self.last_url])
140
177
  return self.last_url
141
178
 
@@ -1,11 +1,11 @@
1
1
  from solana.rpc.core import _ClientCore
2
2
  from typing import Dict, List, Optional, Sequence, Union
3
3
  from solana.rpc.commitment import Commitment, Finalized
4
- from ..abstract_utils.pubkey_utils import get_pubkey,get_sigkey
4
+ from .rate_limiter import RateLimiter
5
+ from ..abstract_utils.pubkey_utils import *
5
6
  import inspect,asyncio,json,requests
6
- from abstract_apis import get_url,make_endpoint,get_headers,asyncPostRequest,get_async_response,get_headers
7
+ from abstract_apis import *
7
8
  from abstract_utilities import is_number
8
- from .rate_limiter import RateLimiter
9
9
  rate_limiter = RateLimiter()
10
10
  def convert_to_lower(string_obj):
11
11
  return ''.join(f"_{char.lower()}" if char.isupper() else char for char in str(string_obj))
@@ -85,10 +85,17 @@ def get_rpc_dict(endpoint,*args,**kwargs):
85
85
  kwargs = get_conversions(variables,*args,**kwargs)
86
86
  kwargs = json.loads(str(call_function(function,**kwargs)))
87
87
  return kwargs
88
- def abstract_solana_rate_limited_call(endpoint,*args,**kwargs):
89
- rpc_dict = get_rpc_dict(endpoint,*args,**kwargs)
90
- url = rate_limiter.get_url(rpc_dict.get('method'))
91
- if isinstance(url,dict):
92
- return url
93
- response = get_async_response(asyncPostRequest,url=url,endpoint=endpoint,data=rpc_dict)
88
+ def make_call(url,body):
89
+ return postRpcRequest(url=url,**body,retry_after =True,status_code=True, headers=get_headers())
90
+ def abstract_solana_rate_limited_call(method, *args, **kwargs):
91
+ # Build the request body
92
+ body = get_rpc_dict(method, *args, **kwargs)
93
+ body_method = body.get('method')
94
+ url = rate_limiter.get_url(body_method)
95
+ response,status_code,retry_after = make_call(url,body)
96
+ rate_limiter.log_response(body_method, response, retry_after)
97
+ if url == rate_limiter.url1 and status_code == 429:
98
+ url = rate_limiter.get_url('get_url2')
99
+ response,status_code,retry_after = make_call(url,body)
100
+ rate_limiter.log_response(body_method, response)
94
101
  return response
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: abstract_solana
3
- Version: 0.0.2.7
3
+ Version: 0.0.2.9
4
4
  Home-page: https://github.com/AbstractEndeavors/abstract_solana
5
5
  Author: putkoff
6
6
  Author-email: partners@abstractendeavors.com
@@ -1,10 +1,10 @@
1
1
  abstract_solana/__init__.py,sha256=uEPtTF-WPCIwpwR8OgAI5prloYQpPbx-YExFgzZvFkQ,59
2
- abstract_solana/abstract_rpcs/__init__.py,sha256=n9WKddaP3T-9sje4KHJ1sXiqRZxg8Jp-jzRbq8cGo9I,156
2
+ abstract_solana/abstract_rpcs/__init__.py,sha256=LIkUCWcuzUWVN1WjzcXjQ9Pl7cbcN8TwknXir-Uk34c,168
3
3
  abstract_solana/abstract_rpcs/db_templates.py,sha256=sjdHfHIq9bO6VuDm3hwzn46NUrXXrGnB0knYNeVU7k8,29839
4
4
  abstract_solana/abstract_rpcs/get_api_gui.py,sha256=OZ61HHb0gwdYjirwFFmnwk8z9x5np3cecCj9MdmuF8U,12372
5
5
  abstract_solana/abstract_rpcs/get_body.py,sha256=UV85217q7mIpYOhVZdnzfmgZxD3QM0w0J0oevXyYtdE,51272
6
- abstract_solana/abstract_rpcs/rate_limiter.py,sha256=5QHubBRUnjH5q-itTwv7h8y8Z_g5YeNPEpvxrkrUwzY,5267
7
- abstract_solana/abstract_rpcs/solana_rpc_client.py,sha256=gVFk5ulsJL9nJ8hm8PvZgflprjKYS1AB6QtYfud8k8U,3845
6
+ abstract_solana/abstract_rpcs/rate_limiter.py,sha256=SjU2i1MZyyLdH6Gyx3fKBScO7w9UVHVF4P9bGT5vT-A,7267
7
+ abstract_solana/abstract_rpcs/solana_rpc_client.py,sha256=s-Mg0mDcDwjU3dm6nK_U6wWysaWTLoC4eXpSHbYsbyY,4132
8
8
  abstract_solana/abstract_utils/__init__.py,sha256=HCbBVQ5BIFCVkFqqTMHp1Y__YQAO4HTq_KHVdMCe89U,296
9
9
  abstract_solana/abstract_utils/account_key_utils.py,sha256=VMJd4GOTK1vn8UZsfXDnjxDOGoQWGY6fvflJqPZ7Xvs,877
10
10
  abstract_solana/abstract_utils/constants.py,sha256=cSmCKzQiNZocX1YkKYrdY-O449aYhi7BT_j-45HZN-E,1418
@@ -20,7 +20,7 @@ abstract_solana/pump_functions/__init__.py,sha256=BiRxwJd1JWwEft63zqYwZ_Xs6UDp4h
20
20
  abstract_solana/pump_functions/buy_sell_pump.py,sha256=gjv_1et20s1Li0ygcURofO29VPkO1v-a5G5Bo_sZs_c,7860
21
21
  abstract_solana/pump_functions/pump_fun_keys.py,sha256=BeWbV9_wd-c6ydF33drW-gZBDPWolbsMZL4cNhP3eOU,8537
22
22
  abstract_solana/pump_functions/token_utils.py,sha256=O-Fgj3L1NhND-k4INa3WvLAEXg2N9u1fVqyLFzn1PwM,2714
23
- abstract_solana-0.0.2.7.dist-info/METADATA,sha256=qmhPPaDQv-x9Xtp5SIb88PLnAHX2OUiEmz8VJPU-sFI,980
24
- abstract_solana-0.0.2.7.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
25
- abstract_solana-0.0.2.7.dist-info/top_level.txt,sha256=SsJYent8eZQ0FU2jmP8wTj7aFZFhNwxxP-5cCTQ2B-o,16
26
- abstract_solana-0.0.2.7.dist-info/RECORD,,
23
+ abstract_solana-0.0.2.9.dist-info/METADATA,sha256=d2CUyfnnY_OgbVFtNo6RNPmQg_kEsFy-TaU-mv8KpEk,980
24
+ abstract_solana-0.0.2.9.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
25
+ abstract_solana-0.0.2.9.dist-info/top_level.txt,sha256=SsJYent8eZQ0FU2jmP8wTj7aFZFhNwxxP-5cCTQ2B-o,16
26
+ abstract_solana-0.0.2.9.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.3.0)
2
+ Generator: setuptools (75.5.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5