pixeltable 0.3.1__py3-none-any.whl → 0.3.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pixeltable might be problematic. Click here for more details.

pixeltable/__version__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  # These version placeholders will be replaced during build.
2
- __version__ = "0.3.1"
3
- __version_tuple__ = (0, 3, 1)
2
+ __version__ = "0.3.2"
3
+ __version_tuple__ = (0, 3, 2)
@@ -68,7 +68,7 @@ class InsertableTable(Table):
68
68
  cat.tbls[tbl._id] = tbl
69
69
 
70
70
  _logger.info(f'Created table `{name}`, id={tbl_version.id}')
71
- print(f'Created table `{name}`.')
71
+ Env.get().console_logger.info(f'Created table `{name}`.')
72
72
  return tbl
73
73
 
74
74
  def get_metadata(self) -> dict[str, Any]:
@@ -134,7 +134,7 @@ class InsertableTable(Table):
134
134
  f'Inserted {status.num_rows} row{"" if status.num_rows == 1 else "s"} '
135
135
  f'with {status.num_excs} error{"" if status.num_excs == 1 else "s"}{cols_with_excs_str}.'
136
136
  )
137
- print(msg)
137
+ Env.get().console_logger.info(msg)
138
138
  _logger.info(f'InsertableTable {self._name}: {msg}')
139
139
  FileCache.get().emit_eviction_warnings()
140
140
  return status
@@ -164,7 +164,7 @@ class InsertableTable(Table):
164
164
  row[col_name] = checked_val
165
165
  except TypeError as e:
166
166
  msg = str(e)
167
- raise excs.Error(f'Error in column {col.name}: {msg[0].lower() + msg[1:]}\nRow: {row}')
167
+ raise excs.Error(f'Error in column {col.name}: {msg[0].lower() + msg[1:]}\nRow: {row}') from e
168
168
 
169
169
  def delete(self, where: Optional['pxt.exprs.Expr'] = None) -> UpdateStatus:
170
170
  """Delete rows in this table.
@@ -1345,7 +1345,7 @@ class Table(SchemaObject):
1345
1345
  raise excs.Error(f'Table `{self._name}` already has an external store with that name: {store.name}')
1346
1346
  _logger.info(f'Linking external store `{store.name}` to table `{self._name}`')
1347
1347
  self._tbl_version.link_external_store(store)
1348
- print(f'Linked external store `{store.name}` to table `{self._name}`.')
1348
+ env.Env.get().console_logger.info(f'Linked external store `{store.name}` to table `{self._name}`.')
1349
1349
 
1350
1350
  def unlink_external_stores(
1351
1351
  self,
@@ -1381,7 +1381,7 @@ class Table(SchemaObject):
1381
1381
 
1382
1382
  for store in stores:
1383
1383
  self._tbl_version.unlink_external_store(store, delete_external_data=delete_external_data)
1384
- print(f'Unlinked external store from table `{self._name}`: {store}')
1384
+ env.Env.get().console_logger.info(f'Unlinked external store from table `{self._name}`: {store}')
1385
1385
 
1386
1386
  def sync(
1387
1387
  self,
@@ -532,7 +532,7 @@ class TableVersion:
532
532
  f'Added {status.num_rows} column value{"" if status.num_rows == 1 else "s"} '
533
533
  f'with {status.num_excs} error{"" if status.num_excs == 1 else "s"}.'
534
534
  )
535
- print(msg)
535
+ Env.get().console_logger.info(msg)
536
536
  _logger.info(f'Columns {[col.name for col in cols]}: {msg}')
537
537
  return status
538
538
 
@@ -156,7 +156,7 @@ class View(Table):
156
156
  plan, num_values_per_row = Planner.create_view_load_plan(view._tbl_version_path)
157
157
  num_rows, num_excs, cols_with_excs = tbl_version.store_tbl.insert_rows(
158
158
  plan, session.connection(), v_min=tbl_version.version)
159
- print(f'Created view `{name}` with {num_rows} rows, {num_excs} exceptions.')
159
+ Env.get().console_logger.info(f'Created view `{name}` with {num_rows} rows, {num_excs} exceptions.')
160
160
 
161
161
  session.commit()
162
162
  cat = Catalog.get()
pixeltable/env.py CHANGED
@@ -18,7 +18,8 @@ import uuid
18
18
  import warnings
19
19
  from dataclasses import dataclass, field
20
20
  from pathlib import Path
21
- from typing import TYPE_CHECKING, Any, Callable, Optional, TypeVar, Type
21
+ from sys import stdout
22
+ from typing import TYPE_CHECKING, Any, Callable, Optional, TypeVar
22
23
  from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
23
24
 
24
25
  import pixeltable_pgserver
@@ -28,6 +29,7 @@ from tqdm import TqdmWarning
28
29
 
29
30
  import pixeltable.exceptions as excs
30
31
  from pixeltable import metadata
32
+ from pixeltable.utils.console_output import ConsoleLogger, ConsoleMessageFilter, ConsoleOutputHandler, map_level
31
33
  from pixeltable.utils.http_server import make_server
32
34
 
33
35
  if TYPE_CHECKING:
@@ -67,6 +69,7 @@ class Env:
67
69
  _httpd: Optional[http.server.HTTPServer]
68
70
  _http_address: Optional[str]
69
71
  _logger: logging.Logger
72
+ _console_logger: ConsoleLogger
70
73
  _default_log_level: int
71
74
  _logfilename: Optional[str]
72
75
  _log_to_stdout: bool
@@ -92,6 +95,8 @@ class Env:
92
95
  cls._instance = env
93
96
 
94
97
  def __init__(self):
98
+ assert self._instance is None, 'Env is a singleton; use Env.get() to access the instance'
99
+
95
100
  self._home = None
96
101
  self._media_dir = None # computed media files
97
102
  self._file_cache_dir = None # cached media files with external URL
@@ -231,6 +236,10 @@ class Env:
231
236
  else:
232
237
  return False
233
238
 
239
+ @property
240
+ def console_logger(self) -> ConsoleLogger:
241
+ return self._console_logger
242
+
234
243
  def _set_up(self, echo: bool = False, reinit_db: bool = False) -> None:
235
244
  if self._initialized:
236
245
  return
@@ -288,6 +297,14 @@ class Env:
288
297
  warnings.simplefilter('ignore', category=UserWarning)
289
298
  warnings.simplefilter('ignore', category=FutureWarning)
290
299
 
300
+ # Set verbose level for user visible console messages
301
+ verbosity = map_level(self._config.get_int_value('verbosity'))
302
+ stdout_handler = ConsoleOutputHandler(stream=stdout)
303
+ stdout_handler.setLevel(verbosity)
304
+ stdout_handler.addFilter(ConsoleMessageFilter())
305
+ self._logger.addHandler(stdout_handler)
306
+ self._console_logger = ConsoleLogger(self._logger)
307
+
291
308
  # configure _logger to log to a file
292
309
  self._logfilename = datetime.datetime.now().strftime('%Y%m%d_%H%M%S') + '.log'
293
310
  fh = logging.FileHandler(self._log_dir / self._logfilename, mode='w')
@@ -361,7 +378,7 @@ class Env:
361
378
  schema.base_metadata.create_all(self._sa_engine)
362
379
  metadata.create_system_info(self._sa_engine)
363
380
 
364
- print(f'Connected to Pixeltable database at: {self.db_url}')
381
+ self.console_logger.info(f'Connected to Pixeltable database at: {self.db_url}')
365
382
 
366
383
  # we now have a home directory and db; start other services
367
384
  self._set_up_runtime()
@@ -32,8 +32,7 @@ class DefaultExprEvaluator(Evaluator):
32
32
  def schedule(self, rows: list[exprs.DataRow], slot_idx: int) -> None:
33
33
  assert self.e.slot_idx >= 0
34
34
  task = asyncio.create_task(self.eval(rows))
35
- self.dispatcher.tasks.add(task)
36
- task.add_done_callback(self.dispatcher.done_cb)
35
+ self.dispatcher.register_task(task)
37
36
 
38
37
  async def eval(self, rows: list[exprs.DataRow]) -> None:
39
38
  rows_with_excs: set[int] = set() # records idxs into rows
@@ -134,8 +133,7 @@ class FnCallEvaluator(Evaluator):
134
133
  scheduler.submit(batched_call_args)
135
134
  else:
136
135
  task = asyncio.create_task(self.eval_batch(batched_call_args))
137
- self.dispatcher.tasks.add(task)
138
- task.add_done_callback(self.dispatcher.done_cb)
136
+ self.dispatcher.register_task(task)
139
137
 
140
138
  elif self.fn.is_async:
141
139
  if self.fn_call.resource_pool is not None:
@@ -147,14 +145,12 @@ class FnCallEvaluator(Evaluator):
147
145
  # create one task per call
148
146
  for item in rows_call_args:
149
147
  task = asyncio.create_task(self.eval_async(item))
150
- self.dispatcher.tasks.add(task)
151
- task.add_done_callback(self.dispatcher.done_cb)
148
+ self.dispatcher.register_task(task)
152
149
 
153
150
  else:
154
151
  # create a single task for all rows
155
152
  task = asyncio.create_task(self.eval(rows_call_args))
156
- self.dispatcher.tasks.add(task)
157
- task.add_done_callback(self.dispatcher.done_cb)
153
+ self.dispatcher.register_task(task)
158
154
 
159
155
  def _queued_call_args_iter(self) -> Iterator[FnCallArgs]:
160
156
  while not self.call_args_queue.empty():
@@ -241,5 +237,4 @@ class FnCallEvaluator(Evaluator):
241
237
  return
242
238
  batched_call_args = self._create_batch_call_args(list(self._queued_call_args_iter()))
243
239
  task = asyncio.create_task(self.eval_batch(batched_call_args))
244
- self.dispatcher.tasks.add(task)
245
- task.add_done_callback(self.dispatcher.done_cb)
240
+ self.dispatcher.register_task(task)
@@ -391,7 +391,11 @@ class ExprEvalNode(ExecNode):
391
391
  _logger.debug(f'Scheduling {len(ready_rows)} rows for slot {slot_idx}')
392
392
  self.slot_evaluators[slot_idx].schedule(ready_rows, slot_idx)
393
393
 
394
- def done_cb(self, t: asyncio.Task) -> None:
394
+ def register_task(self, t: asyncio.Task) -> None:
395
+ self.tasks.add(t)
396
+ t.add_done_callback(self._done_cb)
397
+
398
+ def _done_cb(self, t: asyncio.Task) -> None:
395
399
  self.tasks.discard(t)
396
400
  # end the main loop if we had an unhandled exception
397
401
  try:
@@ -59,11 +59,10 @@ class Dispatcher(Protocol):
59
59
  """
