definite-sdk 0.1.4__py3-none-any.whl → 0.1.9__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.
@@ -0,0 +1,22 @@
1
+ """
2
+ Definite SDK for Python
3
+
4
+ A Python library for interacting with the Definite API and tools.
5
+ """
6
+
7
+ from definite_sdk.client import DefiniteClient
8
+ from definite_sdk.integration import DefiniteIntegrationStore
9
+ from definite_sdk.message import DefiniteMessageClient
10
+ from definite_sdk.secret import DefiniteSecretStore
11
+ from definite_sdk.sql import DefiniteSqlClient
12
+ from definite_sdk.store import DefiniteKVStore
13
+
14
+ __version__ = "0.1.9"
15
+ __all__ = [
16
+ "DefiniteClient",
17
+ "DefiniteIntegrationStore",
18
+ "DefiniteMessageClient",
19
+ "DefiniteSecretStore",
20
+ "DefiniteSqlClient",
21
+ "DefiniteKVStore",
22
+ ]
definite_sdk/client.py CHANGED
@@ -1,5 +1,10 @@
1
+ import os
2
+ from typing import Optional
3
+
1
4
  from definite_sdk.integration import DefiniteIntegrationStore
5
+ from definite_sdk.message import DefiniteMessageClient
2
6
  from definite_sdk.secret import DefiniteSecretStore
7
+ from definite_sdk.sql import DefiniteSqlClient
3
8
  from definite_sdk.store import DefiniteKVStore
4
9
 
5
10
  API_URL = "https://api.definite.app"
@@ -8,11 +13,24 @@ API_URL = "https://api.definite.app"
8
13
  class DefiniteClient:
9
14
  """Client for interacting with the Definite API."""
10
15
 
11
- def __init__(self, api_key: str, api_url: str = API_URL):
16
+ def __init__(self, api_key: Optional[str] = None, api_url: str = API_URL):
12
17
  """Creates a definite client with the provided API key.
13
18
 
19
+ Args:
20
+ api_key: API key for authentication. If not provided, will look for
21
+ DEFINITE_API_KEY or DEF_API_KEY environment variables.
22
+ api_url: Base URL for the Definite API.
23
+
14
24
  See: https://docs.definite.app/definite-api for how to obtain an API key.
15
25
  """
26
+ if api_key is None:
27
+ api_key = os.getenv("DEFINITE_API_KEY") or os.getenv("DEF_API_KEY")
28
+ if not api_key:
29
+ raise ValueError(
30
+ "API key must be provided or set in DEFINITE_API_KEY "
31
+ "or DEF_API_KEY environment variable"
32
+ )
33
+
16
34
  self.api_key = api_key
17
35
  self.api_url = api_url
18
36
 
