kumoai 2.13.0.dev202511131731__cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.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 kumoai might be problematic. Click here for more details.

Files changed (98) hide show
  1. kumoai/__init__.py +294 -0
  2. kumoai/_logging.py +29 -0
  3. kumoai/_singleton.py +25 -0
  4. kumoai/_version.py +1 -0
  5. kumoai/artifact_export/__init__.py +9 -0
  6. kumoai/artifact_export/config.py +209 -0
  7. kumoai/artifact_export/job.py +108 -0
  8. kumoai/client/__init__.py +5 -0
  9. kumoai/client/client.py +221 -0
  10. kumoai/client/connector.py +110 -0
  11. kumoai/client/endpoints.py +150 -0
  12. kumoai/client/graph.py +120 -0
  13. kumoai/client/jobs.py +447 -0
  14. kumoai/client/online.py +78 -0
  15. kumoai/client/pquery.py +203 -0
  16. kumoai/client/rfm.py +112 -0
  17. kumoai/client/source_table.py +53 -0
  18. kumoai/client/table.py +101 -0
  19. kumoai/client/utils.py +130 -0
  20. kumoai/codegen/__init__.py +19 -0
  21. kumoai/codegen/cli.py +100 -0
  22. kumoai/codegen/context.py +16 -0
  23. kumoai/codegen/edits.py +473 -0
  24. kumoai/codegen/exceptions.py +10 -0
  25. kumoai/codegen/generate.py +222 -0
  26. kumoai/codegen/handlers/__init__.py +4 -0
  27. kumoai/codegen/handlers/connector.py +118 -0
  28. kumoai/codegen/handlers/graph.py +71 -0
  29. kumoai/codegen/handlers/pquery.py +62 -0
  30. kumoai/codegen/handlers/table.py +109 -0
  31. kumoai/codegen/handlers/utils.py +42 -0
  32. kumoai/codegen/identity.py +114 -0
  33. kumoai/codegen/loader.py +93 -0
  34. kumoai/codegen/naming.py +94 -0
  35. kumoai/codegen/registry.py +121 -0
  36. kumoai/connector/__init__.py +31 -0
  37. kumoai/connector/base.py +153 -0
  38. kumoai/connector/bigquery_connector.py +200 -0
  39. kumoai/connector/databricks_connector.py +213 -0
  40. kumoai/connector/file_upload_connector.py +189 -0
  41. kumoai/connector/glue_connector.py +150 -0
  42. kumoai/connector/s3_connector.py +278 -0
  43. kumoai/connector/snowflake_connector.py +252 -0
  44. kumoai/connector/source_table.py +471 -0
  45. kumoai/connector/utils.py +1775 -0
  46. kumoai/databricks.py +14 -0
  47. kumoai/encoder/__init__.py +4 -0
  48. kumoai/exceptions.py +26 -0
  49. kumoai/experimental/__init__.py +0 -0
  50. kumoai/experimental/rfm/__init__.py +67 -0
  51. kumoai/experimental/rfm/authenticate.py +433 -0
  52. kumoai/experimental/rfm/infer/__init__.py +11 -0
  53. kumoai/experimental/rfm/infer/categorical.py +40 -0
  54. kumoai/experimental/rfm/infer/id.py +46 -0
  55. kumoai/experimental/rfm/infer/multicategorical.py +48 -0
  56. kumoai/experimental/rfm/infer/timestamp.py +41 -0
  57. kumoai/experimental/rfm/local_graph.py +810 -0
  58. kumoai/experimental/rfm/local_graph_sampler.py +184 -0
  59. kumoai/experimental/rfm/local_graph_store.py +359 -0
  60. kumoai/experimental/rfm/local_pquery_driver.py +689 -0
  61. kumoai/experimental/rfm/local_table.py +545 -0
  62. kumoai/experimental/rfm/pquery/__init__.py +7 -0
  63. kumoai/experimental/rfm/pquery/executor.py +102 -0
  64. kumoai/experimental/rfm/pquery/pandas_executor.py +532 -0
  65. kumoai/experimental/rfm/rfm.py +1130 -0
  66. kumoai/experimental/rfm/utils.py +344 -0
  67. kumoai/formatting.py +30 -0
  68. kumoai/futures.py +99 -0
  69. kumoai/graph/__init__.py +12 -0
  70. kumoai/graph/column.py +106 -0
  71. kumoai/graph/graph.py +948 -0
  72. kumoai/graph/table.py +838 -0
  73. kumoai/jobs.py +80 -0
  74. kumoai/kumolib.cpython-313-x86_64-linux-gnu.so +0 -0
  75. kumoai/mixin.py +28 -0
  76. kumoai/pquery/__init__.py +25 -0
  77. kumoai/pquery/prediction_table.py +287 -0
  78. kumoai/pquery/predictive_query.py +637 -0
  79. kumoai/pquery/training_table.py +424 -0
  80. kumoai/spcs.py +123 -0
  81. kumoai/testing/__init__.py +8 -0
  82. kumoai/testing/decorators.py +57 -0
  83. kumoai/trainer/__init__.py +42 -0
  84. kumoai/trainer/baseline_trainer.py +93 -0
  85. kumoai/trainer/config.py +2 -0
  86. kumoai/trainer/job.py +1192 -0
  87. kumoai/trainer/online_serving.py +258 -0
  88. kumoai/trainer/trainer.py +475 -0
  89. kumoai/trainer/util.py +103 -0
  90. kumoai/utils/__init__.py +10 -0
  91. kumoai/utils/datasets.py +83 -0
  92. kumoai/utils/forecasting.py +209 -0
  93. kumoai/utils/progress_logger.py +177 -0
  94. kumoai-2.13.0.dev202511131731.dist-info/METADATA +60 -0
  95. kumoai-2.13.0.dev202511131731.dist-info/RECORD +98 -0
  96. kumoai-2.13.0.dev202511131731.dist-info/WHEEL +6 -0
  97. kumoai-2.13.0.dev202511131731.dist-info/licenses/LICENSE +9 -0
  98. kumoai-2.13.0.dev202511131731.dist-info/top_level.txt +1 -0