60
60
  Row dispatcher used by Evaluators/Schedulers for post-processing after slot materialization and for task management.
61
61
 
62
- Task management: all tasks need to be recorded in tasks and have done_cb registered with add_done_callback()
62
+ Task management: all tasks need to be registered via register_task()
63
63
  Exceptions: evaluators/schedulers need to check exc_event prior to starting long-running (non-interruptible)
64
64
  computations
65
65
  """
66
- tasks: set[asyncio.Task]
67
66
  row_builder: exprs.RowBuilder
68
67
  exc_event: asyncio.Event
69
68
  schedulers: dict[str, Scheduler] # key: resource pool id
@@ -76,8 +75,8 @@ class Dispatcher(Protocol):
76
75
  """Propagates exception in slot_with_exc to all dependent slots and dispatches the rest; does not block"""
77
76
  ...
78
77
 
79
- def done_cb(self, f: asyncio.Task) -> None:
80
- """Callback for task completion; does not block"""
78
+ def register_task(self, f: asyncio.Task) -> None:
79
+ """Register task with dispatcher for subsequent cleanup; does not block"""
81
80
  ...
82
81
 
83
82
 
@@ -62,8 +62,7 @@ class RateLimitsScheduler(Scheduler):
62
62
  self.queue = asyncio.PriorityQueue()
63
63
  self.dispatcher = dispatcher
64
64
  self.loop_task = asyncio.create_task(self._main_loop())
65
- self.dispatcher.tasks.add(self.loop_task)
66
- self.loop_task.add_done_callback(self.dispatcher.done_cb)
65
+ self.dispatcher.register_task(self.loop_task)
67
66
  self.pool_info = None # initialized in _main_loop by the first request
68
67
  self.est_usage = {}
69
68
  self.num_in_flight = 0
@@ -79,6 +78,19 @@ class RateLimitsScheduler(Scheduler):
79
78
  def submit(self, item: FnCallArgs) -> None:
80
79
  self.queue.put_nowait(self.QueueItem(item, 0))
81
80
 
81
+ def _set_pool_info(self) -> None:
82
+ """Initialize pool_info with the RateLimitsInfo for the resource pool, if available"""
83
+ if self.pool_info is not None:
84
+ return
85
+ self.pool_info = env.Env.get().get_resource_pool_info(self.resource_pool, None)
86
+ if self.pool_info is None:
87
+ return
88
+ assert isinstance(self.pool_info, env.RateLimitsInfo)
89
+ assert hasattr(self.pool_info, 'get_request_resources')
90
+ sig = inspect.signature(self.pool_info.get_request_resources)
91
+ self.get_request_resources_param_names = [p.name for p in sig.parameters.values()]
92
+ self.est_usage = {r: 0 for r in self._resources}
93
+
82
94
  async def _main_loop(self) -> None:
83
95
  item: Optional[RateLimitsScheduler.QueueItem] = None
84
96
  while True:
@@ -95,15 +107,7 @@ class RateLimitsScheduler(Scheduler):
95
107
  item = None
96
108
  # if this was the first request, it created the pool_info
97
109
  if self.pool_info is None:
98
- self.pool_info = env.Env.get().get_resource_pool_info(self.resource_pool, None)
99
- if self.pool_info is None:
100
- # we still don't have rate limits, wait for the next request
101
- continue
102
- assert isinstance(self.pool_info, env.RateLimitsInfo)
103
- assert hasattr(self.pool_info, 'get_request_resources')
104
- sig = inspect.signature(self.pool_info.get_request_resources)
105
- self.get_request_resources_param_names = [p.name for p in sig.parameters.values()]
106
- self.est_usage = {r: 0 for r in self._resources}
110
+ self._set_pool_info()
107
111
  continue
108
112
 
109
113
  # check rate limits
@@ -149,8 +153,7 @@ class RateLimitsScheduler(Scheduler):
149
153
  _logger.debug(f'creating task for {self.resource_pool}')
150
154
  self.num_in_flight += 1
151
155
  task = asyncio.create_task(self._exec(item.request, item.num_retries, is_task=True))
152
- self.dispatcher.tasks.add(task)
153
- task.add_done_callback(self.dispatcher.done_cb)
156
+ self.dispatcher.register_task(task)
154
157
  item = None
155
158
 
156
159
  @property
@@ -206,10 +209,15 @@ class RateLimitsScheduler(Scheduler):
206
209
 
207
210
  self.dispatcher.dispatch(request.rows)
208
211
  except Exception as exc:
212
+ _logger.debug(f'scheduler {self.resource_pool}: exception in slot {request.fn_call.slot_idx}: {exc}')
213
+ if self.pool_info is None:
214
+ # our pool info should be available at this point
215
+ self._set_pool_info()
209
216
  if num_retries < self.MAX_RETRIES and self.pool_info is not None:
210
217
  retry_delay = self.pool_info.get_retry_delay(exc)
211
218
  if retry_delay is not None:
212
219
  self.total_retried += 1
220
+ _logger.debug(f'scheduler {self.resource_pool}: retrying in {retry_delay} seconds')
213
221
  await asyncio.sleep(retry_delay)
214
222
  self.queue.put_nowait(self.QueueItem(request, num_retries + 1))
215
223
  return
@@ -8,13 +8,13 @@ from uuid import UUID
8
8
 
9
9
  import numpy as np
10
10
  import sqlalchemy as sql
11
-
12
11
  import pixeltable.catalog as catalog
13
12
  import pixeltable.exceptions as excs
14
13
  import pixeltable.func as func
15
14
  import pixeltable.utils as utils
16
15
  from pixeltable.utils.media_store import MediaStore
17
16
  from .data_row import DataRow
17
+ from pixeltable.env import Env
18
18
  from .expr import Expr
19
19
  from .expr_set import ExprSet
20
20
 
@@ -32,7 +32,7 @@ class ExecProfile:
32
32
  per_call_time = self.eval_time[i] / self.eval_count[i]
33
33
  calls_per_row = self.eval_count[i] / num_rows
34
34
  multiple_str = f'({calls_per_row}x)' if calls_per_row > 1 else ''
35
- print(f'{self.row_builder.unique_exprs[i]}: {utils.print_perf_counter_delta(per_call_time)} {multiple_str}')
35
+ Env.get().console_logger.info(f'{self.row_builder.unique_exprs[i]}: {utils.print_perf_counter_delta(per_call_time)} {multiple_str}')
36
36
 
37
37
 
38
38
  @dataclass
@@ -137,6 +137,11 @@ async def messages(
137
137
  if not tool_choice['parallel_tool_calls']:
138
138
  tool_choice_['disable_parallel_tool_use'] = True
139
139
 
140
+ # make sure the pool info exists prior to making the request
141
+ resource_pool_id = f'rate-limits:anthropic:{model}'
142
+ rate_limits_info = env.Env.get().get_resource_pool_info(resource_pool_id, AnthropicRateLimitsInfo)
143
+ assert isinstance(rate_limits_info, env.RateLimitsInfo)
144
+
140
145
  # TODO: timeouts should be set system-wide and be user-configurable
141
146
  from anthropic.types import MessageParam
142
147
 
@@ -178,9 +183,6 @@ async def messages(
178
183
  if retry_after_str is not None:
179
184
  _logger.debug(f'retry-after: {retry_after_str}')
180
185
 
181
- resource_pool_id = f'rate-limits:anthropic:{model}'
182
- rate_limits_info = env.Env.get().get_resource_pool_info(resource_pool_id, AnthropicRateLimitsInfo)
183
- assert isinstance(rate_limits_info, env.RateLimitsInfo)
184
186
  rate_limits_info.record(
185
187
  requests=(requests_limit, requests_remaining, requests_reset),
186
188
  input_tokens=(input_tokens_limit, input_tokens_remaining, input_tokens_reset),
@@ -113,7 +113,17 @@ class OpenAIRateLimitsInfo(env.RateLimitsInfo):
113
113
  super().__init__(get_request_resources)
114
114
  import openai
115
115
  self.retryable_errors = (
116
- openai.RateLimitError, openai.APITimeoutError, openai.UnprocessableEntityError, openai.InternalServerError
116
+ # ConnectionError: we occasionally see this error when the AsyncConnectionPool is trying to close
117
+ # expired connections
118
+ # (AsyncConnectionPool._close_expired_connections() fails with ConnectionError when executing
119
+ # 'await connection.aclose()', which is potentially a bug in AsyncConnectionPool)
120
+ openai.APIConnectionError,
121
+
122
+ # the following errors are retryable according to OpenAI's API documentation
123
+ openai.RateLimitError,
124
+ openai.APITimeoutError,
125
+ openai.UnprocessableEntityError,
126
+ openai.InternalServerError,
117
127
  )
118
128
 
119
129
  def get_retry_delay(self, exc: Exception) -> Optional[float]:
@@ -400,6 +410,11 @@ async def chat_completions(
400
410
  if tool_choice is not None and not tool_choice['parallel_tool_calls']:
401
411
  extra_body = {'parallel_tool_calls': False}
402
412
 
413
+ # make sure the pool info exists prior to making the request
414
+ resource_pool = _resource_pool(model)
415
+ rate_limits_info = env.Env.get().get_resource_pool_info(
416
+ resource_pool, lambda: OpenAIRateLimitsInfo(_chat_completions_get_request_resources))
417
+
403
418
  # cast(Any, ...): avoid mypy errors
404
419
  result = await _async_openai_client().chat.completions.with_raw_response.create(
405
420
  messages=messages,
@@ -423,10 +438,7 @@ async def chat_completions(
423
438
  extra_body=extra_body,
424
439
  )
425
440
 
426
- resource_pool = _resource_pool(model)
427
441
  requests_info, tokens_info = _get_header_info(result.headers)
428
- rate_limits_info = env.Env.get().get_resource_pool_info(resource_pool, lambda: OpenAIRateLimitsInfo(
429
- _chat_completions_get_request_resources))
430
442
  rate_limits_info.record(requests=requests_info, tokens=tokens_info)
431
443
 
432
444
  return json.loads(result.text)
@@ -523,13 +535,13 @@ async def embeddings(
523
535
  >>> tbl['embed'] = embeddings(tbl.text, model='text-embedding-3-small')
524
536
  """
