abstract-solana 0.0.2.82__tar.gz → 0.0.2.84__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.

Potentially problematic release.


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

Files changed (29) hide show
  1. {abstract_solana-0.0.2.82 → abstract_solana-0.0.2.84}/PKG-INFO +1 -1
  2. abstract_solana-0.0.2.84/pyproject.toml +3 -0
  3. {abstract_solana-0.0.2.82 → abstract_solana-0.0.2.84}/setup.py +34 -34
  4. {abstract_solana-0.0.2.82 → abstract_solana-0.0.2.84}/src/abstract_solana/abstract_rpcs/__init__.py +1 -1
  5. abstract_solana-0.0.2.84/src/abstract_solana/abstract_rpcs/rate_limiter.py +177 -0
  6. {abstract_solana-0.0.2.82 → abstract_solana-0.0.2.84}/src/abstract_solana/abstract_rpcs/solana_rpc_client.py +13 -6
  7. {abstract_solana-0.0.2.82 → abstract_solana-0.0.2.84}/src/abstract_solana.egg-info/PKG-INFO +1 -1
  8. {abstract_solana-0.0.2.82 → abstract_solana-0.0.2.84}/src/abstract_solana.egg-info/SOURCES.txt +1 -0
  9. abstract_solana-0.0.2.82/src/abstract_solana/abstract_rpcs/rate_limiter.py +0 -142
  10. {abstract_solana-0.0.2.82 → abstract_solana-0.0.2.84}/README.md +0 -0
  11. {abstract_solana-0.0.2.82 → abstract_solana-0.0.2.84}/setup.cfg +0 -0
  12. {abstract_solana-0.0.2.82 → abstract_solana-0.0.2.84}/src/abstract_solana/__init__.py +0 -0
  13. {abstract_solana-0.0.2.82 → abstract_solana-0.0.2.84}/src/abstract_solana/abstract_rpcs/db_templates.py +0 -0
  14. {abstract_solana-0.0.2.82 → abstract_solana-0.0.2.84}/src/abstract_solana/abstract_rpcs/get_api_gui.py +0 -0
  15. {abstract_solana-0.0.2.82 → abstract_solana-0.0.2.84}/src/abstract_solana/abstract_rpcs/get_body.py +0 -0
  16. {abstract_solana-0.0.2.82 → abstract_solana-0.0.2.84}/src/abstract_solana/abstract_utils/__init__.py +0 -0
  17. {abstract_solana-0.0.2.82 → abstract_solana-0.0.2.84}/src/abstract_solana/abstract_utils/account_key_utils.py +0 -0
  18. {abstract_solana-0.0.2.82 → abstract_solana-0.0.2.84}/src/abstract_solana/abstract_utils/constants.py +0 -0
  19. {abstract_solana-0.0.2.82 → abstract_solana-0.0.2.84}/src/abstract_solana/abstract_utils/genesis_functions.py +0 -0
  20. {abstract_solana-0.0.2.82 → abstract_solana-0.0.2.84}/src/abstract_solana/abstract_utils/index_utils.py +0 -0
  21. {abstract_solana-0.0.2.82 → abstract_solana-0.0.2.84}/src/abstract_solana/abstract_utils/keypair_utils.py +0 -0
  22. {abstract_solana-0.0.2.82 → abstract_solana-0.0.2.84}/src/abstract_solana/abstract_utils/log_message_functions.py +0 -0
  23. {abstract_solana-0.0.2.82 → abstract_solana-0.0.2.84}/src/abstract_solana/abstract_utils/price_utils.py +0 -0
  24. {abstract_solana-0.0.2.82 → abstract_solana-0.0.2.84}/src/abstract_solana/abstract_utils/pubkey_utils.py +0 -0
  25. {abstract_solana-0.0.2.82 → abstract_solana-0.0.2.84}/src/abstract_solana/abstract_utils/signature_data_parse.py +0 -0
  26. {abstract_solana-0.0.2.82 → abstract_solana-0.0.2.84}/src/abstract_solana/abstract_utils/utils.py +0 -0
  27. {abstract_solana-0.0.2.82 → abstract_solana-0.0.2.84}/src/abstract_solana.egg-info/dependency_links.txt +0 -0
  28. {abstract_solana-0.0.2.82 → abstract_solana-0.0.2.84}/src/abstract_solana.egg-info/requires.txt +0 -0
  29. {abstract_solana-0.0.2.82 → abstract_solana-0.0.2.84}/src/abstract_solana.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: abstract_solana
