pixeltable 0.2.25__py3-none-any.whl → 0.2.27__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.

Files changed (51) hide show
  1. pixeltable/__version__.py +2 -2
  2. pixeltable/catalog/table.py +118 -44
  3. pixeltable/catalog/view.py +2 -2
  4. pixeltable/dataframe.py +240 -92
  5. pixeltable/env.py +8 -1
  6. pixeltable/exec/__init__.py +1 -1
  7. pixeltable/exec/exec_node.py +6 -7
  8. pixeltable/exec/sql_node.py +91 -44
  9. pixeltable/exprs/__init__.py +1 -0
  10. pixeltable/exprs/arithmetic_expr.py +1 -1
  11. pixeltable/exprs/array_slice.py +1 -1
  12. pixeltable/exprs/column_property_ref.py +1 -1
  13. pixeltable/exprs/column_ref.py +29 -2
  14. pixeltable/exprs/comparison.py +1 -1
  15. pixeltable/exprs/compound_predicate.py +1 -1
  16. pixeltable/exprs/expr.py +11 -5
  17. pixeltable/exprs/expr_set.py +8 -0
  18. pixeltable/exprs/function_call.py +14 -11
  19. pixeltable/exprs/in_predicate.py +1 -1
  20. pixeltable/exprs/inline_expr.py +3 -3
  21. pixeltable/exprs/is_null.py +1 -1
  22. pixeltable/exprs/json_mapper.py +1 -1
  23. pixeltable/exprs/json_path.py +1 -1
  24. pixeltable/exprs/method_ref.py +1 -1
  25. pixeltable/exprs/rowid_ref.py +1 -1
  26. pixeltable/exprs/similarity_expr.py +1 -1
  27. pixeltable/exprs/sql_element_cache.py +4 -0
  28. pixeltable/exprs/type_cast.py +2 -2
  29. pixeltable/exprs/variable.py +3 -0
  30. pixeltable/func/expr_template_function.py +3 -0
  31. pixeltable/functions/__init__.py +2 -2
  32. pixeltable/functions/gemini.py +85 -0
  33. pixeltable/functions/ollama.py +4 -4
  34. pixeltable/globals.py +4 -1
  35. pixeltable/io/__init__.py +1 -1
  36. pixeltable/io/parquet.py +39 -19
  37. pixeltable/iterators/document.py +12 -0
  38. pixeltable/metadata/__init__.py +1 -1
  39. pixeltable/metadata/converters/convert_22.py +17 -0
  40. pixeltable/metadata/notes.py +1 -0
  41. pixeltable/plan.py +128 -50
  42. pixeltable/store.py +1 -1
  43. pixeltable/type_system.py +2 -1
  44. pixeltable/utils/arrow.py +8 -3
  45. pixeltable/utils/description_helper.py +89 -0
  46. pixeltable/utils/documents.py +14 -0
  47. {pixeltable-0.2.25.dist-info → pixeltable-0.2.27.dist-info}/METADATA +26 -10
  48. {pixeltable-0.2.25.dist-info → pixeltable-0.2.27.dist-info}/RECORD +51 -48
  49. {pixeltable-0.2.25.dist-info → pixeltable-0.2.27.dist-info}/WHEEL +1 -1
  50. {pixeltable-0.2.25.dist-info → pixeltable-0.2.27.dist-info}/LICENSE +0 -0
  51. {pixeltable-0.2.25.dist-info → pixeltable-0.2.27.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,85 @@
1
+ """
2
+ Pixeltable [UDFs](https://pixeltable.readme.io/docs/user-defined-functions-udfs)
3
+ that wrap various endpoints from the Google Gemini API. In order to use them, you must
4
+ first `pip install google-generativeai` and configure your Gemini credentials, as described in
5
+ the [Working with Gemini](https://pixeltable.readme.io/docs/working-with-gemini) tutorial.
6
+ """
7
+
8
+ from typing import Optional
9
+
10
+ import pixeltable as pxt
11
+ from pixeltable import env
12
+
13
+
14
+ @env.register_client('gemini')
15
+ def _(api_key: str) -> None:
16
+ import google.generativeai as genai # type: ignore[import-untyped]
17
+ genai.configure(api_key=api_key)
18
+
19
+
20
+ def _ensure_loaded() -> None:
21
+ env.Env.get().get_client('gemini')
22
+
23
+
24
+ @pxt.udf
25
+ def generate_content(
26
+ contents: str,
27
+ *,
28
+ model_name: str,
29
+ candidate_count: Optional[int] = None,
30
+ stop_sequences: Optional[list[str]] = None,
31
+ max_output_tokens: Optional[int] = None,
32
+ temperature: Optional[float] = None,
33
+ top_p: Optional[float] = None,
34
+ top_k: Optional[int] = None,
35
+ response_mime_type: Optional[str] = None,
36
+ response_schema: Optional[dict] = None,
37
+ presence_penalty: Optional[float] = None,
38
+ frequency_penalty: Optional[float] = None,
39
+ response_logprobs: Optional[bool] = None,
40
+ logprobs: Optional[int] = None,
41
+ ) -> dict:
42
+ """
43
+ Generate content from the specified model. For additional details, see:
44
+ <https://ai.google.dev/gemini-api/docs>
45
+
46
+ __Requirements:__
47
+
48
+ - `pip install google-generativeai`
49
+
50
+ Args:
51
+ contents: The input content to generate from.
52
+ model_name: The name of the model to use.
53
+
54
+ For details on the other parameters, see: <https://ai.google.dev/gemini-api/docs>
55
+
56
+ Returns:
57
+ A dictionary containing the response and other metadata.
58
+
59
+ Examples:
60
+ Add a computed column that applies the model `gemini-1.5-flash`
61
+ to an existing Pixeltable column `tbl.prompt` of the table `tbl`:
62
+
63
+ >>> tbl['response'] = generate_content(tbl.prompt, model_name='gemini-1.5-flash')
64
+ """
65
+ env.Env.get().require_package('google.generativeai')
66
+ _ensure_loaded()
67
+ import google.generativeai as genai
68
+
69
+ model = genai.GenerativeModel(model_name=model_name)
70
+ gc = genai.GenerationConfig(
71
+ candidate_count=candidate_count,
72
+ stop_sequences=stop_sequences,
73
+ max_output_tokens=max_output_tokens,
74
+ temperature=temperature,
75
+ top_p=top_p,
76
+ top_k=top_k,
77
+ response_mime_type=response_mime_type,
78
+ response_schema=response_schema,
79
+ presence_penalty=presence_penalty,
80
+ frequency_penalty=frequency_penalty,
81
+ response_logprobs=response_logprobs,
82
+ logprobs=logprobs,
83
+ )
84
+ response = model.generate_content(contents, generation_config=gc)
85
+ return response.to_dict()
@@ -68,7 +68,7 @@ def generate(
68
68
  raw=raw,
69
69
  format=format,
70
70
  options=options,
71
- ) # type: ignore[call-overload]
71
+ ).dict() # type: ignore[call-overload]
72
72
 
