pixeltable 0.3.14__py3-none-any.whl → 0.5.7__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.
Files changed (220) hide show
  1. pixeltable/__init__.py +42 -8
  2. pixeltable/{dataframe.py → _query.py} +470 -206
  3. pixeltable/_version.py +1 -0
  4. pixeltable/catalog/__init__.py +5 -4
  5. pixeltable/catalog/catalog.py +1785 -432
  6. pixeltable/catalog/column.py +190 -113
  7. pixeltable/catalog/dir.py +2 -4
  8. pixeltable/catalog/globals.py +19 -46
  9. pixeltable/catalog/insertable_table.py +191 -98
  10. pixeltable/catalog/path.py +63 -23
  11. pixeltable/catalog/schema_object.py +11 -15
  12. pixeltable/catalog/table.py +843 -436
  13. pixeltable/catalog/table_metadata.py +103 -0
  14. pixeltable/catalog/table_version.py +978 -657
  15. pixeltable/catalog/table_version_handle.py +72 -16
  16. pixeltable/catalog/table_version_path.py +112 -43
  17. pixeltable/catalog/tbl_ops.py +53 -0
  18. pixeltable/catalog/update_status.py +191 -0
  19. pixeltable/catalog/view.py +134 -90
  20. pixeltable/config.py +134 -22
  21. pixeltable/env.py +471 -157
  22. pixeltable/exceptions.py +6 -0
  23. pixeltable/exec/__init__.py +4 -1
  24. pixeltable/exec/aggregation_node.py +7 -8
  25. pixeltable/exec/cache_prefetch_node.py +83 -110
  26. pixeltable/exec/cell_materialization_node.py +268 -0
  27. pixeltable/exec/cell_reconstruction_node.py +168 -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 +11 -7
  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 +106 -56
  37. pixeltable/exec/globals.py +35 -0
  38. pixeltable/exec/in_memory_data_node.py +19 -19
  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 +351 -84
  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 +36 -23
  46. pixeltable/exprs/column_ref.py +213 -89
  47. pixeltable/exprs/comparison.py +5 -5
  48. pixeltable/exprs/compound_predicate.py +5 -4
  49. pixeltable/exprs/data_row.py +164 -54
  50. pixeltable/exprs/expr.py +70 -44
  51. pixeltable/exprs/expr_dict.py +3 -3
  52. pixeltable/exprs/expr_set.py +17 -10
  53. pixeltable/exprs/function_call.py +100 -40
  54. pixeltable/exprs/globals.py +2 -2
  55. pixeltable/exprs/in_predicate.py +4 -4
  56. pixeltable/exprs/inline_expr.py +18 -32
  57. pixeltable/exprs/is_null.py +7 -3
  58. pixeltable/exprs/json_mapper.py +8 -8
  59. pixeltable/exprs/json_path.py +56 -22
  60. pixeltable/exprs/literal.py +27 -5
  61. pixeltable/exprs/method_ref.py +2 -2
  62. pixeltable/exprs/object_ref.py +2 -2
  63. pixeltable/exprs/row_builder.py +167 -67
  64. pixeltable/exprs/rowid_ref.py +25 -10
  65. pixeltable/exprs/similarity_expr.py +58 -40
  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 +17 -11
  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 +29 -27
  78. pixeltable/func/signature.py +46 -19
  79. pixeltable/func/tools.py +31 -13
  80. pixeltable/func/udf.py +18 -20
  81. pixeltable/functions/__init__.py +16 -0
  82. pixeltable/functions/anthropic.py +123 -77
  83. pixeltable/functions/audio.py +147 -10
  84. pixeltable/functions/bedrock.py +13 -6
  85. pixeltable/functions/date.py +7 -4
  86. pixeltable/functions/deepseek.py +35 -43
  87. pixeltable/functions/document.py +81 -0
  88. pixeltable/functions/fal.py +76 -0
  89. pixeltable/functions/fireworks.py +11 -20
  90. pixeltable/functions/gemini.py +195 -39
  91. pixeltable/functions/globals.py +142 -14
  92. pixeltable/functions/groq.py +108 -0
  93. pixeltable/functions/huggingface.py +1056 -24
  94. pixeltable/functions/image.py +115 -57
  95. pixeltable/functions/json.py +1 -1
  96. pixeltable/functions/llama_cpp.py +28 -13
  97. pixeltable/functions/math.py +67 -5
  98. pixeltable/functions/mistralai.py +18 -55
  99. pixeltable/functions/net.py +70 -0
  100. pixeltable/functions/ollama.py +20 -13
  101. pixeltable/functions/openai.py +240 -226
  102. pixeltable/functions/openrouter.py +143 -0
  103. pixeltable/functions/replicate.py +4 -4
  104. pixeltable/functions/reve.py +250 -0
  105. pixeltable/functions/string.py +239 -69
  106. pixeltable/functions/timestamp.py +16 -16
  107. pixeltable/functions/together.py +24 -84
  108. pixeltable/functions/twelvelabs.py +188 -0
  109. pixeltable/functions/util.py +6 -1
  110. pixeltable/functions/uuid.py +30 -0
  111. pixeltable/functions/video.py +1515 -107
  112. pixeltable/functions/vision.py +8 -8
  113. pixeltable/functions/voyageai.py +289 -0
  114. pixeltable/functions/whisper.py +16 -8
  115. pixeltable/functions/whisperx.py +179 -0
  116. pixeltable/{ext/functions → functions}/yolox.py +2 -4
  117. pixeltable/globals.py +362 -115
  118. pixeltable/index/base.py +17 -21
  119. pixeltable/index/btree.py +28 -22
  120. pixeltable/index/embedding_index.py +100 -118
  121. pixeltable/io/__init__.py +4 -2
  122. pixeltable/io/datarows.py +8 -7
  123. pixeltable/io/external_store.py +56 -105
  124. pixeltable/io/fiftyone.py +13 -13
  125. pixeltable/io/globals.py +31 -30
  126. pixeltable/io/hf_datasets.py +61 -16
  127. pixeltable/io/label_studio.py +74 -70
  128. pixeltable/io/lancedb.py +3 -0
  129. pixeltable/io/pandas.py +21 -12
  130. pixeltable/io/parquet.py +25 -105
  131. pixeltable/io/table_data_conduit.py +250 -123
  132. pixeltable/io/utils.py +4 -4
  133. pixeltable/iterators/__init__.py +2 -1
  134. pixeltable/iterators/audio.py +26 -25
  135. pixeltable/iterators/base.py +9 -3
  136. pixeltable/iterators/document.py +112 -78
  137. pixeltable/iterators/image.py +12 -15
  138. pixeltable/iterators/string.py +11 -4
  139. pixeltable/iterators/video.py +523 -120
  140. pixeltable/metadata/__init__.py +14 -3
  141. pixeltable/metadata/converters/convert_13.py +2 -2
  142. pixeltable/metadata/converters/convert_18.py +2 -2
  143. pixeltable/metadata/converters/convert_19.py +2 -2
  144. pixeltable/metadata/converters/convert_20.py +2 -2
  145. pixeltable/metadata/converters/convert_21.py +2 -2
  146. pixeltable/metadata/converters/convert_22.py +2 -2
  147. pixeltable/metadata/converters/convert_24.py +2 -2
  148. pixeltable/metadata/converters/convert_25.py +2 -2
  149. pixeltable/metadata/converters/convert_26.py +2 -2
  150. pixeltable/metadata/converters/convert_29.py +4 -4
  151. pixeltable/metadata/converters/convert_30.py +34 -21
  152. pixeltable/metadata/converters/convert_34.py +2 -2
  153. pixeltable/metadata/converters/convert_35.py +9 -0
  154. pixeltable/metadata/converters/convert_36.py +38 -0
  155. pixeltable/metadata/converters/convert_37.py +15 -0
  156. pixeltable/metadata/converters/convert_38.py +39 -0
  157. pixeltable/metadata/converters/convert_39.py +124 -0
  158. pixeltable/metadata/converters/convert_40.py +73 -0
  159. pixeltable/metadata/converters/convert_41.py +12 -0
  160. pixeltable/metadata/converters/convert_42.py +9 -0
  161. pixeltable/metadata/converters/convert_43.py +44 -0
  162. pixeltable/metadata/converters/util.py +20 -31
  163. pixeltable/metadata/notes.py +9 -0
  164. pixeltable/metadata/schema.py +140 -53
  165. pixeltable/metadata/utils.py +74 -0
  166. pixeltable/mypy/__init__.py +3 -0
  167. pixeltable/mypy/mypy_plugin.py +123 -0
  168. pixeltable/plan.py +382 -115
  169. pixeltable/share/__init__.py +1 -1
  170. pixeltable/share/packager.py +547 -83
  171. pixeltable/share/protocol/__init__.py +33 -0
  172. pixeltable/share/protocol/common.py +165 -0
  173. pixeltable/share/protocol/operation_types.py +33 -0
  174. pixeltable/share/protocol/replica.py +119 -0
  175. pixeltable/share/publish.py +257 -59
  176. pixeltable/store.py +311 -194
  177. pixeltable/type_system.py +373 -211
  178. pixeltable/utils/__init__.py +2 -3
  179. pixeltable/utils/arrow.py +131 -17
  180. pixeltable/utils/av.py +298 -0
  181. pixeltable/utils/azure_store.py +346 -0
  182. pixeltable/utils/coco.py +6 -6
  183. pixeltable/utils/code.py +3 -3
  184. pixeltable/utils/console_output.py +4 -1
  185. pixeltable/utils/coroutine.py +6 -23
  186. pixeltable/utils/dbms.py +32 -6
  187. pixeltable/utils/description_helper.py +4 -5
  188. pixeltable/utils/documents.py +7 -18
  189. pixeltable/utils/exception_handler.py +7 -30
  190. pixeltable/utils/filecache.py +6 -6
  191. pixeltable/utils/formatter.py +86 -48
  192. pixeltable/utils/gcs_store.py +295 -0
  193. pixeltable/utils/http.py +133 -0
  194. pixeltable/utils/http_server.py +2 -3
  195. pixeltable/utils/iceberg.py +1 -2
  196. pixeltable/utils/image.py +17 -0
  197. pixeltable/utils/lancedb.py +90 -0
  198. pixeltable/utils/local_store.py +322 -0
  199. pixeltable/utils/misc.py +5 -0
  200. pixeltable/utils/object_stores.py +573 -0
  201. pixeltable/utils/pydantic.py +60 -0
  202. pixeltable/utils/pytorch.py +5 -6
  203. pixeltable/utils/s3_store.py +527 -0
  204. pixeltable/utils/sql.py +26 -0
  205. pixeltable/utils/system.py +30 -0
  206. pixeltable-0.5.7.dist-info/METADATA +579 -0
  207. pixeltable-0.5.7.dist-info/RECORD +227 -0
  208. {pixeltable-0.3.14.dist-info → pixeltable-0.5.7.dist-info}/WHEEL +1 -1
  209. pixeltable-0.5.7.dist-info/entry_points.txt +2 -0
  210. pixeltable/__version__.py +0 -3
  211. pixeltable/catalog/named_function.py +0 -40
  212. pixeltable/ext/__init__.py +0 -17
  213. pixeltable/ext/functions/__init__.py +0 -11
  214. pixeltable/ext/functions/whisperx.py +0 -77
  215. pixeltable/utils/media_store.py +0 -77
  216. pixeltable/utils/s3.py +0 -17
  217. pixeltable-0.3.14.dist-info/METADATA +0 -434
  218. pixeltable-0.3.14.dist-info/RECORD +0 -186
  219. pixeltable-0.3.14.dist-info/entry_points.txt +0 -3
  220. {pixeltable-0.3.14.dist-info → pixeltable-0.5.7.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__
@@ -1,8 +1,8 @@
1
1
  """
2
- Pixeltable [UDFs](https://pixeltable.readme.io/docs/user-defined-functions-udfs)
2
+ Pixeltable UDFs
3
3
  that wrap various endpoints from the Replicate API. In order to use them, you must
4
4
  first `pip install replicate` and configure your Replicate credentials, as described in
5
- the [Working with Replicate](https://pixeltable.readme.io/docs/working-with-replicate) tutorial.
5
+ the [Working with Replicate](https://docs.pixeltable.com/notebooks/integrations/working-with-replicate) tutorial.
6
6
  """
7
7
 
8
8
  from typing import TYPE_CHECKING, Any
@@ -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/platform/udfs-in-pixeltable) 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__