3
- Version: 0.0.2.82
3
+ Version: 0.0.2.84
4
4
  Home-page: https://github.com/AbstractEndeavors/abstract_solana
5
5
  Author: putkoff
6
6
  Author-email: partners@abstractendeavors.com
@@ -0,0 +1,3 @@
1
+ [build-system]
2
+ requires = ["setuptools>=42", "wheel"]
3
+ build-backend = "setuptools.build_meta"
@@ -1,34 +1,34 @@
1
- from time import time
2
- import setuptools
3
- with open("README.md", "r", encoding="utf-8") as fh:
4
- long_description = fh.read()
5
- setuptools.setup(
6
- name='abstract_solana',
7
- version='0.0.2.082',
8
- author='putkoff',
9
- author_email='partners@abstractendeavors.com',
10
- description="",
11
- long_description=long_description,
12
- long_description_content_type='text/markdown',
13
- url='https://github.com/AbstractEndeavors/abstract_solana',
14
- classifiers=[
15
- 'Development Status :: 3 - Alpha',
16
- 'Intended Audience :: Developers',
17
- 'Topic :: Software Development :: Libraries',
18
- 'License :: OSI Approved :: MIT License',
19
- 'Programming Language :: Python :: 3',
20
- 'Programming Language :: Python :: 3.6',
21
- 'Programming Language :: Python :: 3.7',
22
- 'Programming Language :: Python :: 3.8',
23
- 'Programming Language :: Python :: 3.9',
24
- 'Programming Language :: Python :: 3.10',
25
- 'Programming Language :: Python :: 3.11',
26
- 'Programming Language :: Python :: 3.12',
27
- ],
28
- install_requires=['solders','abstract_solcatcher','abstract_utilities','solana'],
29
- package_dir={"": "src"},
30
- packages=setuptools.find_packages(where="src"),
31
- python_requires=">=3.6",
32
- # Add this line to include wheel format in your distribution
33
- setup_requires=['wheel'],
34
- )
1
+ from time import time
2
+ import setuptools
3
+ with open("README.md", "r", encoding="utf-8") as fh:
4
+ long_description = fh.read()
5
+ setuptools.setup(
6
+ name='abstract_solana',
7
+ version='0.0.2.084',
8
+ author='putkoff',
9
+ author_email='partners@abstractendeavors.com',
10
+ description="",
11
+ long_description=long_description,
12
+ long_description_content_type='text/markdown',
13
+ url='https://github.com/AbstractEndeavors/abstract_solana',
14
+ classifiers=[
15
+ 'Development Status :: 3 - Alpha',
16
+ 'Intended Audience :: Developers',
17
+ 'Topic :: Software Development :: Libraries',
18
+ 'License :: OSI Approved :: MIT License',
19
+ 'Programming Language :: Python :: 3',
20
+ 'Programming Language :: Python :: 3.6',
21
+ 'Programming Language :: Python :: 3.7',
22
+ 'Programming Language :: Python :: 3.8',
23
+ 'Programming Language :: Python :: 3.9',
24
+ 'Programming Language :: Python :: 3.10',
25
+ 'Programming Language :: Python :: 3.11',
26
+ 'Programming Language :: Python :: 3.12',
27
+ ],
28
+ install_requires=['solders','abstract_solcatcher','abstract_utilities','solana'],
29
+ package_dir={"": "src"},
30
+ packages=setuptools.find_packages(where="src"),
31
+ python_requires=">=3.6",
32
+ # Add this line to include wheel format in your distribution
33
+ setup_requires=['wheel'],
34
+ )
@@ -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
3
+ from .solana_rpc_client import get_rpc_dict,abstract_solana_rate_limited_call,make_call
4
4
  from .rate_limiter import *