kumoai/databricks.py ADDED
@@ -0,0 +1,14 @@
1
+ from typing import Optional, Tuple, Union
2
+
3
+ DB_SEP = '__kumo__'
4
+
5
+
6
+ def to_db_table_name(
7
+ table_name: Optional[Union[str, Tuple]] = None) -> Optional[str]:
8
+ r"""For Databricks connectors, return the table name whichs is
9
+ a Tuple as a string with the format `f"{schema}__kumo__{table}"`.
10
+ """
11
+ if table_name and isinstance(table_name, tuple):
12
+ return (f"{table_name[0]}{DB_SEP}"
13
+ f"{table_name[1]}")
14
+ return table_name # type: ignore
@@ -0,0 +1,4 @@
1
+ from kumoapi.encoder import ( # noqa
2
+ NAStrategy, Scaler, Null, Numerical, MaxLogNumerical, MinLogNumerical,
3
+ Index, Hash, MultiCategorical, GloVe, NumericalList, Datetime,
4
+ )
kumoai/exceptions.py ADDED
@@ -0,0 +1,26 @@
1
+ import http
2
+ from typing import Dict, Optional
3
+
4
+
5
+ class HTTPException(Exception):
6
+ r"""An HTTP exception, with detailed information and headers."""
7
+ def __init__(
8
+ self,
9
+ status_code: int,
10
+ detail: Optional[str] = None,
11
+ headers: Optional[Dict[str, str]] = None,
12
+ ) -> None:
13
+ # Derived from starlette/blob/master/starlette/exceptions.py
14
+ if detail is None:
15
+ detail = http.HTTPStatus(status_code).phrase
16
+ self.status_code = status_code
17
+ self.detail = detail
18
+ self.headers = headers
19
+
20
+ def __str__(self) -> str:
21
+ return f"{self.status_code}: {self.detail}"
22
+
23
+ def __repr__(self) -> str:
24
+ class_name = self.__class__.__name__
25
+ return (f"{class_name}(status_code={self.status_code!r}, "
26
+ f"detail={self.detail!r})")
File without changes
@@ -0,0 +1,67 @@
1
+ try:
2
+ import kumoai.kumolib # noqa: F401
3
+ except Exception as e:
4
+ import platform
5
+
6
+ _msg = f"""RFM is not supported in your environment.
7
+
8
+ 💻 Your Environment:
9
+ Python version: {platform.python_version()}
10
+ Operating system: {platform.system()}
11
+ CPU architecture: {platform.machine()}
12
+ glibc version: {platform.libc_ver()[1]}
13
+
14
+ ✅ Supported Environments:
15
+ * Python versions: 3.10, 3.11, 3.12, 3.13
16
+ * Operating systems and CPU architectures:
17
+ * Linux (x86_64)
18
+ * macOS (arm64)
19
+ * Windows (x86_64)
20
+ * glibc versions: >=2.28
21
+
22
+ ❌ Unsupported Environments:
23
+ * Python versions: 3.8, 3.9, 3.14
24
+ * Operating systems and CPU architectures:
25
+ * Linux (arm64)
26
+ * macOS (x86_64)
27
+ * Windows (arm64)
28
+ * glibc versions: <2.28
29
+
30
+ Please create a feature request at 'https://github.com/kumo-ai/kumo-rfm'."""
31
+
32
+ raise RuntimeError(_msg) from e
33
+
34
+ from typing import Optional, Dict
35
+ import os
36
+ import kumoai
37
+ from .local_table import LocalTable
38
+ from .local_graph import LocalGraph
39
+ from .rfm import ExplainConfig, Explanation, KumoRFM
40
+ from .authenticate import authenticate
41
+
42
+
43
+ def init(
44
+ url: Optional[str] = None,
45
+ api_key: Optional[str] = None,
46
+ snowflake_credentials: Optional[Dict[str, str]] = None,
47
+ snowflake_application: Optional[str] = None,
48
+ log_level: str = "INFO",
49
+ ) -> None:
50
+ if url is None:
51
+ url = os.getenv("KUMO_API_URL", "https://kumorfm.ai/api")
52
+
53
+ kumoai.init(url=url, api_key=api_key,
54
+ snowflake_credentials=snowflake_credentials,
55
+ snowflake_application=snowflake_application,
56
+ log_level=log_level)
57
+
58
+
59
+ __all__ = [
60
+ 'LocalTable',
61
+ 'LocalGraph',
62
+ 'KumoRFM',
63
+ 'ExplainConfig',
64
+ 'Explanation',
65
+ 'authenticate',
66
+ 'init',
67
+ ]
@@ -0,0 +1,433 @@
1
+ import logging
2
+ import os
3
+ import platform
4
+ from datetime import datetime
5
+ from typing import Optional
6
+
7
+ from kumoai import in_notebook
8
+
9
+
10
+ def authenticate(api_url: Optional[str] = None) -> None:
11
+ """Authenticates the user and sets the Kumo API key for the SDK.
12
+
13
+ This function detects the current environment and launches the appropriate
14
+ authentication flow:
15
+ - In Google Colab: displays an interactive widget to generate and set the
16
+ API key.
17
+ - In all other environments: opens a browser for OAuth2 login, or allows
18
+ manual API key entry if browser login fails.
19
+
20
+ After successful authentication, the API key is set in the "KUMO_API_KEY"
21
+ environment variable for use by the SDK.
22
+
23
+ Args:
24
+ api_url (str, optional): The base URL for the Kumo API
25
+ (e.g., 'https://kumorfm.ai'). If not provided, uses the
26
+ 'KUMO_API_URL' environment variable.
27
+ """
28
+ import re
29
+
30
+ if api_url is None:
31
+ api_url = os.getenv("KUMO_API_URL", "https://kumorfm.ai")
32
+
33
+ # Remove everything after the domain (keep protocol and domain)
34
+ # e.g. https://kumorfm.ai/api/xyz -> https://kumorfm.ai
35
+ # This is needed to create API keys using the UI popup flow
36
+ api_url = re.sub(
37
+ r"(https?://[^/]+).*", r"\1",
38
+ api_url.rstrip('/')) if '://' in api_url else api_url.split('/')[0]
39
+
40
+ try:
41
+ from google.colab import output # noqa: F401
42
+ except Exception:
43
+ _authenticate_local(api_url)
44
+ else:
45
+ _authenticate_colab(api_url)
46
+
47
+
48
+ def _authenticate_local(api_url: str, redirect_port: int = 8765) -> None:
49
+ """Starts an HTTP server on the user's local machine to handle OAuth2
50
+ or similar login flow, opens the browser for user login, and sets the
51
+ API key via the "KUMO_API_KEY" environment variable.
52
+
53
+ If browser-based authentication fails or is not possible, allows the
54
+ user to manually paste an API key.
55
+
56
+ Args:
57
+ api_url (str): The base URL for authentication (login page).
58
+ redirect_port (int, optional): The port for the local callback
59
+ server (default: 8765).
60
+ """
61
+ import http.server
62
+ import threading
63
+ import time
64
+ import urllib.parse
65
+ import webbrowser
66
+ from getpass import getpass
67
+ from socketserver import TCPServer
68
+ from typing import Any, Dict
69
+
70
+ logger = logging.getLogger('kumoai')
71
+
72
+ token_status: Dict[str, Any] = {
73
+ 'token': None,
74
+ 'token_name': None,
75
+ 'failed': False
76
+ }
77
+
78
+ token_name = (f"sdk-{platform.node().lower()}-" +
79
+ datetime.now().strftime('%Y-%m-%d-%H-%M-%S') + '-Z')
80
+
81
+ class CallbackHandler(http.server.BaseHTTPRequestHandler):
82
+ def do_GET(self) -> None:
83
+ parsed_path = urllib.parse.urlparse(self.path)
84
+ params = urllib.parse.parse_qs(parsed_path.query)
85
+ token = params.get('token', [None])[0]
86
+ received_token_name = params.get('token_name', [None])[0]
87
+
88
+ if token:
89
+ token_status['token'] = token
90
+ token_status['token_name'] = received_token_name
91
+ self.send_response(200)
92
+ self.send_header('Content-type', 'text/html')
93
+ self.end_headers()
94
+ else:
95
+ token_status['failed'] = True
96
+ self.send_response(400)
97
+ self.end_headers()
98
+
99
+ html = f'''
100
+ <!DOCTYPE html>
101
+ <html>
102
+ <head>
103
+ <title>Authenticate SDK</title>
104
+ <style>
105
+ body {{
106
+ margin: 0;
107
+ padding: 0;
108
+ display: flex;
109
+ justify-content: center;
110
+ align-items: center;
111
+ min-height: 100vh;
112
+ font-family:
113
+ -apple-system,
114
+ BlinkMacSystemFont,
115
+ 'Segoe UI', Roboto, sans-serif;
116
+ }}
117
+ .container {{
118
+ text-align: center;
119
+ padding: 40px;
120
+ }}
121
+ svg {{
122
+ margin-bottom: 20px;
123
+ }}
124
+ p {{
125
+ font-size: 18px;
126
+ color: #333;
127
+ }}
128
+ </style>
129
+ </head>
130
+ <body>
131
+ <div class="container">
132
+ <?xml version="1.0" encoding="UTF-8"?>
133
+ <svg xmlns="http://www.w3.org/2000/svg"
134
+ id="kumo-logo" width="183.908" height="91.586"
135
+ viewBox="0 0 183.908 91.586">
136
+ <g id="c">
137
+ <g id="Group_9893" data-name="Group 9893">
138
+ <path id="Path_4831" data-name="Path 4831"
139
+ d="M67.159,67.919V46.238L53.494,59.491,
140
+ 68.862,82.3H61.567L49.1,63.74l-7.011,6.8V82.3h-6.02V29.605h6.02V62.182l16.642-16.36H73.109v22.1c0,5.453,3.611,9.419,9.277,9.419,5.547,0,9.14-3.9,9.2-9.282V0H0V91.586H91.586V80.317a15.7,15.7,0,0,1-9.2,2.828c-8.569,0-15.226-6.02-15.226-15.226Z"
141
+ fill="#d40e8c">
142
+ </path>
143
+ <path id="Path_4832" data-name="Path 4832"
144
+ d="M233.452,121.881h-6.019V98.3c0-4.745-3.117-8.286-7.932-8.286s-7.932,3.541-7.932,8.286v23.583h-6.02V98.3c0-4.745-3.116-8.286-7.932-8.286s-7.932,3.541-7.932,8.286v23.583h-6.02V98.51c0-7.932,5.736-14.023,13.952-14.023a12.106,12.106,0,0,1,10.906,6.02,12.3,12.3,0,0,1,10.978-6.02c8.285,0,13.951,6.091,13.951,14.023v23.37Z"
145
+ transform="translate(-86.054 -39.585)"
146
+ fill="#d40e8c">
147
+ </path>
148
+ <path id="Path_4833" data-name="Path 4833"
149
+ d="M313.7,103.751c0,10.481-7.932,
150
+ 19.051-18.342,19.051-10.341,
151
+ 0-18.343-8.569-18.343-19.051,0-10.623,
152
+ 8-19.263,18.343-19.263C305.767,84.488,
153
+ 313.7,93.128,313.7,103.751Zm-6.02,
154
+ 0c0-7.436-5.523-13.527-12.322-13.527-6.728
155
+ ,0-12.252,6.091-12.252,13.527,0,7.295,
156
+ 5.524,13.244,12.252,13.244,6.8,0,
157
+ 12.322-5.949,12.322-13.244Z"
158
+ transform="translate(-129.791 -39.585)"
159
+ fill="#d40e8c">
160
+ </path>
161
+ </g>
162
+ </g>
163
+ </svg>
164
+
165
+ <div id="success-div"
166
+ style="background: #f2f8f0;
167
+ border: 1px solid #1d8102;
168
+ border-radius: 1px;
169
+ padding: 24px 32px;
170
+ margin: 24px auto 0 auto;
171
+ max-width: 400px;
172
+ text-align: left;
173
+ display: none;"
174
+ >
175
+ <div style="font-size: 1.1em;
176
+ font-weight: bold;
177
+ margin-bottom: 10px;
178
+ text-align: left;"
179
+ >
180
+ Request successful
181
+ </div>
182
+ <div style="font-size: 1.1em;">
183
+ Kumo SDK has been granted a token.
184
+ You may now close this window.
185
+ </div>
186
+ </div>
187
+
188
+ <div id="failure-div"
189
+ style="background: #ffebeb;
190
+ border: 1px solid #ff837a;
191
+ border-radius: 1px;
192
+ padding: 24px 32px;
193
+ margin: 24px auto 0 auto;
194
+ max-width: 400px;
195
+ text-align: left;
196
+ display: none;"
197
+ >
198
+ <div style="font-size: 1.1em;
199
+ font-weight: bold;
200
+ margin-bottom: 10px;
201
+ text-align: left;"
202
+ >
203
+ Request failed
204
+ </div>
205
+ <div style="font-size: 1.1em;">
206
+ Failed to generate a token.
207
+ Please try manually creating a token at
208
+ <a href="{api_url}/api-keys" target="_blank">
209
+ {api_url}/api-keys
210
+ </a>
211
+ or contact Kumo for further assistance.
212
+ </div>
213
+ </div>
214
+
215
+ <script>
216
+ // Show only the appropriate div based on the result
217
+ const search = window.location.search;
218
+ const urlParams = new URLSearchParams(search);
219
+ const hasToken = urlParams.has('token');
220
+ if (hasToken) {{
221
+ document
222
+ .getElementById('success-div')
223
+ .style.display = 'block';
224
+ }} else {{
225
+ document
226
+ .getElementById('failure-div')
227
+ .style.display = 'block';
228
+ }}
229
+ </script>
230
+ </div>
231
+ </body>
232
+ </html>
233
+ '''
234
+ self.wfile.write(html.encode('utf-8'))
235
+
236
+ def log_message(self, format: str, *args: object) -> None:
237
+ return # Suppress logging
238
+
239
+ # Find a free port if needed
240
+ port = redirect_port
241
+ for _ in range(10):
242
+ try:
243
+ with TCPServer(("", port), CallbackHandler) as _:
244
+ break
245
+ except OSError:
246
+ port += 1
247
+ else:
248
+ raise RuntimeError(
249
+ "Could not find a free port for the callback server.")
250
+
251
+ # Start the server in a thread
252
+ def serve() -> None:
253
+ with TCPServer(("", port), CallbackHandler) as httpd:
254
+ httpd.timeout = 60
255
+ while token_status['token'] is None:
256
+ httpd.handle_request()
257
+
258
+ server_thread = threading.Thread(target=serve, daemon=True)
259
+ server_thread.start()
260
+
261
+ # Construct the login URL with callback_url and token_name
262
+ callback_url = f"http://127.0.0.1:{port}/"
263
+ login_url = (f"{api_url}/authenticate-sdk/" +
264
+ f"?callback_url={urllib.parse.quote(callback_url)}" +
265
+ f"&token_name={urllib.parse.quote(token_name)}")
266
+
267
+ print(
268
+ "Opening browser page to automatically generate an API key...\n" +
269
+ "If the page does not open, manually create a new API key at " +
270
+ f"{api_url}/api-keys and set it using os.environ[\"KUMO_API_KEY\"] " +
271
+ "= \"YOUR_API_KEY\"")
272
+
273
+ webbrowser.open(login_url)
274
+
275
+ def get_user_input() -> None:
276
+ token_entered = getpass(
277
+ "or paste the API key here and press enter: ").strip()
278
+
279
+ while (len(token_entered) == 0):
280
+ token_entered = getpass(
281
+ "API Key (type then press enter): ").strip()
282
+
283
+ token_status['token'] = token_entered
284
+
285
+ if not in_notebook():
286
+ user_input_thread = threading.Thread(target=get_user_input,
287
+ daemon=True)
288
+ user_input_thread.start()
289
+
290
+ # Wait for the token (timeout after 120s)
291
+ start = time.time()
292
+ while token_status['token'] is None and time.time() - start < 120:
293
+ time.sleep(1)
294
+
295
+ if not isinstance(token_status['token'], str) or not token_status['token']:
296
+ raise TimeoutError(
297
+ "Timed out waiting for authentication or API key input.")
298
+
299
+ os.environ['KUMO_API_KEY'] = token_status['token']
300
+
301
+ logger.info(
302
+ f"Generated token \"{token_status['token_name'] or token_name}\" " +
303
+ "and saved to KUMO_API_KEY env variable")
304
+
305
+
306
+ def _authenticate_colab(api_url: str) -> None:
307
+ """Displays an interactive widget in Google Colab to authenticate the user
308
+ and generate a Kumo API key.
309
+
310
+ This method is intended to be used within a Google Colab notebook. It
311
+ presents a button that, when clicked, opens a popup for the user to
312
+ authenticate with KumoRFM and generate an API key. Upon successful
313
+ authentication, the API key is set in the notebook's environment using the
314
+ "KUMO_API_KEY" variable. Note that Jupyter Notebook support unavailable
315
+ at this time.
316
+
317
+ Args:
318
+ api_url (str): The base URL for the Kumo API
319
+ (e.g., 'https://kumorfm.ai').
320
+
321
+ Raises:
322
+ ImportError: If not running in a Google Colab environment or
323
+ required modules are missing.
324
+ """
325
+ try:
326
+ from google.colab import output
327
+ from IPython.display import HTML, display
328
+ except Exception:
329
+ raise ImportError(
330
+ 'This method is meant to be used in Google Colab.\n If your' +
331
+ 'python code is running on your local machine, use ' +
332
+ 'kumo.authenticate_local().\n Otherwise, visit ' +
333
+ f'{api_url}/api-keys to generate an API key.')
334
+ else:
335
+ import uuid
336
+ from datetime import datetime
337
+
338
+ token_name = "sdk-colab-" + datetime.now().strftime(
339
+ '%Y-%m-%d-%H-%M-%S') + '-Z'
340
+
341
+ def handle_api_key(api_key: str) -> None:
342
+ os.environ['KUMO_API_KEY'] = api_key
343
+
344
+ callback_id = 'api-key-button-' + str(uuid.uuid4())
345
+
346
+ output.register_callback(callback_id, handle_api_key)
347
+
348
+ display(
349
+ HTML(f"""
350
+ <div style="padding: 10px;">
351
+ <!-- <script src="https://cdn.tailwindcss.com"></script> -->
352
+ <svg width="100" height="50" viewBox="0 0 184 92" fill="none"
353
+ xmlns="http://www.w3.org/2000/svg">
354
+ <g clip-path="url(#clip0_749_1962)">
355
+ <path d="M67.159 67.919V46.238L53.494 59.491L68.862 82.3H61.567L49.1 63.74L42.089 70.54V82.3H36.069V29.605H42.089V62.182L58.731 45.822H73.109V67.922C73.109 73.375 76.72 77.341 82.386 77.341C87.933 77.341 91.526 73.441 91.586 68.059V0H0V91.586H91.586V80.317C88.891 82.1996 85.6731 83.1888 82.386 83.145C73.817 83.145 67.16 77.125 67.16 67.919H67.159Z" # noqa: E501
356
+ fill="#FC1373"/>
357
+ <path d="M147.398 82.296H141.379V58.715C141.379 53.97 138.262 50.429 133.447 50.429C128.632 50.429 125.515 53.97 125.515 58.715V82.298H119.495V58.715C119.495 53.97 116.379 50.429 111.563 50.429C106.747 50.429 103.631 53.97 103.631 58.715V82.298H97.611V58.925C97.611 50.993 103.347 44.902 111.563 44.902C113.756 44.8229 115.929 45.3412 117.85 46.4016C119.771 47.4619 121.367 49.0244 122.469 50.922C123.592 49.0276 125.204 47.4696 127.135 46.4107C129.066 45.3517 131.246 44.8307 133.447 44.902C141.732 44.902 147.398 50.993 147.398 58.925V82.296Z"
358
+ fill="#FC1373"/>
359
+ <path d="M183.909 64.166C183.909 74.647 175.977 83.217 165.567 83.217C155.226 83.217 147.224 74.648 147.224 64.166C147.224 53.543 155.224 44.903 165.567 44.903C175.976 44.903 183.909 53.543 183.909 64.166ZM177.889 64.166C177.889 56.73 172.366 50.639 165.567 50.639C158.839 50.639 153.315 56.73 153.315 64.166C153.315 71.461 158.839 77.41 165.567 77.41C172.367 77.41 177.889 71.461 177.889 64.166Z"
360
+ fill="#FC1373"/>
361
+ </g>
362
+ <defs>
363
+ <clipPath id="clip0_749_1962">
364
+ <rect width="183.908" height="91.586" fill="white"/>
365
+ </clipPath>
366
+ </defs>
367
+ </svg>
368
+ <div id="prompt">
369
+ <p>
370
+ Click the button below to connect to KumoRFM and
371
+ generate your API key.
372
+ </p>
373
+ <button id="{callback_id}">
374
+ Generate API Key
375
+ </button>
376
+ </div>
377
+ <div id="success" style="display: none;">
378
+ <p>
379
+ ✓ Your API key has been created and configured in your
380
+ colab notebook.
381
+ </p>
382
+ To manage all your API keys, visit the
383
+ <a href="{api_url}/api-keys" target="_blank">
384
+ KumoRFM website.
385
+ </a>
386
+ </div>
387
+ <div id="failed" style="display: none; color: red;">
388
+ <p>
389
+ API key creation failed with error:
390
+ <span id="error-message"></span>
391
+ </p>
392
+ </div>
393
+ <script>
394
+ // Listen for messages from the popup
395
+ window.addEventListener('message', function(event) {{
396
+ if (event.data.type === 'API_KEY_GENERATED') {{
397
+ // Call the Python callback with the API key
398
+ google.colab.kernel.invokeFunction(
399
+ '{callback_id}', [event.data.apiKey], {{}}
400
+ );
401
+ document.getElementById('prompt')
402
+ .style.display = "none";
403
+ document.getElementById('success')
404
+ .style.display = "block";
405
+ document.getElementById('failed')
406
+ .style.display = "none";
407
+ }} else if (
408
+ event.data.type === 'API_KEY_GENERATION_FAILED'
409
+ ) {{
410
+ document.getElementById('failed')
411
+ .style.display = "block";
412
+ document.getElementById('error-message')
413
+ .innerHTML = event.data.errorMessage;
414
+ }}
415
+ }});
416
+
417
+ document.getElementById('{callback_id}')
418
+ .onclick = function() {{
419
+ // Open the popup
420
+ const popup = window.open(
421
+ '{api_url}/authenticate-sdk?opener=colab&token_name={token_name}',
422
+ 'apiKeyPopup',
423
+ 'width=600,height=700,scrollbars=yes,resizable=yes'
424
+ );
425
+
426
+ // Focus the popup
427
+ if (popup) {{
428
+ popup.focus();
429
+ }}
430
+ }};
431
+ </script>
432
+ </div>
433
+ """))
@@ -0,0 +1,11 @@
1
+ from .id import contains_id
2
+ from .timestamp import contains_timestamp
3
+ from .categorical import contains_categorical
4
+ from .multicategorical import contains_multicategorical
5
+
6
+ __all__ = [
7
+ 'contains_id',
8
+ 'contains_timestamp',
9
+ 'contains_categorical',
10
+ 'contains_multicategorical',
11
+ ]
@@ -0,0 +1,40 @@
1
+ import re
2
+
3
+ import pandas as pd
4
+ from kumoapi.typing import Dtype, Stype
5
+
6
+
7
+ def contains_categorical(
8
+ ser: pd.Series,
9
+ column_name: str,
10
+ dtype: Dtype,
11
+ ) -> bool:
12
+
13
+ if not Stype.categorical.supports_dtype(dtype):
14
+ return False
15
+
16
+ if Dtype == Dtype.bool:
17
+ return True
18
+
19
+ if dtype.is_numerical():
20
+ match = re.search(
21
+ (r'(^|_)(price|sales|amount|quantity|total|cost|score|rating|'
22
+ 'avg|average|recency|age|num|pos|number|position)(_|$)'),
23
+ column_name,
24
+ re.IGNORECASE,
25
+ )
26
+ if match is not None:
27
+ return False
28
+
29
+ ser = ser.iloc[:1000]
30
+ ser = ser.dropna()
31
+
32
+ num_unique = ser.nunique()
33
+
34
+ if num_unique < 20:
35
+ return True
36
+
37
+ if dtype.is_string():
38
+ return num_unique / len(ser) <= 0.5
39
+
40
+ return num_unique / len(ser) <= 0.05
@@ -0,0 +1,46 @@
1
+ import re
2
+
3
+ import pandas as pd
4
+ from kumoapi.typing import Dtype, Stype
5
+
6
+ # Column names suffixes that end in "id" but should not be given the ID stype.
7
+ _IGNORED_ID_SUFFIXES = [
8
+ 'bid',
9
+ 'acid',
10
+ 'grid',
11
+ 'maid',
12
+ 'paid',
13
+ 'raid',
14
+ 'void',
15
+ 'avoid',
16
+ 'braid',
17
+ 'covid',
18
+ 'fluid',
19
+ 'rabid',
20
+ 'solid',
21
+ 'hybrid',
22
+ 'inlaid',
23
+ 'liquid',
24
+ ]
25
+
26
+
27
+ def contains_id(ser: pd.Series, column_name: str, dtype: Dtype) -> bool:
28
+ if not Stype.ID.supports_dtype(dtype):
29
+ return False
30
+
31
+ column_name = column_name.lower()
32
+
33
+ match = re.search(
34
+ r'(^|_)(id|hash|key|code|uuid)(_|$)',
35
+ column_name,
36
+ re.IGNORECASE,
37
+ )
38
+ if match is not None:
39
+ return True
40
+
41
+ if not column_name.endswith('id'):
42
+ return False
43
+ for suffix in _IGNORED_ID_SUFFIXES:
44
+ if column_name.endswith(suffix):
45
+ return False
46
+ return True