databubble 0.2.0__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.
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.4
2
+ Name: databubble
3
+ Version: 0.2.0
4
+ Summary: Statistical Intelligence as a Service — rigorous analysis skills via API
5
+ Author: DataBubble AI
6
+ License: MIT
7
+ Project-URL: Homepage, https://databubble.ai
8
+ Project-URL: Documentation, https://api.databubble.ai
9
+ Project-URL: Repository, https://github.com/sss828412/databubble-python
10
+ Project-URL: Issues, https://github.com/sss828412/databubble-python/issues
11
+ Keywords: statistics,data analysis,machine learning,api
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ Provides-Extra: recommended
23
+ Requires-Dist: httpx>=0.24; extra == "recommended"
24
+ Requires-Dist: pandas>=1.5; extra == "recommended"
25
+ Provides-Extra: dev
26
+ Requires-Dist: httpx>=0.24; extra == "dev"
27
+ Requires-Dist: pandas>=1.5; extra == "dev"
28
+ Requires-Dist: pytest>=7; extra == "dev"
29
+ Requires-Dist: pytest-mock; extra == "dev"
30
+
31
+ # DataBubble SDK
32
+
33
+ Statistical Intelligence as a Service.
34
+
35
+ ```bash
36
+ pip install databubble httpx pandas
37
+ ```
38
+
39
+ ## Quick start
40
+
41
+ ```python
42
+ from databubble import DataBubble
43
+ import pandas as pd
44
+
45
+ db = DataBubble(api_key="dbk_...")
46
+
47
+ # Univariate analysis
48
+ result = db.skills.univariate(df["price"])
49
+ print(result.summary)
50
+ print(result.warnings)
51
+
52
+ # Missing value profiling
53
+ result = db.skills.missing_values(df)
54
+
55
+ # Export session memory for next session
56
+ mem = db.memory.export(df, label="POS data June 2026")
57
+ mem.save("pos_memory.json")
58
+ ```
59
+
60
+ ## Available skills
61
+
62
+ | Skill | Input | What it does |
63
+ |---|---|---|
64
+ | `univariate` | Series or DataFrame + column= | Distribution analysis, skewness, bounded ordinal detection, MNAR flags |
65
+ | `outliers` | Series or DataFrame + column= | IQR + Z-score outlier detection |
66
+ | `missing_values` | DataFrame | MCAR/MAR/MNAR profiling, treatment recommendations |
67
+ | `leakage` | DataFrame + outcome= | Post-outcome timing detection, correlation proxy check |
68
+ | `bivariate` | DataFrame + x= + y= | Relationship analysis, linearity check |
69
+ | `correlation` | DataFrame + x= + y= | Pearson + Spearman, non-linearity flag |
70
+
71
+ ## Tiers
72
+
73
+ | Tier | Price | Calls/month | Skills |
74
+ |---|---|---|---|
75
+ | Developer | Free | 500 | Core analysis skills |
76
+ | Pro | $49/month | 10,000 | All skills |
77
+ | Business | $299/month | 100,000 | All skills + journey endpoints |
78
+ | Enterprise | Custom | Unlimited | Everything |
79
+
80
+ Get a key at [databubble.ai](https://databubble.ai).
@@ -0,0 +1,50 @@
1
+ # DataBubble SDK
2
+
3
+ Statistical Intelligence as a Service.
4
+
5
+ ```bash
6
+ pip install databubble httpx pandas
7
+ ```
8
+
9
+ ## Quick start
10
+
11
+ ```python
12
+ from databubble import DataBubble
13
+ import pandas as pd
14
+
15
+ db = DataBubble(api_key="dbk_...")
16
+
17
+ # Univariate analysis
18
+ result = db.skills.univariate(df["price"])
19
+ print(result.summary)
20
+ print(result.warnings)
21
+
22
+ # Missing value profiling
23
+ result = db.skills.missing_values(df)
24
+
25
+ # Export session memory for next session
26
+ mem = db.memory.export(df, label="POS data June 2026")
27
+ mem.save("pos_memory.json")
28
+ ```
29
+
30
+ ## Available skills
31
+
32
+ | Skill | Input | What it does |
33
+ |---|---|---|
34
+ | `univariate` | Series or DataFrame + column= | Distribution analysis, skewness, bounded ordinal detection, MNAR flags |
35
+ | `outliers` | Series or DataFrame + column= | IQR + Z-score outlier detection |
36
+ | `missing_values` | DataFrame | MCAR/MAR/MNAR profiling, treatment recommendations |
37
+ | `leakage` | DataFrame + outcome= | Post-outcome timing detection, correlation proxy check |
38
+ | `bivariate` | DataFrame + x= + y= | Relationship analysis, linearity check |
39
+ | `correlation` | DataFrame + x= + y= | Pearson + Spearman, non-linearity flag |
40
+
41
+ ## Tiers
42
+
43
+ | Tier | Price | Calls/month | Skills |
44
+ |---|---|---|---|
45
+ | Developer | Free | 500 | Core analysis skills |
46
+ | Pro | $49/month | 10,000 | All skills |
47
+ | Business | $299/month | 100,000 | All skills + journey endpoints |
48
+ | Enterprise | Custom | Unlimited | Everything |
49
+
50
+ Get a key at [databubble.ai](https://databubble.ai).
@@ -0,0 +1,33 @@
1
+ # databubble/__init__.py
2
+ """
3
+ DataBubble SDK — Statistical Intelligence as a Service.
4
+
5
+ Quick start:
6
+ from databubble import DataBubble
7
+ db = DataBubble(api_key="dbk_...")
8
+ result = db.skills.univariate(df["price"])
9
+ print(result.summary)
10
+ """
11
+
12
+ from databubble.client import DataBubble
13
+ from databubble.models import SkillResult, MemoryResult, ReconciliationResult, JourneyResult
14
+ from databubble.exceptions import (
15
+ DataBubbleError, AuthError, ForbiddenError,
16
+ RateLimitError, SkillError, ServerError, SDKUsageError,
17
+ )
18
+
19
+ __version__ = "0.2.0"
20
+ __all__ = [
21
+ "DataBubble",
22
+ "SkillResult",
23
+ "MemoryResult",
24
+ "ReconciliationResult",
25
+ "JourneyResult",
26
+ "DataBubbleError",
27
+ "AuthError",
28
+ "ForbiddenError",
29
+ "RateLimitError",
30
+ "SkillError",
31
+ "ServerError",
32
+ "SDKUsageError",
33
+ ]
@@ -0,0 +1,204 @@
1
+ # databubble/client.py
2
+ """
3
+ DataBubble — root client.
4
+
5
+ Usage:
6
+ from databubble import DataBubble
7
+ db = DataBubble(api_key="dbk_...")
8
+ result = db.skills.univariate(df["price"])
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ from typing import Optional
15
+
16
+ from databubble.exceptions import (
17
+ AuthError, ForbiddenError, RateLimitError,
18
+ SkillError, ServerError, DataBubbleError,
19
+ )
20
+ from databubble.skills import SkillsClient
21
+ from databubble.memory import MemoryClient
22
+ from databubble.journeys import JourneysClient
23
+
24
+
25
+ DEFAULT_BASE_URL = "https://api.databubble.ai"
26
+
27
+
28
+ class _HTTPClient:
29
+ """
30
+ Thin HTTP client. Uses httpx if available, falls back to urllib.
31
+ Handles auth header injection and error mapping.
32
+ """
33
+
34
+ def __init__(self, api_key: str, base_url: str, timeout: float):
35
+ self._api_key = api_key
36
+ self._base_url = base_url.rstrip("/")
37
+ self._timeout = timeout
38
+ self._session = None
39
+ self._init_session()
40
+
41
+ def _init_session(self):
42
+ try:
43
+ import httpx
44
+ self._session = httpx.Client(
45
+ base_url=self._base_url,
46
+ headers={"X-API-Key": self._api_key},
47
+ timeout=self._timeout,
48
+ )
49
+ self._backend = "httpx"
50
+ except ImportError:
51
+ # Fall back to urllib — no session, headers injected per-request
52
+ self._backend = "urllib"
53
+
54
+ def _raise_for_status(self, status_code: int, body: dict):
55
+ # Route-level errors wrap in {"detail": {"error": ...}};
56
+ # middleware errors use flat {"error": ...}. Extract from both (M-9).
57
+ detail = body.get("detail") or {}
58
+ msg = (
59
+ (detail.get("error") if isinstance(detail, dict) else None)
60
+ or body.get("error")
61
+ or f"HTTP {status_code}"
62
+ )
63
+ if status_code == 401:
64
+ raise AuthError(msg, status_code, body)
65
+ if status_code == 403:
66
+ raise ForbiddenError(msg, status_code, body)
67
+ if status_code == 429:
68
+ raise RateLimitError(msg, status_code, body)
69
+ if status_code in (400, 422): # 422 was falling through to generic DataBubbleError (D-4)
70
+ raise SkillError(msg, status_code, body)
71
+ if status_code >= 500:
72
+ raise ServerError(f"Server error ({status_code}): {msg}", status_code, body)
73
+ if status_code >= 400:
74
+ raise DataBubbleError(msg, status_code, body)
75
+
76
+ def post_json(self, path: str, payload: dict) -> dict:
77
+ """POST with JSON body. Returns parsed response dict."""
78
+ url = f"{self._base_url}{path}"
79
+
80
+ if self._backend == "httpx":
81
+ response = self._session.post(path, json=payload)
82
+ body = response.json()
83
+ self._raise_for_status(response.status_code, body)
84
+ return body
85
+ else:
86
+ import urllib.request, urllib.error
87
+ data = json.dumps(payload).encode()
88
+ req = urllib.request.Request(
89
+ url, data=data,
90
+ headers={
91
+ "Content-Type": "application/json",
92
+ "X-API-Key": self._api_key,
93
+ },
94
+ method="POST",
95
+ )
96
+ try:
97
+ with urllib.request.urlopen(req, timeout=self._timeout) as resp:
98
+ return json.loads(resp.read())
99
+ except urllib.error.HTTPError as e:
100
+ body = json.loads(e.read())
101
+ self._raise_for_status(e.code, body)
102
+ # _raise_for_status always raises for 4xx/5xx; this is a safety net
103
+ raise ServerError(f"Unexpected response ({e.code})", e.code, body)
104
+
105
+ def post_multipart(self, path: str, fields: dict, files) -> dict:
106
+ """
107
+ POST with multipart form data. Returns parsed response dict.
108
+
109
+ files may be:
110
+ - dict: {field_name: (filename, data, content_type)} — single file per field
111
+ - list of (field_name, (filename, data, content_type)) — supports repeated field names
112
+ for list[UploadFile] parameters (M-5: memory_files needs repeated "memory_files" key)
113
+ """
114
+ url = f"{self._base_url}{path}"
115
+
116
+ if self._backend == "httpx":
117
+ form_data = {k: v for k, v in fields.items() if v is not None}
118
+ # httpx accepts files as a list of (name, content) tuples for repeated keys
119
+ if isinstance(files, dict):
120
+ file_list = [(k, v) for k, v in files.items()]
121
+ else:
122
+ file_list = list(files)
123
+ response = self._session.post(path, data=form_data, files=file_list)
124
+ body = response.json()
125
+ self._raise_for_status(response.status_code, body)
126
+ return body
127
+ else:
128
+ raise DataBubbleError(
129
+ "Multipart upload requires httpx. "
130
+ "Install with: pip install httpx"
131
+ )
132
+
133
+ def close(self):
134
+ if self._backend == "httpx" and self._session:
135
+ self._session.close()
136
+
137
+ def __enter__(self):
138
+ return self
139
+
140
+ def __exit__(self, *args):
141
+ self.close()
142
+
143
+
144
+ class DataBubble:
145
+ """
146
+ DataBubble API client.
147
+
148
+ Args:
149
+ api_key: Your API key (starts with dbk_). Get one at databubble.ai.
150
+ base_url: API base URL. Defaults to https://api.databubble.ai.
151
+ Override for local development: http://localhost:8000
152
+ timeout: Request timeout in seconds. Default 60.
153
+
154
+ Example:
155
+ from databubble import DataBubble
156
+ db = DataBubble(api_key="dbk_...")
157
+
158
+ # Single-column skill
159
+ result = db.skills.univariate(df["price"])
160
+ print(result.summary)
161
+ print(result.warnings)
162
+
163
+ # Whole-dataset skill
164
+ result = db.skills.missing_values(df)
165
+
166
+ # Memory workflow
167
+ mem = db.memory.export(df, label="POS data June 2026")
168
+ mem.save("pos_memory.json")
169
+ """
170
+
171
+ def __init__(
172
+ self,
173
+ api_key: Optional[str] = None,
174
+ base_url: str = DEFAULT_BASE_URL,
175
+ timeout: float = 60.0,
176
+ ):
177
+ import os
178
+ resolved_key = api_key or os.environ.get("DATABUBBLE_API_KEY", "")
179
+ if not resolved_key:
180
+ raise ValueError(
181
+ "API key required. Pass api_key= or set DATABUBBLE_API_KEY env var. "
182
+ "Get a key at databubble.ai."
183
+ )
184
+ if not resolved_key.startswith("dbk_"):
185
+ raise ValueError(
186
+ f"Invalid API key format. Keys start with 'dbk_'. Got: {resolved_key[:8]}..."
187
+ )
188
+
189
+ self._http = _HTTPClient(resolved_key, base_url, timeout)
190
+ self.skills = SkillsClient(self._http)
191
+ self.memory = MemoryClient(self._http)
192
+ self.journeys = JourneysClient(self._http)
193
+
194
+ def close(self):
195
+ self._http.close()
196
+
197
+ def __enter__(self):
198
+ return self
199
+
200
+ def __exit__(self, *args):
201
+ self.close()
202
+
203
+ def __repr__(self):
204
+ return f"DataBubble(base_url='{self._http._base_url}')"
@@ -0,0 +1,47 @@
1
+ # databubble/exceptions.py
2
+ """
3
+ Typed exceptions for the DataBubble SDK.
4
+ Every HTTP error from the API maps to a specific exception class
5
+ so callers can handle them explicitly without parsing status codes.
6
+ """
7
+
8
+
9
+ class DataBubbleError(Exception):
10
+ """Base exception for all SDK errors."""
11
+ def __init__(self, message: str, status_code: int = 0, response_body: dict = None):
12
+ super().__init__(message)
13
+ self.status_code = status_code
14
+ self.response_body = response_body or {}
15
+
16
+
17
+ class AuthError(DataBubbleError):
18
+ """Invalid or missing API key. HTTP 401."""
19
+ pass
20
+
21
+
22
+ class ForbiddenError(DataBubbleError):
23
+ """Skill not available on this tier. HTTP 403."""
24
+ pass
25
+
26
+
27
+ class RateLimitError(DataBubbleError):
28
+ """Monthly call limit reached. HTTP 429."""
29
+ pass
30
+
31
+
32
+ class SkillError(DataBubbleError):
33
+ """Skill executed but returned an error (bad input, halted, etc). HTTP 400."""
34
+ pass
35
+
36
+
37
+ class ServerError(DataBubbleError):
38
+ """Unexpected server error. HTTP 5xx."""
39
+ pass
40
+
41
+
42
+ class SDKUsageError(Exception):
43
+ """
44
+ Raised for incorrect SDK usage — wrong argument types, missing required args.
45
+ Not an API error — never reaches the server.
46
+ """
47
+ pass