abstract-solcatcher-database 0.0.0.1__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.
- abstract_solcatcher_database/__init__.py +6 -0
- abstract_solcatcher_database/dbUtils.py +327 -0
- abstract_solcatcher_database/db_funcs.py +454 -0
- abstract_solcatcher_database/managers/__init__.py +1 -0
- abstract_solcatcher_database/managers/meta_data_manager.py +120 -0
- abstract_solcatcher_database/pair_updates.py +186 -0
- abstract_solcatcher_database/python/__init__.py +2 -0
- abstract_solcatcher_database/python/db/__init__.py +2 -0
- abstract_solcatcher_database/python/db/dbConfig.py +51 -0
- abstract_solcatcher_database/python/db/dbQueries.py +21 -0
- abstract_solcatcher_database/python/utils/__init__.py +2 -0
- abstract_solcatcher_database/python/utils/pathUtils.py +15 -0
- abstract_solcatcher_database/python/utils/queryUtils.py +8 -0
- abstract_solcatcher_database/query_functions/__init__.py +3 -0
- abstract_solcatcher_database/query_functions/call_functions/__init__.py +7 -0
- abstract_solcatcher_database/query_functions/call_functions/call_account_data.py +17 -0
- abstract_solcatcher_database/query_functions/call_functions/call_log_data.py +11 -0
- abstract_solcatcher_database/query_functions/call_functions/call_meta_data.py +25 -0
- abstract_solcatcher_database/query_functions/call_functions/call_misc.py +66 -0
- abstract_solcatcher_database/query_functions/call_functions/call_pair_data.py +48 -0
- abstract_solcatcher_database/query_functions/call_functions/call_transactions_data.py +25 -0
- abstract_solcatcher_database/query_functions/call_functions/misc_calls.py +54 -0
- abstract_solcatcher_database/query_functions/call_functions/queries.py +3 -0
- abstract_solcatcher_database/query_functions/filter_functions/__init__.py +1 -0
- abstract_solcatcher_database/query_functions/filter_functions/filter_data_functions.py +62 -0
- abstract_solcatcher_database/query_functions/filter_functions/queries.py +3 -0
- abstract_solcatcher_database/query_functions/queries.py +3 -0
- abstract_solcatcher_database/query_functions/query_functions/__init__.py +2 -0
- abstract_solcatcher_database/query_functions/query_functions/fetch_utils.py +105 -0
- abstract_solcatcher_database/query_functions/query_functions/queries.py +132 -0
- abstract_solcatcher_database/query_functions/utils/__init__.py +2 -0
- abstract_solcatcher_database/query_functions/utils/get_time_data.py +34 -0
- abstract_solcatcher_database/query_functions/utils/utils.py +78 -0
- abstract_solcatcher_database/transaction_utils/__init__.py +3 -0
- abstract_solcatcher_database/transaction_utils/charts/__init__.py +1 -0
- abstract_solcatcher_database/transaction_utils/charts/price_chart.py +131 -0
- abstract_solcatcher_database/transaction_utils/pair_txns/__init__.py +2 -0
- abstract_solcatcher_database/transaction_utils/pair_txns/pool_metrics.py +255 -0
- abstract_solcatcher_database/transaction_utils/pair_txns/transaction_processor.py +428 -0
- abstract_solcatcher_database/transaction_utils/user_txns/__init__.py +2 -0
- abstract_solcatcher_database/transaction_utils/user_txns/tally_profits.py +35 -0
- abstract_solcatcher_database/transaction_utils/user_txns/tally_utils.py +53 -0
- abstract_solcatcher_database/transaction_utils/user_txns/transaction_history.py +115 -0
- abstract_solcatcher_database/utils/__init__.py +3 -0
- abstract_solcatcher_database/utils/get_time_data.py +182 -0
- abstract_solcatcher_database/utils/image_utils.py +120 -0
- abstract_solcatcher_database/utils/utils.py +103 -0
- abstract_solcatcher_database-0.0.0.1.dist-info/METADATA +37 -0
- abstract_solcatcher_database-0.0.0.1.dist-info/RECORD +51 -0
- abstract_solcatcher_database-0.0.0.1.dist-info/WHEEL +5 -0
- abstract_solcatcher_database-0.0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import psycopg2,logging,traceback
|
|
2
|
+
from psycopg2.extras import DictCursor
|
|
3
|
+
from psycopg2 import sql, pool
|
|
4
|
+
from abstract_utilities import make_list,SingletonMeta
|
|
5
|
+
from abstract_security import get_env_value
|
|
6
|
+
from abstract_utilities import get_logFile
|
|
7
|
+
import logging,requests,httpx
|
|
8
|
+
from logging.handlers import RotatingFileHandler
|
|
9
|
+
from .python import *
|
|
10
|
+
from typing import *
|
|
11
|
+
from psycopg2.extras import RealDictCursor
|
|
12
|
+
SOLCATCHER_POSTGRESQL_DBURL = get_env_value("SOLCATCHER_POSTGRESQL_DBURL")
|
|
13
|
+
from threading import Lock
|
|
14
|
+
from abstract_apis import get_headers
|
|
15
|
+
from .python.db.dbConfig import getConnection
|
|
16
|
+
# Load queries from JSON file
|
|
17
|
+
# Asynchronous function to call Solcatcher TS
|
|
18
|
+
async def async_call_solcatcher_ts(endpoint: str, **kwargs) -> Optional[Dict[str, Any]]:
|
|
19
|
+
async with httpx.AsyncClient() as client:
|
|
20
|
+
try:
|
|
21
|
+
response = await client.post(
|
|
22
|
+
url=f'https://solcatcher.io/ts/{endpoint}',
|
|
23
|
+
json=kwargs,
|
|
24
|
+
headers=get_headers()
|
|
25
|
+
)
|
|
26
|
+
response.raise_for_status()
|
|
27
|
+
response_data = response.json()
|
|
28
|
+
if isinstance(response_data, dict):
|
|
29
|
+
return response_data.get('result', response_data)
|
|
30
|
+
return response_data
|
|
31
|
+
except httpx.HTTPError as http_err:
|
|
32
|
+
logger.error(f"HTTP error occurred in async_call_solcatcher_ts: {http_err}")
|
|
33
|
+
return None
|
|
34
|
+
except Exception as err:
|
|
35
|
+
logger.error(f"An error occurred in async_call_solcatcher_ts: {err}")
|
|
36
|
+
return None
|
|
37
|
+
def call_solcatcher_ts(endpoint: str, **kwargs) -> Optional[Dict[str, Any]]:
|
|
38
|
+
try:
|
|
39
|
+
response = requests.post(
|
|
40
|
+
url=f'https://solcatcher.io/ts/{endpoint}',
|
|
41
|
+
json=kwargs,
|
|
42
|
+
headers=get_headers()
|
|
43
|
+
)
|
|
44
|
+
response.raise_for_status()
|
|
45
|
+
response_data = response.json()
|
|
46
|
+
if isinstance(response_data, dict):
|
|
47
|
+
return response_data.get('result', response_data)
|
|
48
|
+
return response_data
|
|
49
|
+
except httpx.HTTPError as http_err:
|
|
50
|
+
logger.error(f"HTTP error occurred in async_call_solcatcher_ts: {http_err}")
|
|
51
|
+
return None
|
|
52
|
+
except Exception as err:
|
|
53
|
+
logger.error(f"An error occurred in async_call_solcatcher_ts: {err}")
|
|
54
|
+
return None
|
|
55
|
+
# Asynchronous function to call Solcatcher DB
|
|
56
|
+
async def async_call_solcatcher_db(endpoint: str, **kwargs) -> Optional[Dict[str, Any]]:
|
|
57
|
+
async with httpx.AsyncClient() as client:
|
|
58
|
+
try:
|
|
59
|
+
response = await client.post(
|
|
60
|
+
url=f'https://solcatcher.io/dbCalls/{endpoint}',
|
|
61
|
+
json=kwargs,
|
|
62
|
+
headers=get_headers()
|
|
63
|
+
)
|
|
64
|
+
response.raise_for_status()
|
|
65
|
+
response_data = response.json()
|
|
66
|
+
if isinstance(response_data, dict):
|
|
67
|
+
return response_data.get('result', response_data)
|
|
68
|
+
return response_data
|
|
69
|
+
except httpx.HTTPError as http_err:
|
|
70
|
+
logger.error(f"HTTP error occurred in async_call_solcatcher_db: {http_err}")
|
|
71
|
+
return None
|
|
72
|
+
except Exception as err:
|
|
73
|
+
logger.error(f"An error occurred in async_call_solcatcher_db: {err}")
|
|
74
|
+
return None
|
|
75
|
+
# Asynchronous function to call Solcatcher DB
|
|
76
|
+
def call_solcatcher_db(endpoint: str, **kwargs) -> Optional[Dict[str, Any]]:
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
response = requests.post(
|
|
80
|
+
url=f'https://solcatcher.io/dbCalls/{endpoint}',
|
|
81
|
+
json=kwargs,
|
|
82
|
+
headers=get_headers()
|
|
83
|
+
)
|
|
84
|
+
response.raise_for_status()
|
|
85
|
+
response_data = response.json()
|
|
86
|
+
if isinstance(response_data, dict):
|
|
87
|
+
return response_data.get('result', response_data)
|
|
88
|
+
return response_data
|
|
89
|
+
except httpx.HTTPError as http_err:
|
|
90
|
+
logger.error(f"HTTP error occurred in async_call_solcatcher_db: {http_err}")
|
|
91
|
+
return None
|
|
92
|
+
except Exception as err:
|
|
93
|
+
logger.error(f"An error occurred in async_call_solcatcher_db: {err}")
|
|
94
|
+
return None
|
|
95
|
+
logger=get_logFile('db')
|
|
96
|
+
def get_connection():
|
|
97
|
+
"""Get a connection from the pool."""
|
|
98
|
+
try:
|
|
99
|
+
logger.debug("Acquiring connection...")
|
|
100
|
+
conn = getConnection()
|
|
101
|
+
logger.debug("Connection acquired.")
|
|
102
|
+
return conn
|
|
103
|
+
except Exception as e:
|
|
104
|
+
logger.error(f"Error getting a connection from the pool: {e}")
|
|
105
|
+
return None
|
|
106
|
+
def execute_query(query, values=None):
|
|
107
|
+
conn = get_connection()
|
|
108
|
+
try:
|
|
109
|
+
with conn.cursor(cursor_factory=RealDictCursor) as cursor:
|
|
110
|
+
logger.debug(f"Executing query: {query} with values: {values}")
|
|
111
|
+
cursor.execute(query, values)
|
|
112
|
+
if query.strip().lower().startswith("select"):
|
|
113
|
+
results = cursor.fetchall() # Returns list of dictionaries
|
|
114
|
+
return results
|
|
115
|
+
else:
|
|
116
|
+
conn.commit()
|
|
117
|
+
return None
|
|
118
|
+
except Exception as e:
|
|
119
|
+
logger.error(f"Error executing query: {query} | Values: {values} | Error: {e}")
|
|
120
|
+
raise
|
|
121
|
+
finally:
|
|
122
|
+
release_connection(conn)
|
|
123
|
+
def release_connection(conn):
|
|
124
|
+
"""Release a connection back to the pool."""
|
|
125
|
+
try:
|
|
126
|
+
if conn:
|
|
127
|
+
logger.debug("Releasing connection...")
|
|
128
|
+
conn.close() # Use conn.close() to release the connection
|
|
129
|
+
logger.debug("Connection released.")
|
|
130
|
+
except Exception as e:
|
|
131
|
+
logger.error(f"Error releasing the connection back to the pool: {e}")
|
|
132
|
+
class columnNamesManager(metaclass=SingletonMeta):
|
|
133
|
+
def __init__(self):
|
|
134
|
+
if not hasattr(self, 'initialized'):
|
|
135
|
+
self.initialized = True
|
|
136
|
+
self.columnNames = {}
|
|
137
|
+
self.lock = Lock()
|
|
138
|
+
|
|
139
|
+
def get_column_names(self, tableName, schema='public'):
|
|
140
|
+
with self.lock:
|
|
141
|
+
if tableName not in self.columnNames:
|
|
142
|
+
self.columnNames[tableName] = self.fetch_column_names(tableName, schema)
|
|
143
|
+
return self.columnNames[tableName]
|
|
144
|
+
|
|
145
|
+
def fetch_column_names(self, tableName, schema='public'):
|
|
146
|
+
"""
|
|
147
|
+
Query column names for a table in a schema using information_schema.columns.
|
|
148
|
+
"""
|
|
149
|
+
query = sql.SQL("""
|
|
150
|
+
SELECT column_name
|
|
151
|
+
FROM information_schema.columns
|
|
152
|
+
WHERE table_name = %s AND table_schema = %s
|
|
153
|
+
ORDER BY ordinal_position;
|
|
154
|
+
""")
|
|
155
|
+
|
|
156
|
+
results = execute_query(query, [tableName, schema])
|
|
157
|
+
|
|
158
|
+
if not results:
|
|
159
|
+
logger.warning(f"No columns found for table {tableName} in schema {schema}")
|
|
160
|
+
|
|
161
|
+
return [row['column_name'] for row in results]
|
|
162
|
+
|
|
163
|
+
def zip_rows(self, tableName, rows, schema='public'):
|
|
164
|
+
column_names = self.get_column_names(tableName, schema)
|
|
165
|
+
return [dict(zip(column_names, row)) for row in rows]
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def run_query(query, values=()):
|
|
171
|
+
"""
|
|
172
|
+
Run a SQL query and return the result.
|
|
173
|
+
"""
|
|
174
|
+
conn = get_connection()
|
|
175
|
+
try:
|
|
176
|
+
with conn.cursor(cursor_factory=RealDictCursor) as cursor:
|
|
177
|
+
logger.debug(f"Running query: {query} with values: {values}")
|
|
178
|
+
cursor.execute(query, values)
|
|
179
|
+
if query.strip().lower().startswith("select"):
|
|
180
|
+
return cursor.fetchall() # Return results as list of dictionaries
|
|
181
|
+
conn.commit() # Commit for non-SELECT queries
|
|
182
|
+
return None
|
|
183
|
+
except Exception as e:
|
|
184
|
+
logger.error(f"Error running query: {query} | Values: {values} | Error: {e}")
|
|
185
|
+
raise
|
|
186
|
+
finally:
|
|
187
|
+
release_connection(conn)
|
|
188
|
+
|
|
189
|
+
def aggregate_rows(query, values=None, errorMsg='Error Fetching Rows'):
|
|
190
|
+
|
|
191
|
+
try:
|
|
192
|
+
execute_query(query, values=values)
|
|
193
|
+
except Exception as e:
|
|
194
|
+
error_details = f"{errorMsg}:\n{str(e)}\nTraceback:\n{traceback.format_exc()}"
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def fetch_any_combo(columnNames='*', tableName='*', searchColumn=None, searchValue=None, anyValue=False, notNull=False, zipit=True, schema='public'):
|
|
200
|
+
conn = get_connection()
|
|
201
|
+
try:
|
|
202
|
+
# Validate inputs
|
|
203
|
+
if anyValue:
|
|
204
|
+
if not isinstance(searchValue, (list, tuple)):
|
|
205
|
+
raise ValueError("When 'anyValue' is True, 'searchValue' must be a list or tuple.")
|
|
206
|
+
if not searchValue:
|
|
207
|
+
raise ValueError("'searchValue' list cannot be empty when 'anyValue' is True.")
|
|
208
|
+
else:
|
|
209
|
+
if isinstance(searchValue, (list, tuple)):
|
|
210
|
+
if len(searchValue) != 1:
|
|
211
|
+
raise ValueError("When 'anyValue' is False, 'searchValue' must be a single value.")
|
|
212
|
+
searchValue = searchValue[0] # Extract the single value
|
|
213
|
+
|
|
214
|
+
# Prepare base query
|
|
215
|
+
if columnNames != '*':
|
|
216
|
+
if not isinstance(columnNames, list):
|
|
217
|
+
raise ValueError("'columnNames' must be a list of column names or '*'")
|
|
218
|
+
columns = sql.SQL(", ").join([sql.Identifier(col) for col in columnNames])
|
|
219
|
+
else:
|
|
220
|
+
columns = sql.SQL('*')
|
|
221
|
+
|
|
222
|
+
query = sql.SQL("SELECT {} FROM {}.{}").format(
|
|
223
|
+
columns,
|
|
224
|
+
sql.Identifier(schema),
|
|
225
|
+
sql.Identifier(tableName),
|
|
226
|
+
)
|
|
227
|
+
params = []
|
|
228
|
+
|
|
229
|
+
# Handle search conditions
|
|
230
|
+
if searchColumn:
|
|
231
|
+
if searchValue is not None:
|
|
232
|
+
if anyValue: # Handle multiple values using IN
|
|
233
|
+
query += sql.SQL(" WHERE {} IN %s").format(sql.Identifier(searchColumn))
|
|
234
|
+
params.append(tuple(searchValue))
|
|
235
|
+
else: # Single value match
|
|
236
|
+
query += sql.SQL(" WHERE {} = %s").format(sql.Identifier(searchColumn))
|
|
237
|
+
params.append(searchValue)
|
|
238
|
+
elif notNull:
|
|
239
|
+
query += sql.SQL(" WHERE {} IS NOT NULL").format(sql.Identifier(searchColumn))
|
|
240
|
+
|
|
241
|
+
rows = execute_query(query, params)
|
|
242
|
+
|
|
243
|
+
# Zip results if required
|
|
244
|
+
if zipit:
|
|
245
|
+
rows = getZipRows(tableName, rows, schema)
|
|
246
|
+
return rows
|
|
247
|
+
|
|
248
|
+
except Exception as e:
|
|
249
|
+
logger.error(f"Error in fetch_any_combo: {e}", exc_info=True)
|
|
250
|
+
return []
|
|
251
|
+
finally:
|
|
252
|
+
release_connection(conn)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def real_query(query,params=[]):
|
|
257
|
+
conn = get_connection()
|
|
258
|
+
try:
|
|
259
|
+
with conn.cursor(cursor_factory=RealDictCursor) as cursor: # Use RealDictCursor for named columns
|
|
260
|
+
cursor.execute(query, params)
|
|
261
|
+
results = cursor.fetchall() # Fetch all rows as dictionaries
|
|
262
|
+
return results # Already zipped by column names
|
|
263
|
+
except Exception as e:
|
|
264
|
+
logger.error(f"Error executing query: {e}", exc_info=True)
|
|
265
|
+
return []
|
|
266
|
+
finally:
|
|
267
|
+
release_connection(conn)
|
|
268
|
+
|
|
269
|
+
def fetch_filtered_transactions_paginated(
|
|
270
|
+
sol_amount,
|
|
271
|
+
operator="<",
|
|
272
|
+
timestamp=None,
|
|
273
|
+
timestamp_operator=None,
|
|
274
|
+
limit=50,
|
|
275
|
+
offset=0
|
|
276
|
+
):
|
|
277
|
+
# SQL Query
|
|
278
|
+
query = f"""
|
|
279
|
+
SELECT
|
|
280
|
+
p.*,
|
|
281
|
+
t.*,
|
|
282
|
+
m.*,
|
|
283
|
+
t2.id AS related_txn
|
|
284
|
+
FROM
|
|
285
|
+
transactions t
|
|
286
|
+
JOIN
|
|
287
|
+
pairs p ON t.pair_id = p.id
|
|
288
|
+
LEFT JOIN
|
|
289
|
+
metadata m ON p.meta_id = m.id
|
|
290
|
+
LEFT JOIN
|
|
291
|
+
transactions t2 ON t.pair_id = t2.pair_id AND t.signature != t2.signature
|
|
292
|
+
WHERE
|
|
293
|
+
t.signature IN (
|
|
294
|
+
SELECT signature FROM pairs WHERE signature IS NOT NULL
|
|
295
|
+
)
|
|
296
|
+
AND
|
|
297
|
+
t.program_id = p.program_id
|
|
298
|
+
AND
|
|
299
|
+
EXISTS (
|
|
300
|
+
SELECT 1
|
|
301
|
+
FROM jsonb_array_elements(t.tcns) AS elem
|
|
302
|
+
WHERE (elem ->> 'sol_amount')::numeric {operator} %s
|
|
303
|
+
"""
|
|
304
|
+
|
|
305
|
+
# Parameters for query execution
|
|
306
|
+
params = [sol_amount]
|
|
307
|
+
|
|
308
|
+
# Add timestamp filtering if provided
|
|
309
|
+
if timestamp and timestamp_operator:
|
|
310
|
+
query += f" AND (elem ->> 'timestamp')::bigint {timestamp_operator} %s"
|
|
311
|
+
params.append(int(timestamp))
|
|
312
|
+
|
|
313
|
+
# Add sorting and pagination
|
|
314
|
+
query += f") ORDER BY t.updated_at DESC LIMIT %s OFFSET %s;"
|
|
315
|
+
params.extend([limit, offset])
|
|
316
|
+
|
|
317
|
+
# Database connection and execution
|
|
318
|
+
response = execute_query(query, params)
|
|
319
|
+
return response
|
|
320
|
+
|
|
321
|
+
def get_column_names(tableName):
|
|
322
|
+
columns = columnNamesManager().get_column_names(tableName, schema='public')
|
|
323
|
+
return columns
|
|
324
|
+
def getZipRows(tableName, rows, schema='public'):
|
|
325
|
+
columnNames = get_column_names(tableName)
|
|
326
|
+
return [dict(zip(columnNames, row)) for row in rows]
|
|
327
|
+
|