@@ -39,3 +57,36 @@ class DefiniteClient:
39
57
  """
40
58
 
41
59
  return DefiniteIntegrationStore(self.api_key, self.api_url)
60
+
61
+ def get_sql_client(self) -> DefiniteSqlClient:
62
+ """Initializes the SQL client for executing SQL queries.
63
+
64
+ See DefiniteSqlClient for more how to execute SQL queries.
65
+ """
66
+
67
+ return DefiniteSqlClient(self.api_key, self.api_url)
68
+
69
+ # Alias methods for consistency
70
+ def kv_store(self, name: str) -> DefiniteKVStore:
71
+ """Alias for get_kv_store."""
72
+ return self.get_kv_store(name)
73
+
74
+ def secret_store(self) -> DefiniteSecretStore:
75
+ """Alias for get_secret_store."""
76
+ return self.get_secret_store()
77
+
78
+ def integration_store(self) -> DefiniteIntegrationStore:
79
+ """Alias for get_integration_store."""
80
+ return self.get_integration_store()
81
+
82
+ def get_message_client(self) -> DefiniteMessageClient:
83
+ """Initializes the message client for sending messages via various channels.
84
+
85
+ See DefiniteMessageClient for more how to send messages.
86
+ """
87
+
88
+ return DefiniteMessageClient(self.api_key, self.api_url)
89
+
90
+ def message_client(self) -> DefiniteMessageClient:
91
+ """Alias for get_message_client."""
92
+ return self.get_message_client()
definite_sdk/dlt.py ADDED
@@ -0,0 +1,237 @@
1
+ """DLT (Data Load Tool) integration for Definite SDK."""
2
+
3
+ import os
4
+ import json
5
+ from typing import Any, Dict, Optional, Tuple, TYPE_CHECKING
6
+
7
+ if TYPE_CHECKING:
8
+ import dlt
9
+ from dlt.common import Destination
10
+ from dlt.pipeline import Pipeline
11
+ else:
12
+ try:
13
+ import dlt
14
+ from dlt.common import Destination
15
+ from dlt.pipeline import Pipeline
16
+ except ImportError:
17
+ dlt = None # type: ignore
18
+ Destination = None # type: ignore
19
+ Pipeline = None # type: ignore
20
+
21
+ from .client import DefiniteClient
22
+
23
+
24
+ class DefiniteDLTPipeline:
25
+ """Wrapper for DLT pipelines with Definite state management."""
26
+
27
+ def __init__(
28
+ self,
29
+ name: str,
30
+ dataset_name: Optional[str] = None,
31
+ destination: Optional["Destination"] = None,
32
+ **kwargs: Any,
33
+ ):
34
+ """Initialize a DLT pipeline with Definite integration.
35
+
36
+ Args:
37
+ name: Pipeline name
38
+ dataset_name: Dataset name for the destination
39
+ destination: DLT destination (defaults to duckdb)
40
+ **kwargs: Additional arguments passed to dlt.pipeline()
41
+ """
42
+ if dlt is None:
43
+ raise ImportError(
44
+ "dlt package not installed. Install with: pip install definite-sdk[dlt]"
45
+ )
46
+
47
+ self.name = name
48
+ self._client = DefiniteClient()
49
+ self._state_store = self._client.kv_store(f"dlt_state_{name}")
50
+
51
+ # Default to DuckDB if no destination specified
52
+ if destination is None:
53
+ destination = "duckdb"
54
+
55
+ # Use dataset_name or default to pipeline name
56
+ if dataset_name is None:
57
+ dataset_name = name
58
+
59
+ # Create the underlying DLT pipeline
60
+ self._pipeline = dlt.pipeline(
61
+ pipeline_name=name,
62
+ destination=destination,
63
+ dataset_name=dataset_name,
64
+ **kwargs,
65
+ )
66
+
67
+ @property
68
+ def pipeline(self) -> "Pipeline":
69
+ """Get the underlying DLT pipeline."""
70
+ return self._pipeline
71
+
72
+ @property
73
+ def state(self) -> Dict[str, Any]:
74
+ """Get the current pipeline state."""
75
+ return dict(self._pipeline.state)
76
+
77
+ def run(
78
+ self,
79
+ data: Any,
80
+ *,
81
+ table_name: Optional[str] = None,
82
+ write_disposition: Optional[str] = None,
83
+ **kwargs: Any,
84
+ ) -> Any:
85
+ """Run the pipeline with the given data.
86
+
87
+ Args:
88
+ data: Data to load (resource, source, or iterator)
89
+ table_name: Name of the table to load data into
90
+ write_disposition: How to write data (append, replace, merge)
91
+ **kwargs: Additional arguments passed to pipeline.run()
92
+
93
+ Returns:
94
+ Load info from the pipeline run
95
+ """
96
+ # Run the pipeline
97
+ load_info = self._pipeline.run(
98
+ data, table_name=table_name, write_disposition=write_disposition, **kwargs
99
+ )
100
+
101
+ # Persist state to Definite after successful run
102
+ self._persist_state()
103
+
104
+ return load_info
105
+
106
+ def _persist_state(self) -> None:
107
+ """Persist current pipeline state to Definite."""
108
+ state = self._pipeline.state
109
+ if state:
110
+ # Store each state key separately for easier access
111
+ for key, value in state.items():
112
+ # Serialize value to JSON string
113
+ self._state_store[key] = json.dumps(value)
114
+ self._state_store.commit()
115
+
116
+ def get_state(self, key: Optional[str] = None) -> Any:
117
+ """Retrieve state from Definite store.
118
+
119
+ Args:
120
+ key: Specific state key to retrieve. If None, returns all state.
121
+
122
+ Returns:
123
+ State value or entire state dict
124
+ """
125
+ if key is not None:
126
+ value = self._state_store.get(key, None)
127
+ return json.loads(value) if value else None
128
+
129
+ # Return all state as dict
130
+ return {k: json.loads(self._state_store[k]) for k in self._state_store}
131
+
132
+ def set_state(self, key: str, value: Any) -> None:
133
+ """Set a state value.
134
+
135
+ Args:
136
+ key: State key
137
+ value: State value
138
+ """
139
+ self._pipeline.state[key] = value
140
+ # Also persist to Definite immediately
141
+ self._state_store[key] = json.dumps(value)
142
+ self._state_store.commit()
143
+
144
+ def resume_from_state(self) -> None:
145
+ """Resume pipeline from previously stored state."""
146
+ # Load state from Definite
147
+ stored_state: Dict[str, Any] = {
148
+ k: json.loads(self._state_store[k]) for k in self._state_store
149
+ }
150
+
151
+ # Apply state to pipeline
152
+ for key, value in stored_state.items():
153
+ self._pipeline.state[key] = value
154
+
155
+ def reset_state(self) -> None:
156
+ """Reset pipeline state."""
157
+ # Clear pipeline state
158
+ self._pipeline.state.clear()
159
+
160
+ # Clear Definite state
161
+ keys_to_delete = list(self._state_store)
162
+ for key in keys_to_delete:
163
+ del self._state_store[key]
164
+ self._state_store.commit()
165
+
166
+
167
+ class DLTStateAdapter:
168
+ """Adapter for DLT state management conforming to Definite patterns."""
169
+
170
+ def __init__(self, pipeline_name: str):
171
+ """Initialize state adapter.
172
+
173
+ Args:
174
+ pipeline_name: Name of the DLT pipeline
175
+ """
176
+ self.pipeline_name = pipeline_name
177
+ self._client = DefiniteClient()
178
+ self._store = self._client.kv_store(f"dlt_state_{pipeline_name}")
179
+
180
+ def save_state(self, state: Dict[str, Any]) -> None:
181
+ """Save pipeline state.
182
+
183
+ Args:
184
+ state: State dictionary to save
185
+ """
186
+ for key, value in state.items():
187
+ self._store[key] = json.dumps(value)
188
+ self._store.commit()
189
+
190
+ def load_state(self) -> Dict[str, Any]:
191
+ """Load pipeline state.
192
+
193
+ Returns:
194
+ State dictionary
195
+ """
196
+ return {k: json.loads(self._store[k]) for k in self._store}
197
+
198
+ def clear_state(self) -> None:
199
+ """Clear all state."""
200
+ keys_to_delete = list(self._store)
201
+ for key in keys_to_delete:
202
+ del self._store[key]
203
+ self._store.commit()
204
+
205
+
206
+ def get_duckdb_connection() -> Optional[Tuple[str, Any]]:
207
+ """Get DuckDB connection from Definite integration.
208
+
209
+ Uses the DEFINITE_API_KEY environment variable to authenticate and
210
+ lookup the team's DuckDB integration.
211
+
212
+ Returns:
213
+ Optional[Tuple[str, Any]]: Tuple of (integration_id, connection) if found,
214
+ None if no DuckDB integration or API key.
215
+ """
216
+ api_key = os.getenv("DEFINITE_API_KEY")
217
+ if not api_key:
218
+ return None
219
+
220
+ try:
221
+ import duckdb
222
+ except ImportError:
223
+ raise ImportError(
224
+ "duckdb package not installed. Install with: pip install duckdb"
225
+ )
226
+
227
+ client = DefiniteClient(api_key=api_key)
228
+ integration_store = client.integration_store()
229
+
230
+ result = integration_store.lookup_duckdb_integration()
231
+ if result:
232
+ integration_id, connection_uri = result
233
+ # Create DuckDB connection
234
+ connection = duckdb.connect(connection_uri)
235
+ return (integration_id, connection)
236
+
237
+ return None
@@ -1,8 +1,8 @@
1
- from typing import Iterator
1
+ from typing import Iterator, Optional, Tuple
2
2
 
3
3
  import requests
4
4
 
5
- SECRET_STORE_ENDPOINT = "/v1/api/integration"
5
+ INTEGRATION_ENDPOINT = "/v1/api/integration"
6
6
 
7
7
 
8
8
  class DefiniteIntegrationStore:
@@ -26,8 +26,8 @@ class DefiniteIntegrationStore:
26
26
  api_key (str): The API key for authorization.
27
27
  """
