sovai 0.2.34__tar.gz → 0.2.36__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.
- {sovai-0.2.34 → sovai-0.2.36}/PKG-INFO +2 -1
- {sovai-0.2.34 → sovai-0.2.36}/pyproject.toml +2 -1
- {sovai-0.2.34 → sovai-0.2.36}/sovai/get_data.py +35 -21
- {sovai-0.2.34 → sovai-0.2.36}/sovai/tools/authentication/authentication.py +56 -4
- {sovai-0.2.34 → sovai-0.2.36}/sovai/utils/client_side_s3.py +8 -0
- sovai-0.2.36/sovai/utils/client_side_s3_part_high.py +321 -0
- {sovai-0.2.34 → sovai-0.2.36}/LICENSE +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/README.md +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/.DS_Store +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/__init__.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/api_config.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/assets/dropdown.css +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/assets/features_mapping.parq +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/basic_auth.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/computations/__init__.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/computations/functions.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/errors/__init__.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/errors/sovai_errors.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/__init__.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/anomalies.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/ask_df_llm.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/change_point_generator.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/clustering.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/core_kshape.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/cum_sum_torch.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/cusum.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/dimensionality_reduction.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/feature_extraction.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/feature_importance.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/feature_neutralizer.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/filter_df.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/fractional_differencing.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/nowcasting.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/pairwise.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/pandas_extensions.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/parallel_functions.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/pfa_feature_selector.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/regime_change.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/regime_change_pca.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/shapley_global_importance.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/shapley_importance.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/signal_evaluation.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/technical_indicators.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/time_decomposition.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/weight_optimization.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/get_compute.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/get_plots.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/get_reports.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/get_tools.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/__init__.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/accounting/__init__.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/accounting/accounting_plots.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/allocation/__init__.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/allocation/allocation_plots.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/bankruptcy/__init__.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/bankruptcy/bankruptcy_plots.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/breakout/__init__.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/breakout/breakout_plots.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/corp_risk/__init__.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/corp_risk/corp_risk_plots.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/earnings_surprise/__init__.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/earnings_surprise/earnings_surprise_plots.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/insider/__init__.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/insider/insider_plots.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/institutional/__init__.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/institutional/institutional_plots.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/news/__init__.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/news/news_plots.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/ratios/__init__.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/ratios/ratios_plots.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/reports/accounting/__init__.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/reports/accounting/accounting_balance_sheet.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/reports/bankruptcy/__init__.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/reports/bankruptcy/bankruptcy_monthly_top.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/reports/general/__init__.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/reports/general/general_plots.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/reports/news/__inti__.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/reports/news/news_econometric_analysis.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/studies/nowcasting.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/token_auth.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/tools/authentication/__init__.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/tools/sec/__init__.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/tools/sec/graphs.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/tools/sec/llm_code_generator.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/tools/sec/sec_10_k_8_k_filings.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/tools/sec/sec_edgar_search.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/utils/__init__.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/utils/client_side.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/utils/converter.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/utils/datetime_formats.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/utils/file_management.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/utils/get_tickers.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/utils/helpers.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/utils/pandas_extensions copy.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/utils/plot.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/utils/port_manager.py +0 -0
- {sovai-0.2.34 → sovai-0.2.36}/sovai/utils/stream.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sovai
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.36
|
|
4
4
|
Summary: python for asset management
|
|
5
5
|
License: Proprietary License
|
|
6
6
|
Author: Derek Snow
|
|
@@ -39,6 +39,7 @@ Requires-Dist: pyvis (>=0.3.2,<0.4.0)
|
|
|
39
39
|
Requires-Dist: rapidfuzz (>=3.9.7,<4.0.0)
|
|
40
40
|
Requires-Dist: requests (>=2.20)
|
|
41
41
|
Requires-Dist: ruptures (>=1.0) ; extra == "full"
|
|
42
|
+
Requires-Dist: s3fs (>=2024.10.0,<2025.0.0)
|
|
42
43
|
Requires-Dist: scikit-learn (>=1.0) ; extra == "full"
|
|
43
44
|
Requires-Dist: scipy (>=1.0) ; extra == "full"
|
|
44
45
|
Requires-Dist: seaborn (>=0.13.2,<0.14.0)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "sovai"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.36"
|
|
4
4
|
description = "python for asset management"
|
|
5
5
|
authors = ["Derek Snow <d.snow@outlook.com>"]
|
|
6
6
|
license = "Proprietary License"
|
|
@@ -65,6 +65,7 @@ yachalk = "^0.1.6"
|
|
|
65
65
|
pyvis = "^0.3.2"
|
|
66
66
|
networkx = "^3.3"
|
|
67
67
|
seaborn = "^0.13.2"
|
|
68
|
+
s3fs = "^2024.10.0"
|
|
68
69
|
|
|
69
70
|
|
|
70
71
|
[tool.poetry.extras]
|
|
@@ -20,8 +20,11 @@ from sovai.errors.sovai_errors import InvalidInputData
|
|
|
20
20
|
from sovai.utils.converter import convert_data2df
|
|
21
21
|
from sovai.utils.stream import stream_data, stream_data_pyarrow
|
|
22
22
|
from sovai.utils.datetime_formats import datetime_format
|
|
23
|
+
|
|
23
24
|
from sovai.utils.client_side import client_side_frame
|
|
24
25
|
from sovai.utils.client_side_s3 import load_frame_s3
|
|
26
|
+
from sovai.utils.client_side_s3_part_high import load_frame_s3_paritioned_high
|
|
27
|
+
|
|
25
28
|
|
|
26
29
|
## Note run these for TEST - IT COULD BE HERE some silent problems
|
|
27
30
|
try:
|
|
@@ -267,6 +270,10 @@ endpoint_to_ticker = {
|
|
|
267
270
|
"/market/closeadj": "",
|
|
268
271
|
"/lobbying/public": "",
|
|
269
272
|
|
|
273
|
+
"/liquidity/price_improvement":"",
|
|
274
|
+
"/liquidity/market_opportunity":"",
|
|
275
|
+
|
|
276
|
+
|
|
270
277
|
|
|
271
278
|
## remember to add leading "/"
|
|
272
279
|
"/trials/predict": "",
|
|
@@ -292,6 +299,9 @@ def normalize_endpoint(endpoint):
|
|
|
292
299
|
|
|
293
300
|
## If you also want access by full file, please also add to endpoint_to_ticker
|
|
294
301
|
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
# Define your endpoint sets
|
|
295
305
|
client_side_endpoints_gcs = {
|
|
296
306
|
"ratios/relative",
|
|
297
307
|
"market/prices",
|
|
@@ -302,7 +312,6 @@ client_side_endpoints_gcs = {
|
|
|
302
312
|
"lobbying/public",
|
|
303
313
|
}
|
|
304
314
|
|
|
305
|
-
## If you also want access by full file, please also add to endpoint_to_ticker
|
|
306
315
|
client_side_endpoints_s3 = {
|
|
307
316
|
"sec/10k",
|
|
308
317
|
"trials/predict",
|
|
@@ -310,9 +319,18 @@ client_side_endpoints_s3 = {
|
|
|
310
319
|
"trials/all/predict",
|
|
311
320
|
"trials/all/decribe",
|
|
312
321
|
"trials/all",
|
|
322
|
+
}
|
|
313
323
|
|
|
324
|
+
client_side_endpoints_s3_part_high = {
|
|
325
|
+
"patents/applications"
|
|
314
326
|
}
|
|
315
327
|
|
|
328
|
+
# Create a list of handlers
|
|
329
|
+
handlers = [
|
|
330
|
+
(client_side_endpoints_gcs, client_side_frame, "Grabbing GCS client side"),
|
|
331
|
+
(client_side_endpoints_s3, load_frame_s3, "Grabbing S3 client side"),
|
|
332
|
+
(client_side_endpoints_s3_part_high, load_frame_s3_paritioned_high, "Grabbing S3 Partitioned High client side"),
|
|
333
|
+
]
|
|
316
334
|
|
|
317
335
|
|
|
318
336
|
# In your main function or wherever this logic is implemented:
|
|
@@ -600,26 +618,22 @@ def data(
|
|
|
600
618
|
|
|
601
619
|
normalized_endpoint = normalize_endpoint(endpoint)
|
|
602
620
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
)
|
|
612
|
-
|
|
621
|
+
# ACTAULLY I DON'T CARE, TICKERS CAN BE NONE FOR PATENTS
|
|
622
|
+
|
|
623
|
+
# Iterate through the handlers to find a matching handler
|
|
624
|
+
for endpoint_set, handler_func, message in handlers:
|
|
625
|
+
if (
|
|
626
|
+
normalized_endpoint in endpoint_set and
|
|
627
|
+
(tickers is not None or start_date is not None) and
|
|
628
|
+
frequency is None
|
|
629
|
+
):
|
|
630
|
+
verbose_mode.vprint(message)
|
|
631
|
+
_query_cache[cache_key] = handler_func(
|
|
632
|
+
normalized_endpoint, tickers, columns, start_date, end_date
|
|
633
|
+
)
|
|
634
|
+
return _query_cache[cache_key]
|
|
635
|
+
|
|
613
636
|
|
|
614
|
-
elif (
|
|
615
|
-
normalized_endpoint in client_side_endpoints_s3
|
|
616
|
-
and tickers is not None
|
|
617
|
-
and frequency is None
|
|
618
|
-
):
|
|
619
|
-
verbose_mode.vprint("Grabbing s3 client side")
|
|
620
|
-
_query_cache[cache_key] = load_frame_s3(
|
|
621
|
-
normalized_endpoint, tickers, columns, start_date, end_date)
|
|
622
|
-
return _query_cache[cache_key]
|
|
623
637
|
try:
|
|
624
638
|
if tickers is not None and not is_all(tickers):
|
|
625
639
|
params = ticker_mapper(params,verbose)
|
|
@@ -745,7 +759,7 @@ def data(
|
|
|
745
759
|
return None
|
|
746
760
|
return data
|
|
747
761
|
except Exception as err:
|
|
748
|
-
verbose_mode.vprint("An error occurred, check
|
|
762
|
+
verbose_mode.vprint("An error occurred, check dictionaries for 'data/name':'None':", err)
|
|
749
763
|
return data
|
|
750
764
|
if res.status_code == 404:
|
|
751
765
|
msg = res.json()
|
|
@@ -3,7 +3,9 @@ from sovai.api_config import ApiConfig
|
|
|
3
3
|
import requests
|
|
4
4
|
import pickle
|
|
5
5
|
import base64
|
|
6
|
-
from pyarrow.fs import S3FileSystem
|
|
6
|
+
from pyarrow.fs import S3FileSystem as S3File
|
|
7
|
+
import s3fs
|
|
8
|
+
|
|
7
9
|
|
|
8
10
|
def get_s3_filesystem_pickle(storage_provider: str, verbose: bool = False):
|
|
9
11
|
"""
|
|
@@ -38,6 +40,56 @@ def get_s3_filesystem_pickle(storage_provider: str, verbose: bool = False):
|
|
|
38
40
|
print(f"Failed to retrieve S3FileSystem for {storage_provider}: {e}")
|
|
39
41
|
return None
|
|
40
42
|
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_s3fs_filesystem_json(storage_provider: str, verbose: bool = False):
|
|
46
|
+
"""
|
|
47
|
+
Retrieve the S3FileSystem for the specified storage provider using JSON.
|
|
48
|
+
|
|
49
|
+
:param storage_provider: Either 'wasabi' or 'digitalocean'
|
|
50
|
+
:param verbose: If True, print additional information about the request
|
|
51
|
+
:return: S3FileSystem object, or None if the request fails
|
|
52
|
+
"""
|
|
53
|
+
if storage_provider.lower() not in ['wasabi', 'digitalocean']:
|
|
54
|
+
raise ValueError("storage_provider must be either 'wasabi' or 'digitalocean'")
|
|
55
|
+
|
|
56
|
+
url = f"{ApiConfig.base_url}/credentials/get_s3_filesystem_json"
|
|
57
|
+
headers = {
|
|
58
|
+
"Authorization": f"Bearer {ApiConfig.token}",
|
|
59
|
+
"Content-Type": "application/json"
|
|
60
|
+
}
|
|
61
|
+
params = {"storage_provider": storage_provider.lower()}
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
response = requests.get(url, headers=headers, params=params)
|
|
65
|
+
response.raise_for_status()
|
|
66
|
+
|
|
67
|
+
cred = response.json()
|
|
68
|
+
|
|
69
|
+
if verbose:
|
|
70
|
+
print(f"Successfully retrieved S3 credentials for {storage_provider}")
|
|
71
|
+
|
|
72
|
+
s3fsa = s3fs.S3FileSystem(
|
|
73
|
+
key=cred['access_key'],
|
|
74
|
+
secret=cred['secret_key'],
|
|
75
|
+
client_kwargs={'endpoint_url': cred['endpoint']}
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
s3fsa.ls('/')
|
|
80
|
+
if verbose:
|
|
81
|
+
print(f"Successfully verified S3FileSystem for {storage_provider}")
|
|
82
|
+
return s3fsa
|
|
83
|
+
except Exception as e:
|
|
84
|
+
if verbose:
|
|
85
|
+
print(f"Failed to verify S3FileSystem: {str(e)}")
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
except requests.RequestException as e:
|
|
89
|
+
if verbose:
|
|
90
|
+
print(f"Failed to retrieve S3 credentials for {storage_provider}: {e}")
|
|
91
|
+
return None
|
|
92
|
+
|
|
41
93
|
def get_s3_filesystem_json(storage_provider: str, verbose: bool = False):
|
|
42
94
|
"""
|
|
43
95
|
Retrieve the S3FileSystem for the specified storage provider using JSON.
|
|
@@ -65,17 +117,17 @@ def get_s3_filesystem_json(storage_provider: str, verbose: bool = False):
|
|
|
65
117
|
if verbose:
|
|
66
118
|
print(f"Successfully retrieved S3 credentials for {storage_provider}")
|
|
67
119
|
|
|
68
|
-
|
|
120
|
+
s3fsa = S3File(
|
|
69
121
|
access_key=cred['access_key'],
|
|
70
122
|
secret_key=cred['secret_key'],
|
|
71
123
|
endpoint_override=cred['endpoint']
|
|
72
124
|
)
|
|
73
125
|
|
|
74
126
|
try:
|
|
75
|
-
|
|
127
|
+
s3fsa.ls('/')
|
|
76
128
|
if verbose:
|
|
77
129
|
print(f"Successfully verified S3FileSystem for {storage_provider}")
|
|
78
|
-
return
|
|
130
|
+
return s3fsa
|
|
79
131
|
except Exception as e:
|
|
80
132
|
if verbose:
|
|
81
133
|
print(f"Failed to verify S3FileSystem: {str(e)}")
|
|
@@ -182,6 +182,14 @@ def load_frame_s3(endpoint, tickers=None, columns=None, start_date=None, end_dat
|
|
|
182
182
|
"partitioned": True,
|
|
183
183
|
|
|
184
184
|
},
|
|
185
|
+
"patents/applications": {
|
|
186
|
+
"storage_provider": {
|
|
187
|
+
"digitalocean": "sovai/sovai-patents-export/applications",
|
|
188
|
+
"wasabi": "sovai-patents-export/applications"
|
|
189
|
+
},
|
|
190
|
+
"identifier_columns": ["date_partitioned", "ticker_partitioned"],
|
|
191
|
+
"partitioned": True,
|
|
192
|
+
},
|
|
185
193
|
"trials/predict": {
|
|
186
194
|
"storage_provider": {
|
|
187
195
|
"digitalocean": "sovai/sovai-clinical-trials-export/prediction_public",
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import pyarrow.dataset as ds
|
|
2
|
+
from pyarrow.fs import S3FileSystem
|
|
3
|
+
import pyarrow as pa
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from functools import lru_cache
|
|
6
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
7
|
+
from sovai.tools.authentication import authentication
|
|
8
|
+
import os
|
|
9
|
+
import pandas as pd
|
|
10
|
+
import pyarrow as pa
|
|
11
|
+
import pyarrow.dataset as ds
|
|
12
|
+
import s3fs
|
|
13
|
+
import logging
|
|
14
|
+
import datetime
|
|
15
|
+
from functools import lru_cache
|
|
16
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
17
|
+
from tqdm import tqdm
|
|
18
|
+
from pyarrow.fs import S3FileSystem
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Try to import CustomDataFrame, use regular DataFrame if not available
|
|
22
|
+
try:
|
|
23
|
+
from sovai.extensions.pandas_extensions import CustomDataFrame
|
|
24
|
+
HAS_CUSTOM_DATAFRAME = True
|
|
25
|
+
except ImportError:
|
|
26
|
+
HAS_CUSTOM_DATAFRAME = False
|
|
27
|
+
CustomDataFrame = pd.DataFrame # Fallback to regular DataFrame
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@lru_cache(maxsize=2)
|
|
31
|
+
def get_cached_s3_filesystem(storage_provider):
|
|
32
|
+
return authentication.get_s3_filesystem_pickle(storage_provider, verbose=True)
|
|
33
|
+
|
|
34
|
+
@lru_cache(maxsize=2)
|
|
35
|
+
def get_cached_s3fs_filesystem(storage_provider):
|
|
36
|
+
return authentication.get_s3fs_filesystem_json(storage_provider, verbose=True)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Configure logging to output to both console and a log file
|
|
40
|
+
logging.basicConfig(
|
|
41
|
+
level=logging.INFO,
|
|
42
|
+
format='%(asctime)s - %(levelname)s - %(message)s',
|
|
43
|
+
handlers=[
|
|
44
|
+
logging.FileHandler("data_operations.log"),
|
|
45
|
+
logging.StreamHandler()
|
|
46
|
+
]
|
|
47
|
+
)
|
|
48
|
+
logger = logging.getLogger(__name__)
|
|
49
|
+
|
|
50
|
+
# -------------------------------
|
|
51
|
+
# Helper Functions
|
|
52
|
+
# -------------------------------
|
|
53
|
+
|
|
54
|
+
def construct_s3_path(type_name, provider, partition_type, ticker=None, publish_date=None, year=None, all_dates_after=False):
|
|
55
|
+
"""
|
|
56
|
+
Construct the S3 path based on the partitioning scheme.
|
|
57
|
+
|
|
58
|
+
Parameters:
|
|
59
|
+
- type_name (str): Type of data (e.g., 'applications')
|
|
60
|
+
- provider (str): 'wasabi' or 'digitalocean'
|
|
61
|
+
- partition_type (str): 'date' or 'ticker'
|
|
62
|
+
- ticker (str, optional): Ticker symbol
|
|
63
|
+
- publish_date (str, optional): Publish date in 'YYYY-MM-DD' format
|
|
64
|
+
- year (int, optional): Year extracted from publish_date
|
|
65
|
+
- all_dates_after (bool, optional): If True, scan all date partitions after a certain date
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
- str: Constructed S3 path
|
|
69
|
+
"""
|
|
70
|
+
if provider=="digitalocean":
|
|
71
|
+
bucket ="sovai/sovai-patents-export"
|
|
72
|
+
else:
|
|
73
|
+
bucket = "sovai-patents-export"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
if partition_type == "date":
|
|
77
|
+
if all_dates_after:
|
|
78
|
+
# Return the parent directory to scan all date partitions
|
|
79
|
+
path = f"{type_name}/date/"
|
|
80
|
+
else:
|
|
81
|
+
if not publish_date:
|
|
82
|
+
raise ValueError("publish_date must be provided for date partitioning.")
|
|
83
|
+
path = f"{type_name}/date/date_partitioned={publish_date}/"
|
|
84
|
+
elif partition_type == "ticker":
|
|
85
|
+
if not ticker or not year:
|
|
86
|
+
raise ValueError("Both ticker and year must be provided for ticker partitioning.")
|
|
87
|
+
path = f"{type_name}/ticker/ticker_partitioned={ticker}/year={year}/"
|
|
88
|
+
else:
|
|
89
|
+
raise ValueError("Invalid partition_type. Choose 'date' or 'ticker'.")
|
|
90
|
+
|
|
91
|
+
return f"{bucket}/{path}"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def list_date_partitions(provider, type_name):
|
|
95
|
+
s3_path = construct_s3_path(type_name, provider, partition_type="date", all_dates_after=True)
|
|
96
|
+
fs = get_cached_s3fs_filesystem(provider)
|
|
97
|
+
try:
|
|
98
|
+
partitions = fs.ls(s3_path)
|
|
99
|
+
dates = []
|
|
100
|
+
for partition in partitions:
|
|
101
|
+
basename = os.path.basename(partition.rstrip('/'))
|
|
102
|
+
if basename.startswith('date_partitioned='):
|
|
103
|
+
date_str = basename.split('=')[1]
|
|
104
|
+
try:
|
|
105
|
+
date_obj = datetime.datetime.strptime(date_str, '%Y-%m-%d').date()
|
|
106
|
+
dates.append((date_obj, partition))
|
|
107
|
+
except ValueError:
|
|
108
|
+
logger.warning(f"Unable to parse date from partition {partition}")
|
|
109
|
+
return dates
|
|
110
|
+
except Exception as e:
|
|
111
|
+
logger.error(f"Error listing date partitions in {s3_path} ({provider}): {e}")
|
|
112
|
+
return []
|
|
113
|
+
|
|
114
|
+
def load_parquet_from_s3(s3_path, provider, columns=None, start_date=None):
|
|
115
|
+
"""
|
|
116
|
+
Load a Parquet file from S3 into a pandas DataFrame with optional date filtering.
|
|
117
|
+
|
|
118
|
+
Parameters:
|
|
119
|
+
- s3_path (str): S3 path to the Parquet file or directory
|
|
120
|
+
- provider (str): 'wasabi' or 'digitalocean'
|
|
121
|
+
- columns (list, optional): List of columns to select
|
|
122
|
+
- start_date (str, optional): Minimum date in 'YYYY-MM-DD' format to filter the data
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
- pandas.DataFrame: Loaded DataFrame
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
fs = get_cached_s3_filesystem(provider)
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
dataset = ds.dataset(s3_path, filesystem=fs, format='parquet')
|
|
132
|
+
|
|
133
|
+
if start_date:
|
|
134
|
+
# Convert start_date string to pyarrow date32 scalar
|
|
135
|
+
min_date_pa = pa.scalar(pd.to_datetime(start_date).date(), type=pa.date32())
|
|
136
|
+
# Use ds.field to construct the filter expression
|
|
137
|
+
filter_expr = ds.field('date') >= min_date_pa
|
|
138
|
+
dataset = dataset.filter(filter_expr)
|
|
139
|
+
|
|
140
|
+
table = dataset.to_table(columns=columns)
|
|
141
|
+
|
|
142
|
+
# Log the schema for debugging
|
|
143
|
+
logger.info(f"Schema for {s3_path} ({provider}):")
|
|
144
|
+
logger.info(table.schema)
|
|
145
|
+
|
|
146
|
+
df = table.to_pandas()
|
|
147
|
+
|
|
148
|
+
partition_cols = ['ticker', 'date']
|
|
149
|
+
existing_cols = [col for col in partition_cols if col in df.columns]
|
|
150
|
+
other_cols = [col for col in df.columns if col not in partition_cols]
|
|
151
|
+
df = df[existing_cols + other_cols]
|
|
152
|
+
|
|
153
|
+
return df
|
|
154
|
+
except Exception as e:
|
|
155
|
+
logger.error(f"Error loading data from {s3_path} ({provider}): {e}")
|
|
156
|
+
return pd.DataFrame()
|
|
157
|
+
|
|
158
|
+
# -------------------------------
|
|
159
|
+
# Loading Functions
|
|
160
|
+
# -------------------------------
|
|
161
|
+
|
|
162
|
+
def load_data_by_ticker(
|
|
163
|
+
type_name,
|
|
164
|
+
providers=['wasabi', 'digitalocean'],
|
|
165
|
+
tickers=None,
|
|
166
|
+
start_date=None,
|
|
167
|
+
columns=None,
|
|
168
|
+
max_workers=4
|
|
169
|
+
):
|
|
170
|
+
"""
|
|
171
|
+
Load data for specified tickers and/or dates from S3 storage providers.
|
|
172
|
+
|
|
173
|
+
Parameters:
|
|
174
|
+
- type_name (str): Type of data (e.g., 'applications', 'prediction_all', etc.)
|
|
175
|
+
- providers (list, optional): List of storage providers to load from ('wasabi', 'digitalocean')
|
|
176
|
+
- tickers (str or list, optional): Ticker symbol(s) to load data for
|
|
177
|
+
- start_date (str, optional): Minimum publishDate to filter data by (format 'YYYY-MM-DD') (only for date partitioning)
|
|
178
|
+
- columns (list, optional): List of columns to select
|
|
179
|
+
- max_workers (int, optional): Number of parallel threads
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
- pandas.DataFrame: Concatenated DataFrame containing the loaded data
|
|
183
|
+
"""
|
|
184
|
+
date_tasks = []
|
|
185
|
+
ticker_tasks = []
|
|
186
|
+
results = []
|
|
187
|
+
|
|
188
|
+
# Normalize input parameters
|
|
189
|
+
if tickers and isinstance(tickers, str):
|
|
190
|
+
tickers = [tickers]
|
|
191
|
+
if not tickers:
|
|
192
|
+
tickers = []
|
|
193
|
+
|
|
194
|
+
# If both tickers and min_date are provided
|
|
195
|
+
if tickers and start_date:
|
|
196
|
+
try:
|
|
197
|
+
min_date_obj = datetime.datetime.strptime(start_date, '%Y-%m-%d').date()
|
|
198
|
+
min_year = min_date_obj.year
|
|
199
|
+
logger.info(f"Minimum date provided: {min_date_obj} (Year: {min_year})")
|
|
200
|
+
except ValueError:
|
|
201
|
+
logger.error(f"Invalid min_date format: {start_date}. Expected 'YYYY-MM-DD'.")
|
|
202
|
+
return pd.DataFrame()
|
|
203
|
+
|
|
204
|
+
for provider in providers:
|
|
205
|
+
for ticker in tickers:
|
|
206
|
+
# Determine the range of years to load based on start_date
|
|
207
|
+
# Assuming data starts from 2011
|
|
208
|
+
start_year = min_year
|
|
209
|
+
current_year = datetime.datetime.now().year
|
|
210
|
+
for year in range(start_year, current_year + 1):
|
|
211
|
+
s3_path = construct_s3_path(
|
|
212
|
+
type_name, provider, partition_type="ticker",
|
|
213
|
+
ticker=ticker, year=year
|
|
214
|
+
)
|
|
215
|
+
ticker_tasks.append((s3_path, provider, columns, None)) # No additional filtering here
|
|
216
|
+
|
|
217
|
+
# If only min_date is provided
|
|
218
|
+
elif start_date and not tickers:
|
|
219
|
+
try:
|
|
220
|
+
min_date_obj = datetime.datetime.strptime(start_date, '%Y-%m-%d').date()
|
|
221
|
+
logger.info(f"Minimum date provided: {min_date_obj}")
|
|
222
|
+
except ValueError:
|
|
223
|
+
logger.error(f"Invalid start_date format: {start_date}. Expected 'YYYY-MM-DD'.")
|
|
224
|
+
return pd.DataFrame()
|
|
225
|
+
|
|
226
|
+
for provider in providers:
|
|
227
|
+
# List all date-based partitions
|
|
228
|
+
date_partitions = list_date_partitions(provider, type_name)
|
|
229
|
+
for date_obj, partition_path in date_partitions:
|
|
230
|
+
if date_obj >= min_date_obj:
|
|
231
|
+
date_tasks.append((partition_path, provider, columns, None))
|
|
232
|
+
|
|
233
|
+
# If only tickers are provided
|
|
234
|
+
elif tickers and not start_date:
|
|
235
|
+
for provider in providers:
|
|
236
|
+
for ticker in tickers:
|
|
237
|
+
# Assuming data starts from 2011
|
|
238
|
+
start_year = 2010 ## Change this to 2008 eventually
|
|
239
|
+
current_year = datetime.datetime.now().year
|
|
240
|
+
for year in range(start_year, current_year + 1):
|
|
241
|
+
s3_path = construct_s3_path(
|
|
242
|
+
type_name, provider, partition_type="ticker",
|
|
243
|
+
ticker=ticker, year=year
|
|
244
|
+
)
|
|
245
|
+
ticker_tasks.append((s3_path, provider, columns, None))
|
|
246
|
+
|
|
247
|
+
else:
|
|
248
|
+
logger.warning("No tickers or start_date provided. Please provide at least one.")
|
|
249
|
+
return pd.DataFrame()
|
|
250
|
+
|
|
251
|
+
# Combine all tasks
|
|
252
|
+
all_tasks = ticker_tasks + date_tasks
|
|
253
|
+
|
|
254
|
+
if not all_tasks:
|
|
255
|
+
logger.warning("No tasks to process. Exiting.")
|
|
256
|
+
return pd.DataFrame()
|
|
257
|
+
|
|
258
|
+
def load_task(task):
|
|
259
|
+
s3_path, provider, columns, ticker_filter = task
|
|
260
|
+
df = load_parquet_from_s3(s3_path, provider, columns, start_date=start_date if ticker_filter is None else None)
|
|
261
|
+
if not df.empty and ticker_filter:
|
|
262
|
+
if 'ticker' in df.columns:
|
|
263
|
+
df = df[df['ticker'].isin(ticker_filter)]
|
|
264
|
+
else:
|
|
265
|
+
logger.warning(f"'ticker' column not found in data from {s3_path} ({provider}). Skipping ticker filter.")
|
|
266
|
+
return df
|
|
267
|
+
|
|
268
|
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
269
|
+
future_to_task = {executor.submit(load_task, task): task for task in all_tasks}
|
|
270
|
+
for future in tqdm(as_completed(future_to_task), total=len(all_tasks), desc="Loading data"):
|
|
271
|
+
df = future.result()
|
|
272
|
+
if not df.empty:
|
|
273
|
+
results.append(df)
|
|
274
|
+
|
|
275
|
+
if results:
|
|
276
|
+
final_df = pd.concat(results, ignore_index=True)
|
|
277
|
+
logger.info(f"Successfully loaded data with {len(final_df)} records.")
|
|
278
|
+
else:
|
|
279
|
+
final_df = pd.DataFrame()
|
|
280
|
+
logger.warning("No data found.")
|
|
281
|
+
|
|
282
|
+
return final_df
|
|
283
|
+
|
|
284
|
+
# -------------------------------
|
|
285
|
+
# Example Usage
|
|
286
|
+
# -------------------------------
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def load_frame_s3_paritioned_high(endpoint, tickers=None, columns=None, start_date=None, end_date=None):
|
|
290
|
+
|
|
291
|
+
endpoint_config = {
|
|
292
|
+
"patents/applications": {
|
|
293
|
+
"bucket": {
|
|
294
|
+
"digitalocean": "sovai/sovai-patents-export",
|
|
295
|
+
"wasabi": "sovai-patents-export"
|
|
296
|
+
},
|
|
297
|
+
"type": "applications"
|
|
298
|
+
},
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if endpoint not in endpoint_config:
|
|
302
|
+
raise ValueError(f"Invalid endpoint: {endpoint}")
|
|
303
|
+
|
|
304
|
+
config = endpoint_config[endpoint]
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
loaded_df_both = load_data_by_ticker(
|
|
308
|
+
type_name=config["type"],
|
|
309
|
+
providers=['digitalocean'],
|
|
310
|
+
tickers=tickers,
|
|
311
|
+
start_date=start_date,
|
|
312
|
+
columns=columns,
|
|
313
|
+
max_workers=8 # Adjust based on your system's capabilities
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
if HAS_CUSTOM_DATAFRAME:
|
|
318
|
+
return CustomDataFrame(loaded_df_both)
|
|
319
|
+
else:
|
|
320
|
+
return loaded_df_both # Returns a regular pandas DataFrame if CustomDataFrame is not available
|
|
321
|
+
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|