@@ -0,0 +1,177 @@
1
+ import time
2
+ import os
3
+ import json
4
+ from datetime import datetime
5
+
6
+ from abstract_utilities import *
7
+ from abstract_security import get_env_value
8
+ def getAbsFile():
9
+ return os.path.abspath(__file__)
10
+ def getAbsDir():
11
+ return os.path.dirname(getAbsFile())
12
+ def getAbsPath(path):
13
+ return os.path.join(getAbsDir(),path)
14
+ def getSaveStatePath():
15
+ return getAbsPath('rate_limiter_state.json')
16
+ def readSaveState(url1,url2,path=None):
17
+ path= path or getSaveStatePath()
18
+ if not os.path.isfile(path):
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)
22
+ def is_time_interval(time_obj, interval):
23
+ return (time.time() - time_obj) < interval-1
24
+
25
+ def get_mb(sum_list, limit, last_mb):
26
+ return (sum_list + last_mb) > limit
27
+
28
+ def datasize(data):
29
+ if isinstance(data, str):
30
+ size = len(data.encode('utf-8'))
31
+ elif isinstance(data, (bytes, bytearray)):
32
+ size = len(data)
33
+ elif isinstance(data, list) or isinstance(data, dict):
34
+ size = len(json.dumps(data).encode('utf-8'))
35
+ else:
36
+ size = len(str(data).encode('utf-8'))
37
+ return size/1000
38
+ class RateLimiter(metaclass=SingletonMeta):
39
+ def __init__(self, rpc_url=None, fallback_rpc_url=None, env_directory=None,save_state_path = None):
40
+ if not hasattr(self, 'initialized'): # Prevent reinitialization
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
+
54
+ def save_state(self):
55
+ state = {
56
+ 'last_method': self.last_method,
57
+ 'rate_limits': self.rate_limits,
58
+ 'last_mb': self.last_mb,
59
+ 'cooldown_times': self.cooldown_times,
60
+ 'last_url': self.last_url
61
+ }
62
+ safe_dump_to_file(data=state, file_path=self.state_file)
63
+
64
+ def load_state(self):
65
+ state = readSaveState(self.url1,self.url2)
66
+ self.last_method = state.get('last_method')
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]
83
+ return {}
84
+
85
+ def is_all_limit(self, url, method):
86
+ if url == self.url1 and method not in self.last_mb:
87
+ self.last_mb[url][method] = 0
88
+
89
+ if self.set_cooldown(url, method):
90
+ print(f'set_cooldown for method {method} in {url} hit')
91
+ return True
92
+
93
+ # Clean up expired queries for the current URL
94
+ self.rate_limits[url] = [
95
+ query for query in self.rate_limits[url] if is_time_interval(query.get('time') or 0, 30)
96
+ ]
97
+ last_rate_limit = self.get_last_rate_limit(url)
98
+
99
+ # Check data size limits
100
+ total_mb = sum(query.get('data', 0) for query in self.rate_limits[url])
101
+ mb = get_mb(total_mb, 100, self.last_mb[url][method])
102
+ if mb:
103
+ print(f'mb {total_mb} of limit 100 hit')
104
+ return True
105
+
106
+ # Check if the last request for the same method was within 10 seconds
107
+ time_rate = [
108
+ query for query in self.rate_limits[url] if is_time_interval(query.get('time') or 0, 10)
109
+ ]
110
+ if len(time_rate) > 100:
111
+ print(f'time_rate {time_rate} of timerate limit 100 hit')
112
+ return True
113
+
114
+ method_specific_time_rate = [
115
+ query for query in time_rate if query['method'] == method
116
+ ]
117
+ if len(method_specific_time_rate) > 40:
118
+ print(f'method_specific_time_rate {len(method_specific_time_rate)} of method_specific_time_rate limit 40 hit')
119
+ return True
120
+
121
+ return False
122
+
123
+ def log_response(self, method=None, response=None, retry_after=None):
124
+ method = method or 'default_method'
125
+ response = response or {}
126
+ data_size = datasize(response)
127
+ active_url = self.last_url
128
+
129
+ # Handle Retry-After logic
130
+ if retry_after:
131
+ try:
132
+ wait_time = int(retry_after)
133
+ except ValueError:
134
+ retry_after_date = datetime.strptime(retry_after, '%a, %d %b %Y %H:%M:%S GMT')
135
+ wait_time = (retry_after_date - datetime.utcnow()).total_seconds()
136
+ self.set_cooldown(active_url, method, add=max(wait_time, 0))
137
+
138
+ if active_url == self.url1:
139
+ self.rate_limits[active_url].append({'method': method, 'data': data_size, 'time': time.time()})
140
+
141
+ # Clean up expired entries for the current URL
142
+ self.rate_limits[active_url] = [
143
+ query for query in self.rate_limits[active_url] if is_time_interval(query['time'], 30)
144
+ ]
145
+ self.save_state()
146
+ def get_cooldown_for_method(self,url,method):
147
+ wait_time = 0
148
+ if self.set_cooldown(url,method):
149
+ wait_time = int(self.cooldown_times[url][method]) - time.time()
150
+ if wait_time <= 0:
151
+ del self.cooldown_times[url][method]
152
+
153
+ else:
154
+ return wait_time
155
+ return False
156
+ def get_url(self, method=None):
157
+ method = method or 'default_method'
158
+ wait_time = self.get_cooldown_for_method(self.url1,method)
159
+
160
+ if wait_time:
161
+ wait_time = int(self.cooldown_times[self.url1][method]) - time.time()
162
+ if wait_time > 0:
163
+ self.last_url = self.url2
164
+ #retry_after_date = datetime.strptime(str(int(self.cooldown_times[method])), '%a, %d %b %Y %H:%M:%S GMT')
165
+ print(f"{method} is on cooldown for {wait_time} more seconds")
166
+ if method == 'get_url2':
167
+ self.last_url = self.url2
168
+ return self.last_url
169
+ # If fallback URL is selected, skip all limits
170
+
171
+ is_limit = self.is_all_limit(self.url1, method)
172
+ if not is_limit:
173
+ self.last_method = method
174
+ self.last_url = self.url1
175
+ print([is_limit,self.last_url])
176
+ return self.last_url
177
+
@@ -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
- url=url.get('url')
93
- response = postRpcRequest(url=url,**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.82
3
+ Version: 0.0.2.84
4
4
  Home-page: https://github.com/AbstractEndeavors/abstract_solana
5
5
  Author: putkoff
6
6
  Author-email: partners@abstractendeavors.com
@@ -1,4 +1,5 @@
1
1
  README.md
2
+ pyproject.toml
2
3
  setup.py
3
4
  src/abstract_solana/__init__.py
4
5
  src/abstract_solana.egg-info/PKG-INFO
@@ -1,142 +0,0 @@
1
- import time,os,json
2
- from abstract_utilities import *
3
- from abstract_security import *
4
- def getAbsFile():
5
- return os.path.abspath(__file__)
6
- def getAbsDir():
7
- return os.path.dirname(getAbsFile())
8
- def getAbsPath(path):
9
- return os.path.join(getAbsDir(),path)
10
- def getSaveStatePath():
11
- return getAbsPath('rate_limiter_state.json')
12
- def readSaveState():
13
- path= getSaveStatePath()
14
- 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(data=state,file_path=path)
17
- return safe_read_from_json(getSaveStatePath())
18
- def is_time_interval(time_obj, interval):
19
- return (time.time() - time_obj) < interval-1
20
-
21
- def get_mb(sum_list, limit, last_mb):
22
- return (sum_list + last_mb) > limit
23
-
24
- def datasize(data):
25
- if isinstance(data, str):
26
- return len(data.encode('utf-8'))
27
- elif isinstance(data, (bytes, bytearray)):
28
- return len(data)
29
- elif isinstance(data, list) or isinstance(data, dict):
30
- return len(json.dumps(data).encode('utf-8'))
31
- else:
32
- return len(str(data).encode('utf-8'))
33
-
34
- class RateLimiter(metaclass=SingletonMeta):
35
- def __init__(self,rpc_url = None,fallback_rpc_url=None,env_directory=None):
36
- if not hasattr(self, 'initialized'): # Prevent reinitialization
37
- self.initialized = True
38
- self.rpc_url = rpc_url or get_env_value(key="solana_primary_rpc_url",path=env_directory) or "https://api.mainnet-beta.solana.com"
39
- self.fallback_rpc_url = fallback_rpc_url or get_env_value(key="solana_fallback_rpc_url",path=env_directory)
40
- self.initialized = True
41
- self.rate_limit = []
42
- self.last_mb = {}
43
- self.cooldown_time = False
44
- self.url1 = self.rpc_url
45
- self.url2 = self.fallback_rpc_url
46
- self.state_file = getSaveStatePath()
47
- self.last_url = None
48
- self.last_method=None
49
- self.load_state()
50
- def get_url_2(self):
51
- return self.url2
52
-
53
- def save_state(self):
54
- state = {
55
- 'last_method':self.last_method,
56
- 'rate_limit': self.rate_limit,
57
- 'last_mb': self.last_mb,
58
- 'cooldown_time': self.cooldown_time,
59
- 'last_url': self.last_url
60
- }
61
- safe_dump_to_file(data=state,file_path=self.state_file)
62
-
63
- def load_state(self):
64
- state = readSaveState()
65
- self.last_method = state.get('last_method')
66
- self.rate_limit = state.get('rate_limit', [])
67
- self.last_mb = state.get('last_mb', {})
68
- self.last_url = state.get('last_url')
69
- self.cooldown_time = state.get('cooldown_time', False)
70
-
71
- def set_cooldown(self, add=False):
72
- if add:
73
- self.cooldown_time = time.time() + add
74
- if self.cooldown_time and (time.time() > self.cooldown_time):
75
- self.cooldown_time = False
76
- return bool(self.cooldown_time)
77
-
78
- def get_last_rate_limit(self):
79
- if self.rate_limit:
80
- return self.rate_limit[-1]
81
- return {}
82
-
83
- def is_all_limit(self, method):
84
- if method not in self.last_mb:
85
- self.last_mb[method] = 0
86
-
87
- if self.set_cooldown():
88
- return True
89
-
90
- self.rate_limit = [query for query in self.rate_limit if is_time_interval(query.get('time') or 0, 30)]
91
- last_rate_limit = self.get_last_rate_limit()
92
-
93
- # Check if data size exceeds limit
94
- if get_mb(sum(query.get('data', 0) for query in self.rate_limit), 100, self.last_mb[method]):
95
- return True
96
-
97
- # Check if the last request for the same method was within 10 seconds
98
- if self.last_method == method and is_time_interval(last_rate_limit.get('time') or 0, 10):
99
- return True
100
-
101
- # Check if more than 100 requests in the last 10 seconds
102
- time_rate = [query for query in self.rate_limit if is_time_interval(query.get('time') or 0, 10)]
103
- if len(time_rate) > 100:
104
- return True
105
-
106
- # Check if more than 40 requests for the same method in the last 10 seconds
107
- method_specific_time_rate = [query for query in time_rate if query['method'] == method]
108
- if len(method_specific_time_rate) > 40:
109
- return True
110
-
111
- return False
112
-
113
- def log_response(self, method=None, response=None):
114
- method = method or 'default_method'
115
- response = response or {}
116
- data_size = datasize(response)
117
- self.last_mb[method] = data_size
118
-
119
- if self.last_url == self.url1:
120
- self.rate_limit.append({'method': method, 'data': data_size, 'time': time.time()})
121
-
122
- self.rate_limit = [query for query in self.rate_limit if is_time_interval(query['time'], 30)]
123
- self.save_state()
124
-
125
- def get_url(self, method=None):
126
- method = method or 'default_method'
127
- if self.url2 and method == 'get_url_2':
128
- self.last_url = self.url2
129
- return self.url2
130
-
131
- if not self.is_all_limit(method):
132
- self.last_method = method
133
-
134
- self.last_url = self.url1
135
- elif self.url2:
136
- self.last_url = self.url2
137
- else:
138
- return {"rate_limited":"limit has been reached"}
139
- return self.last_url
140
-
141
-
142
-