28
28
  self._api_key = api_key
29
- self._integration_store_url = api_url + SECRET_STORE_ENDPOINT
30
-
29
+ self._integration_store_url = api_url + INTEGRATION_ENDPOINT
30
+
31
31
  def list_integrations(self) -> Iterator[dict]:
32
32
  """
33
33
  Lists all integrations in the store.
@@ -57,4 +57,43 @@ class DefiniteIntegrationStore:
57
57
  headers={"Authorization": "Bearer " + self._api_key},
58
58
  )
59
59
  response.raise_for_status()
60
- return response.json()
60
+ return dict(response.json())
61
+
62
+ def lookup_duckdb_integration(self) -> Optional[Tuple[str, str]]:
63
+ """
64
+ Look up the team's DuckDB integration.
65
+
66
+ Note: Currently, the API only returns extractor (source) integrations.
67
+ Destination integrations like DuckDB are not yet exposed through this endpoint.
68
+ This method is provided for future compatibility when the API is updated.
69
+
70
+ Returns:
71
+ Optional[Tuple[str, str]]: Tuple of (integration_id, connection_uri)
72
+ if found, None if no DuckDB integration exists.
73
+ """
74
+ try:
75
+ response = requests.get(
76
+ self._integration_store_url,
77
+ headers={"Authorization": "Bearer " + self._api_key},
78
+ )
79
+ response.raise_for_status()
80
+ integrations = response.json()
81
+ except:
82
+ return None
83
+
84
+ # Check for DuckDB in the integrations
85
+ # Note: Currently this will not find DuckDB as it's a destination integration
86
+ for integration in integrations:
87
+ integration_type = integration.get("integration_type", "").lower()
88
+ if integration_type == "duckdb" and integration.get("active", True):
89
+ integration_id = integration.get("id")
90
+ # Connection URI might be in config or connection_string field
91
+ connection_uri = (
92
+ integration.get("connection_string")
93
+ or integration.get("config", {}).get("database_path")
94
+ or integration.get("config", {}).get("connection_string")
95
+ )
96
+ if integration_id and connection_uri:
97
+ return (integration_id, connection_uri)
98
+
99
+ return None
@@ -0,0 +1,199 @@
1
+ from typing import Any, Dict, List, Optional
2
+
3
+ import requests
4
+
5
+ MESSAGE_ENDPOINT = "/v3"
6
+
7
+
8
+ class DefiniteMessageClient:
9
+ """
10
+ A message client for sending messages via various channels through the Definite API.
11
+
12
+ Initialization:
13
+ >>> client = DefiniteClient("MY_API_KEY")
14
+ >>> message_client = client.get_message_client()
15
+
16
+ Sending messages:
17
+ >>> # Send a Slack message
18
+ >>> result = message_client.send_message(
19
+ ... channel="slack",
20
+ ... integration_id="slack_integration_id",
21
+ ... to="C0920MVPWFN", # channel_id
22
+ ... content="Hello from Definite SDK!"
23
+ ... )
24
+
25
+ >>> # Send a Slack message with blocks and thread
26
+ >>> result = message_client.send_message(
27
+ ... channel="slack",
28
+ ... integration_id="slack_integration_id",
29
+ ... to="C0920MVPWFN",
30
+ ... content="Hello!",
31
+ ... blocks=[{"type": "section", "text": {"type": "mrkdwn", "text": "Hello!"}}],
32
+ ... thread_ts="1234567890.123456"
33
+ ... )
34
+ """
35
+
36
+ def __init__(self, api_key: str, api_url: str):
37
+ """
38
+ Initializes the DefiniteMessageClient.
39
+
40
+ Args:
41
+ api_key (str): The API key for authorization.
42
+ api_url (str): The base URL for the Definite API.
43
+ """
44
+ self._api_key = api_key
45
+ self._message_url = api_url + MESSAGE_ENDPOINT
46
+
47
+ def send_message(
48
+ self,
49
+ channel: str,
50
+ integration_id: str,
51
+ to: str,
52
+ content: str,
53
+ subject: Optional[str] = None,
54
+ blocks: Optional[List[Dict[str, Any]]] = None,
55
+ thread_ts: Optional[str] = None,
56
+ **kwargs: Any
57
+ ) -> Dict[str, Any]:
58
+ """
59
+ Sends a message through the specified channel.
60
+
61
+ Args:
62
+ channel (str): The messaging channel to use (e.g., "slack", "email").
63
+ integration_id (str): The ID of the integration to use.
64
+ to (str): The recipient identifier (channel_id for Slack, email address for email).
65
+ content (str): The message content (text for Slack, body for email).
66
+ subject (Optional[str]): Subject line (used for email).
67
+ blocks (Optional[List[Dict[str, Any]]]): Slack Block Kit blocks for formatting.
68
+ thread_ts (Optional[str]): Slack thread timestamp to reply to.
69
+ **kwargs: Additional channel-specific parameters.
70
+
71
+ Returns:
72
+ Dict[str, Any]: The response from the API.
73
+
74
+ Raises:
75
+ requests.HTTPError: If the API request fails.
76
+ ValueError: If the channel is not supported.
77
+
78
+ Example:
79
+ >>> # Slack message
80
+ >>> result = message_client.send_message(
81
+ ... channel="slack",
82
+ ... integration_id="slack_123",
83
+ ... to="C0920MVPWFN",
84
+ ... content="Hello team!"
85
+ ... )
86
+
87
+ >>> # Slack message with blocks
88
+ >>> result = message_client.send_message(
89
+ ... channel="slack",
90
+ ... integration_id="slack_123",
91
+ ... to="C0920MVPWFN",
92
+ ... content="Fallback text",
93
+ ... blocks=[{
94
+ ... "type": "section",
95
+ ... "text": {"type": "mrkdwn", "text": "*Important Update*"}
96
+ ... }]
97
+ ... )
98
+ """
99
+ channel_lower = channel.lower()
100
+
101
+ if channel_lower == "slack":
102
+ return self._send_slack_message(
103
+ integration_id=integration_id,
104
+ channel_id=to,
105
+ text=content,
106
+ blocks=blocks,
107
+ thread_ts=thread_ts,
108
+ **kwargs
109
+ )
110
+ else:
111
+ raise ValueError(f"Unsupported channel: {channel}")
112
+
113
+ def _send_slack_message(
114
+ self,
115
+ integration_id: str,
116
+ channel_id: str,
117
+ text: str,
118
+ blocks: Optional[List[Dict[str, Any]]] = None,
119
+ thread_ts: Optional[str] = None,
120
+ **kwargs: Any
121
+ ) -> Dict[str, Any]:
122
+ """
123
+ Internal method to send a Slack message.
124
+
125
+ Args:
126
+ integration_id (str): The Slack integration ID.
127
+ channel_id (str): The Slack channel ID.
128
+ text (str): The message text.
129
+ blocks (Optional[List[Dict[str, Any]]]): Slack blocks for formatting.
130
+ thread_ts (Optional[str]): Thread timestamp for replies.
131
+ **kwargs: Additional Slack-specific parameters.
132
+
133
+ Returns:
134
+ Dict[str, Any]: The API response.
135
+ """
136
+ url = f"{self._message_url}/slack/message"
137
+
138
+ payload = {
139
+ "integration_id": integration_id,
140
+ "channel_id": channel_id,
141
+ "text": text
142
+ }
143
+
144
+ if blocks:
145
+ payload["blocks"] = blocks
146
+ if thread_ts:
147
+ payload["thread_ts"] = thread_ts
148
+
149
+ # Add any additional kwargs to the payload
150
+ payload.update(kwargs)
151
+
152
+ response = requests.post(
153
+ url,
154
+ json=payload,
155
+ headers={"Authorization": "Bearer " + self._api_key},
156
+ )
157
+ response.raise_for_status()
158
+ return response.json()
159
+
160
+ def send_slack_message(
161
+ self,
162
+ integration_id: str,
163
+ channel_id: str,
164
+ text: str,
165
+ blocks: Optional[List[Dict[str, Any]]] = None,
166
+ thread_ts: Optional[str] = None,
167
+ **kwargs: Any
168
+ ) -> Dict[str, Any]:
169
+ """
170
+ Convenience method to send a Slack message directly.
171
+
172
+ Args:
173
+ integration_id (str): The Slack integration ID.
174
+ channel_id (str): The Slack channel ID.
175
+ text (str): The message text.
176
+ blocks (Optional[List[Dict[str, Any]]]): Slack blocks for formatting.
177
+ thread_ts (Optional[str]): Thread timestamp for replies.
178
+ **kwargs: Additional Slack-specific parameters.
179
+
180
+ Returns:
181
+ Dict[str, Any]: The API response.
182
+
183
+ Example:
184
+ >>> result = message_client.send_slack_message(
185
+ ... integration_id="slack_123",
186
+ ... channel_id="C0920MVPWFN",
187
+ ... text="Hello from Definite SDK! 👋"
188
+ ... )
189
+ >>> print(f"Message sent! Timestamp: {result['ts']}")
190
+ """
191
+ return self.send_message(
192
+ channel="slack",
193
+ integration_id=integration_id,
194
+ to=channel_id,
195
+ content=text,
196
+ blocks=blocks,
197
+ thread_ts=thread_ts,
198
+ **kwargs
199
+ )
definite_sdk/py.typed ADDED
File without changes
definite_sdk/secret.py CHANGED
@@ -47,7 +47,7 @@ class DefiniteSecretStore:
47
47
  )
48
48
  response.raise_for_status()
49
49
  return iter(response.json()["secrets"])
50
-
50
+
51
51
  def get_secret(self, key: str) -> str:
52
52
  """
