ingestr 0.14.92__py3-none-any.whl → 0.14.93__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 ingestr might be problematic. Click here for more details.
- ingestr/src/allium/__init__.py +128 -0
- ingestr/src/buildinfo.py +1 -1
- ingestr/src/factory.py +2 -0
- ingestr/src/sources.py +69 -0
- {ingestr-0.14.92.dist-info → ingestr-0.14.93.dist-info}/METADATA +1 -1
- {ingestr-0.14.92.dist-info → ingestr-0.14.93.dist-info}/RECORD +9 -8
- {ingestr-0.14.92.dist-info → ingestr-0.14.93.dist-info}/WHEEL +0 -0
- {ingestr-0.14.92.dist-info → ingestr-0.14.93.dist-info}/entry_points.txt +0 -0
- {ingestr-0.14.92.dist-info → ingestr-0.14.93.dist-info}/licenses/LICENSE.md +0 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Allium source for data extraction via REST API.
|
|
3
|
+
|
|
4
|
+
This source provides access to Allium blockchain data via asynchronous query execution.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import time
|
|
8
|
+
from typing import Any, Iterator
|
|
9
|
+
|
|
10
|
+
import dlt
|
|
11
|
+
|
|
12
|
+
from ingestr.src.http_client import create_client
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dlt.source(max_table_nesting=0, name="allium_source")
|
|
16
|
+
def allium_source(
|
|
17
|
+
api_key: str,
|
|
18
|
+
query_id: str,
|
|
19
|
+
parameters: dict[str, Any] | None = None,
|
|
20
|
+
limit: int | None = None,
|
|
21
|
+
compute_profile: str | None = None,
|
|
22
|
+
) -> Any:
|
|
23
|
+
"""
|
|
24
|
+
Allium data source for blockchain data extraction.
|
|
25
|
+
|
|
26
|
+
This source connects to Allium API, runs async queries, and fetches results.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
api_key: Allium API key for authentication
|
|
30
|
+
query_id: The query ID to execute (e.g., 'abc123')
|
|
31
|
+
parameters: Optional parameters for the query (e.g., {'start_date': '2025-02-01', 'end_date': '2025-02-02'})
|
|
32
|
+
limit: Limit the number of rows in the result (max 250,000)
|
|
33
|
+
compute_profile: Compute profile identifier
|
|
34
|
+
|
|
35
|
+
Yields:
|
|
36
|
+
DltResource: Data resources for Allium query results
|
|
37
|
+
"""
|
|
38
|
+
base_url = "https://api.allium.so/api/v1/explorer"
|
|
39
|
+
session = create_client()
|
|
40
|
+
headers = {"X-API-Key": api_key}
|
|
41
|
+
|
|
42
|
+
@dlt.resource(
|
|
43
|
+
name="query_results",
|
|
44
|
+
write_disposition="replace",
|
|
45
|
+
)
|
|
46
|
+
def fetch_query_results() -> Iterator[dict[str, Any]]:
|
|
47
|
+
"""
|
|
48
|
+
Fetch query results from Allium.
|
|
49
|
+
|
|
50
|
+
This function:
|
|
51
|
+
1. Starts an async query execution
|
|
52
|
+
2. Polls for completion status
|
|
53
|
+
3. Fetches and yields the results
|
|
54
|
+
"""
|
|
55
|
+
# Step 1: Start async query execution
|
|
56
|
+
run_config: dict[str, Any] = {}
|
|
57
|
+
if limit is not None:
|
|
58
|
+
run_config["limit"] = limit
|
|
59
|
+
if compute_profile is not None:
|
|
60
|
+
run_config["compute_profile"] = compute_profile
|
|
61
|
+
|
|
62
|
+
run_payload = {"parameters": parameters or {}, "run_config": run_config}
|
|
63
|
+
|
|
64
|
+
run_response = session.post(
|
|
65
|
+
f"{base_url}/queries/{query_id}/run-async",
|
|
66
|
+
json=run_payload,
|
|
67
|
+
headers=headers,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
run_data = run_response.json()
|
|
71
|
+
|
|
72
|
+
if "run_id" not in run_data:
|
|
73
|
+
raise ValueError(f"Failed to start query execution: {run_data}")
|
|
74
|
+
|
|
75
|
+
run_id = run_data["run_id"]
|
|
76
|
+
|
|
77
|
+
# Step 2: Poll for completion
|
|
78
|
+
max_retries = 8640 # Max 12 hours with 5-second intervals
|
|
79
|
+
retry_count = 0
|
|
80
|
+
poll_interval = 5 # seconds
|
|
81
|
+
|
|
82
|
+
while retry_count < max_retries:
|
|
83
|
+
status_response = session.get(
|
|
84
|
+
f"{base_url}/query-runs/{run_id}/status",
|
|
85
|
+
headers=headers,
|
|
86
|
+
)
|
|
87
|
+
status_response.raise_for_status()
|
|
88
|
+
status_data = status_response.json()
|
|
89
|
+
|
|
90
|
+
# Handle both string and dict responses
|
|
91
|
+
if isinstance(status_data, str):
|
|
92
|
+
status = status_data
|
|
93
|
+
else:
|
|
94
|
+
status = status_data.get("status")
|
|
95
|
+
|
|
96
|
+
if status == "success":
|
|
97
|
+
break
|
|
98
|
+
elif status == "failed":
|
|
99
|
+
error_msg = (
|
|
100
|
+
status_data.get("error", "Unknown error")
|
|
101
|
+
if isinstance(status_data, dict)
|
|
102
|
+
else "Unknown error"
|
|
103
|
+
)
|
|
104
|
+
raise ValueError(f"Query execution failed: {error_msg}")
|
|
105
|
+
elif status in ["pending", "running", "queued"]:
|
|
106
|
+
time.sleep(poll_interval)
|
|
107
|
+
retry_count += 1
|
|
108
|
+
else:
|
|
109
|
+
raise ValueError(f"Unknown status: {status}")
|
|
110
|
+
|
|
111
|
+
if retry_count >= max_retries:
|
|
112
|
+
raise TimeoutError(
|
|
113
|
+
f"Query execution timed out after {max_retries * poll_interval} seconds"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Step 3: Fetch results
|
|
117
|
+
results_response = session.get(
|
|
118
|
+
f"{base_url}/query-runs/{run_id}/results",
|
|
119
|
+
headers=headers,
|
|
120
|
+
params={"f": "json"},
|
|
121
|
+
)
|
|
122
|
+
results_response.raise_for_status()
|
|
123
|
+
query_output = results_response.json()
|
|
124
|
+
|
|
125
|
+
# Extract and yield all data
|
|
126
|
+
yield query_output.get("data", [])
|
|
127
|
+
|
|
128
|
+
return (fetch_query_results,)
|
ingestr/src/buildinfo.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
version = "v0.14.
|
|
1
|
+
version = "v0.14.93"
|
ingestr/src/factory.py
CHANGED
|
@@ -28,6 +28,7 @@ from ingestr.src.destinations import (
|
|
|
28
28
|
from ingestr.src.sources import (
|
|
29
29
|
AdjustSource,
|
|
30
30
|
AirtableSource,
|
|
31
|
+
AlliumSource,
|
|
31
32
|
AnthropicSource,
|
|
32
33
|
AppleAppStoreSource,
|
|
33
34
|
ApplovinMaxSource,
|
|
@@ -156,6 +157,7 @@ class SourceDestinationFactory:
|
|
|
156
157
|
source_scheme: str
|
|
157
158
|
destination_scheme: str
|
|
158
159
|
sources: Dict[str, Type[SourceProtocol]] = {
|
|
160
|
+
"allium": AlliumSource,
|
|
159
161
|
"anthropic": AnthropicSource,
|
|
160
162
|
"csv": LocalCsvSource,
|
|
161
163
|
"docebo": DoceboSource,
|
ingestr/src/sources.py
CHANGED
|
@@ -3997,3 +3997,72 @@ class MailchimpSource:
|
|
|
3997
3997
|
).with_resources(table)
|
|
3998
3998
|
except ResourcesNotFoundError:
|
|
3999
3999
|
raise UnsupportedResourceError(table, "Mailchimp")
|
|
4000
|
+
|
|
4001
|
+
|
|
4002
|
+
class AlliumSource:
|
|
4003
|
+
def handles_incrementality(self) -> bool:
|
|
4004
|
+
return False
|
|
4005
|
+
|
|
4006
|
+
def dlt_source(self, uri: str, table: str, **kwargs):
|
|
4007
|
+
parsed_uri = urlparse(uri)
|
|
4008
|
+
query_params = parse_qs(parsed_uri.query)
|
|
4009
|
+
api_key = query_params.get("api_key")
|
|
4010
|
+
|
|
4011
|
+
if api_key is None:
|
|
4012
|
+
raise MissingValueError("api_key", "Allium")
|
|
4013
|
+
|
|
4014
|
+
# Extract query_id and custom parameters from table parameter
|
|
4015
|
+
# Format: query_id or query:query_id or query:query_id:param1=value1¶m2=value2
|
|
4016
|
+
query_id = table
|
|
4017
|
+
custom_params = {}
|
|
4018
|
+
limit = None
|
|
4019
|
+
compute_profile = None
|
|
4020
|
+
|
|
4021
|
+
if ":" in table:
|
|
4022
|
+
parts = table.split(":", 2) # Split into max 3 parts
|
|
4023
|
+
if len(parts) >= 2:
|
|
4024
|
+
query_id = parts[1]
|
|
4025
|
+
if len(parts) == 3:
|
|
4026
|
+
# Parse custom parameters from query string format
|
|
4027
|
+
param_string = parts[2]
|
|
4028
|
+
for param in param_string.split("&"):
|
|
4029
|
+
if "=" in param:
|
|
4030
|
+
key, value = param.split("=", 1)
|
|
4031
|
+
# Extract run_config parameters
|
|
4032
|
+
if key == "limit":
|
|
4033
|
+
limit = int(value)
|
|
4034
|
+
elif key == "compute_profile":
|
|
4035
|
+
compute_profile = value
|
|
4036
|
+
else:
|
|
4037
|
+
custom_params[key] = value
|
|
4038
|
+
|
|
4039
|
+
# Extract parameters from interval_start and interval_end
|
|
4040
|
+
# Default: 2 days ago 00:00 to yesterday 00:00
|
|
4041
|
+
now = pendulum.now()
|
|
4042
|
+
default_start = now.subtract(days=2).start_of("day")
|
|
4043
|
+
default_end = now.subtract(days=1).start_of("day")
|
|
4044
|
+
|
|
4045
|
+
parameters = {}
|
|
4046
|
+
interval_start = kwargs.get("interval_start")
|
|
4047
|
+
interval_end = kwargs.get("interval_end")
|
|
4048
|
+
|
|
4049
|
+
start_date = interval_start if interval_start is not None else default_start
|
|
4050
|
+
end_date = interval_end if interval_end is not None else default_end
|
|
4051
|
+
|
|
4052
|
+
parameters["start_date"] = start_date.strftime("%Y-%m-%d")
|
|
4053
|
+
parameters["end_date"] = end_date.strftime("%Y-%m-%d")
|
|
4054
|
+
parameters["start_timestamp"] = str(int(start_date.timestamp()))
|
|
4055
|
+
parameters["end_timestamp"] = str(int(end_date.timestamp()))
|
|
4056
|
+
|
|
4057
|
+
# Merge custom parameters (they override default parameters)
|
|
4058
|
+
parameters.update(custom_params)
|
|
4059
|
+
|
|
4060
|
+
from ingestr.src.allium import allium_source
|
|
4061
|
+
|
|
4062
|
+
return allium_source(
|
|
4063
|
+
api_key=api_key[0],
|
|
4064
|
+
query_id=query_id,
|
|
4065
|
+
parameters=parameters if parameters else None,
|
|
4066
|
+
limit=limit,
|
|
4067
|
+
compute_profile=compute_profile,
|
|
4068
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ingestr
|
|
3
|
-
Version: 0.14.
|
|
3
|
+
Version: 0.14.93
|
|
4
4
|
Summary: ingestr is a command-line application that ingests data from various sources and stores them in any database.
|
|
5
5
|
Project-URL: Homepage, https://github.com/bruin-data/ingestr
|
|
6
6
|
Project-URL: Issues, https://github.com/bruin-data/ingestr/issues
|
|
@@ -2,23 +2,24 @@ ingestr/conftest.py,sha256=OE2yxeTCosS9CUFVuqNypm-2ftYvVBeeq7egm3878cI,1981
|
|
|
2
2
|
ingestr/main.py,sha256=qo0g3wCFl8a_1jUwXagX8L1Q8PKKQlTF7md9pfnzW0Y,27155
|
|
3
3
|
ingestr/src/.gitignore,sha256=8cX1AZTSI0TcdZFGTmS_oyBjpfCzhOEt0DdAo2dFIY8,203
|
|
4
4
|
ingestr/src/blob.py,sha256=UUWMjHUuoR9xP1XZQ6UANQmnMVyDx3d0X4-2FQC271I,2138
|
|
5
|
-
ingestr/src/buildinfo.py,sha256=
|
|
5
|
+
ingestr/src/buildinfo.py,sha256=gpczaxQtINGa_cWhMVJsfeFoUxh-gKIyba1YESpTmpk,21
|
|
6
6
|
ingestr/src/destinations.py,sha256=QtjE0AGs0WkPHaI2snWPHJ8HHi4lwXUBYLJPklz8Mvk,27772
|
|
7
7
|
ingestr/src/errors.py,sha256=fhJ2BxOqOsBfOxuTDKfZblvawBrPG3x_1VikIxMZBRI,874
|
|
8
|
-
ingestr/src/factory.py,sha256=
|
|
8
|
+
ingestr/src/factory.py,sha256=k_8jgehOM2sHwCsjliYXmQhICl2B1UYoAs6vspjadv8,7770
|
|
9
9
|
ingestr/src/filters.py,sha256=0n0sNAVG_f-B_1r7lW5iNtw9z_G1bxWzPaiL1i6tnbU,1665
|
|
10
10
|
ingestr/src/http_client.py,sha256=bxqsk6nJNXCo-79gW04B53DQO-yr25vaSsqP0AKtjx4,732
|
|
11
11
|
ingestr/src/loader.py,sha256=9NaWAyfkXdqAZSS-N72Iwo36Lbx4PyqIfaaH1dNdkFs,1712
|
|
12
12
|
ingestr/src/masking.py,sha256=VN0LdfvExhQ1bZMRylGtaBUIoH-vjuIUmRnYKwo3yiY,11358
|
|
13
13
|
ingestr/src/partition.py,sha256=BrIP6wFJvyR7Nus_3ElnfxknUXeCipK_E_bB8kZowfc,969
|
|
14
14
|
ingestr/src/resource.py,sha256=ZqmZxFQVGlF8rFPhBiUB08HES0yoTj8sZ--jKfaaVps,1164
|
|
15
|
-
ingestr/src/sources.py,sha256=
|
|
15
|
+
ingestr/src/sources.py,sha256=D4VxA-yqilzTG0VBJBxnw9MUJ1Qeo2EpKjVGJfoMKoY,142289
|
|
16
16
|
ingestr/src/table_definition.py,sha256=REbAbqdlmUMUuRh8nEQRreWjPVOQ5ZcfqGkScKdCrmk,390
|
|
17
17
|
ingestr/src/time.py,sha256=H_Fk2J4ShXyUM-EMY7MqCLZQhlnZMZvO952bmZPc4yE,254
|
|
18
18
|
ingestr/src/version.py,sha256=J_2xgZ0mKlvuHcjdKCx2nlioneLH0I47JiU_Slr_Nwc,189
|
|
19
19
|
ingestr/src/adjust/__init__.py,sha256=-DkqpkCuwohw7BlwB9ZvtpbwQAY1Gr8J1T4KyFwsA8E,3315
|
|
20
20
|
ingestr/src/adjust/adjust_helpers.py,sha256=IHSS94A7enOWkZ8cP5iW3RdYt0Xl3qZGAmDc1Xy4qkI,3802
|
|
21
21
|
ingestr/src/airtable/__init__.py,sha256=XzRsS39xszUlh_s7P1_zq5v8vLfjz3m-NtTPaa8TTZU,2818
|
|
22
|
+
ingestr/src/allium/__init__.py,sha256=pLNvKKy8OBVgUPK0zJQTASf6CCZIW17BfrVYXxyd5nc,4087
|
|
22
23
|
ingestr/src/anthropic/__init__.py,sha256=D23oY20fE_RP9yPVkx7i6l3G1IfRLrJ2XwA8y2ot7JM,8482
|
|
23
24
|
ingestr/src/anthropic/helpers.py,sha256=Co8kmWQwKMHxcUwDU9959LTU6rFxqDIIbIvVSMGatrc,16105
|
|
24
25
|
ingestr/src/applovin/__init__.py,sha256=X_YCLppPrnL8KXfYWICE_uDfMzHHH3JZ-DBGZ1RlaOI,6984
|
|
@@ -183,8 +184,8 @@ ingestr/testdata/merge_expected.csv,sha256=DReHqWGnQMsf2PBv_Q2pfjsgvikYFnf1zYcQZ
|
|
|
183
184
|
ingestr/testdata/merge_part1.csv,sha256=Pw8Z9IDKcNU0qQHx1z6BUf4rF_-SxKGFOvymCt4OY9I,185
|
|
184
185
|
ingestr/testdata/merge_part2.csv,sha256=T_GiWxA81SN63_tMOIuemcvboEFeAmbKc7xRXvL9esw,287
|
|
185
186
|
ingestr/tests/unit/test_smartsheets.py,sha256=zf3DXT29Y4TH2lNPBFphdjlaelUUyPJcsW2UO68RzDs,4862
|
|
186
|
-
ingestr-0.14.
|
|
187
|
-
ingestr-0.14.
|
|
188
|
-
ingestr-0.14.
|
|
189
|
-
ingestr-0.14.
|
|
190
|
-
ingestr-0.14.
|
|
187
|
+
ingestr-0.14.93.dist-info/METADATA,sha256=ttKTQKjoXX_xzXbQb2LisUnePWrFx5GXQf2dHCsG48g,15327
|
|
188
|
+
ingestr-0.14.93.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
189
|
+
ingestr-0.14.93.dist-info/entry_points.txt,sha256=oPJy0KBnPWYjDtP1k8qwAihcTLHSZokSQvRAw_wtfJM,46
|
|
190
|
+
ingestr-0.14.93.dist-info/licenses/LICENSE.md,sha256=cW8wIhn8HFE-KLStDF9jHQ1O_ARWP3kTpk_-eOccL24,1075
|
|
191
|
+
ingestr-0.14.93.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|