pixeltable 0.4.0rc3__py3-none-any.whl → 0.4.20__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 (202) hide show
  1. pixeltable/__init__.py +23 -5
  2. pixeltable/_version.py +1 -0
  3. pixeltable/catalog/__init__.py +5 -3
  4. pixeltable/catalog/catalog.py +1318 -404
  5. pixeltable/catalog/column.py +186 -115
  6. pixeltable/catalog/dir.py +1 -2
  7. pixeltable/catalog/globals.py +11 -43
  8. pixeltable/catalog/insertable_table.py +167 -79
  9. pixeltable/catalog/path.py +61 -23
  10. pixeltable/catalog/schema_object.py +9 -10
  11. pixeltable/catalog/table.py +626 -308
  12. pixeltable/catalog/table_metadata.py +101 -0
  13. pixeltable/catalog/table_version.py +713 -569
  14. pixeltable/catalog/table_version_handle.py +37 -6
  15. pixeltable/catalog/table_version_path.py +42 -29
  16. pixeltable/catalog/tbl_ops.py +50 -0
  17. pixeltable/catalog/update_status.py +191 -0
  18. pixeltable/catalog/view.py +108 -94
  19. pixeltable/config.py +128 -22
  20. pixeltable/dataframe.py +188 -100
  21. pixeltable/env.py +407 -136
  22. pixeltable/exceptions.py +6 -0
  23. pixeltable/exec/__init__.py +3 -0
  24. pixeltable/exec/aggregation_node.py +7 -8
  25. pixeltable/exec/cache_prefetch_node.py +83 -110
  26. pixeltable/exec/cell_materialization_node.py +231 -0
  27. pixeltable/exec/cell_reconstruction_node.py +135 -0
  28. pixeltable/exec/component_iteration_node.py +4 -3
  29. pixeltable/exec/data_row_batch.py +8 -65
  30. pixeltable/exec/exec_context.py +16 -4
  31. pixeltable/exec/exec_node.py +13 -36
  32. pixeltable/exec/expr_eval/evaluators.py +7 -6
  33. pixeltable/exec/expr_eval/expr_eval_node.py +27 -12
  34. pixeltable/exec/expr_eval/globals.py +8 -5
  35. pixeltable/exec/expr_eval/row_buffer.py +1 -2
  36. pixeltable/exec/expr_eval/schedulers.py +190 -30
  37. pixeltable/exec/globals.py +32 -0
  38. pixeltable/exec/in_memory_data_node.py +18 -18
  39. pixeltable/exec/object_store_save_node.py +293 -0
  40. pixeltable/exec/row_update_node.py +16 -9
  41. pixeltable/exec/sql_node.py +206 -101
  42. pixeltable/exprs/__init__.py +1 -1
  43. pixeltable/exprs/arithmetic_expr.py +27 -22
  44. pixeltable/exprs/array_slice.py +3 -3
  45. pixeltable/exprs/column_property_ref.py +34 -30
  46. pixeltable/exprs/column_ref.py +92 -96
  47. pixeltable/exprs/comparison.py +5 -5
  48. pixeltable/exprs/compound_predicate.py +5 -4
  49. pixeltable/exprs/data_row.py +152 -55
  50. pixeltable/exprs/expr.py +62 -43
  51. pixeltable/exprs/expr_dict.py +3 -3
  52. pixeltable/exprs/expr_set.py +17 -10
  53. pixeltable/exprs/function_call.py +75 -37
  54. pixeltable/exprs/globals.py +1 -2
  55. pixeltable/exprs/in_predicate.py +4 -4
  56. pixeltable/exprs/inline_expr.py +10 -27
  57. pixeltable/exprs/is_null.py +1 -3
  58. pixeltable/exprs/json_mapper.py +8 -8
  59. pixeltable/exprs/json_path.py +56 -22
  60. pixeltable/exprs/literal.py +5 -5
  61. pixeltable/exprs/method_ref.py +2 -2
  62. pixeltable/exprs/object_ref.py +2 -2
  63. pixeltable/exprs/row_builder.py +127 -53
  64. pixeltable/exprs/rowid_ref.py +8 -12
  65. pixeltable/exprs/similarity_expr.py +50 -25
  66. pixeltable/exprs/sql_element_cache.py +4 -4
  67. pixeltable/exprs/string_op.py +5 -5
  68. pixeltable/exprs/type_cast.py +3 -5
  69. pixeltable/func/__init__.py +1 -0
  70. pixeltable/func/aggregate_function.py +8 -8
  71. pixeltable/func/callable_function.py +9 -9
  72. pixeltable/func/expr_template_function.py +10 -10
  73. pixeltable/func/function.py +18 -20
  74. pixeltable/func/function_registry.py +6 -7
  75. pixeltable/func/globals.py +2 -3
  76. pixeltable/func/mcp.py +74 -0
  77. pixeltable/func/query_template_function.py +20 -18
  78. pixeltable/func/signature.py +43 -16
  79. pixeltable/func/tools.py +23 -13
  80. pixeltable/func/udf.py +18 -20
  81. pixeltable/functions/__init__.py +6 -0
  82. pixeltable/functions/anthropic.py +93 -33
  83. pixeltable/functions/audio.py +114 -10
  84. pixeltable/functions/bedrock.py +13 -6
  85. pixeltable/functions/date.py +1 -1
  86. pixeltable/functions/deepseek.py +20 -9
  87. pixeltable/functions/fireworks.py +2 -2
  88. pixeltable/functions/gemini.py +28 -11
  89. pixeltable/functions/globals.py +13 -13
  90. pixeltable/functions/groq.py +108 -0
  91. pixeltable/functions/huggingface.py +1046 -23
  92. pixeltable/functions/image.py +9 -18
  93. pixeltable/functions/llama_cpp.py +23 -8
  94. pixeltable/functions/math.py +3 -4
  95. pixeltable/functions/mistralai.py +4 -15
  96. pixeltable/functions/ollama.py +16 -9
  97. pixeltable/functions/openai.py +104 -82
  98. pixeltable/functions/openrouter.py +143 -0
  99. pixeltable/functions/replicate.py +2 -2
  100. pixeltable/functions/reve.py +250 -0
  101. pixeltable/functions/string.py +21 -28
  102. pixeltable/functions/timestamp.py +13 -14
  103. pixeltable/functions/together.py +4 -6
  104. pixeltable/functions/twelvelabs.py +92 -0
  105. pixeltable/functions/util.py +6 -1
  106. pixeltable/functions/video.py +1388 -106
  107. pixeltable/functions/vision.py +7 -7
  108. pixeltable/functions/whisper.py +15 -7
  109. pixeltable/functions/whisperx.py +179 -0
  110. pixeltable/{ext/functions → functions}/yolox.py +2 -4
  111. pixeltable/globals.py +332 -105
  112. pixeltable/index/base.py +13 -22
  113. pixeltable/index/btree.py +23 -22
  114. pixeltable/index/embedding_index.py +32 -44
  115. pixeltable/io/__init__.py +4 -2
  116. pixeltable/io/datarows.py +7 -6
  117. pixeltable/io/external_store.py +49 -77
  118. pixeltable/io/fiftyone.py +11 -11
  119. pixeltable/io/globals.py +29 -28
  120. pixeltable/io/hf_datasets.py +17 -9
  121. pixeltable/io/label_studio.py +70 -66
  122. pixeltable/io/lancedb.py +3 -0
  123. pixeltable/io/pandas.py +12 -11
  124. pixeltable/io/parquet.py +13 -93
  125. pixeltable/io/table_data_conduit.py +71 -47
  126. pixeltable/io/utils.py +3 -3
  127. pixeltable/iterators/__init__.py +2 -1
  128. pixeltable/iterators/audio.py +21 -11
  129. pixeltable/iterators/document.py +116 -55
  130. pixeltable/iterators/image.py +5 -2
  131. pixeltable/iterators/video.py +293 -13
  132. pixeltable/metadata/__init__.py +4 -2
  133. pixeltable/metadata/converters/convert_18.py +2 -2
  134. pixeltable/metadata/converters/convert_19.py +2 -2
  135. pixeltable/metadata/converters/convert_20.py +2 -2
  136. pixeltable/metadata/converters/convert_21.py +2 -2
  137. pixeltable/metadata/converters/convert_22.py +2 -2
  138. pixeltable/metadata/converters/convert_24.py +2 -2
  139. pixeltable/metadata/converters/convert_25.py +2 -2
  140. pixeltable/metadata/converters/convert_26.py +2 -2
  141. pixeltable/metadata/converters/convert_29.py +4 -4
  142. pixeltable/metadata/converters/convert_34.py +2 -2
  143. pixeltable/metadata/converters/convert_36.py +2 -2
  144. pixeltable/metadata/converters/convert_37.py +15 -0
  145. pixeltable/metadata/converters/convert_38.py +39 -0
  146. pixeltable/metadata/converters/convert_39.py +124 -0
  147. pixeltable/metadata/converters/convert_40.py +73 -0
  148. pixeltable/metadata/converters/util.py +13 -12
  149. pixeltable/metadata/notes.py +4 -0
  150. pixeltable/metadata/schema.py +79 -42
  151. pixeltable/metadata/utils.py +74 -0
  152. pixeltable/mypy/__init__.py +3 -0
  153. pixeltable/mypy/mypy_plugin.py +123 -0
  154. pixeltable/plan.py +274 -223
  155. pixeltable/share/__init__.py +1 -1
  156. pixeltable/share/packager.py +259 -129
  157. pixeltable/share/protocol/__init__.py +34 -0
  158. pixeltable/share/protocol/common.py +170 -0
  159. pixeltable/share/protocol/operation_types.py +33 -0
  160. pixeltable/share/protocol/replica.py +109 -0
  161. pixeltable/share/publish.py +213 -57
  162. pixeltable/store.py +238 -175
  163. pixeltable/type_system.py +104 -63
  164. pixeltable/utils/__init__.py +2 -3
  165. pixeltable/utils/arrow.py +108 -13
  166. pixeltable/utils/av.py +298 -0
  167. pixeltable/utils/azure_store.py +305 -0
  168. pixeltable/utils/code.py +3 -3
  169. pixeltable/utils/console_output.py +4 -1
  170. pixeltable/utils/coroutine.py +6 -23
  171. pixeltable/utils/dbms.py +31 -5
  172. pixeltable/utils/description_helper.py +4 -5
  173. pixeltable/utils/documents.py +5 -6
  174. pixeltable/utils/exception_handler.py +7 -30
  175. pixeltable/utils/filecache.py +6 -6
  176. pixeltable/utils/formatter.py +4 -6
  177. pixeltable/utils/gcs_store.py +283 -0
  178. pixeltable/utils/http_server.py +2 -3
  179. pixeltable/utils/iceberg.py +1 -2
  180. pixeltable/utils/image.py +17 -0
  181. pixeltable/utils/lancedb.py +88 -0
  182. pixeltable/utils/local_store.py +316 -0
  183. pixeltable/utils/misc.py +5 -0
  184. pixeltable/utils/object_stores.py +528 -0
  185. pixeltable/utils/pydantic.py +60 -0
  186. pixeltable/utils/pytorch.py +5 -6
  187. pixeltable/utils/s3_store.py +392 -0
  188. pixeltable-0.4.20.dist-info/METADATA +587 -0
  189. pixeltable-0.4.20.dist-info/RECORD +218 -0
  190. {pixeltable-0.4.0rc3.dist-info → pixeltable-0.4.20.dist-info}/WHEEL +1 -1
  191. pixeltable-0.4.20.dist-info/entry_points.txt +2 -0
  192. pixeltable/__version__.py +0 -3
  193. pixeltable/ext/__init__.py +0 -17
  194. pixeltable/ext/functions/__init__.py +0 -11
  195. pixeltable/ext/functions/whisperx.py +0 -77
  196. pixeltable/utils/media_store.py +0 -77
  197. pixeltable/utils/s3.py +0 -17
  198. pixeltable/utils/sample.py +0 -25
  199. pixeltable-0.4.0rc3.dist-info/METADATA +0 -435
  200. pixeltable-0.4.0rc3.dist-info/RECORD +0 -189
  201. pixeltable-0.4.0rc3.dist-info/entry_points.txt +0 -3
  202. {pixeltable-0.4.0rc3.dist-info → pixeltable-0.4.20.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,143 @@
1
+ """
2
+ Pixeltable UDFs that wrap the OpenRouter API.
3
+
4
+ OpenRouter provides a unified interface to multiple LLM providers. In order to use it,
5
+ you must first sign up at https://openrouter.ai, create an API key, and configure it
6
+ as described in the Working with OpenRouter tutorial.
7
+ """
8
+
9
+ from typing import TYPE_CHECKING, Any
10
+
11
+ import pixeltable as pxt
12
+ from pixeltable.env import Env, register_client
13
+ from pixeltable.utils.code import local_public_names
14
+
15
+ if TYPE_CHECKING:
16
+ import openai
17
+
18
+
19
+ @register_client('openrouter')
20
+ def _(api_key: str, site_url: str | None = None, app_name: str | None = None) -> 'openai.AsyncOpenAI':
21
+ import openai
22
+
23
+ # Create default headers for OpenRouter
24
+ default_headers: dict[str, Any] = {}
25
+ if site_url:
26
+ default_headers['HTTP-Referer'] = site_url
27
+ if app_name:
28
+ default_headers['X-Title'] = app_name
29
+
30
+ return openai.AsyncOpenAI(base_url='https://openrouter.ai/api/v1', api_key=api_key, default_headers=default_headers)
31
+
32
+
33
+ def _openrouter_client() -> 'openai.AsyncOpenAI':
34
+ return Env.get().get_client('openrouter')
35
+
36
+
37
+ @pxt.udf(resource_pool='request-rate:openrouter')
38
+ async def chat_completions(
39
+ messages: list,
40
+ *,
41
+ model: str,
42
+ model_kwargs: dict[str, Any] | None = None,
43
+ tools: list[dict[str, Any]] | None = None,
44
+ tool_choice: dict[str, Any] | None = None,
45
+ provider: dict[str, Any] | None = None,
46
+ transforms: list[str] | None = None,
47
+ ) -> dict:
48
+ """
49
+ Chat Completion API via OpenRouter.
50
+
51
+ OpenRouter provides access to multiple LLM providers through a unified API.
52
+ For additional details, see: <https://openrouter.ai/docs>
53
+
54
+ Supported models can be found at: <https://openrouter.ai/models>
55
+
56
+ Request throttling:
57
+ Applies the rate limit set in the config (section `openrouter`, key `rate_limit`). If no rate
58
+ limit is configured, uses a default of 600 RPM.
59
+
60
+ __Requirements:__
61
+
62
+ - `pip install openai`
63
+
64
+ Args:
65
+ messages: A list of messages comprising the conversation so far.
66
+ model: ID of the model to use (e.g., 'anthropic/claude-3.5-sonnet', 'openai/gpt-4').
67
+ model_kwargs: Additional OpenAI-compatible parameters.
68
+ tools: List of tools available to the model.
69
+ tool_choice: Controls which (if any) tool is called by the model.
70
+ provider: OpenRouter-specific provider preferences (e.g., {'order': ['Anthropic', 'OpenAI']}).
71
+ transforms: List of message transforms to apply (e.g., ['middle-out']).
72
+
73
+ Returns:
74
+ A dictionary containing the response in OpenAI format.
75
+
76
+ Examples:
77
+ Basic chat completion:
78
+
79
+ >>> messages = [{'role': 'user', 'content': tbl.prompt}]
80
+ ... tbl.add_computed_column(
81
+ ... response=chat_completions(
82
+ ... messages,
83
+ ... model='anthropic/claude-3.5-sonnet'
84
+ ... )
85
+ ... )
86
+
87
+ With provider routing:
88
+
89
+ >>> tbl.add_computed_column(
90
+ ... response=chat_completions(
91
+ ... messages,
92
+ ... model='anthropic/claude-3.5-sonnet',
93
+ ... provider={'require_parameters': True, 'order': ['Anthropic']}
94
+ ... )
95
+ ... )
96
+
97
+ With transforms:
98
+
99
+ >>> tbl.add_computed_column(
100
+ ... response=chat_completions(
101
+ ... messages,
102
+ ... model='openai/gpt-4',
103
+ ... transforms=['middle-out'] # Optimize for long contexts
104
+ ... )
105
+ ... )
106
+ """
107
+ if model_kwargs is None:
108
+ model_kwargs = {}
109
+
110
+ Env.get().require_package('openai')
111
+
112
+ # Handle tools if provided
113
+ if tools is not None:
114
+ model_kwargs['tools'] = [{'type': 'function', 'function': tool} for tool in tools]
115
+
116
+ if tool_choice is not None:
117
+ if tool_choice['auto']:
118
+ model_kwargs['tool_choice'] = 'auto'
119
+ elif tool_choice['required']:
120
+ model_kwargs['tool_choice'] = 'required'
121
+ else:
122
+ assert tool_choice['tool'] is not None
123
+ model_kwargs['tool_choice'] = {'type': 'function', 'function': {'name': tool_choice['tool']}}
124
+
125
+ # Prepare OpenRouter-specific parameters for extra_body
126
+ extra_body: dict[str, Any] = {}
127
+ if provider is not None:
128
+ extra_body['provider'] = provider
129
+ if transforms is not None:
130
+ extra_body['transforms'] = transforms
131
+
132
+ # Make the API call
133
+ result = await _openrouter_client().chat.completions.create(
134
+ messages=messages, model=model, extra_body=extra_body if extra_body else None, **model_kwargs
135
+ )
136
+ return result.model_dump()
137
+
138
+
139
+ __all__ = local_public_names(__name__)
140
+
141
+
142
+ def __dir__() -> list[str]:
143
+ return __all__
@@ -12,7 +12,7 @@ from pixeltable.env import Env, register_client
12
12
  from pixeltable.utils.code import local_public_names
13
13
 
14
14
  if TYPE_CHECKING:
15
- import replicate # type: ignore[import-untyped]
15
+ import replicate
16
16
 
17
17
 
18
18
  @register_client('replicate')
@@ -27,7 +27,7 @@ def _replicate_client() -> 'replicate.Client':
27
27
 
28
28
 
29
29
  @pxt.udf(resource_pool='request-rate:replicate')
30
- async def run(input: dict[str, Any], *, ref: str) -> dict[str, Any]:
30
+ async def run(input: dict[str, Any], *, ref: str) -> pxt.Json:
31
31
  """
32
32
  Run a model on Replicate.
33
33
 
@@ -0,0 +1,250 @@
1
+ """
2
+ Pixeltable [UDFs](https://docs.pixeltable.com/datastore/custom-functions) that wrap [Reve](https://app.reve.com/) image
3
+ generation API. In order to use them, the API key must be specified either with `REVE_API_KEY` environment variable,
4
+ or as `api_key` in the `reve` section of the Pixeltable config file.
5
+ """
6
+
7
+ import asyncio
8
+ import atexit
9
+ import logging
10
+ import re
11
+ from io import BytesIO
12
+
13
+ import aiohttp
14
+ import PIL.Image
15
+
16
+ import pixeltable as pxt
17
+ from pixeltable.env import Env, register_client
18
+ from pixeltable.utils.code import local_public_names
19
+ from pixeltable.utils.image import to_base64
20
+
21
+ _logger = logging.getLogger('pixeltable')
22
+
23
+
24
+ class ReveRateLimitedError(Exception):
25
+ pass
26
+
27
+
28
+ class ReveContentViolationError(Exception):
29
+ pass
30
+
31
+
32
+ class ReveUnexpectedError(Exception):
33
+ pass
34
+
35
+
36
+ class _ReveClient:
37
+ """
38
+ Client for interacting with the Reve API. Maintains a long-lived HTTP session to the service.
39
+ """
40
+
41
+ api_key: str
42
+ session: aiohttp.ClientSession
43
+
44
+ def __init__(self, api_key: str):
45
+ self.api_key = api_key
46
+ self.session = Env.get().event_loop.run_until_complete(self._start_session())
47
+ atexit.register(lambda: asyncio.run(self.session.close()))
48
+
49
+ async def _start_session(self) -> aiohttp.ClientSession:
50
+ # Maintains a long-lived TPC connection. The default keepalive timeout is 15 seconds.
51
+ return aiohttp.ClientSession(base_url='https://api.reve.com')
52
+
53
+ async def _post(self, endpoint: str, *, payload: dict) -> PIL.Image.Image:
54
+ # Reve supports other formats as well, but we only use PNG for now
55
+ requested_content_type = 'image/png'
56
+ request_headers = {
57
+ 'Authorization': f'Bearer {self.api_key}',
58
+ 'Content-Type': 'application/json',
59
+ 'Accept': requested_content_type,
60
+ }
61
+
62
+ async with self.session.post(endpoint, json=payload, headers=request_headers) as resp:
63
+ request_id = resp.headers.get('X-Reve-Request-Id')
64
+ error_code = resp.headers.get('X-Reve-Error-Code')
65
+ match resp.status:
66
+ case 200:
67
+ if error_code is not None:
68
+ raise ReveUnexpectedError(
69
+ f'Reve request {request_id} returned an unexpected error {error_code}'
70
+ )
71
+ content_violation = resp.headers.get('X-Reve-Content-Violation', 'false')
72
+ if content_violation.lower() != 'false':
73
+ raise ReveContentViolationError(
74
+ f'Reve request {request_id} resulted in a content violation error'
75
+ )
76
+ if resp.content_type != requested_content_type:
77
+ raise ReveUnexpectedError(
78
+ f'Reve request {request_id} expected content type {requested_content_type}, '
79
+ f'got {resp.content_type}'
80
+ )
81
+
82
+ img_data = await resp.read()
83
+ if len(img_data) == 0:
84
+ raise ReveUnexpectedError(f'Reve request {request_id} resulted in an empty image')
85
+ img = PIL.Image.open(BytesIO(img_data))
86
+ img.load()
87
+ _logger.debug(
88
+ f'Reve request {request_id} successful. Image bytes: {len(img_data)}, size: {img.size}'
89
+ f', format: {img.format}, mode: {img.mode}'
90
+ )
91
+ return img
92
+ case 429:
93
+ # Try to honor the server-provided Retry-After value if present
94
+ # Note: Retry-After value can also be given in the form of HTTP Date, which we don't currently
95
+ # handle
96
+ retry_after_seconds = None
97
+ retry_after_header = resp.headers.get('Retry-After')
98
+ if retry_after_header is not None and re.fullmatch(r'\d{1,2}', retry_after_header):
99
+ retry_after_seconds = int(retry_after_header)
100
+ _logger.info(
101
+ f'Reve request {request_id} failed due to rate limiting, retry after header value: '
102
+ f'{retry_after_header}'
103
+ )
104
+ # This error message is formatted specifically so that RequestRateScheduler can extract the retry
105
+ # delay from it
106
+ raise ReveRateLimitedError(
107
+ f'Reve request {request_id} failed due to rate limiting (429). retry-after:'
108
+ f'{retry_after_seconds}'
109
+ )
110
+ case _:
111
+ _logger.info(
112
+ f'Reve request {request_id} failed with status code {resp.status} and error code {error_code}'
113
+ )
114
+ raise ReveUnexpectedError(
115
+ f'Reve request failed with status code {resp.status} and error code {error_code}'
116
+ )
117
+
118
+
119
+ @register_client('reve')
120
+ def _(api_key: str) -> _ReveClient:
121
+ return _ReveClient(api_key=api_key)
122
+
123
+
124
+ def _client() -> _ReveClient:
125
+ return Env.get().get_client('reve')
126
+
127
+
128
+ # TODO Regarding rate limiting: Reve appears to be going for a credits per minute rate limiting model, but does not
129
+ # currently communicate rate limit information in responses. Therefore neither of the currently implemented limiting
130
+ # strategies is a perfect match, but "request-rate" is the closest. Reve does not currently enforce the rate limits,
131
+ # but when it does, we can revisit this choice.
132
+ @pxt.udf(resource_pool='request-rate:reve')
133
+ async def create(prompt: str, *, aspect_ratio: str | None = None, version: str | None = None) -> PIL.Image.Image:
134
+ """
135
+ Creates an image from a text prompt.
136
+
137
+ This UDF wraps the `https://api.reve.com/v1/image/create` endpoint. For more information, refer to the official
138
+ [API documentation](https://api.reve.com/console/docs/create).
139
+
140
+ Args:
141
+ prompt: prompt describing the desired image
142
+ aspect_ratio: desired image aspect ratio, e.g. '3:2', '16:9', '1:1', etc.
143
+ version: specific model version to use. Latest if not specified.
144
+
145
+ Returns:
146
+ A generated image
147
+
148
+ Examples:
149
+ Add a computed column with generated square images to a table with text prompts:
150
+
151
+ >>> t.add_computed_column(
152
+ ... img=reve.create(t.prompt, aspect_ratio='1:1')
153
+ ... )
154
+ """
155
+ payload = {'prompt': prompt}
156
+ if aspect_ratio is not None:
157
+ payload['aspect_ratio'] = aspect_ratio
158
+ if version is not None:
159
+ payload['version'] = version
160
+
161
+ result = await _client()._post('/v1/image/create', payload=payload)
162
+ return result
163
+
164
+
165
+ @pxt.udf(resource_pool='request-rate:reve')
166
+ async def edit(image: PIL.Image.Image, edit_instruction: str, *, version: str | None = None) -> PIL.Image.Image:
167
+ """
168
+ Edits images based on a text prompt.
169
+
170
+ This UDF wraps the `https://api.reve.com/v1/image/edit` endpoint. For more information, refer to the official
171
+ [API documentation](https://api.reve.com/console/docs/edit)
172
+
173
+ Args:
174
+ image: image to edit
175
+ edit_instruction: text prompt describing the desired edit
176
+ version: specific model version to use. Latest if not specified.
177
+
178
+ Returns:
179
+ A generated image
180
+
181
+ Examples:
182
+ Add a computed column with catalog-ready images to the table with product pictures:
183
+
184
+ >>> t.add_computed_column(
185
+ ... catalog_img=reve.edit(
186
+ ... t.product_img,
187
+ ... 'Remove background and distractions from the product picture, improve lighting.'
188
+ ... )
189
+ ... )
190
+ """
191
+ payload = {'edit_instruction': edit_instruction, 'reference_image': to_base64(image)}
192
+ if version is not None:
193
+ payload['version'] = version
194
+
195
+ result = await _client()._post('/v1/image/edit', payload=payload)
196
+ return result
197
+
198
+
199
+ @pxt.udf(resource_pool='request-rate:reve')
200
+ async def remix(
201
+ prompt: str, images: list[PIL.Image.Image], *, aspect_ratio: str | None = None, version: str | None = None
202
+ ) -> PIL.Image.Image:
203
+ """
204
+ Creates images based on a text prompt and reference images.
205
+
206
+ The prompt may include `<img>0</img>`, `<img>1</img>`, etc. tags to refer to the images in the `images` argument.
207
+
208
+ This UDF wraps the `https://api.reve.com/v1/image/remix` endpoint. For more information, refer to the official
209
+ [API documentation](https://api.reve.com/console/docs/remix)
210
+
211
+ Args:
212
+ prompt: prompt describing the desired image
213
+ images: list of reference images
214
+ aspect_ratio: desired image aspect ratio, e.g. '3:2', '16:9', '1:1', etc.
215
+ version: specific model version to use. Latest by default.
216
+
217
+ Returns:
218
+ A generated image
219
+
220
+ Examples:
221
+ Add a computed column with promotional collages to a table with original images:
222
+
223
+ >>> t.add_computed_column(
224
+ ... promo_img=(
225
+ ... reve.remix(
226
+ ... 'Generate a product promotional image by combining the image of the product'
227
+ ... ' from <img>0</img> with the landmark scene from <img>1</img>',
228
+ ... images=[t.product_img, t.local_landmark_img],
229
+ ... aspect_ratio='16:9',
230
+ ... )
231
+ ... )
232
+ ... )
233
+ """
234
+ if len(images) == 0:
235
+ raise pxt.Error('Must include at least 1 reference image')
236
+
237
+ payload = {'prompt': prompt, 'reference_images': [to_base64(img) for img in images]}
238
+ if version is not None:
239
+ payload['version'] = version
240
+ if aspect_ratio is not None:
241
+ payload['aspect_ratio'] = aspect_ratio
242
+ result = await _client()._post('/v1/image/remix', payload=payload)
243
+ return result
244
+
245
+
246
+ __all__ = local_public_names(__name__)
247
+
248
+
249
+ def __dir__() -> list[str]:
250
+ return __all__
@@ -15,7 +15,7 @@ import builtins
15
15
  import re
16
16
  import textwrap
17
17
  from string import whitespace
18
- from typing import Any, Optional
18
+ from typing import Any
19
19
 
20
20
  import sqlalchemy as sql
21
21
 
@@ -78,9 +78,7 @@ def contains(self: str, substr: str, case: bool = True) -> bool:
78
78
 
79
79
 
80
80
  @contains.to_sql
81
- def _(
82
- self: sql.ColumnElement, substr: sql.ColumnElement, case: Optional[sql.ColumnElement] = None
83
- ) -> sql.ColumnElement:
81
+ def _(self: sql.ColumnElement, substr: sql.ColumnElement, case: sql.ColumnElement | None = None) -> sql.ColumnElement:
84
82
  # Replace all occurrences of `%`, `_`, and `\` with escaped versions
85
83
  escaped_substr = sql.func.regexp_replace(substr, r'(%|_|\\)', r'\\\1', 'g')
86
84
  if case is None:
@@ -153,7 +151,7 @@ def fill(self: str, width: int, **kwargs: Any) -> str:
153
151
 
154
152
 
155
153
  @pxt.udf(is_method=True)
156
- def find(self: str, substr: str, start: int = 0, end: Optional[int] = None) -> int:
154
+ def find(self: str, substr: str, start: int = 0, end: int | None = None) -> int:
157
155
  """
158
156
  Return the lowest index in string where `substr` is found within the slice `s[start:end]`.
159
157
 
@@ -169,10 +167,7 @@ def find(self: str, substr: str, start: int = 0, end: Optional[int] = None) -> i
169
167
 
170
168
  @find.to_sql
171
169
  def _(
172
- self: sql.ColumnElement,
173
- substr: sql.ColumnElement,
174
- start: sql.ColumnElement,
175
- end: Optional[sql.ColumnElement] = None,
170
+ self: sql.ColumnElement, substr: sql.ColumnElement, start: sql.ColumnElement, end: sql.ColumnElement | None = None
176
171
  ) -> sql.ColumnElement:
177
172
  sl = pxt.functions.string.slice._to_sql(self, start, end)
178
173
  if sl is None:
@@ -227,7 +222,7 @@ def fullmatch(self: str, pattern: str, case: bool = True, flags: int = 0) -> boo
227
222
 
228
223
 
229
224
  @pxt.udf(is_method=True)
230
- def index(self: str, substr: str, start: int = 0, end: Optional[int] = None) -> int:
225
+ def index(self: str, substr: str, start: int = 0, end: int | None = None) -> int:
231
226
  """
232
227
  Return the lowest index in string where `substr` is found within the slice `[start:end]`.
233
228
  Raises ValueError if `substr` is not found.
@@ -413,7 +408,7 @@ def _(self: sql.ColumnElement) -> sql.ColumnElement:
413
408
 
414
409
 
415
410
  @pxt.udf(is_method=True)
416
- def lstrip(self: str, chars: Optional[str] = None) -> str:
411
+ def lstrip(self: str, chars: str | None = None) -> str:
417
412
  """
418
413
  Return a copy of the string with leading characters removed. The `chars` argument is a string specifying the set of
419
414
  characters to be removed. If omitted or `None`, whitespace characters are removed.
@@ -427,7 +422,7 @@ def lstrip(self: str, chars: Optional[str] = None) -> str:
427
422
 
428
423
 
429
424
  @lstrip.to_sql
430
- def _(self: sql.ColumnElement, chars: Optional[sql.ColumnElement] = None) -> sql.ColumnElement:
425
+ def _(self: sql.ColumnElement, chars: sql.ColumnElement | None = None) -> sql.ColumnElement:
431
426
  return sql.func.ltrim(self, chars if chars is not None else whitespace)
432
427
 
433
428
 
@@ -535,7 +530,7 @@ def _(self: sql.ColumnElement, n: sql.ColumnElement) -> sql.ColumnElement:
535
530
 
536
531
 
537
532
  @pxt.udf(is_method=True)
538
- def replace(self: str, substr: str, repl: str, n: Optional[int] = None) -> str:
533
+ def replace(self: str, substr: str, repl: str, n: int | None = None) -> str:
539
534
  """
540
535
  Replace occurrences of `substr` with `repl`.
541
536
 
@@ -551,7 +546,7 @@ def replace(self: str, substr: str, repl: str, n: Optional[int] = None) -> str:
551
546
 
552
547
  @replace.to_sql
553
548
  def _(
554
- self: sql.ColumnElement, substr: sql.ColumnElement, repl: sql.ColumnElement, n: Optional[sql.ColumnElement] = None
549
+ self: sql.ColumnElement, substr: sql.ColumnElement, repl: sql.ColumnElement, n: sql.ColumnElement | None = None
555
550
  ) -> sql.ColumnElement:
556
551
  if n is not None:
557
552
  return None # SQL does not support bounding the number of replacements
@@ -560,7 +555,7 @@ def _(
560
555
 
561
556
 
562
557
  @pxt.udf(is_method=True)
563
- def replace_re(self: str, pattern: str, repl: str, n: Optional[int] = None, flags: int = 0) -> str:
558
+ def replace_re(self: str, pattern: str, repl: str, n: int | None = None, flags: int = 0) -> str:
564
559
  """
565
560
  Replace occurrences of a regular expression pattern with `repl`.
566
561
 
@@ -591,7 +586,7 @@ def _(self: sql.ColumnElement) -> sql.ColumnElement:
591
586
 
592
587
 
593
588
  @pxt.udf(is_method=True)
594
- def rfind(self: str, substr: str, start: Optional[int] = 0, end: Optional[int] = None) -> int:
589
+ def rfind(self: str, substr: str, start: int | None = 0, end: int | None = None) -> int:
595
590
  """
596
591
  Return the highest index where `substr` is found, such that `substr` is contained within `[start:end]`.
597
592
 
@@ -606,7 +601,7 @@ def rfind(self: str, substr: str, start: Optional[int] = 0, end: Optional[int] =
606
601
 
607
602
 
608
603
  @pxt.udf(is_method=True)
609
- def rindex(self: str, substr: str, start: Optional[int] = 0, end: Optional[int] = None) -> int:
604
+ def rindex(self: str, substr: str, start: int | None = 0, end: int | None = None) -> int:
610
605
  """
611
606
  Return the highest index where `substr` is found, such that `substr` is contained within `[start:end]`.
612
607
  Raises ValueError if `substr` is not found.
@@ -643,7 +638,7 @@ def rpartition(self: str, sep: str = ' ') -> list:
643
638
 
644
639
 
645
640
  @pxt.udf(is_method=True)
646
- def rstrip(self: str, chars: Optional[str] = None) -> str:
641
+ def rstrip(self: str, chars: str | None = None) -> str:
647
642
  """
648
643
  Return a copy of string with trailing characters removed.
649
644
 
@@ -656,12 +651,12 @@ def rstrip(self: str, chars: Optional[str] = None) -> str:
656
651
 
657
652
 
658
653
  @rstrip.to_sql
659
- def _(self: sql.ColumnElement, chars: Optional[sql.ColumnElement] = None) -> sql.ColumnElement:
654
+ def _(self: sql.ColumnElement, chars: sql.ColumnElement | None = None) -> sql.ColumnElement:
660
655
  return sql.func.rtrim(self, chars if chars is not None else whitespace)
661
656
 
662
657
 
663
658
  @pxt.udf(is_method=True)
664
- def slice(self: str, start: Optional[int] = None, stop: Optional[int] = None, step: Optional[int] = None) -> str:
659
+ def slice(self: str, start: int | None = None, stop: int | None = None, step: int | None = None) -> str:
665
660
  """
666
661
  Return a slice.
667
662
 
@@ -676,9 +671,9 @@ def slice(self: str, start: Optional[int] = None, stop: Optional[int] = None, st
676
671
  @slice.to_sql
677
672
  def _(
678
673
  self: sql.ColumnElement,
679
- start: Optional[sql.ColumnElement] = None,
680
- stop: Optional[sql.ColumnElement] = None,
681
- step: Optional[sql.ColumnElement] = None,
674
+ start: sql.ColumnElement | None = None,
675
+ stop: sql.ColumnElement | None = None,
676
+ step: sql.ColumnElement | None = None,
682
677
  ) -> sql.ColumnElement:
683
678
  if step is not None:
684
679
  return None
@@ -709,9 +704,7 @@ def _(
709
704
 
710
705
 
711
706
  @pxt.udf(is_method=True)
712
- def slice_replace(
713
- self: str, start: Optional[int] = None, stop: Optional[int] = None, repl: Optional[str] = None
714
- ) -> str:
707
+ def slice_replace(self: str, start: int | None = None, stop: int | None = None, repl: str | None = None) -> str:
715
708
  """
716
709
  Replace a positional slice with another value.
717
710
 
@@ -744,7 +737,7 @@ def _(self: sql.ColumnElement, substr: sql.ColumnElement) -> sql.ColumnElement:
744
737
 
745
738
 
746
739
  @pxt.udf(is_method=True)
747
- def strip(self: str, chars: Optional[str] = None) -> str:
740
+ def strip(self: str, chars: str | None = None) -> str:
748
741
  """
749
742
  Return a copy of string with leading and trailing characters removed.
750
743
 
@@ -757,7 +750,7 @@ def strip(self: str, chars: Optional[str] = None) -> str:
757
750
 
758
751
 
759
752
  @strip.to_sql
760
- def _(self: sql.ColumnElement, chars: Optional[sql.ColumnElement] = None) -> sql.ColumnElement:
753
+ def _(self: sql.ColumnElement, chars: sql.ColumnElement | None = None) -> sql.ColumnElement:
761
754
  return sql.func.trim(self, chars if chars is not None else whitespace)
762
755
 
763
756
 
@@ -11,7 +11,6 @@ t.select(t.timestamp_col.year, t.timestamp_col.weekday()).collect()
11
11
  """
12
12
 
13
13
  from datetime import datetime
14
- from typing import Optional
15
14
 
16
15
  import sqlalchemy as sql
17
16
 
@@ -237,12 +236,12 @@ def _(
237
236
  microsecond: sql.ColumnElement = _SQL_ZERO,
238
237
  ) -> sql.ColumnElement:
239
238
  return sql.func.make_timestamptz(
240
- sql.cast(year, sql.Integer),
241
- sql.cast(month, sql.Integer),
242
- sql.cast(day, sql.Integer),
243
- sql.cast(hour, sql.Integer),
244
- sql.cast(minute, sql.Integer),
245
- sql.cast(second + microsecond / 1000000.0, sql.Float),
239
+ year.cast(sql.Integer),
240
+ month.cast(sql.Integer),
241
+ day.cast(sql.Integer),
242
+ hour.cast(sql.Integer),
243
+ minute.cast(sql.Integer),
244
+ (second + microsecond / 1000000.0).cast(sql.Float),
246
245
  )
247
246
 
248
247
 
@@ -271,13 +270,13 @@ def _(
271
270
  @pxt.udf(is_method=True)
272
271
  def replace(
273
272
  self: datetime,
274
- year: Optional[int] = None,
275
- month: Optional[int] = None,
276
- day: Optional[int] = None,
277
- hour: Optional[int] = None,
278
- minute: Optional[int] = None,
279
- second: Optional[int] = None,
280
- microsecond: Optional[int] = None,
273
+ year: int | None = None,
274
+ month: int | None = None,
275
+ day: int | None = None,
276
+ hour: int | None = None,
277
+ minute: int | None = None,
278
+ second: int | None = None,
279
+ microsecond: int | None = None,
281
280
  ) -> datetime:
282
281
  """
283
282
  Return a datetime with the same attributes, except for those attributes given new values by whichever keyword
@@ -7,7 +7,7 @@ the [Working with Together AI](https://pixeltable.readme.io/docs/together-ai) tu
7
7
 
8
8
  import base64
9
9
  import io
10
- from typing import TYPE_CHECKING, Any, Callable, Optional, TypeVar
10
+ from typing import TYPE_CHECKING, Any, Callable, TypeVar
11
11
 
12
12
  import numpy as np
13
13
  import PIL.Image
@@ -50,7 +50,7 @@ def _retry(fn: Callable[..., T]) -> Callable[..., T]:
50
50
 
51
51
 
52
52
  @pxt.udf(resource_pool='request-rate:together:chat')
53
- async def completions(prompt: str, *, model: str, model_kwargs: Optional[dict[str, Any]] = None) -> dict:
53
+ async def completions(prompt: str, *, model: str, model_kwargs: dict[str, Any] | None = None) -> dict:
54
54
  """
55
55
  Generate completions based on a given prompt using a specified model.
56
56
 
@@ -89,7 +89,7 @@ async def completions(prompt: str, *, model: str, model_kwargs: Optional[dict[st
89
89
 
90
90
  @pxt.udf(resource_pool='request-rate:together:chat')
91
91
  async def chat_completions(
92
- messages: list[dict[str, str]], *, model: str, model_kwargs: Optional[dict[str, Any]] = None
92
+ messages: list[dict[str, str]], *, model: str, model_kwargs: dict[str, Any] | None = None
93
93
  ) -> dict:
94
94
  """
95
95
  Generate chat completions based on a given prompt using a specified model.
@@ -183,9 +183,7 @@ def _(model: str) -> ts.ArrayType:
183
183
 
184
184
 
185
185
  @pxt.udf(resource_pool='request-rate:together:images')
186
- async def image_generations(
187
- prompt: str, *, model: str, model_kwargs: Optional[dict[str, Any]] = None
188
- ) -> PIL.Image.Image:
186
+ async def image_generations(prompt: str, *, model: str, model_kwargs: dict[str, Any] | None = None) -> PIL.Image.Image:
189
187
  """
190
188
  Generate images based on a given prompt using a specified model.
191
189