53
53
  Retrieves the value of a secret.
@@ -63,8 +63,8 @@ class DefiniteSecretStore:
63
63
  headers={"Authorization": "Bearer " + self._api_key},
64
64
  )
65
65
  response.raise_for_status()
66
- return response.json()["value"]
67
-
66
+ return str(response.json()["value"])
67
+
68
68
  def set_secret(self, key: str, value: str):
69
69
  """
70
70
  Sets the value of a secret.
definite_sdk/sql.py ADDED
@@ -0,0 +1,126 @@
1
+ from typing import Any, Dict, List, Optional, Union
2
+
3
+ import requests
4
+
5
+ SQL_ENDPOINT = "/v1/query"
6
+
7
+
8
+ class DefiniteSqlClient:
9
+ """
10
+ A SQL client for executing SQL queries via the Definite API.
11
+
12
+ Initialization:
13
+ >>> client = DefiniteClient("MY_API_KEY")
14
+ >>> sql_client = client.get_sql_client()
15
+
16
+ Executing SQL queries:
17
+ >>> result = sql_client.execute("SELECT * FROM my_table LIMIT 10")
18
+ >>> print(result)
19
+
20
+ Executing SQL queries with integration ID:
21
+ >>> result = sql_client.execute(
22
+ ... "SELECT * FROM my_table LIMIT 10",
23
+ ... integration_id="my_integration_id"
24
+ ... )
25
+ >>> print(result)
26
+
27
+ Executing Cube queries:
28
+ >>> cube_query = {
29
+ ... "dimensions": [],
30
+ ... "measures": ["sales.total_amount"],
31
+ ... "timeDimensions": [{"dimension": "sales.date", "granularity": "month"}],
32
+ ... "limit": 1000
33
+ ... }
34
+ >>> result = sql_client.execute_cube_query(cube_query, integration_id="my_cube_integration")
35
+ >>> print(result)
36
+ """
37
+
38
+ def __init__(self, api_key: str, api_url: str):
39
+ """
40
+ Initializes the DefiniteSqlClient.
41
+
42
+ Args:
43
+ api_key (str): The API key for authorization.
44
+ api_url (str): The base URL for the Definite API.
45
+ """
46
+ self._api_key = api_key
47
+ self._sql_url = api_url + SQL_ENDPOINT
48
+
49
+ def execute(
50
+ self,
51
+ sql: str,
52
+ integration_id: Optional[str] = None
53
+ ) -> Dict[str, Any]:
54
+ """
55
+ Executes a SQL query against a database integration.
56
+
57
+ Args:
58
+ sql (str): The SQL query to execute.
59
+ integration_id (Optional[str]): The integration ID to query against.
60
+ If not provided, the default integration will be used.
61
+
62
+ Returns:
63
+ Dict[str, Any]: The query result as returned by the API.
64
+
65
+ Raises:
66
+ requests.HTTPError: If the API request fails.
67
+
68
+ Example:
69
+ >>> result = sql_client.execute("SELECT COUNT(*) FROM users")
70
+ >>> print(result)
71
+ """
72
+ payload = {"sql": sql}
73
+ if integration_id:
74
+ payload["integration_id"] = integration_id
75
+
76
+ response = requests.post(
77
+ self._sql_url,
78
+ json=payload,
79
+ headers={"Authorization": "Bearer " + self._api_key},
80
+ )
81
+ response.raise_for_status()
82
+ return response.json()
83
+
84
+ def execute_cube_query(
85
+ self,
86
+ cube_query: Dict[str, Any],
87
+ integration_id: Optional[str] = None
88
+ ) -> Dict[str, Any]:
89
+ """
90
+ Executes a Cube query against a Cube integration.
91
+
92
+ Args:
93
+ cube_query (Dict[str, Any]): The Cube query in JSON format.
94
+ integration_id (Optional[str]): The Cube integration ID to query against.
95
+ If not provided, the default integration will be used.
96
+
97
+ Returns:
98
+ Dict[str, Any]: The query result as returned by the API.
99
+
100
+ Raises:
101
+ requests.HTTPError: If the API request fails.
102
+
103
+ Example:
104
+ >>> cube_query = {
105
+ ... "dimensions": [],
106
+ ... "measures": ["sales.total_amount"],
107
+ ... "timeDimensions": [{
108
+ ... "dimension": "sales.date",
109
+ ... "granularity": "month"
110
+ ... }],
111
+ ... "limit": 1000
112
+ ... }
113
+ >>> result = sql_client.execute_cube_query(cube_query, "my_cube_integration")
114
+ >>> print(result)
115
+ """
116
+ payload = {"cube_query": cube_query}
117
+ if integration_id:
118
+ payload["integration_id"] = integration_id
119
+
120
+ response = requests.post(
121
+ self._sql_url,
122
+ json=payload,
123
+ headers={"Authorization": "Bearer " + self._api_key},
124
+ )
125
+ response.raise_for_status()
126
+ return response.json()
definite_sdk/store.py CHANGED
@@ -1,4 +1,4 @@
1
- from typing import Iterator
1
+ from typing import Iterator, Union, Optional
2
2
 
3
3
  import requests
4
4
 
@@ -109,7 +109,7 @@ class DefiniteKVStore:
109
109
  self._data = {}
110
110
  self._version_id = None
111
111
 
112
- def __getitem__(self, key: str) -> str | None:
112
+ def __getitem__(self, key: str) -> Optional[str]:
113
113
  """