73
73
 
74
74
  @pxt.udf
@@ -103,7 +103,7 @@ def chat(
103
103
  tools=tools,
104
104
  format=format,
105
105
  options=options,
106
- ) # type: ignore[call-overload]
106
+ ).dict() # type: ignore[call-overload]
107
107
 
108
108
 
109
109
  @pxt.udf(batch_size=16)
@@ -135,8 +135,8 @@ def embed(
135
135
  model=model,
136
136
  input=input,
137
137
  truncate=truncate,
138
- options=options, # type: ignore[arg-type]
139
- )
138
+ options=options,
139
+ ).dict()
140
140
  return [np.array(data, dtype=np.float64) for data in results['embeddings']]
141
141
 
142
142
 
pixeltable/globals.py CHANGED
@@ -46,6 +46,7 @@ def create_table(
46
46
  num_retained_versions: Number of versions of the table to retain.
47
47
  comment: An optional comment; its meaning is user-defined.
48
48
  media_validation: Media validation policy for the table.
49
+
49
50
  - `'on_read'`: validate media files at query time
50
51
  - `'on_write'`: validate media files during insert/update operations
51
52
 
@@ -149,7 +150,9 @@ def create_view(
149
150
  tbl_version_path = base._tbl_version_path
150
151
  elif isinstance(base, DataFrame):
151
152
  base._validate_mutable('create_view')
152
- tbl_version_path = base.tbl
153
+ if len(base._from_clause.tbls) > 1:
154
+ raise excs.Error('Cannot create a view of a join')
155
+ tbl_version_path = base._from_clause.tbls[0]
153
156
  where = base.where_clause
154
157
  else:
155
158
  raise excs.Error('`base` must be an instance of `Table` or `DataFrame`')
pixeltable/io/__init__.py CHANGED
@@ -2,7 +2,7 @@ from .external_store import ExternalStore, SyncStatus
2
2
  from .globals import create_label_studio_project, export_images_as_fo_dataset, import_json, import_rows
3
3
  from .hf_datasets import import_huggingface_dataset
4
4
  from .pandas import import_csv, import_excel, import_pandas
5
- from .parquet import import_parquet
5
+ from .parquet import import_parquet, export_parquet
6
6
 
7
7
  __default_dir = set(symbol for symbol in dir() if not symbol.startswith('_'))
8
8
  __removed_symbols = {'globals', 'hf_datasets', 'pandas', 'parquet'}
pixeltable/io/parquet.py CHANGED
@@ -7,11 +7,14 @@ import random
7
7
  import typing
8
8
  from collections import deque
9
9
  from pathlib import Path
10
- from typing import Any, Optional
10
+ from typing import Any, Optional, Union
11
11
 
12
12
  import numpy as np
13
13
  import PIL.Image
14
+ import datetime
14
15
 
16
+ import pixeltable as pxt
17
+ from pixeltable.env import Env
15
18
  import pixeltable.exceptions as exc
16
19
  import pixeltable.type_system as ts
17
20
  from pixeltable.utils.transactional_directory import transactional_directory
@@ -39,28 +42,44 @@ def _write_batch(value_batch: dict[str, deque], schema: pa.Schema, output_path:
39
42
  parquet.write_table(tab, str(output_path))
40
43
 
41
44
 
42
- def save_parquet(df: pxt.DataFrame, dest_path: Path, partition_size_bytes: int = 100_000_000) -> None:
45
+ def export_parquet(
46
+ table_or_df: Union[pxt.Table, pxt.DataFrame],
47
+ parquet_path: Path,
48
+ partition_size_bytes: int = 100_000_000,
49
+ inline_images: bool = False
50
+ ) -> None:
43
51
  """
44
- Internal method to stream dataframe data to parquet format.
45
- Does not materialize the dataset to memory.
52
+ Exports a dataframe's data to one or more Parquet files. Requires pyarrow to be installed.
46
53
 
47
- It preserves pixeltable type metadata in a json file, which would otherwise
54
+ It additionally writes the pixeltable metadata in a json file, which would otherwise
48
55
  not be available in the parquet format.
49
56
 
50
- Images are stored inline in a compressed format in their parquet file.
51
-
52
57
  Args:
53
- df : dataframe to save.
54
- dest_path : path to directory to save the parquet files to.
55
- partition_size_bytes : maximum target size for each chunk. Default 100_000_000 bytes.
58
+ table_or_df : Table or Dataframe to export.
59
+ parquet_path : Path to directory to write the parquet files to.
60
+ partition_size_bytes : The maximum target size for each chunk. Default 100_000_000 bytes.
61
+ inline_images : If True, images are stored inline in the parquet file. This is useful
62
+ for small images, to be imported as pytorch dataset. But can be inefficient
63
+ for large images, and cannot be imported into pixeltable.
64
+ If False, will raise an error if the Dataframe has any image column.
65
+ Default False.
56
66
  """
57
67
  from pixeltable.utils.arrow import to_arrow_schema
58
68
 
69
+ df: pxt.DataFrame
70
+ if isinstance(table_or_df, pxt.catalog.Table):
71
+ df = table_or_df._df()
72
+ else:
73
+ df = table_or_df
74
+
59
75
  type_dict = {k: v.as_dict() for k, v in df.schema.items()}
60
76
  arrow_schema = to_arrow_schema(df.schema)
61
77
 
78
+ if not inline_images and any(col_type.is_image_type() for col_type in df.schema.values()):
79
+ raise exc.Error('Cannot export Dataframe with image columns when inline_images is False')
80
+
62
81
  # store the changes atomically
63
- with transactional_directory(dest_path) as temp_path:
82
+ with transactional_directory(parquet_path) as temp_path:
64
83
  # dump metadata json file so we can inspect what was the source of the parquet file later on.
65
84
  json.dump(df.as_dict(), (temp_path / '.pixeltable.json').open('w'))
66
85
  json.dump(type_dict, (temp_path / '.pixeltable.column_types.json').open('w')) # keep type metadata
@@ -111,6 +130,7 @@ def save_parquet(df: pxt.DataFrame, dest_path: Path, partition_size_bytes: int =
111
130
  elif col_type.is_bool_type():
112
131
  length = 1
113
132
  elif col_type.is_timestamp_type():
133
+ val = val.astimezone(datetime.timezone.utc)
114
134
  length = 8
115
135
  else:
116
136
  assert False, f'unknown type {col_type} for {col_name}'
@@ -139,7 +159,7 @@ def parquet_schema_to_pixeltable_schema(parquet_path: str) -> dict[str, Optional
139
159
 
140
160
 
141
161
  def import_parquet(
142
- table_path: str,
162
+ table: str,
143
163
  *,
144
164
  parquet_path: str,
145
165
  schema_overrides: Optional[dict[str, ts.ColumnType]] = None,
@@ -148,7 +168,7 @@ def import_parquet(
148
168
  """Creates a new base table from a Parquet file or set of files. Requires pyarrow to be installed.
149
169
 
150
170
  Args:
151
- table_path: Path to the table.
171
+ table: Fully qualified name of the table to import the data into.
152
172
  parquet_path: Path to an individual Parquet file or directory of Parquet files.
153
173
  schema_overrides: If specified, then for each (name, type) pair in `schema_overrides`, the column with
154
174
  name `name` will be given type `type`, instead of being inferred from the Parquet dataset. The keys in
@@ -157,7 +177,7 @@ def import_parquet(
157
177
  kwargs: Additional arguments to pass to `create_table`.
158
178
 
159
179
  Returns:
160
- A handle to the newly created [`Table`][pixeltable.Table].
180
+ A handle to the newly created table.
161
181
  """
162
182
  from pyarrow import parquet
163
183
 
@@ -176,11 +196,11 @@ def import_parquet(
176
196
  if v is None:
177
197
  raise exc.Error(f'Could not infer pixeltable type for column {k} from parquet file')
178
198
 
179
- if table_path in pxt.list_tables():
180
- raise exc.Error(f'Table {table_path} already exists')
199
+ if table in pxt.list_tables():
200
+ raise exc.Error(f'Table {table} already exists')
181
201
 
182
202
  try:
183
- tmp_name = f'{table_path}_tmp_{random.randint(0, 100000000)}'
203
+ tmp_name = f'{table}_tmp_{random.randint(0, 100000000)}'
184
204
  tab = pxt.create_table(tmp_name, schema, **kwargs)
185
205
  for fragment in parquet_dataset.fragments: # type: ignore[attr-defined]
186
206
  for batch in fragment.to_batches():
@@ -190,5 +210,5 @@ def import_parquet(
190
210
  _logger.error(f'Error while inserting Parquet file into table: {e}')
191
211
  raise e
192
212
 
193
- pxt.move(tmp_name, table_path)
194
- return pxt.get_table(table_path)
213
+ pxt.move(tmp_name, table)
214
+ return pxt.get_table(table)
@@ -151,6 +151,9 @@ class DocumentSplitter(ComponentIterator):
151
151
  elif self._doc_handle.format == DocumentType.DocumentFormat.PDF:
152
152
  assert self._doc_handle.pdf_doc is not None
153
153
  self._sections = self._pdf_sections()
154
+ elif self._doc_handle.format == DocumentType.DocumentFormat.TXT:
155
+ assert self._doc_handle.txt_doc is not None
156
+ self._sections = self._txt_sections()
154
157
  else:
155
158
  assert False, f'Unsupported document format: {self._doc_handle.format}'
156
159
 
@@ -389,6 +392,15 @@ class DocumentSplitter(ComponentIterator):
389
392
  if accumulated_text and not emit_on_page:
390
393
  yield DocumentSection(text=_emit_text(), metadata=DocumentSectionMetadata())
391
394
 
395
+ def _txt_sections(self) -> Iterator[DocumentSection]:
396
+ """Create DocumentSections for text files.
397
+
398
+ Currently, it returns the entire text as a single section.
399
+ TODO: Add support for paragraphs.
400
+ """
401
+ assert self._doc_handle.txt_doc is not None
402
+ yield DocumentSection(text=ftfy.fix_text(self._doc_handle.txt_doc), metadata=DocumentSectionMetadata())
403
+
392
404
  def _sentence_sections(self, input_sections: Iterable[DocumentSection]) -> Iterator[DocumentSection]:
393
405
  """Split the input sections into sentences"""
394
406
  for section in input_sections:
@@ -10,7 +10,7 @@ import sqlalchemy.orm as orm
10
10
  from .schema import SystemInfo, SystemInfoMd
11
11
 
12
12
  # current version of the metadata; this is incremented whenever the metadata schema changes
13
- VERSION = 22
13
+ VERSION = 23
14
14
 
15
15
 
16
16
  def create_system_info(engine: sql.engine.Engine) -> None:
@@ -0,0 +1,17 @@
1
+ from typing import Any, Optional
2
+ import sqlalchemy as sql
3
+
4
+ from pixeltable.metadata import register_converter
5
+ from pixeltable.metadata.converters.util import convert_table_md
6
+
7
+
8
+ @register_converter(version=22)
9
+ def _(engine: sql.engine.Engine) -> None:
10
+ convert_table_md(engine, substitution_fn=__substitute_md)
11
+
12
+
13
+ def __substitute_md(k: Optional[str], v: Any) -> Optional[tuple[Optional[str], Any]]:
14
+ if isinstance(v, dict) and '_classname' in v and v['_classname'] == 'DataFrame':
15
+ v['from_clause'] = {'tbls': [v['tbl']], 'join_clauses': []}
16
+ return k, v
17
+ return None
@@ -2,6 +2,7 @@
2
2
  # rather than as a comment, so that the existence of a description can be enforced by
3
3
  # the unit tests when new versions are added.
4
4
  VERSION_NOTES = {
5
+ 23: 'DataFrame.from_clause',
5
6
  22: 'TableMd/ColumnMd.media_validation',
6
7
  21: 'Separate InlineArray and InlineList',
7
8
  20: 'Store DB timestamps in UTC',