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.

@@ -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.92"
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&param2=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.92
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=HwY092G4VBM9UY-e_f_-cOrkOxcgUZGOsco8jwXJ7aw,21
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=z6lcaGwK8o7AcaeshX7TP4WfB_jsIi0TvarcquGYnK0,7720
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=DSJAiwfVV0czMpHX1d93gQ7xIk_RyGPubQ31KfT5N8I,139639
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.92.dist-info/METADATA,sha256=jgxtI06WwAK2kR3fSLE7yU3iw_n686Iol31Axlf4jnU,15327
187
- ingestr-0.14.92.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
188
- ingestr-0.14.92.dist-info/entry_points.txt,sha256=oPJy0KBnPWYjDtP1k8qwAihcTLHSZokSQvRAw_wtfJM,46
189
- ingestr-0.14.92.dist-info/licenses/LICENSE.md,sha256=cW8wIhn8HFE-KLStDF9jHQ1O_ARWP3kTpk_-eOccL24,1075
190
- ingestr-0.14.92.dist-info/RECORD,,
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,,