114
114
  Gets the value for a given key.
115
115
 
@@ -190,3 +190,19 @@ class DefiniteKVStore:
190
190
  print(store)
191
191
  """
192
192
  return repr(self._data)
193
+
194
+ def get(self, key: str, default: Optional[str] = None) -> Optional[str]:
195
+ """
196
+ Gets the value for a given key with a default.
197
+
198
+ Args:
199
+ key (str): The key to retrieve the value for.
200
+ default: The default value to return if key doesn't exist.
201
+
202
+ Returns:
203
+ The value associated with the key, or default if the key does not exist.
204
+
205
+ Example:
206
+ value = store.get("key1", "default_value")
207
+ """
208
+ return self._data.get(key, default)
@@ -0,0 +1,314 @@
1
+ Metadata-Version: 2.3
2
+ Name: definite-sdk
3
+ Version: 0.1.9
4
+ Summary: Definite SDK for Python
5
+ License: MIT
6
+ Author: Definite
7
+ Author-email: hello@definite.app
8
+ Requires-Python: >=3.9,<4.0
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Provides-Extra: dlt
17
+ Requires-Dist: dlt (>=1.0,<2.0) ; extra == "dlt"
18
+ Requires-Dist: duckdb (>=1.0,<2.0) ; extra == "dlt"
19
+ Requires-Dist: requests (>=2.31.0,<3.0.0)
20
+ Description-Content-Type: text/markdown
21
+
22
+ # Definite SDK
23
+
24
+ A Python client for interacting with the Definite API, providing a convenient interface for key-value store operations, SQL query execution, secrets management, messaging capabilities, and DLT (Data Load Tool) integration with state persistence.
25
+
26
+ ## Installation
27
+
28
+ **pip:**
29
+ ```bash
30
+ pip install definite-sdk
31
+
32
+ # For dlt support
33
+ pip install "definite-sdk[dlt]"
34
+ ```
35
+
36
+ **poetry:**
37
+ ```bash
38
+ poetry add definite-sdk
39
+
40
+ # For dlt support
41
+ poetry add "definite-sdk[dlt]"
42
+ ```
43
+
44
+ ## Quick Start
45
+
46
+ ```python
47
+ from definite_sdk import DefiniteClient
48
+
49
+ # Initialize the client
50
+ client = DefiniteClient("YOUR_API_KEY")
51
+ ```
52
+
53
+ ## Features
54
+
55
+ - **Key-Value Store**: Persistent storage with version control and transactional commits
56
+ - **SQL Query Execution**: Execute SQL queries against your connected database integrations
57
+ - **Cube Query Execution**: Execute Cube queries for advanced analytics and data modeling
58
+ - **Secret Management**: Secure storage and retrieval of application secrets
59
+ - **Integration Store**: Read-only access to integration configurations
60
+ - **Messaging**: Send messages through various channels (Slack, and more coming soon)
61
+ - **dlt Integration**: Run dlt pipelines with automatic state persistence to Definite
62
+ - **DuckDB Support**: Automatic discovery and connection to team's DuckDB integrations
63
+
64
+ ## Basic Usage
65
+
66
+ ### 🗄️ Key-Value Store
67
+
68
+ Store and retrieve key-value pairs that can be accessed by custom Python scripts hosted on Definite.
69
+
70
+ ```python
71
+ # Initialize or retrieve an existing key-value store
72
+ store = client.get_kv_store('test_store')
73
+ # Or use the alias method
74
+ store = client.kv_store('test_store')
75
+
76
+ # Add or update key-value pairs
77
+ store['replication_key'] = 'created_at'
78
+ store['replication_state'] = '2024-05-20'
79
+ store["key1"] = "value1"
80
+ store["key2"] = {"nested": "data"}
81
+
82
+ # Commit changes
83
+ store.commit()
84
+
85
+ # Retrieve values
86
+ print(store['replication_key']) # 'created_at'
87
+ value = store["key1"]
88
+ ```
89
+
90
+ ### 🗃️ SQL Query Execution
91
+
92
+ Execute SQL queries against your connected database integrations.
93
+
94
+ ```python
95
+ # Initialize the SQL client
96
+ sql_client = client.get_sql_client()
97
+
98
+ # Execute a SQL query
99
+ result = sql_client.execute("SELECT * FROM users LIMIT 10")
100
+ print(result)
101
+
102
+ # Execute a SQL query with a specific integration
103
+ result = sql_client.execute(
104
+ "SELECT COUNT(*) FROM orders WHERE status = 'completed'",
105
+ integration_id="my_database_integration"
106
+ )
107
+ print(result)
108
+ ```
109
+
110
+ ### 📊 Cube Query Execution
111
+
112
+ Execute Cube queries for advanced analytics and data modeling.
113
+
114
+ ```python
115
+ # Prepare a Cube query
116
+ cube_query = {
117
+ "dimensions": [],
118
+ "measures": ["sales.total_amount"],
119
+ "timeDimensions": [{
120
+ "dimension": "sales.date",
121
+ "granularity": "month"
122
+ }],
123
+ "limit": 1000
124
+ }
125
+
126
+ # Execute the Cube query
127
+ result = sql_client.execute_cube_query(
128
+ cube_query,
129
+ integration_id="my_cube_integration"
130
+ )
131
+ print(result)
132
+ ```
133
+
134
+ ### 🔒 Secret Store
135
+
136
+ Securely store and retrieve secrets for your integrations.
137
+
138
+ ```python
139
+ # Initialize the secret store
140
+ secret_store = client.get_secret_store()
141
+ # Or use the alias method
142
+ secret_store = client.secret_store()
143
+
144
+ # Set a secret
145
+ secret_store.set_secret("database_password", "my_secure_password")
146
+
147
+ # Get a secret
148
+ password = secret_store.get_secret("database_password")
149
+
150
+ # List all secrets
151
+ secrets = list(secret_store.list_secrets())
152
+ ```
153
+
154
+ ### 🔗 Integration Management
155
+
156
+ Manage your data integrations and connections.
157
+
158
+ ```python
159
+ # Initialize the integration store
160
+ integration_store = client.get_integration_store()
161
+ # Or use the alias method
162
+ integration_store = client.integration_store()
163
+
164
+ # List all integrations
165
+ integrations = list(integration_store.list_integrations())
166
+
167
+ # Get a specific integration
168
+ integration = integration_store.get_integration("my_integration")
169
+ ```
170
+
171
+ ### 💬 Messaging
172
+
173
+ Send messages through various channels using the messaging client.
174
+
175
+ ```python
176
+ # Initialize the message client
177
+ message_client = client.get_message_client()
178
+ # Or use the alias method
179
+ message_client = client.message_client()
180
+
181
+ # Send a Slack message using the unified interface
182
+ result = message_client.send_message(
183
+ channel="slack",
184
+ integration_id="your_slack_integration_id",
185
+ to="C0920MVPWFN", # Slack channel ID
186
+ content="Hello from Definite SDK! 👋"
187
+ )
188
+
189
+ # Send a Slack message with blocks and threading
190
+ result = message_client.send_message(
191
+ channel="slack",
192
+ integration_id="your_slack_integration_id",
193
+ to="C0920MVPWFN",
194
+ content="Fallback text",
195
+ blocks=[{
196
+ "type": "section",
197
+ "text": {"type": "mrkdwn", "text": "*Important Update*"}
198
+ }],
199
+ thread_ts="1234567890.123456" # Reply in thread
200
+ )
201
+
202
+ # Or use the convenience method for Slack
203
+ result = message_client.send_slack_message(
204
+ integration_id="your_slack_integration_id",
205
+ channel_id="C0920MVPWFN",
206
+ text="Quick message using the convenience method!",
207
+ blocks=[{
208
+ "type": "section",
209
+ "text": {"type": "mrkdwn", "text": "Message with *rich* _formatting_"}
210
+ }]
211
+ )
212
+ ```
213
+
214
+ ### dlt Integration
215
+
216
+ ```python
217
+ from definite_sdk.dlt import DefiniteDLTPipeline
218
+ import dlt
219
+
220
+ # Create an incremental resource
221
+ @dlt.resource(primary_key="id", write_disposition="merge")
222
+ def orders(cursor=dlt.sources.incremental("created_at")):
223
+ # Your data loading logic here
224
+ pass
225
+
226
+ # Create and run pipeline
227
+ pipeline = DefiniteDLTPipeline("orders_sync")
228
+ pipeline.run(orders())
229
+
230
+ # State is automatically persisted to Definite
231
+ last_cursor = pipeline.get_state("orders")
232
+ ```
233
+
234
+ ### DuckDB Integration Discovery
235
+
236
+ ```python
237
+ from definite_sdk.dlt import get_duckdb_connection
238
+
239
+ # Automatically discovers DuckDB integration using DEFINITE_API_KEY env var
240
+ result = get_duckdb_connection()
241
+ if result:
242
+ integration_id, connection = result
243
+ # Use the DuckDB connection
244
+ connection.execute("SELECT * FROM my_table")
245
+ ```
246
+
247
+ **Note**: DuckDB integration discovery is currently limited as the API only exposes source integrations, not destination integrations. This functionality is provided for future compatibility.
248
+
249
+ ### State Management
250
+
251
+ ```python
252
+ # Set custom state
253
+ pipeline.set_state("custom_key", "custom_value")
254
+
255
+ # Get all state
256
+ all_state = pipeline.get_state()
257
+
258
+ # Resume from previous state
259
+ pipeline.resume_from_state()
260
+
261
+ # Reset state
262
+ pipeline.reset_state()
263
+ ```
264
+
265
+ ## Authentication
266
+
267
+ To use the Definite SDK, you'll need an API key. You can find and copy your API key from the bottom left user menu in your Definite workspace.
268
+
269
+ For SQL queries, you'll also need your integration ID, which can be found in your integration's page URL.
270
+
271
+ ## Environment Variables
272
+
273
+ - `DEFINITE_API_KEY`: Your Definite API key (auto-injected in Definite runtime)
274
+ - `DEF_API_KEY`: Alternative environment variable for API key
275
+
276
+ ## Error Handling
277
+
278
+ The SDK uses standard HTTP status codes and raises `requests.HTTPError` for API errors:
279
+
280
+ ```python
281
+ import requests
282
+
283
+ try:
284
+ result = sql_client.execute("SELECT * FROM invalid_table")
285
+ except requests.HTTPError as e:
286
+ print(f"API Error: {e}")
287
+ ```
288
+
289
+ ## Testing
290
+
291
+ ```bash
292
+ # Run all tests
293
+ DEF_API_KEY=your_api_key poetry run pytest
294
+
295
+ # Run specific test file
296
+ DEF_API_KEY=your_api_key poetry run pytest tests/test_dlt.py
297
+ ```
298
+
299
+ ## Contributing
300
+
301
+ Contributions are welcome! Please feel free to submit a Pull Request.
302
+
303
+ ## License
304
+
305
+ This project is licensed under the MIT License.
306
+
307
+ ## Documentation
308
+
309
+ For more detailed documentation, visit: https://docs.definite.app/
310
+
311
+ ## Support
312
+
313
+ If you encounter any issues or have questions, please reach out to hello@definite.app
314
+
@@ -0,0 +1,13 @@
1
+ definite_sdk/__init__.py,sha256=Y3GwfDOkpWt6EILoZ7PRT7nXuoVnk0zHrNOPHlkEHS4,604
2
+ definite_sdk/client.py,sha256=2wRrFFwMMeGfP5riaEGhCLk1OiyBsJj_uMqFb3hIooY,3199
3
+ definite_sdk/dlt.py,sha256=JC-S1jd6zqnpGSnvk9kYb8GQ1IPZRd1zVKTVYAbeA4w,7188
4
+ definite_sdk/integration.py,sha256=v15QIpWm3eAZr-cmavfA39LXTjpskEEQ2SM_TBV4E1U,3395
5
+ definite_sdk/message.py,sha256=I3qx2v9gIcC9NZyr5zEp_Seft8clRoER4WeUbAaDiFc,6549
6
+ definite_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ definite_sdk/secret.py,sha256=3wvEnTZ946hOxJBdB29dxvppwJ0x-TJV_LxRcXGqtis,2503
8
+ definite_sdk/sql.py,sha256=K5cPXM7B60Fzd8CyI-B4rXwK7hLpLT1Z8XZSZJjSogQ,3878
9
+ definite_sdk/store.py,sha256=7VYvROIrlWyM_ZX3LpqWWAfVLNDm0XMccMLv6qi9id8,5575
10
+ definite_sdk-0.1.9.dist-info/LICENSE,sha256=jMd7PtwNWiMoGIDgFutC1v4taO69W9SRZLKe9o470Vk,1064
11
+ definite_sdk-0.1.9.dist-info/METADATA,sha256=jZfdfG0EFt7kfN6HpbSCnAzhZHBrEy7SV7WKH-K0LVU,8196
12
+ definite_sdk-0.1.9.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
13
+ definite_sdk-0.1.9.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.0
2
+ Generator: poetry-core 2.1.3
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,21 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: definite-sdk
3
- Version: 0.1.4
4
- Summary: Definite SDK for Python
5
- License: MIT
6
- Author: Definite
7
- Author-email: hello@definite.app
8
- Requires-Python: >=3.9,<4.0
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Programming Language :: Python :: 3
11
- Classifier: Programming Language :: Python :: 3.9
12
- Classifier: Programming Language :: Python :: 3.10
13
- Classifier: Programming Language :: Python :: 3.11
14
- Classifier: Programming Language :: Python :: 3.12
15
- Requires-Dist: requests (>=2.31.0,<3.0.0)
16
- Description-Content-Type: text/markdown
17
-
18
- # Definite SDK
19
-
20
- A Python client for interacting with the Definite API, providing a convenient interface for key-value store operations.
21
-
@@ -1,8 +0,0 @@
1
- definite_sdk/client.py,sha256=DjckW-kZOGSQ5RKwRfpPub_ucxAcUaTLknZz5YKe4nE,1354
2
- definite_sdk/integration.py,sha256=z7i_q9m5dMNJPG3_J29aH1jov0b5u6rhO3IZj1V9KVg,1647
3
- definite_sdk/secret.py,sha256=pgRzY40MwOaZBVEw-yQPWX3ag1B7AMoCJKJjzcLQnTs,2506
4
- definite_sdk/store.py,sha256=FimC3RqLtiJc3PlOSORCIiMFaUcNtc-r45c54j3l92g,5034
5
- definite_sdk-0.1.4.dist-info/LICENSE,sha256=jMd7PtwNWiMoGIDgFutC1v4taO69W9SRZLKe9o470Vk,1064
6
- definite_sdk-0.1.4.dist-info/METADATA,sha256=PWkSNNRsjSKb1oU0a32FPUtqnP010jriQfRlI67p0i4,702
7
- definite_sdk-0.1.4.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
8
- definite_sdk-0.1.4.dist-info/RECORD,,