kumoai 2.14.0.dev202512301731__cp312-cp312-macosx_11_0_arm64.whl → 2.14.0.dev202601041732__cp312-cp312-macosx_11_0_arm64.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.
kumoai/_version.py CHANGED
@@ -1 +1 @@
1
- __version__ = '2.14.0.dev202512301731'
1
+ __version__ = '2.14.0.dev202601041732'
kumoai/connector/utils.py CHANGED
@@ -1,10 +1,10 @@
1
1
  import asyncio
2
2
  import csv
3
- import gc
4
3
  import io
5
4
  import math
6
5
  import os
7
6
  import re
7
+ import sys
8
8
  import tempfile
9
9
  import threading
10
10
  import time
@@ -920,7 +920,10 @@ def _read_remote_file_with_progress(
920
920
  if capture_first_line and not seen_nl:
921
921
  header_line = bytes(header_acc)
922
922
 
923
- mv = buf.getbuffer() # zero-copy view of BytesIO internal buffer
923
+ if sys.version_info >= (3, 13):
924
+ mv = memoryview(buf.getvalue())
925
+ else:
926
+ mv = buf.getbuffer() # zero-copy view of BytesIO internal buffer
924
927
  return buf, mv, header_line
925
928
 
926
929
 
@@ -999,7 +1002,10 @@ def _iter_mv_chunks(mv: memoryview,
999
1002
  n = mv.nbytes
1000
1003
  while pos < n:
1001
1004
  nxt = min(n, pos + part_size)
1002
- yield mv[pos:nxt] # zero-copy slice
1005
+ if sys.version_info >= (3, 13):
1006
+ yield mv[pos:nxt].tobytes()
1007
+ else:
1008
+ yield mv[pos:nxt] # zero-copy slice
1003
1009
  pos = nxt
1004
1010
 
1005
1011
 
@@ -1473,13 +1479,17 @@ def _remote_upload_file(name: str, fs: Filesystem, url: str, info: dict,
1473
1479
  if renamed_cols_msg:
1474
1480
  logger.info(renamed_cols_msg)
1475
1481
 
1482
+ try:
1483
+ if isinstance(data_mv, memoryview):
1484
+ data_mv.release()
1485
+ except Exception:
1486
+ pass
1487
+
1476
1488
  try:
1477
1489
  if buf:
1478
1490
  buf.close()
1479
1491
  except Exception:
1480
1492
  pass
1481
- del buf, data_mv, header_line
1482
- gc.collect()
1483
1493
 
1484
1494
  logger.info("Upload complete. Validated table %s.", name)
1485
1495
 
@@ -1719,13 +1729,17 @@ def _remote_upload_directory(
1719
1729
  else:
1720
1730
  break
1721
1731
 
1732
+ try:
1733
+ if isinstance(data_mv, memoryview):
1734
+ data_mv.release()
1735
+ except Exception:
1736
+ pass
1737
+
1722
1738
  try:
1723
1739
  if buf:
1724
1740
  buf.close()
1725
1741
  except Exception:
1726
1742
  pass
1727
- del buf, data_mv, header_line
1728
- gc.collect()
1729
1743
 
1730
1744
  _safe_bar_update(file_bar, 1)
1731
1745
  _merge_status_update(fpath)
@@ -20,6 +20,7 @@ from .sagemaker import (
20
20
  from .base import Table
21
21
  from .backend.local import LocalTable
22
22
  from .graph import Graph
23
+ from .task_table import TaskTable
23
24
  from .rfm import ExplainConfig, Explanation, KumoRFM
24
25
 
25
26
  logger = logging.getLogger('kumoai_rfm')
@@ -202,6 +203,7 @@ __all__ = [
202
203
  'Table',
203
204
  'LocalTable',
204
205
  'Graph',
206
+ 'TaskTable',
205
207
  'KumoRFM',
206
208
  'ExplainConfig',
207
209
  'Explanation',
@@ -271,8 +271,8 @@ class Table(ABC):
271
271
  no such primary key is present.
272
272
 
273
273
  The setter sets a column as a primary key on this table, and raises a
274
- :class:`ValueError` if the primary key has a non-ID semantic type or
275
- if the column name does not match a column in the data frame.
274
+ :class:`ValueError` if the primary key has a non-ID compatible data
275
+ type or if the column name does not match a column in the data frame.
276
276
  """
277
277
  if self._primary_key is None:
278
278
  return None
@@ -316,8 +316,9 @@ class Table(ABC):
316
316
  such time column is present.
317
317
 
318
318
  The setter sets a column as a time column on this table, and raises a
319
- :class:`ValueError` if the time column has a non-timestamp semantic
320
- type or if the column name does not match a column in the data frame.
319
+ :class:`ValueError` if the time column has a non-timestamp compatible
320
+ data type or if the column name does not match a column in the data
321
+ frame.
321
322
  """
322
323
  if self._time_column is None:
323
324
  return None
@@ -362,8 +363,8 @@ class Table(ABC):
362
363
 
363
364
  The setter sets a column as an end time column on this table, and
364
365
  raises a :class:`ValueError` if the end time column has a non-timestamp
365
- semantic type or if the column name does not match a column in the data
366
- frame.
366
+ compatible data type or if the column name does not match a column in
367
+ the data frame.
367
368
  """
368
369
  if self._end_time_column is None:
369
370
  return None
@@ -726,30 +726,34 @@ class KumoRFM:
726
726
  "`predict()` or `evaluate()` methods to perform "
727
727
  "predictions or evaluations.")
728
728
 
729
- try:
730
- request = RFMParseQueryRequest(
731
- query=query,
732
- graph_definition=self._graph_def,
733
- )
734
-
735
- resp = self._api_client.parse_query(request)
736
-
737
- if len(resp.validation_response.warnings) > 0:
738
- msg = '\n'.join([
739
- f'{i+1}. {warning.title}: {warning.message}' for i, warning
740
- in enumerate(resp.validation_response.warnings)
741
- ])
742
- warnings.warn(f"Encountered the following warnings during "
743
- f"parsing:\n{msg}")
729
+ request = RFMParseQueryRequest(
730
+ query=query,
731
+ graph_definition=self._graph_def,
732
+ )
744
733
 
745
- return resp.query
746
- except HTTPException as e:
734
+ for attempt in range(self.num_retries + 1):
747
735
  try:
748
- msg = json.loads(e.detail)['detail']
749
- except Exception:
750
- msg = e.detail
751
- raise ValueError(f"Failed to parse query '{query}'. "
752
- f"{msg}") from None
736
+ resp = self._api_client.parse_query(request)
737
+ break
738
+ except HTTPException as e:
739
+ if attempt == self.num_retries:
740
+ try:
741
+ msg = json.loads(e.detail)['detail']
742
+ except Exception:
743
+ msg = e.detail
744
+ raise ValueError(f"Failed to parse query '{query}'. {msg}")
745
+
746
+ time.sleep(2**attempt) # 1s, 2s, 4s, 8s, ...
747
+
748
+ if len(resp.validation_response.warnings) > 0:
749
+ msg = '\n'.join([
750
+ f'{i+1}. {warning.title}: {warning.message}'
751
+ for i, warning in enumerate(resp.validation_response.warnings)
752
+ ])
753
+ warnings.warn(f"Encountered the following warnings during "
754
+ f"parsing:\n{msg}")
755
+
756
+ return resp.query
753
757
 
754
758
  @staticmethod
755
759
  def _get_task_type(
@@ -0,0 +1,247 @@
1
+ import copy
2
+ from collections.abc import Sequence
3
+
4
+ import pandas as pd
5
+ from kumoapi.task import TaskType
6
+ from kumoapi.typing import Dtype, Stype
7
+ from typing_extensions import Self
8
+
9
+ from kumoai.experimental.rfm.base import Column
10
+ from kumoai.experimental.rfm.infer import contains_timestamp, infer_dtype
11
+
12
+
13
+ class TaskTable:
14
+ r"""A :class:`TaskTable` fully specifies the task, *i.e.* its context and
15
+ prediction examples with entity IDs, targets and timestamps.
16
+
17
+ Args:
18
+ task_type: The task type.
19
+ context_df: The data frame holding context examples.
20
+ pred_df: The data frame holding prediction examples.
21
+ entity_table_name: The entity table to predict for. For link prediction
22
+ tasks, needs to hold both entity and target table names.
23
+ entity_column: The name of the entity column.
24
+ target_column: The name of the target column.
25
+ time_column: The name of the time column, if it exists.
26
+ """
27
+ def __init__(
28
+ self,
29
+ task_type: TaskType,
30
+ context_df: pd.DataFrame,
31
+ pred_df: pd.DataFrame,
32
+ entity_table_name: str | Sequence[str],
33
+ entity_column: str,
34
+ target_column: str,
35
+ time_column: str | None = None,
36
+ ) -> None:
37
+
38
+ task_type = TaskType(task_type)
39
+ if task_type not in { # Currently supported task types:
40
+ TaskType.BINARY_CLASSIFICATION,
41
+ TaskType.MULTICLASS_CLASSIFICATION,
42
+ TaskType.REGRESSION,
43
+ TaskType.TEMPORAL_LINK_PREDICTION,
44
+ }:
45
+ raise ValueError # TODO
46
+ self._task_type = task_type
47
+
48
+ # TODO Check dfs (unify from local table)
49
+ self._context_df = context_df.copy(deep=False)
50
+ self._pred_df = pred_df.copy(deep=False)
51
+
52
+ self._dtype_dict: dict[str, Dtype] = {
53
+ column_name: infer_dtype(context_df[column_name])
54
+ for column_name in context_df.columns
55
+ }
56
+
57
+ self._entity_table_names: tuple[str] | tuple[str, str]
58
+ if isinstance(entity_table_name, str):
59
+ self._entity_table_names = (entity_table_name, )
60
+ elif len(entity_table_name) == 1:
61
+ self._entity_table_names = (entity_table_name[0], )
62
+ elif len(entity_table_name) == 2:
63
+ self._entity_table_names = (
64
+ entity_table_name[0],
65
+ entity_table_name[1],
66
+ )
67
+ else:
68
+ raise ValueError # TODO
69
+
70
+ self._entity_column: str = ''
71
+ self._target_column: str = ''
72
+ self._time_column: str | None = None
73
+
74
+ self.entity_column = entity_column
75
+ self.target_column = target_column
76
+ if time_column is not None:
77
+ self.time_column = time_column
78
+
79
+ @property
80
+ def task_type(self) -> TaskType:
81
+ r"""The task type."""
82
+ return self._task_type
83
+
84
+ def narrow(self, start: int, length: int) -> Self:
85
+ r"""Returns a new :class:`TaskTable` that holds a narrowed version of
86
+ prediction examples.
87
+
88
+ Args:
89
+ start: Index of the prediction examples to start narrowing.
90
+ length: Length of the prediction examples.
91
+ """
92
+ out = copy.copy(self)
93
+ df = out._pred_df.iloc[start:start + length].reset_index(drop=True)
94
+ out._pred_df = df
95
+ return out
96
+
97
+ # Entity column ###########################################################
98
+
99
+ @property
100
+ def entity_table_name(self) -> str:
101
+ return self._entity_table_names[0]
102
+
103
+ @property
104
+ def entity_table_names(self) -> tuple[str] | tuple[str, str]:
105
+ return self._entity_table_names
106
+
107
+ @property
108
+ def entity_column(self) -> Column:
109
+ return Column(
110
+ name=self._entity_column,
111
+ expr=None,
112
+ dtype=self._dtype_dict[self._entity_column],
113
+ stype=Stype.ID,
114
+ )
115
+
116
+ @entity_column.setter
117
+ def entity_column(self, name: str) -> None:
118
+ if name in {self._target_column, self._time_column}:
119
+ raise ValueError # TODO
120
+ if name not in self._context_df:
121
+ raise ValueError # TODO
122
+ if name not in self._pred_df:
123
+ raise ValueError # TODO
124
+ if not Stype.ID.supports_dtype(self._dtype_dict[name]):
125
+ raise ValueError # TODO
126
+
127
+ self._entity_column = name
128
+
129
+ # Target column ###########################################################
130
+
131
+ @property
132
+ def _target_stype(self) -> Stype:
133
+ if self.task_type in {
134
+ TaskType.BINARY_CLASSIFICATION,
135
+ TaskType.MULTICLASS_CLASSIFICATION,
136
+ }:
137
+ return Stype.categorical
138
+ if self.task_type in {TaskType.REGRESSION}:
139
+ return Stype.numerical
140
+ if self.task_type.is_link_pred:
141
+ return Stype.multicategorical
142
+ raise ValueError
143
+
144
+ @property
145
+ def target_column(self) -> Column:
146
+ return Column(
147
+ name=self._target_column,
148
+ expr=None,
149
+ dtype=self._dtype_dict[self._target_column],
150
+ stype=self._target_stype,
151
+ )
152
+
153
+ @target_column.setter
154
+ def target_column(self, name: str) -> None:
155
+ if name in {self._entity_column, self._time_column}:
156
+ raise ValueError # TODO
157
+ if name not in self._context_df:
158
+ raise ValueError # TODO
159
+ if not self._target_stype.supports_dtype(self._dtype_dict[name]):
160
+ raise ValueError # TODO
161
+
162
+ self._target_column = name
163
+
164
+ # Time column #############################################################
165
+
166
+ def has_time_column(self) -> bool:
167
+ r"""Returns ``True`` if this task has a time column; ``False``
168
+ otherwise.
169
+ """
170
+ return self._time_column is not None
171
+
172
+ @property
173
+ def time_column(self) -> Column | None:
174
+ r"""The time column of this task.
175
+
176
+ The getter returns the time column of this task, or ``None`` if no
177
+ such time column is present.
178
+
179
+ The setter sets a column as a time column for this task, and raises a
180
+ :class:`ValueError` if the time column has a non-timestamp compatible
181
+ data type or if the column name does not match a column in the data
182
+ frame.
183
+ """
184
+ if self._time_column is None:
185
+ return None
186
+ return Column(
187
+ name=self._time_column,
188
+ expr=None,
189
+ dtype=self._dtype_dict[self._time_column],
190
+ stype=Stype.timestamp,
191
+ )
192
+
193
+ @time_column.setter
194
+ def time_column(self, name: str | None) -> None:
195
+ if name is None:
196
+ self._time_column = None
197
+ return
198
+
199
+ if name in {self._entity_column, self._target_column}:
200
+ raise ValueError # TODO
201
+ if name not in self._context_df:
202
+ raise ValueError # TODO
203
+ if name not in self._pred_df:
204
+ raise ValueError # TODO
205
+ if not contains_timestamp(
206
+ ser=self._context_df[name],
207
+ column_name=name,
208
+ dtype=self._dtype_dict[name],
209
+ ):
210
+ raise ValueError # TODO
211
+
212
+ self._time_column = name
213
+
214
+ # Metadata ################################################################
215
+
216
+ @property
217
+ def metadata(self) -> pd.DataFrame:
218
+ raise NotImplementedError
219
+
220
+ def print_metadata(self) -> None:
221
+ raise NotImplementedError
222
+
223
+ # Python builtins #########################################################
224
+
225
+ def __hash__(self) -> int:
226
+ return hash((
227
+ self.task_type,
228
+ self.entity_table_names,
229
+ self._entity_column,
230
+ self._target_column,
231
+ self._time_column,
232
+ ))
233
+
234
+ def __repr__(self) -> str:
235
+ if self.task_type.is_link_pred:
236
+ entity_table_repr = f'entity_table_names={self.entity_table_names}'
237
+ else:
238
+ entity_table_repr = f'entity_table_name={self.entity_table_name}'
239
+ return (f'{self.__class__.__name__}(\n'
240
+ f' task_type={self.task_type},\n'
241
+ f' num_context_examples={len(self._context_df)},\n'
242
+ f' num_prediction_examples={len(self._pred_df)},\n'
243
+ f' {entity_table_repr},\n'
244
+ f' entity_column={self._entity_column},\n'
245
+ f' target_column={self._target_column},\n'
246
+ f' time_column={self._time_column},\n'
247
+ f')')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kumoai
3
- Version: 2.14.0.dev202512301731
3
+ Version: 2.14.0.dev202601041732
4
4
  Summary: AI on the Modern Data Stack
5
5
  Author-email: "Kumo.AI" <hello@kumo.ai>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  kumoai/_logging.py,sha256=U2_5ROdyk92P4xO4H2WJV8EC7dr6YxmmnM-b7QX9M7I,886
2
2
  kumoai/mixin.py,sha256=MP413xzuCqWhxAPUHmloLA3j4ZyF1tEtfi516b_hOXQ,812
3
- kumoai/_version.py,sha256=zkmtgpHzS-8suGoRkSmHrktIFS142gX_ptBF0P9S3u4,39
3
+ kumoai/_version.py,sha256=ZgEOgzhMS-JHEcx_StKbib3QyH7C44Vgs2zNQ7IM43A,39
4
4
  kumoai/kumolib.cpython-312-darwin.so,sha256=xQvdWHx9xmQ11y3F3ywxJv6A0sDk6D3-2fQbxSdM1z4,232576
5
5
  kumoai/__init__.py,sha256=x6Emn6VesHQz0wR7ZnbddPRYO9A5-0JTHDkzJ3Ocq6w,10907
6
6
  kumoai/formatting.py,sha256=jA_rLDCGKZI8WWCha-vtuLenVKTZvli99Tqpurz1H84,953
@@ -13,10 +13,11 @@ kumoai/_singleton.py,sha256=UTwrbDkoZSGB8ZelorvprPDDv9uZkUi1q_SrmsyngpQ,836
13
13
  kumoai/experimental/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
14
  kumoai/experimental/rfm/relbench.py,sha256=cVsxxV3TIL3PLEoYb-8tAVW3GSef6NQAd3rxdHJL63I,2276
15
15
  kumoai/experimental/rfm/graph.py,sha256=H9lIQLDkL5zJMwEHh7PgruvMUxWsjpynXUT7gnmTTUM,46351
16
- kumoai/experimental/rfm/__init__.py,sha256=TAy2TntkZdwB82wURsZasUsQ-yi06LEXT2u2qTNCVxc,6965
16
+ kumoai/experimental/rfm/__init__.py,sha256=bW2XyYtkbdiu_iICYFF2Fu1Fx5fyGbqne6m_6c1P-fY,7016
17
17
  kumoai/experimental/rfm/sagemaker.py,sha256=6fyXO1Jd_scq-DH7kcv6JcV8QPyTbh4ceqwQDPADlZ0,4963
18
- kumoai/experimental/rfm/rfm.py,sha256=Qna-oSk5lgzmVC_KPolYo5Y6m81qKpyw9wfrvirT3Oc,49526
18
+ kumoai/experimental/rfm/rfm.py,sha256=M5Q_vasSVrAX-5ucFPrdWoTZdpE4VYxKjKZz7BccITc,49672
19
19
  kumoai/experimental/rfm/authenticate.py,sha256=G2RkRWznMVQUzvhvbKhn0bMCY7VmoNYxluz3THRqSdE,18851
20
+ kumoai/experimental/rfm/task_table.py,sha256=SPwkEdKRTwHHFcLqbvC1cDkyLXN2-3DpY5ujAyHRE-Q,8377
20
21
  kumoai/experimental/rfm/backend/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
22
  kumoai/experimental/rfm/backend/sqlite/__init__.py,sha256=jl-DBbhsqQ-dUXyWhyQTM1AU2qNAtXCmi1mokdhtBTg,902
22
23
  kumoai/experimental/rfm/backend/sqlite/table.py,sha256=WqYtd_rwlawItRMXZUfv14qdyU6huQmODuFjDo483dI,6683
@@ -42,7 +43,7 @@ kumoai/experimental/rfm/infer/timestamp.py,sha256=vM9--7eStzaGG13Y-oLYlpNJyhL6f9
42
43
  kumoai/experimental/rfm/infer/stype.py,sha256=fu4zsOB-C7jNeMnq6dsK4bOZSewe7PtZe_AkohSRLoM,894
43
44
  kumoai/experimental/rfm/base/sql_sampler.py,sha256=qurkEVlMhDZw3d9SM2uGud6TMv_Wx_iqWoCgEKd_g9o,5094
44
45
  kumoai/experimental/rfm/base/__init__.py,sha256=rjmMux5lG8srw1bjQGcFQFv6zET9e5riP81nPkw28Jg,724
45
- kumoai/experimental/rfm/base/table.py,sha256=JWaSOcVYfGveUHFZpu85CUr4trLt1PJmAtgsz3QC8N8,26534
46
+ kumoai/experimental/rfm/base/table.py,sha256=6qZeTMfnQejrn6TwqQeJGzJG7C0dSjJ7-NMLX38dvns,26563
46
47
  kumoai/experimental/rfm/base/sampler.py,sha256=tXYnVEyKC5NjSIpe8pNYp0V3Qbg-KbUE_QB0Emy2YiQ,30882
47
48
  kumoai/experimental/rfm/base/expression.py,sha256=Y7NtLTnKlx6euG_N3fLTcrFKheB6P5KS_jhCfoXV9DE,1252
48
49
  kumoai/experimental/rfm/base/source.py,sha256=bwu3GU2TvIXR2fwKAmJ1-5BDoNXMnI1SU3Fgdk8lWnc,301
@@ -87,7 +88,7 @@ kumoai/connector/bigquery_connector.py,sha256=IkyRqvF8Cg96kApUuuz86eYnl-BqBmDX1f
87
88
  kumoai/connector/source_table.py,sha256=QLT8bEYaxeMwy-b168url0VfnkTrs5K6VKLbxTI4hEY,17539
88
89
  kumoai/connector/__init__.py,sha256=9g6oNJ0qHWFlL5enTSoK4_SSH_5hP74xUDZx-9SggC4,842
89
90
  kumoai/connector/file_upload_connector.py,sha256=swp03HgChOvmNPJetuujBSAqADe7NRmS_T0F3o9it4w,7008
90
- kumoai/connector/utils.py,sha256=wlqQxMmPvnFNoCcczGkKYjSu05h8OhWh4fhTzQm_2bQ,64694
91
+ kumoai/connector/utils.py,sha256=sD3_Dmf42FobMfVayzMVkDHIfXzPN-htD3RHd6Kw8hQ,65055
91
92
  kumoai/connector/s3_connector.py,sha256=3kbv-h7DwD8O260Q0h1GPm5wwQpLt-Tb3d_CBSaie44,10155
92
93
  kumoai/connector/base.py,sha256=cujXSZF3zAfuxNuEw54DSL1T7XCuR4t0shSMDuPUagQ,5291
93
94
  kumoai/pquery/__init__.py,sha256=uTXr7t1eXcVfM-ETaM_1ImfEqhrmaj8BjiIvy1YZTL8,533
@@ -114,8 +115,8 @@ kumoai/trainer/__init__.py,sha256=zUdFl-f-sBWmm2x8R-rdVzPBeU2FaMzUY5mkcgoTa1k,93
114
115
  kumoai/trainer/online_serving.py,sha256=9cddb5paeZaCgbUeceQdAOxysCtV5XP-KcsgFz_XR5w,9566
115
116
  kumoai/trainer/distilled_trainer.py,sha256=2pPs5clakNxkLfaak7uqPJOrpTWe1RVVM7ztDSqQZvU,6484
116
117
  kumoai/trainer/trainer.py,sha256=hBXO7gwpo3t59zKFTeIkK65B8QRmWCwO33sbDuEAPlY,20133
117
- kumoai-2.14.0.dev202512301731.dist-info/RECORD,,
118
- kumoai-2.14.0.dev202512301731.dist-info/WHEEL,sha256=V1loQ6TpxABu1APUg0MoTRBOzSKT5xVc3skizX-ovCU,136
119
- kumoai-2.14.0.dev202512301731.dist-info/top_level.txt,sha256=YjU6UcmomoDx30vEXLsOU784ED7VztQOsFApk1SFwvs,7
120
- kumoai-2.14.0.dev202512301731.dist-info/METADATA,sha256=XW8jzm0aptnoLAkWA04ZBBd_H9QnrcVQLUO5ZaF_HJk,2557
121
- kumoai-2.14.0.dev202512301731.dist-info/licenses/LICENSE,sha256=TbWlyqRmhq9PEzCaTI0H0nWLQCCOywQM8wYH8MbjfLo,1102
118
+ kumoai-2.14.0.dev202601041732.dist-info/RECORD,,
119
+ kumoai-2.14.0.dev202601041732.dist-info/WHEEL,sha256=V1loQ6TpxABu1APUg0MoTRBOzSKT5xVc3skizX-ovCU,136
120
+ kumoai-2.14.0.dev202601041732.dist-info/top_level.txt,sha256=YjU6UcmomoDx30vEXLsOU784ED7VztQOsFApk1SFwvs,7
121
+ kumoai-2.14.0.dev202601041732.dist-info/METADATA,sha256=dmLN7vMtkp6iM92XWX0BtKZCF8yU3RmoAKhJ-gwc4ME,2557
122
+ kumoai-2.14.0.dev202601041732.dist-info/licenses/LICENSE,sha256=TbWlyqRmhq9PEzCaTI0H0nWLQCCOywQM8wYH8MbjfLo,1102