525
537
  _logger.debug(f'embeddings: batch_size={len(input)}')
538
+ resource_pool = _resource_pool(model)
539
+ rate_limits_info = env.Env.get().get_resource_pool_info(
540
+ resource_pool, lambda: OpenAIRateLimitsInfo(_embeddings_get_request_resources))
526
541
  result = await _async_openai_client().embeddings.with_raw_response.create(
527
542
  input=input, model=model, dimensions=_opt(dimensions), user=_opt(user), encoding_format='float'
528
543
  )
529
- resource_pool = _resource_pool(model)
530
544
  requests_info, tokens_info = _get_header_info(result.headers)
531
- rate_limits_info = env.Env.get().get_resource_pool_info(
532
- resource_pool, lambda: OpenAIRateLimitsInfo(_embeddings_get_request_resources))
533
545
  rate_limits_info.record(requests=requests_info, tokens=tokens_info)
534
546
  return [np.array(data['embedding'], dtype=np.float64) for data in json.loads(result.content)['data']]
535
547
 
pixeltable/globals.py CHANGED
@@ -606,8 +606,7 @@ def create_dir(path_str: str, if_exists: Literal['error', 'ignore', 'replace', '
606
606
  dir = catalog.Dir(dir_record.id, parent._id, path.name)
607
607
  cat.paths[path] = dir
608
608
  session.commit()
609
- _logger.info(f'Created directory `{path_str}`.')
610
- print(f'Created directory `{path_str}`.')
609
+ Env.get().console_logger.info(f'Created directory `{path_str}`.')
611
610
  return dir
612
611
 
613
612
  def drop_dir(path_str: str, force: bool = False, if_not_exists: Literal['error', 'ignore'] = 'error') -> None:
@@ -13,7 +13,7 @@ from pixeltable import exceptions as excs
13
13
  if typing.TYPE_CHECKING:
14
14
  import datasets # type: ignore[import-untyped]
15
15
 
16
- _logger = logging.getLogger(__name__)
16
+ _logger = logging.getLogger('pixeltable')
17
17
 
18
18
  # use 100MB as the batch size limit for loading a huggingface dataset into pixeltable.
19
19
  # The primary goal is to bound memory use, regardless of dataset size.
@@ -230,7 +230,7 @@ class LabelStudioProject(Project):
230
230
  self.project.create_predictions(predictions)
231
231
  tasks_created += 1
232
232
 
233
- print(f'Created {tasks_created} new task(s) in {self}.')
233
+ env.Env.get().console_logger.info(f'Created {tasks_created} new task(s) in {self}.')
234
234
 
235
235
  sync_status = SyncStatus(external_rows_created=tasks_created)
236
236
 
@@ -330,7 +330,7 @@ class LabelStudioProject(Project):
330
330
  if len(page) > 0:
331
331
  self.project.import_tasks(page)
332
332
 
333
- print(f'Created {tasks_created} new task(s) and updated {tasks_updated} existing task(s) in {self}.')
333
+ env.Env.get().console_logger.info(f'Created {tasks_created} new task(s) and updated {tasks_updated} existing task(s) in {self}.')
334
334
 
335
335
  sync_status = SyncStatus(external_rows_created=tasks_created, external_rows_updated=tasks_updated)
336
336
 
@@ -363,7 +363,7 @@ class LabelStudioProject(Project):
363
363
 
364
364
  if len(tasks_to_delete) > 0:
365
365
  self.project.delete_tasks(tasks_to_delete)
366
- print(f'Deleted {len(tasks_to_delete)} tasks(s) in {self} that are no longer present in Pixeltable.')
366
+ env.Env.get().console_logger.info(f'Deleted {len(tasks_to_delete)} tasks(s) in {self} that are no longer present in Pixeltable.')
367
367
 
368
368
  # Remove them from the `existing_tasks` dict so that future updates are applied correctly
369
369
  for rowid in deleted_rowids:
@@ -406,7 +406,7 @@ class LabelStudioProject(Project):
406
406
  assert ancestor._base is not None
407
407
  ancestor = ancestor._base
408
408
  update_status = ancestor.batch_update(updates)
409
- print(f'Updated annotation(s) from {len(updates)} task(s) in {self}.')
409
+ env.Env.get().console_logger.info(f'Updated annotation(s) from {len(updates)} task(s) in {self}.')
410
410
  return SyncStatus(pxt_rows_updated=update_status.num_rows, num_excs=update_status.num_excs)
411
411
  else:
412
412
  return SyncStatus.empty()
@@ -529,7 +529,7 @@ class LabelStudioProject(Project):
529
529
  """
530
530
  title = self.project_title
531
531
  _label_studio_client().delete_project(self.project_id)
532
- print(f'Deleted Label Studio project: {title}')
532
+ env.Env.get().console_logger.info(f'Deleted Label Studio project: {title}')
533
533
 
534
534
  def __eq__(self, other) -> bool:
535
535
  return isinstance(other, LabelStudioProject) and self.project_id == other.project_id
pixeltable/io/parquet.py CHANGED
@@ -23,7 +23,7 @@ if typing.TYPE_CHECKING:
23
23
  import pyarrow as pa
24
24
  import pixeltable as pxt
25
25
 
26
- _logger = logging.getLogger(__name__)
26
+ _logger = logging.getLogger('pixeltable')
27
27
 
28
28
 
29
29
  def _write_batch(value_batch: dict[str, deque], schema: pa.Schema, output_path: Path) -> None:
@@ -47,7 +47,8 @@ def upgrade_md(engine: sql.engine.Engine) -> None:
47
47
  while md_version < VERSION:
48
48
  if md_version not in converter_cbs:
49
49
  raise RuntimeError(f'No metadata converter for version {md_version}')
50
- print(f'Converting metadata from version {md_version} to {md_version + 1}')
50
+ from pixeltable.env import Env
51
+ Env.get().console_logger.info(f'Converting metadata from version {md_version} to {md_version + 1}')
51
52
  converter_cbs[md_version](engine)
52
53
  md_version += 1
53
54
  # update system info
pixeltable/type_system.py CHANGED
@@ -793,12 +793,19 @@ class JsonType(ColumnType):
793
793
 
794
794
 
795
795
  class ArrayType(ColumnType):
796
- def __init__(self, shape: tuple[Union[int, None], ...], dtype: ColumnType, nullable: bool = False):
796
+
797
+ shape: Optional[tuple[Optional[int], ...]]
798
+ pxt_dtype: Optional[ColumnType]
799
+ dtype: Optional[ColumnType.Type]
800
+
801
+ def __init__(self, shape: Optional[tuple[Optional[int], ...]] = None, dtype: Optional[ColumnType] = None, nullable: bool = False):
797
802
  super().__init__(self.Type.ARRAY, nullable=nullable)
803
+ assert shape is None or dtype is not None, (shape, dtype) # cannot specify a shape without a dtype
804
+ assert dtype is None or dtype.is_int_type() or dtype.is_float_type() or dtype.is_bool_type() or dtype.is_string_type()
805
+
798
806
  self.shape = shape
799
- assert dtype.is_int_type() or dtype.is_float_type() or dtype.is_bool_type() or dtype.is_string_type()
800
- self.pxt_dtype = dtype
801
- self.dtype = dtype._type
807
+ self.pxt_dtype = dtype # we need this for copy() and __str__()
808
+ self.dtype = None if dtype is None else dtype._type
802
809
 
803
810
  def copy(self, nullable: bool) -> ColumnType:
804
811
  return ArrayType(self.shape, self.pxt_dtype, nullable=nullable)
@@ -812,28 +819,38 @@ class ArrayType(ColumnType):
812
819
  def supertype(self, other: ColumnType) -> Optional[ArrayType]:
813
820
  if not isinstance(other, ArrayType):
814
821
  return None
822
+ super_dtype = self.Type.supertype(self.dtype, other.dtype, self.common_supertypes)
823
+ if super_dtype is None:
824
+ # if the dtypes are incompatible, then the supertype is a fully general array
825
+ return ArrayType(nullable=(self.nullable or other.nullable))
826
+ super_shape: Optional[tuple[Optional[int], ...]]
815
827
  if len(self.shape) != len(other.shape):
816
- return None
817
- base_type = self.Type.supertype(self.dtype, other.dtype, self.common_supertypes)
818
- if base_type is None:
819
- return None
820
- shape = [n1 if n1 == n2 else None for n1, n2 in zip(self.shape, other.shape)]
821
- return ArrayType(tuple(shape), self.make_type(base_type), nullable=(self.nullable or other.nullable))
828
+ super_shape = None
829
+ else:
830
+ super_shape = tuple(n1 if n1 == n2 else None for n1, n2 in zip(self.shape, other.shape))
831
+ return ArrayType(super_shape, self.make_type(super_dtype), nullable=(self.nullable or other.nullable))
822
832
 
823
833
  def _as_dict(self) -> dict:
824
834
  result = super()._as_dict()
825
- result.update(shape=list(self.shape), dtype=self.dtype.value)
835
+ shape_as_list = None if self.shape is None else list(self.shape)
836
+ dtype_value = None if self.dtype is None else self.dtype.value
837
+ result.update(shape=shape_as_list, dtype=dtype_value)
826
838
  return result
827
839
 
828
840
  def _to_base_str(self) -> str:
841
+ if self.shape is None and self.dtype is None:
842
+ return 'Array'
843
+ if self.shape is None:
844
+ return f'Array[{self.pxt_dtype}]'
845
+ assert self.dtype is not None
829
846
  return f'Array[{self.shape}, {self.pxt_dtype}]'
830
847
 
831
848
  @classmethod
832
849
  def _from_dict(cls, d: dict) -> ColumnType:
833
850
  assert 'shape' in d
834
851
  assert 'dtype' in d
835
- shape = tuple(d['shape'])
836
- dtype = cls.make_type(cls.Type(d['dtype']))
852
+ shape = None if d['shape'] is None else tuple(d['shape'])
853
+ dtype = None if d['dtype'] is None else cls.make_type(cls.Type(d['dtype']))
837
854
  return cls(shape, dtype, nullable=d['nullable'])
838
855
 
839
856
  @classmethod
@@ -855,18 +872,30 @@ class ArrayType(ColumnType):
855
872
  def is_valid_literal(self, val: np.ndarray) -> bool:
856
873
  if not isinstance(val, np.ndarray):
857
874
  return False
858
- if len(val.shape) != len(self.shape):
875
+
876
+ # If a dtype is specified, check that there's a match
877
+ if self.dtype is not None and not np.issubdtype(val.dtype, self.numpy_dtype()):
859
878
  return False
860
- # check that the shapes are compatible
861
- for n1, n2 in zip(val.shape, self.shape):
862
- if n1 is None:
863
- return False
864
- if n2 is None:
865
- # wildcard
866
- continue
867
- if n1 != n2:
879
+
880
+ # If no dtype is specified, we still need to check that the dtype is one of the supported types
881
+ if self.dtype is None and not any(
882
+ np.issubdtype(val.dtype, ndtype) for ndtype in [np.int64, np.float32, np.bool_, np.str_]
883
+ ):
884
+ return False
885
+
886
+ # If a shape is specified, check that there's a match
887
+ if self.shape is not None:
888
+ if len(val.shape) != len(self.shape):
868
889
  return False
869
- return np.issubdtype(val.dtype, self.numpy_dtype())
890
+ # check that the shapes are compatible
891
+ for n1, n2 in zip(val.shape, self.shape):
892
+ assert n1 is not None # `val` must have a concrete shape
893
+ if n2 is None:
894
+ continue # wildcard
895
+ if n1 != n2:
896
+ return False
897
+
898
+ return True
870
899
 
871
900
  def _to_json_schema(self) -> dict[str, Any]:
872
901
  return {
@@ -878,9 +907,17 @@ class ArrayType(ColumnType):
878
907
  if not isinstance(val, np.ndarray):
879
908
  raise TypeError(f'Expected numpy.ndarray, got {val.__class__.__name__}')
880
909
  if not self.is_valid_literal(val):
881
- raise TypeError((
882
- f'Expected ndarray({self.shape}, dtype={self.numpy_dtype()}), '
883
- f'got ndarray({val.shape}, dtype={val.dtype})'))
910
+ if self.shape is not None:
911
+ raise TypeError(
912
+ f'Expected numpy.ndarray({self.shape}, dtype={self.numpy_dtype()}), '
913
+ f'got numpy.ndarray({val.shape}, dtype={val.dtype})'
914
+ )
915
+ elif self.dtype is not None:
916
+ raise TypeError(
917
+ f'Expected numpy.ndarray of dtype {self.numpy_dtype()}, got numpy.ndarray of dtype {val.dtype}'
918
+ )
919
+ else:
920
+ raise TypeError(f'Unsupported dtype for numpy.ndarray: {val.dtype}')
884
921
 
885
922
  def _create_literal(self, val: Any) -> Any:
886
923
  if isinstance(val, (list, tuple)):
@@ -892,7 +929,9 @@ class ArrayType(ColumnType):
892
929
  def to_sa_type(self) -> sql.types.TypeEngine:
893
930
  return sql.LargeBinary()
894
931
 
895
- def numpy_dtype(self) -> np.dtype:
932
+ def numpy_dtype(self) -> Optional[np.dtype]:
933
+ if self.dtype is None:
934
+ return None
896
935
  if self.dtype == self.Type.INT:
897
936
  return np.dtype(np.int64)
898
937
  if self.dtype == self.Type.FLOAT:
@@ -901,7 +940,7 @@ class ArrayType(ColumnType):
901
940
  return np.dtype(np.bool_)
902
941
  if self.dtype == self.Type.STRING:
903
942
  return np.dtype(np.str_)
904
- assert False
943
+ assert False, self.dtype
905
944
 
906
945
 
907
946
  class ImageType(ColumnType):
@@ -1174,6 +1213,8 @@ class Array(np.ndarray, _PxtType):
1174
1213
  params = item if isinstance(item, tuple) else (item,)
1175
1214
  shape: Optional[tuple] = None
1176
1215
  dtype: Optional[ColumnType] = None
1216
+ if not any(isinstance(param, (type, _AnnotatedAlias)) for param in params):
1217
+ raise TypeError('Array type parameter must include a dtype.')
1177
1218
  for param in params:
1178
1219
  if isinstance(param, tuple):
1179
1220
  if not all(n is None or (isinstance(n, int) and n >= 1) for n in param):
@@ -1181,21 +1222,17 @@ class Array(np.ndarray, _PxtType):
1181
1222
  if shape is not None:
1182
1223
  raise TypeError(f'Duplicate Array type parameter: {param}')
1183
1224
  shape = param
1184
- elif isinstance(param, type) or isinstance(param, _AnnotatedAlias):
1225
+ elif isinstance(param, (type, _AnnotatedAlias)):
1185
1226
  if dtype is not None:
1186
1227
  raise TypeError(f'Duplicate Array type parameter: {param}')
1187
1228
  dtype = ColumnType.normalize_type(param, allow_builtin_types=False)
1188
1229
  else:
1189
1230
  raise TypeError(f'Invalid Array type parameter: {param}')
1190
- if shape is None:
1191
- raise TypeError('Array type is missing parameter: shape')
1192
- if dtype is None:
1193
- raise TypeError('Array type is missing parameter: dtype')
1194
1231
  return typing.Annotated[np.ndarray, ArrayType(shape=shape, dtype=dtype, nullable=False)]
1195
1232
 
1196
1233
  @classmethod
1197
1234
  def as_col_type(cls, nullable: bool) -> ColumnType:
1198
- raise TypeError('Array type cannot be used without specifying shape and dtype')
1235
+ return ArrayType(nullable=nullable)
1199
1236
 
1200
1237
 
1201
1238
  class Image(PIL.Image.Image, _PxtType):
pixeltable/utils/arrow.py CHANGED
@@ -1,4 +1,3 @@
1
- import logging
2
1
  from typing import Any, Iterator, Optional, Union
3
2
 
4
3
  import numpy as np
@@ -6,11 +5,7 @@ import pyarrow as pa
6
5
  import datetime
7
6
 
8
7
  import pixeltable.type_system as ts
9
- from pixeltable.env import Env
10
8
 
11
- _tz_def = Env().get().default_time_zone
12
-
13
- _logger = logging.getLogger(__name__)
14
9
 
15
10
  _pa_to_pt: dict[pa.DataType, ts.ColumnType] = {
16
11
  pa.string(): ts.StringType(nullable=True),
@@ -0,0 +1,41 @@
1
+ import logging
2
+
3
+ def map_level(verbosity: int) -> int:
4
+ """
5
+ Map verbosity level to logging level.
6
+ 0 - minimum logging - warn and above
7
+ 1 - default logging - info and above
8
+ 2 - more logging - debug and above
9
+ Args:
10
+
11
+ Returns:
12
+ Logging level as integer
13
+ """
14
+ if verbosity == 0:
15
+ return logging.WARN
16
+ if verbosity == 1:
17
+ return logging.INFO
18
+ if verbosity == 2:
19
+ return logging.DEBUG
20
+ return logging.INFO
21
+
22
+ class ConsoleOutputHandler(logging.StreamHandler):
23
+ def __init__(self, stream):
24
+ super().__init__(stream)
25
+
26
+ def emit(self, record):
27
+ if record.msg.endswith('\n'):
28
+ self.stream.write(record.msg)
29
+ else:
30
+ self.stream.write(record.msg + '\n')
31
+
32
+ class ConsoleMessageFilter(logging.Filter):
33
+ def filter(self, record: logging.LogRecord) -> bool:
34
+ if hasattr(record, 'user_visible') and record.user_visible:
35
+ return True
36
+ return False
37
+
38
+ class ConsoleLogger(logging.LoggerAdapter):
39
+ def __init__(self, logger:logging.Logger):
40
+ super().__init__(logger, extra={'user_visible' : True})
41
+
@@ -239,4 +239,4 @@ class FileCache:
239
239
 
240
240
  def debug_print(self) -> None:
241
241
  for entry in self.cache.values():
242
- print(f'CacheEntry: tbl_id={entry.tbl_id}, col_id={entry.col_id}, size={entry.size}')
242
+ _logger.debug(f'CacheEntry: tbl_id={entry.tbl_id}, col_id={entry.col_id}, size={entry.size}')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pixeltable
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: AI Data Infrastructure: Declarative, Multimodal, and Incremental
5
5
  Home-page: https://pixeltable.com/
6
6
  License: Apache-2.0
@@ -27,6 +27,7 @@ Requires-Dist: av (>=10.0.0)
27
27
  Requires-Dist: beautifulsoup4 (>=4.0.0,<5.0.0)
28
28
  Requires-Dist: cloudpickle (>=2.2.1,<3.0.0)
29
29
  Requires-Dist: ftfy (>=6.2.0,<7.0.0)
30
+ Requires-Dist: httpcore (>=1.0.3)
30
31
  Requires-Dist: httpx (>=0.27)
31
32
  Requires-Dist: jinja2 (>=3.1.3,<4.0.0)
32
33
  Requires-Dist: jmespath (>=1.0.1,<2.0.0)
@@ -1,21 +1,21 @@
1
1
  pixeltable/__init__.py,sha256=c-Z21TqJUbzGQKsO4CS4lVFwWlZnVhpIR94cL6RNmDo,1411
2
- pixeltable/__version__.py,sha256=zVI6ltlHGwKITzBi4TSc2jGBnWUb4mnqjF_PYoGqTuw,112
2
+ pixeltable/__version__.py,sha256=Ftdd_iaB0tNUSNnDBsJFrHIR6gBXSgcgIbcjGk_8ds4,112
3
3
  pixeltable/catalog/__init__.py,sha256=u8Ods4ncTY7DI5w0jyHVC0QDVDLHAy8Rqia3qsM5OH8,517
4
4
  pixeltable/catalog/catalog.py,sha256=tyDyI5wQw7vV6_FChrp9qgGCRClcjiSdW3eygYT0p9s,7849
5
5
  pixeltable/catalog/column.py,sha256=ezeKoGl6aBTzSZBihDA6vdETcvyCguAD4OsmrxWs73A,9595
6
6
  pixeltable/catalog/dir.py,sha256=-dV0-gn5GfqRmVIJbuUBOXqPuNLDm219cud_kBTNzuk,1203
7
7
  pixeltable/catalog/globals.py,sha256=VgcmwtKYYDQtvtHShVde52n4FzGBtAIFYxoS-WD7OBw,3242
8
- pixeltable/catalog/insertable_table.py,sha256=hOsdYhkhtRcDOrRkweIGFUfjweWF3fLUErkUTlGYoSU,7172
8
+ pixeltable/catalog/insertable_table.py,sha256=VgZ6C0l_NI1orCqeZF_lOC5RZnie36_bQSx3y6SRtW4,7227
9
9
  pixeltable/catalog/named_function.py,sha256=cWf9WxAagSY9uqE7mM0IwWSsDOvQUkJlcHlGqgVciJg,1225
10
10
  pixeltable/catalog/path.py,sha256=QgccEi_QOfaKt8YsR2zLtd_z7z7QQkU_1kprJFi2SPQ,1677
11
11
  pixeltable/catalog/path_dict.py,sha256=V7YQM0QhUWr4FbgUiDUIweGbmMJr687QCm2p614Xrs0,6473
12
12
  pixeltable/catalog/schema_object.py,sha256=NUE6Fx6Km0UUJ6WcE9mOgL2UQCLBMQjINQLp_mljzKA,2382
13
- pixeltable/catalog/table.py,sha256=Gqb1tT9l8oB5eeeFuj45fy0_1fdCqizmRWvyjFH-U9Y,62404
14
- pixeltable/catalog/table_version.py,sha256=wuvFYbr6WrwhDWC202tUIgF8T61P69WOtduBUDq5EsI,58601
13
+ pixeltable/catalog/table.py,sha256=DbCX6H1xZL3raytoMrii0ceZiwD_X4FhkUw-1Djuaf0,62460
14
+ pixeltable/catalog/table_version.py,sha256=Ue_iByiT9JdPl_QwXnwq93Nd71Z_w0BHlRjugOLSI60,58625
15
15
  pixeltable/catalog/table_version_path.py,sha256=CczGbcz5ESq663arreri_p4chMZHozgG6k3y-ajkJN4,5787
16
- pixeltable/catalog/view.py,sha256=qUjou_z_YwKTqTIRUY4Q3JxWUEiKvl2pDi7DNOnw4fw,10709
16
+ pixeltable/catalog/view.py,sha256=hzTzS2jtrE8fyI5IeCKlmUQl_w-xE0C0fslmdBOBBZU,10733
17
17
  pixeltable/dataframe.py,sha256=KrAB-l_qdArlT3pnlSH69k0mqs0AK2wFhQ4V_7Wcjyw,48639
18
- pixeltable/env.py,sha256=VMS4Kj-RVEeFgmLzuQ2sFRQFtG4dLRbBZ9VzVzvO7hk,34993
18
+ pixeltable/env.py,sha256=J0e2dN42O6u1N67sg4OIyBKcElbSueQWdRFBC_8LFRY,35776
19
19
  pixeltable/exceptions.py,sha256=NuFY2WtkQpLfLHT_J70kOw9Tr0kEDkkgo-u7As4Gaq4,410
20
20
  pixeltable/exec/__init__.py,sha256=E_ub_ftAJVebEmKHZs47egeJqMfAbbx4SS1l7jR-Nd0,489
21
21
  pixeltable/exec/aggregation_node.py,sha256=iE3UHHW0IbkHsY7kYJAg6m1UKyKUu2_tJuzgL_rQUVE,4038
@@ -25,11 +25,11 @@ pixeltable/exec/data_row_batch.py,sha256=5WhdS3ahtCP3wROw4NPJjtvaQUAWKwyN1rHKbfd
25
25
  pixeltable/exec/exec_context.py,sha256=-FbfYLcGOL7mviru5dE1A7x4YxLbdKoXBHN3OSbqbcg,1084
26
26
  pixeltable/exec/exec_node.py,sha256=qDp63PFlMQUVwav0JIjuP0r0BH9EwW7s_NgqFE_Zn20,3735
27
27
  pixeltable/exec/expr_eval/__init__.py,sha256=t0o2T7MxfH09TI7KlzxEjPUySejn2vkK06LC8t4FjsQ,41
28
- pixeltable/exec/expr_eval/evaluators.py,sha256=HtcrWivzoGWzpEAYRgyKvFRGVKq4rMO-QpK7fZq4fE8,11349
29
- pixeltable/exec/expr_eval/expr_eval_node.py,sha256=37c5eevFMAvvYmSbqzcO4Wjxouy3R5a5vSnLLjifeic,19786
30
- pixeltable/exec/expr_eval/globals.py,sha256=UAlOAOJNHw4sJ3CXXk2Gh0qhOxEm2cGtNlkvvvebx1g,3895
28
+ pixeltable/exec/expr_eval/evaluators.py,sha256=Zb_TW_VLiicVSVZjbmU4eSDzuzO37J6VIvmLZo5fjPY,11061
29
+ pixeltable/exec/expr_eval/expr_eval_node.py,sha256=XSddV59hHkhUGKeRYgbviY5GcB0p61Y9qzIvBcdwpXM,19911
30
+ pixeltable/exec/expr_eval/globals.py,sha256=q3WBI5HRBxgg2msrqcZhuh1oZ_KkI4i1MzICjePGIwM,3856
31
31
  pixeltable/exec/expr_eval/row_buffer.py,sha256=Wjc6ZklxSW4-sCAMEGIzESII1uuC6Urzmq6JeUO3ALA,2737
32
- pixeltable/exec/expr_eval/schedulers.py,sha256=MqO0r5TaQhywozpRPL87S8YwZmKEc8sdEerX2LHDaCY,10962
32
+ pixeltable/exec/expr_eval/schedulers.py,sha256=_Tq_JglwcAI_rvhDgWm0tVH2eBPCOsHctoZaK-jiFlo,11257
33
33
  pixeltable/exec/in_memory_data_node.py,sha256=CwuwkA2xdyApMfPfSQamlLdbb-IQn7R7zeBBJiI8I7g,3479
34
34
  pixeltable/exec/row_update_node.py,sha256=MEvI3Yzw6YcUDTufSNVNVtyG83Hj6zHzEI9fvKtAgRQ,2859
35
35
  pixeltable/exec/sql_node.py,sha256=vMHNHzhrlk2Ej58vMZzk7hYAeHFHlBtkLwYZsOVn3CQ,19573
@@ -54,7 +54,7 @@ pixeltable/exprs/json_path.py,sha256=UTf3VUcyEmEBJs6yU4kONGcZ8fy1o6mIe9MlntnUd6M
54
54
  pixeltable/exprs/literal.py,sha256=Nhh-fXYMqGL4Tfm2YiFUd4ALMjzG7nJ2H4jHZe9XS1Y,4394
55
55
  pixeltable/exprs/method_ref.py,sha256=3O_uFMP6wWGiwJWri8on-47EVl-QD3AiAc7hXJMADxs,2615
56
56
  pixeltable/exprs/object_ref.py,sha256=GVg6uxZnKwFVTC0kouJq-wMFP-gUPb_ld_syHrsGMdE,1283
57
- pixeltable/exprs/row_builder.py,sha256=hpvmQyBie5pRtOkMKQu0xCxB18EK8iw_aUbtxYlzWcs,19862
57
+ pixeltable/exprs/row_builder.py,sha256=hR-lpd9BDsPesJQ_02Mh0NB3Kou8ELIUnchGI1e--vg,19916
58
58
  pixeltable/exprs/rowid_ref.py,sha256=iNBZ0wIhBAGkGCrNP9UQ2FeK8OajVX-eI4dtDA_bfHU,4401
59
59
  pixeltable/exprs/similarity_expr.py,sha256=GhiopGxN3wu5MOEqfWN4PNbqgSszg2VIC1_bqVPpm3Q,4333
60
60
  pixeltable/exprs/sql_element_cache.py,sha256=8i9ZslKWKTd1lUN7JvwRtQ-PFD1QMi5LBYdwcGA2-p0,1364
@@ -76,7 +76,7 @@ pixeltable/func/signature.py,sha256=wWf07OawOwDmqZOjUPgHJHAhAQ-fd61PGBx82aWLBgA,
76
76
  pixeltable/func/tools.py,sha256=7OAHVb9KbmcPtfMWsbKAoRu3kx-_japSTE-hgRQJefM,5988
77
77
  pixeltable/func/udf.py,sha256=p7imgwk7S03q3lHZxqAZUKr63XSeSsrD8kWTbwOLQWo,8882
78
78
  pixeltable/functions/__init__.py,sha256=C7Okwst3JdhzwIFWXhyJ3F0EHKGKor2R8dRMvQ3y6Us,428
79
- pixeltable/functions/anthropic.py,sha256=rSgMWmR2YVRfrdbbAPY5OvKkbyV1DHBJIHFGdDwVnP4,8800
79
+ pixeltable/functions/anthropic.py,sha256=tAMImdf-L6ZBnHGbX98uM5VuPkiwxzDAjdMZsY26lPI,8866
80
80
  pixeltable/functions/audio.py,sha256=7213nTnqKJ6vM9kalaoJ283OwX5SGEJN10vDhaRNZ6E,644
81
81
  pixeltable/functions/fireworks.py,sha256=qwFC_eIaDs-glxyJ_IVXaNGkpgPzeRsQ_SdpzueBxq0,2605
82
82
  pixeltable/functions/gemini.py,sha256=0aqsDpxI0AgeWu6zXQxtFbp3nhhuwXqzulZyot6UKdg,2613
@@ -88,7 +88,7 @@ pixeltable/functions/llama_cpp.py,sha256=1awALuAXVpQH64l7vQlM8gvxLDix4c1-6DV3nG5
88
88
  pixeltable/functions/math.py,sha256=WPoH9zD9_GdwvBs-FSC3Sqb70gOPNouhPcBZABsuLwI,1541
89
89
  pixeltable/functions/mistralai.py,sha256=GpxtT-a8ltx1kQC8XTJRflxkh-17VhzzFTkxqIUsba8,5494
90
90
  pixeltable/functions/ollama.py,sha256=z-g0cCJ-WMf6RI3SXIVVbbOlrWNR8nxGTyzET1rratU,4320
91
- pixeltable/functions/openai.py,sha256=Sh2zqwW72CYiMoyEELLpY1fwTmxuLGnAQbtEkmTac4c,24030
91
+ pixeltable/functions/openai.py,sha256=kXIhIMB2wNrrUO81H7ycy30avhgRApKepnkhE3C4uxA,24606
92
92
  pixeltable/functions/replicate.py,sha256=j8ZedScOMInmHWmriQSUOviw6tp8gQr-W6n21PNNL2g,2188
93
93
  pixeltable/functions/string.py,sha256=VqzhVildxTt_XblW89Kl5Zd6MVOU71eaX2LTMY5jkUg,20366
94
94
  pixeltable/functions/timestamp.py,sha256=KOm5eVF51PqOCTqwq8cKCAEJNWMgu2xN_yTtGf7yixU,9095
@@ -97,7 +97,7 @@ pixeltable/functions/util.py,sha256=GgKTzCjvzUQNjWtSObTkfxkvJ9GVUOzKimY45WhE25M,
97
97
  pixeltable/functions/video.py,sha256=12jnOdU0G-Hk42rJx-S5QC77-bDkUflkxfkjej8n5pM,6702
98
98
  pixeltable/functions/vision.py,sha256=MEgkNp-4tUUeQS05VEJBrMgQFM48aNsNlksfAl9rH7w,15494
99
99
  pixeltable/functions/whisper.py,sha256=f2wqRd0n9jSBqRZN3W93UaetiAHtbsK0j9jXR2j2kkQ,2913
100
- pixeltable/globals.py,sha256=9UfD7FOn71dbzPZjV-LxzhxXBhwPcpLt46J0_RVPynk,33899
100
+ pixeltable/globals.py,sha256=7gYQdZs685OwJm1W49g4LlRdrhzhKTSU_DpJr_MRaXg,33866
101
101
  pixeltable/index/__init__.py,sha256=XBwetNQQwnz0fiKwonOKhyy_U32l_cjt77kNvEIdjWs,102
102
102
  pixeltable/index/base.py,sha256=zo0YvJI3oXiK6hZJztB36ZftKKhLfO75Zq3t-PeQA6M,1556
103
103
  pixeltable/index/btree.py,sha256=JFerLyyLoBaD0FSF_jJ6iJFBVa-z_et--KdNR02xjRg,2264
@@ -106,17 +106,17 @@ pixeltable/io/__init__.py,sha256=j3qDyGO1ejLce-UzIncK3eRqyOCLoOlDqClecMBSJGc,563
106
106
  pixeltable/io/external_store.py,sha256=H1jt7MDn464QRgBvU-PmcPcFlo3EZBCG7fKWEZXOfyc,16676
107
107
  pixeltable/io/fiftyone.py,sha256=hH-FahW6BuMQY8lGa2atnNnJto_pK8kWrP_y_EMsq6g,6965
108
108
  pixeltable/io/globals.py,sha256=9S9wnlIAuhZq7eC_GklTM_UX0UATK9fEagk8-SRCeXQ,17794
109
- pixeltable/io/hf_datasets.py,sha256=o5fqm2CJAjhFd3z-NYGxN0jM1tfrp4szuUX0TGnyNRY,8316
110
- pixeltable/io/label_studio.py,sha256=Yx2ES_Y32eZA8JvJ1k17tSU0GiRTcU0_R116c7RCbZo,31091
109
+ pixeltable/io/hf_datasets.py,sha256=1_NRn1748OT69UlBpnb_AWkEitEHrYiRO4vbJ7XwdQw,8320
110
+ pixeltable/io/label_studio.py,sha256=RtupR5mtql0NUHnoocCdfOhYfO6ienS9_lhEztx6ECU,31231
111
111
  pixeltable/io/pandas.py,sha256=7eHg7wnAfRA9eBk4iC0iSSVTKOM59Ne4pXokKWdt3dY,9793
112
- pixeltable/io/parquet.py,sha256=bvwTRIKhS99M3bAcAP63jnh5-R6Qww1SrYBGWjZvN1g,8800
112
+ pixeltable/io/parquet.py,sha256=3n-4XgCVS8-V3fph_meyDOrvz-f_a-7WbvKMTEfYNUY,8804
113
113
  pixeltable/iterators/__init__.py,sha256=qA9cJTwPO3gk-7y8mUXw2anWSbnUAVGwzl52SxiUjNU,398
114
114
  pixeltable/iterators/base.py,sha256=ZC0ZvXL4iw6AmT8cu-Mdx-T2UG9nmJYV1C6LK4efAfw,1669
115
115
  pixeltable/iterators/document.py,sha256=e5EMUAW4f5SqfC5mRN7Oo2E3gw_vcMSzq2707NVLByU,20254
116
116
  pixeltable/iterators/image.py,sha256=7eJupbyLEl_q4bemEozL8WgTgkxjl-DYnSXyHTQ7Rck,3628
117
117
  pixeltable/iterators/string.py,sha256=NG_fWc_GAITDfzl6MvrDOMrSoMcZdMZf6hPQztCSatE,1305
118
118
  pixeltable/iterators/video.py,sha256=anedQpSOnjYhHLa8UiEx-6ROagb14rpXh9am8oTriUg,9382
119
- pixeltable/metadata/__init__.py,sha256=S99VPq09bEYI7nF6HQ55Fyp9bJ0TUhk9L9gsOQi5XT4,2209
119
+ pixeltable/metadata/__init__.py,sha256=7Ep8e48O7sLiNxarTF_kDunRn7maaj7tTBYUPC76VnI,2276
120
120
  pixeltable/metadata/converters/convert_10.py,sha256=J1_r7LNNAWTdb042AwqFpJ4sEB-i4qhUdk5iOjcZk34,719
121
121
  pixeltable/metadata/converters/convert_12.py,sha256=Ci-qyZW1gqci-8wnjeOB5afdq7KTuN-hVSV9OqSPx8g,162
122
122
  pixeltable/metadata/converters/convert_13.py,sha256=yFR6lD3pOrZ46ZQBFKYvxiIYa7rRxh46Bsq7yiCBNak,1356
@@ -138,14 +138,15 @@ pixeltable/metadata/schema.py,sha256=H9t51cbhXOhNu9Xog2VTjZlkTCx4cjQAlwOXi3HSd2E
138
138
  pixeltable/plan.py,sha256=DgzP5kDmBooaUioZ0KBlSUcx_jpqy456CnbDRVzFBJM,41501
139
139
  pixeltable/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
140
140
  pixeltable/store.py,sha256=-A7oRGHDpvhUZzx68QSde0t2NvUjeM1rOw9KdwukQ9s,22497
141
- pixeltable/type_system.py,sha256=nR004iZjvA5BIBSXsBXaHbpUPbddtiUCUzB0To1Bakw,48115
141
+ pixeltable/type_system.py,sha256=t7IOdQj90vBriJiexuSkmx-RSbzZYoijmX9FqYC778s,50019
142
142
  pixeltable/utils/__init__.py,sha256=UYlrf6TIWJT0g-Hac0b34-dEk478B5Qx8dGco34YlIk,439
143
- pixeltable/utils/arrow.py,sha256=iuZhueel1FT_3UDAMITFXxodLB7w8TrUpsMpie3IDXg,3991
143
+ pixeltable/utils/arrow.py,sha256=Fb6dLTC7jiTMH6l2UoVlrcnoIbe-mJH2zmpBFIjajKo,3866
144
144
  pixeltable/utils/coco.py,sha256=WCUyoj0dTyJFbPB7frEQUvY92SlEPxQ068r-3QAwROE,7285
145
145
  pixeltable/utils/code.py,sha256=AOw1u2r8_DQXpX-lxJhyHWARGrCRDXOJHFVgKOi54Uc,1231
146
+ pixeltable/utils/console_output.py,sha256=SB4ibt43-HqIUkv3SR93xyEcu8WhjwPSpMlAs6xyejc,1145
146
147
  pixeltable/utils/description_helper.py,sha256=P-8EE2pRFP8s3coe73frgV68yt4Dp3saErCUehMxKUw,3759
147
148
  pixeltable/utils/documents.py,sha256=pytTYY167wYc1rGaDd9HPK6GOrz1ML78eAD3hIHrG68,2930
148
- pixeltable/utils/filecache.py,sha256=6HKQdItqSSTQvj2HkSJulyhfBedi4PgC7umwxXGOVG8,10637
149
+ pixeltable/utils/filecache.py,sha256=kgRf5tlNZN642wrYIf5qpUMSA8ULHmnoIrvhkR0MwYU,10645
149
150
  pixeltable/utils/formatter.py,sha256=5E_gDg11ClFI-5SthwkiqyE3hAok3JHDj4OSK9cJklM,9257
150
151
  pixeltable/utils/http_server.py,sha256=xYPTvmYrkUpKfOaLDq08D-eHswkcgDf4qAt76ZFH6lM,2411
151
152
  pixeltable/utils/media_store.py,sha256=YwvTjbVqC_aLbDvLuqnDSL8xeIVMZcmzp0ANuM6uMbw,3092
@@ -153,8 +154,8 @@ pixeltable/utils/pytorch.py,sha256=6RvOCjy_QV4gc-aht-3d0zoASkuv-warfpl87vgmuKw,3
153
154
  pixeltable/utils/s3.py,sha256=huA5hxDGkPIu18zWet76o0FsO7Vbtp-iPmnOzCe-MvA,586
154
155
  pixeltable/utils/sql.py,sha256=j_tj0h4ffm-DhUIJbvGphxrVyBKlNTwDKqWGhRQ5_PY,795
155
156
  pixeltable/utils/transactional_directory.py,sha256=UGzCrGtLR3hEEf8sYGuWBzLVFAEQml3vdIavigWeTBM,1349
156
- pixeltable-0.3.1.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
157
- pixeltable-0.3.1.dist-info/METADATA,sha256=tRC6nn3plpwtLqC5TZsNDfDRB9SCj2NyNJIvzK6kWMA,19370
158
- pixeltable-0.3.1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
159
- pixeltable-0.3.1.dist-info/entry_points.txt,sha256=ToOd-pRgG7AitEBgYoBCRRB4-KVDQ0pj_9T4a1LgwA4,97
160
- pixeltable-0.3.1.dist-info/RECORD,,
157
+ pixeltable-0.3.2.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
158
+ pixeltable-0.3.2.dist-info/METADATA,sha256=-8DaK2FpbjMasYQkI49akVlIuoPnhMWA0lKLQ_5Ww-o,19404
159
+ pixeltable-0.3.2.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
160
+ pixeltable-0.3.2.dist-info/entry_points.txt,sha256=ToOd-pRgG7AitEBgYoBCRRB4-KVDQ0pj_9T4a1LgwA4,97
161
+ pixeltable-0.3.2.dist-info/RECORD,,