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.
- pixeltable/__version__.py +2 -2
- pixeltable/catalog/table.py +118 -44
- pixeltable/catalog/view.py +2 -2
- pixeltable/dataframe.py +240 -92
- pixeltable/env.py +8 -1
- pixeltable/exec/__init__.py +1 -1
- pixeltable/exec/exec_node.py +6 -7
- pixeltable/exec/sql_node.py +91 -44
- pixeltable/exprs/__init__.py +1 -0
- pixeltable/exprs/arithmetic_expr.py +1 -1
- pixeltable/exprs/array_slice.py +1 -1
- pixeltable/exprs/column_property_ref.py +1 -1
- pixeltable/exprs/column_ref.py +29 -2
- pixeltable/exprs/comparison.py +1 -1
- pixeltable/exprs/compound_predicate.py +1 -1
- pixeltable/exprs/expr.py +11 -5
- pixeltable/exprs/expr_set.py +8 -0
- pixeltable/exprs/function_call.py +14 -11
- pixeltable/exprs/in_predicate.py +1 -1
- pixeltable/exprs/inline_expr.py +3 -3
- pixeltable/exprs/is_null.py +1 -1
- pixeltable/exprs/json_mapper.py +1 -1
- pixeltable/exprs/json_path.py +1 -1
- pixeltable/exprs/method_ref.py +1 -1
- pixeltable/exprs/rowid_ref.py +1 -1
- pixeltable/exprs/similarity_expr.py +1 -1
- pixeltable/exprs/sql_element_cache.py +4 -0
- pixeltable/exprs/type_cast.py +2 -2
- pixeltable/exprs/variable.py +3 -0
- pixeltable/func/expr_template_function.py +3 -0
- pixeltable/functions/__init__.py +2 -2
- pixeltable/functions/gemini.py +85 -0
- pixeltable/functions/ollama.py +4 -4
- pixeltable/globals.py +4 -1
- pixeltable/io/__init__.py +1 -1
- pixeltable/io/parquet.py +39 -19
- pixeltable/iterators/document.py +12 -0
- pixeltable/metadata/__init__.py +1 -1
- pixeltable/metadata/converters/convert_22.py +17 -0
- pixeltable/metadata/notes.py +1 -0
- pixeltable/plan.py +128 -50
- pixeltable/store.py +1 -1
- pixeltable/type_system.py +2 -1
- pixeltable/utils/arrow.py +8 -3
- pixeltable/utils/description_helper.py +89 -0
- pixeltable/utils/documents.py +14 -0
- {pixeltable-0.2.25.dist-info → pixeltable-0.2.27.dist-info}/METADATA +26 -10
- {pixeltable-0.2.25.dist-info → pixeltable-0.2.27.dist-info}/RECORD +51 -48
- {pixeltable-0.2.25.dist-info → pixeltable-0.2.27.dist-info}/WHEEL +1 -1
- {pixeltable-0.2.25.dist-info → pixeltable-0.2.27.dist-info}/LICENSE +0 -0
- {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()
|
pixeltable/functions/ollama.py
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
54
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
180
|
-
raise exc.Error(f'Table {
|
|
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'{
|
|
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,
|
|
194
|
-
return pxt.get_table(
|
|
213
|
+
pxt.move(tmp_name, table)
|
|
214
|
+
return pxt.get_table(table)
|
pixeltable/iterators/document.py
CHANGED
|
@@ -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:
|
pixeltable/metadata/__init__.py
CHANGED
|
@@ -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 =
|
|
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
|
pixeltable/metadata/notes.py
CHANGED
|
@@ -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',
|