vgi-python 0.8.0__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 (124) hide show
  1. vgi/__init__.py +152 -0
  2. vgi/_duckdb.py +62 -0
  3. vgi/_storage_profile.py +132 -0
  4. vgi/_test_fixtures/__init__.py +20 -0
  5. vgi/_test_fixtures/accumulate/__init__.py +19 -0
  6. vgi/_test_fixtures/accumulate/worker.py +762 -0
  7. vgi/_test_fixtures/aggregate/__init__.py +62 -0
  8. vgi/_test_fixtures/aggregate/_common.py +21 -0
  9. vgi/_test_fixtures/aggregate/basic.py +232 -0
  10. vgi/_test_fixtures/aggregate/dynamic.py +409 -0
  11. vgi/_test_fixtures/aggregate/generic.py +86 -0
  12. vgi/_test_fixtures/aggregate/listagg.py +71 -0
  13. vgi/_test_fixtures/aggregate/percentile.py +107 -0
  14. vgi/_test_fixtures/aggregate/streaming.py +192 -0
  15. vgi/_test_fixtures/aggregate/varargs.py +75 -0
  16. vgi/_test_fixtures/aggregate/window.py +380 -0
  17. vgi/_test_fixtures/attach_options.py +308 -0
  18. vgi/_test_fixtures/bad_protocol.py +62 -0
  19. vgi/_test_fixtures/cancellable.py +336 -0
  20. vgi/_test_fixtures/catalog.py +813 -0
  21. vgi/_test_fixtures/http_server.py +394 -0
  22. vgi/_test_fixtures/nest_tensor.py +614 -0
  23. vgi/_test_fixtures/orchard_catalog.py +47 -0
  24. vgi/_test_fixtures/projection_repro/__init__.py +6 -0
  25. vgi/_test_fixtures/projection_repro/worker.py +454 -0
  26. vgi/_test_fixtures/scalar/__init__.py +116 -0
  27. vgi/_test_fixtures/scalar/_common.py +69 -0
  28. vgi/_test_fixtures/scalar/arithmetic.py +321 -0
  29. vgi/_test_fixtures/scalar/binary.py +120 -0
  30. vgi/_test_fixtures/scalar/formatting.py +176 -0
  31. vgi/_test_fixtures/scalar/geo.py +300 -0
  32. vgi/_test_fixtures/scalar/null_handling.py +107 -0
  33. vgi/_test_fixtures/scalar/random_demo.py +171 -0
  34. vgi/_test_fixtures/scalar/settings_secrets.py +102 -0
  35. vgi/_test_fixtures/scalar/type_info.py +219 -0
  36. vgi/_test_fixtures/schema_reconcile/__init__.py +29 -0
  37. vgi/_test_fixtures/schema_reconcile/worker.py +653 -0
  38. vgi/_test_fixtures/simple_writable.py +793 -0
  39. vgi/_test_fixtures/table/__init__.py +221 -0
  40. vgi/_test_fixtures/table/_common.py +162 -0
  41. vgi/_test_fixtures/table/batch_index.py +283 -0
  42. vgi/_test_fixtures/table/batch_index_broken.py +200 -0
  43. vgi/_test_fixtures/table/catalog_scans.py +162 -0
  44. vgi/_test_fixtures/table/filters.py +1005 -0
  45. vgi/_test_fixtures/table/late_materialization.py +249 -0
  46. vgi/_test_fixtures/table/make_series.py +273 -0
  47. vgi/_test_fixtures/table/misc.py +499 -0
  48. vgi/_test_fixtures/table/order_modes.py +164 -0
  49. vgi/_test_fixtures/table/pairs.py +437 -0
  50. vgi/_test_fixtures/table/partition_columns.py +472 -0
  51. vgi/_test_fixtures/table/partition_columns_broken.py +304 -0
  52. vgi/_test_fixtures/table/profiling_example.py +195 -0
  53. vgi/_test_fixtures/table/required_filters.py +234 -0
  54. vgi/_test_fixtures/table/sequence.py +710 -0
  55. vgi/_test_fixtures/table/settings.py +426 -0
  56. vgi/_test_fixtures/table/transaction_storage.py +162 -0
  57. vgi/_test_fixtures/table/tt_pushdown.py +191 -0
  58. vgi/_test_fixtures/table/versioned.py +230 -0
  59. vgi/_test_fixtures/table_in_out.py +1392 -0
  60. vgi/_test_fixtures/versioned.py +155 -0
  61. vgi/_test_fixtures/versioned_tables.py +595 -0
  62. vgi/_test_fixtures/worker.py +1631 -0
  63. vgi/_test_fixtures/writable/__init__.py +8 -0
  64. vgi/_test_fixtures/writable/generic.py +236 -0
  65. vgi/_test_fixtures/writable/table.py +149 -0
  66. vgi/_test_fixtures/writable/worker.py +1148 -0
  67. vgi/aggregate_function.py +607 -0
  68. vgi/argument_spec.py +472 -0
  69. vgi/arguments.py +1747 -0
  70. vgi/auth.py +55 -0
  71. vgi/catalog/__init__.py +88 -0
  72. vgi/catalog/attach_option.py +206 -0
  73. vgi/catalog/catalog_interface.py +2767 -0
  74. vgi/catalog/descriptors.py +870 -0
  75. vgi/catalog/duckdb_statistics.py +377 -0
  76. vgi/catalog/secret_type.py +96 -0
  77. vgi/catalog/setting.py +253 -0
  78. vgi/catalog/storage.py +372 -0
  79. vgi/client/__init__.py +67 -0
  80. vgi/client/catalog_mixin.py +1251 -0
  81. vgi/client/cli.py +582 -0
  82. vgi/client/cli_catalog.py +182 -0
  83. vgi/client/cli_schema.py +270 -0
  84. vgi/client/cli_table.py +907 -0
  85. vgi/client/cli_transaction.py +97 -0
  86. vgi/client/cli_utils.py +441 -0
  87. vgi/client/cli_view.py +303 -0
  88. vgi/client/client.py +2183 -0
  89. vgi/exceptions.py +205 -0
  90. vgi/function.py +245 -0
  91. vgi/function_storage.py +1636 -0
  92. vgi/function_storage_azure_sql.py +922 -0
  93. vgi/function_storage_cf_do.py +740 -0
  94. vgi/http/__init__.py +25 -0
  95. vgi/http/demo_storage.py +212 -0
  96. vgi/http/worker_page.py +1252 -0
  97. vgi/invocation.py +154 -0
  98. vgi/logging_config.py +93 -0
  99. vgi/meta_worker.py +661 -0
  100. vgi/metadata.py +1403 -0
  101. vgi/otel.py +406 -0
  102. vgi/protocol.py +2418 -0
  103. vgi/protocol_version.txt +1 -0
  104. vgi/py.typed +0 -0
  105. vgi/scalar_function.py +1211 -0
  106. vgi/schema_utils.py +234 -0
  107. vgi/secret_protocol.py +124 -0
  108. vgi/secret_service.py +238 -0
  109. vgi/serve.py +769 -0
  110. vgi/table_buffering_function.py +443 -0
  111. vgi/table_filter_pushdown.py +1528 -0
  112. vgi/table_function.py +1130 -0
  113. vgi/table_in_out_function.py +383 -0
  114. vgi/transactor/__init__.py +24 -0
  115. vgi/transactor/_duckdb_compat.py +27 -0
  116. vgi/transactor/client.py +137 -0
  117. vgi/transactor/protocol.py +149 -0
  118. vgi/transactor/server.py +740 -0
  119. vgi/worker.py +4761 -0
  120. vgi_python-0.8.0.dist-info/METADATA +735 -0
  121. vgi_python-0.8.0.dist-info/RECORD +124 -0
  122. vgi_python-0.8.0.dist-info/WHEEL +4 -0
  123. vgi_python-0.8.0.dist-info/entry_points.txt +5 -0
  124. vgi_python-0.8.0.dist-info/licenses/LICENSE +134 -0
@@ -0,0 +1,321 @@
1
+ # Copyright 2025, 2026 Query Farm LLC - https://query.farm
2
+
3
+ """Arithmetic scalar fixtures (multiply, double, add, sum, concat)."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import Annotated, Any
8
+
9
+ import pyarrow as pa
10
+ import pyarrow.compute as pc
11
+
12
+ from vgi._test_fixtures.scalar._common import _is_addable_type, _is_multipliable_type, _promote_for_addition
13
+ from vgi.arguments import ConstParam, Param, Returns
14
+ from vgi.metadata import FunctionExample
15
+ from vgi.scalar_function import BindParameters, BindResult, ScalarFunction
16
+
17
+
18
+ class MultiplyFunction(ScalarFunction):
19
+ """Multiplies a value by a constant factor.
20
+
21
+ This example demonstrates type inference with array classes:
22
+ - pa.Int64Array -> pa.int64() (inferred from Annotated type)
23
+ - ConstParam() for constant scalar input (receives Python value at runtime)
24
+ - Returns() output type is also inferred from pa.Int64Array
25
+
26
+ Example:
27
+ SQL: SELECT multiply(price, 2) FROM products
28
+ Input: price=[10, 20, 30]
29
+ Args: factor=2
30
+ Output: result=[20, 40, 60]
31
+
32
+ """
33
+
34
+ class Meta:
35
+ """Function metadata."""
36
+
37
+ name = "multiply"
38
+ description = "Multiplies a value by a constant factor"
39
+ examples = [
40
+ FunctionExample(
41
+ sql="SELECT multiply(price, 2) FROM products",
42
+ description="Double all prices",
43
+ ),
44
+ FunctionExample(
45
+ sql="SELECT multiply(quantity, 10) FROM inventory",
46
+ description="Scale quantities by 10",
47
+ ),
48
+ ]
49
+
50
+ @classmethod
51
+ def compute(
52
+ cls,
53
+ value: Annotated[pa.Int64Array, Param(doc="Integer value to multiply")],
54
+ factor: Annotated[int, ConstParam("Multiplication factor")],
55
+ ) -> Annotated[pa.Int64Array, Returns()]:
56
+ """Multiply values by the constant factor."""
57
+ return pc.multiply(value, factor)
58
+
59
+
60
+ class DoubleFunction(ScalarFunction):
61
+ """Doubles numeric values.
62
+
63
+ This example demonstrates the Annotated API with:
64
+ - AnyArrow type (arrow_type=None) with type_bound for flexible numeric input
65
+ - Dynamic output type computed in bind()
66
+
67
+ Example:
68
+ SQL: SELECT double(price) FROM products
69
+ Input: price=[1, 2, 3]
70
+ Output: result=[2, 4, 6]
71
+
72
+ """
73
+
74
+ class Meta:
75
+ """Function metadata."""
76
+
77
+ name = "double"
78
+ description = "Doubles numeric values"
79
+ examples = [
80
+ FunctionExample(
81
+ sql="SELECT double(price) FROM products",
82
+ description="Double the price values",
83
+ ),
84
+ FunctionExample(
85
+ sql="SELECT double(quantity) FROM inventory",
86
+ description="Double inventory quantities",
87
+ ),
88
+ ]
89
+
90
+ @classmethod
91
+ def on_bind(cls, params: BindParameters) -> BindResult:
92
+ """Compute output type from input value type."""
93
+ field = params.arguments_schema.field(0)
94
+ return BindResult(_promote_for_addition(field.type))
95
+
96
+ @classmethod
97
+ def compute(
98
+ cls,
99
+ value: Annotated[
100
+ pa.Array[Any],
101
+ Param(doc="Numeric value to double", type_bound=_is_multipliable_type),
102
+ ],
103
+ ) -> Annotated[pa.Array[Any], Returns()]:
104
+ """Double the input values."""
105
+ if pa.types.is_decimal(value.type):
106
+ # pc.multiply on decimals follows the SQL rule
107
+ # decimal(p1,s1) * decimal(p2,s2) -> decimal(p1+p2+1, s1+s2);
108
+ # multiplying by the literal 2 (decimal128(19, 0)) blows past
109
+ # decimal128's 38-digit cap for any input wider than ~18 digits.
110
+ # Compute `value + value` (which only adds 1 to precision) and
111
+ # cast the result to the declared output type. We do the add at
112
+ # the input precision, then cast — for inputs at the 38-digit
113
+ # cap we need decimal256 just to hold the +1 intermediate.
114
+ in_p, in_s = value.type.precision, value.type.scale
115
+ work_type = pa.decimal256(in_p, in_s) if in_p >= 38 else value.type
116
+ casted = pc.cast(value, work_type) if work_type != value.type else value
117
+ summed: pa.Array[Any] = pc.add(casted, casted) # decimal(p+1, s)
118
+ out_type = _promote_for_addition(value.type)
119
+ if summed.type == out_type:
120
+ return summed
121
+ return pc.cast(summed, out_type)
122
+ result: pa.Array[Any] = pc.multiply(value, 2)
123
+ return result
124
+
125
+
126
+ class AddValuesFunction(ScalarFunction):
127
+ """Adds two numeric values together.
128
+
129
+ This example demonstrates:
130
+ - Multiple Param() annotations with type_bound validation
131
+ - Dynamic output type with type promotion for overflow safety
132
+
133
+ Validates that both values are numeric types (integer, float, decimal, or
134
+ temporal) at compute time, raising SchemaValidationError if not.
135
+
136
+ Example:
137
+ SQL: SELECT add_values(price, tax) FROM orders
138
+ Input: price=[1, 2, 3], tax=[10, 20, 30]
139
+ Output: result=[11, 22, 33]
140
+
141
+ Raises:
142
+ SchemaValidationError: If either value is not a numeric type.
143
+
144
+ """
145
+
146
+ class Meta:
147
+ """Function metadata."""
148
+
149
+ name = "add_values"
150
+ description = "Adds two numeric values"
151
+ examples = [
152
+ FunctionExample(
153
+ sql="SELECT add_values(price, tax) FROM orders",
154
+ description="Calculate total by adding price and tax",
155
+ ),
156
+ FunctionExample(
157
+ sql="SELECT add_values(quantity, reserved) FROM inventory",
158
+ description="Sum quantity and reserved amounts",
159
+ ),
160
+ ]
161
+
162
+ _output_type: pa.DataType
163
+
164
+ @classmethod
165
+ def on_bind(cls, params: BindParameters) -> BindResult:
166
+ """Compute output type from input value types."""
167
+ field1 = params.arguments_schema.field(0)
168
+ field2 = params.arguments_schema.field(1)
169
+
170
+ # Compute the output type by promoting to the wider of the two types,
171
+ # then promoting again to reduce overflow risk.
172
+ common_type = pc.add(pa.nulls(1, type=field1.type), pa.nulls(1, type=field2.type)).type
173
+ return BindResult(_promote_for_addition(common_type))
174
+
175
+ @classmethod
176
+ def compute(
177
+ cls,
178
+ col1: Annotated[
179
+ pa.Array[Any],
180
+ Param(doc="First numeric value", type_bound=_is_addable_type),
181
+ ],
182
+ col2: Annotated[
183
+ pa.Array[Any],
184
+ Param(doc="Second numeric value", type_bound=_is_addable_type),
185
+ ],
186
+ ) -> Annotated[pa.Array[Any], Returns()]:
187
+ """Add the two values together."""
188
+ result: pa.Array[Any] = pc.add(col1, col2)
189
+ return result
190
+
191
+
192
+ class SumValuesFunction(ScalarFunction):
193
+ """Sums multiple numeric values.
194
+
195
+ This example demonstrates:
196
+ - varargs=True to accept variable number of values
197
+ - type_bound validation on all varargs values
198
+ - Dynamic output type computed in bind()
199
+
200
+ Example:
201
+ SQL: SELECT sum_values(price, tax, shipping) FROM orders
202
+ Input: price=[1, 2], tax=[10, 20], shipping=[100, 200]
203
+ Output: result=[111, 222]
204
+
205
+ """
206
+
207
+ class Meta:
208
+ """Function metadata."""
209
+
210
+ name = "sum_values"
211
+ description = "Sum multiple numeric values"
212
+ examples = [
213
+ FunctionExample(
214
+ sql="SELECT sum_values(price, tax, shipping) FROM orders",
215
+ description="Calculate total cost from multiple values",
216
+ ),
217
+ ]
218
+
219
+ @classmethod
220
+ def on_bind(
221
+ cls,
222
+ params: BindParameters,
223
+ ) -> BindResult:
224
+ """Compute output type from first value, promoted for overflow safety."""
225
+ first_type = params.arguments_schema.field(0).type
226
+ return BindResult(_promote_for_addition(first_type))
227
+
228
+ @classmethod
229
+ def compute(
230
+ cls,
231
+ values: Annotated[
232
+ list[pa.Array[Any]],
233
+ Param(
234
+ doc="Numeric values to sum",
235
+ type_bound=_is_addable_type,
236
+ varargs=True,
237
+ ),
238
+ ],
239
+ ) -> Annotated[pa.Array[Any], Returns()]:
240
+ """Sum all specified values."""
241
+ result: pa.Array[Any] = values[0]
242
+ for val in values[1:]:
243
+ result = pc.add(result, val)
244
+ return result
245
+
246
+
247
+ class ConcatValuesIntFunction(ScalarFunction):
248
+ """Concatenate integer column values as their string sum.
249
+
250
+ Varargs overload for integer columns: sums all values per row and
251
+ returns the result as a string.
252
+
253
+ Example:
254
+ SQL: SELECT concat_values(a, b, c) FROM t
255
+ Input: a=[1, 2], b=[10, 20], c=[100, 200]
256
+ Output: result=['111', '222']
257
+
258
+ """
259
+
260
+ class Meta:
261
+ """Function metadata."""
262
+
263
+ name = "concat_values"
264
+ description = "Sum integer varargs and return as string"
265
+
266
+ @classmethod
267
+ def on_bind(cls, params: BindParameters) -> BindResult:
268
+ """Output is always string."""
269
+ return BindResult(pa.string())
270
+
271
+ @classmethod
272
+ def compute(
273
+ cls,
274
+ values: Annotated[
275
+ list[pa.Int64Array],
276
+ Param(doc="Integer values to sum", varargs=True),
277
+ ],
278
+ ) -> Annotated[pa.Array[Any], Returns()]:
279
+ """Sum all integer columns and return as string."""
280
+ result: pa.Array[Any] = values[0]
281
+ for val in values[1:]:
282
+ result = pc.add(result, val)
283
+ return pc.cast(result, pa.string())
284
+
285
+
286
+ class ConcatValuesStrFunction(ScalarFunction):
287
+ """Concatenate string column values.
288
+
289
+ Varargs overload for string columns: concatenates all string values per row.
290
+
291
+ Example:
292
+ SQL: SELECT concat_values(a, b) FROM t
293
+ Input: a=['hello', 'foo'], b=[' world', 'bar']
294
+ Output: result=['hello world', 'foobar']
295
+
296
+ """
297
+
298
+ class Meta:
299
+ """Function metadata."""
300
+
301
+ name = "concat_values"
302
+ description = "Concatenate string varargs"
303
+
304
+ @classmethod
305
+ def on_bind(cls, params: BindParameters) -> BindResult:
306
+ """Output is always string."""
307
+ return BindResult(pa.string())
308
+
309
+ @classmethod
310
+ def compute(
311
+ cls,
312
+ values: Annotated[
313
+ list[pa.StringArray],
314
+ Param(doc="String values to concatenate", varargs=True),
315
+ ],
316
+ ) -> Annotated[pa.Array[Any], Returns()]:
317
+ """Concatenate all string columns."""
318
+ result: pa.StringArray = values[0]
319
+ for val in values[1:]:
320
+ result = pc.binary_join_element_wise(result, val, "") # type: ignore[call-overload]
321
+ return result
@@ -0,0 +1,120 @@
1
+ # Copyright 2025, 2026 Query Farm LLC - https://query.farm
2
+
3
+ """Binary-payload and string-cased scalar fixtures (binary_packet, upper_case)."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import Annotated, Any
8
+
9
+ import pyarrow as pa
10
+ import pyarrow.compute as pc
11
+
12
+ from vgi.arguments import ConstParam, Param, Returns
13
+ from vgi.metadata import FunctionExample
14
+ from vgi.scalar_function import ScalarFunction
15
+
16
+ _CONFIG_STRUCT_TYPE = pa.struct([("label", pa.string()), ("version", pa.int64())])
17
+
18
+
19
+ class BinaryPacketFunction(ScalarFunction):
20
+ """Builds binary packets with header, payload, and config metadata.
21
+
22
+ This example demonstrates complex ConstParam types:
23
+ - header (binary): Constant prefix bytes at the start
24
+ - payload (binary column): Variable binary data per row
25
+ - config (struct): Constant metadata struct at the end
26
+
27
+ The constant parameters bracket the column parameter (first and last).
28
+
29
+ The function concatenates: header + payload + config.label encoded + version byte
30
+
31
+ Example:
32
+ SQL: SELECT binary_packet(x'CAFE', data, {label: 'v1', version: 1}) FROM t
33
+ Input: data=[x'0102', x'0304']
34
+ Args: header=x'CAFE', config={label: 'v1', version: 1}
35
+ Output: result=[x'CAFE0102763101', x'CAFE0304763101']
36
+
37
+ """
38
+
39
+ class Meta:
40
+ """Function metadata."""
41
+
42
+ name = "binary_packet"
43
+ description = "Build binary packets with header, payload, and config"
44
+ examples = [
45
+ FunctionExample(
46
+ sql="SELECT binary_packet(x'FF', payload, {label: 'msg', version: 1}) FROM t",
47
+ description="Build packets with 0xFF header",
48
+ ),
49
+ ]
50
+
51
+ @classmethod
52
+ def compute(
53
+ cls,
54
+ header: Annotated[
55
+ bytes,
56
+ ConstParam("Header bytes to prepend", arrow_type=pa.binary()),
57
+ ],
58
+ payload: Annotated[pa.BinaryArray, Param(doc="Binary payload data")],
59
+ config: Annotated[
60
+ dict[str, Any],
61
+ ConstParam("Config {label, version}", arrow_type=_CONFIG_STRUCT_TYPE),
62
+ ],
63
+ ) -> Annotated[pa.BinaryArray, Returns()]:
64
+ """Build binary packets from header, payload, and config."""
65
+ # Extract config fields
66
+ label: str = config["label"]
67
+ version: int = config["version"]
68
+
69
+ # Build suffix from config: label bytes + version as single byte
70
+ suffix = label.encode("utf-8") + bytes([version & 0xFF])
71
+
72
+ # Concatenate header + payload + suffix for each row
73
+ results: list[bytes] = []
74
+ for i in range(len(payload)):
75
+ if payload[i].is_valid:
76
+ payload_bytes: bytes = payload[i].as_py()
77
+ results.append(header + payload_bytes + suffix)
78
+ else:
79
+ results.append(header + suffix) # Empty payload for nulls
80
+
81
+ return pa.array(results, type=pa.binary())
82
+
83
+
84
+ class UpperCaseFunction(ScalarFunction):
85
+ """Converts string values to uppercase.
86
+
87
+ This example demonstrates type inference with pa.StringArray:
88
+ - pa.StringArray -> pa.string() (inferred from Annotated type)
89
+ - Returns() output type is also inferred from pa.StringArray
90
+
91
+ Example:
92
+ SQL: SELECT upper_case(name) FROM users
93
+ Input: name=["alice", "bob", "charlie"]
94
+ Output: result=["ALICE", "BOB", "CHARLIE"]
95
+
96
+ """
97
+
98
+ class Meta:
99
+ """Function metadata."""
100
+
101
+ name = "upper_case"
102
+ description = "Converts string values to uppercase"
103
+ examples = [
104
+ FunctionExample(
105
+ sql="SELECT upper_case(name) FROM users",
106
+ description="Convert user names to uppercase",
107
+ ),
108
+ FunctionExample(
109
+ sql="SELECT upper_case(status) FROM orders WHERE id = 1",
110
+ description="Uppercase the status field",
111
+ ),
112
+ ]
113
+
114
+ @classmethod
115
+ def compute(
116
+ cls,
117
+ value: Annotated[pa.StringArray, Param(doc="String value to uppercase")],
118
+ ) -> Annotated[pa.StringArray, Returns()]:
119
+ """Convert the string values to uppercase."""
120
+ return pc.utf8_upper(value)
@@ -0,0 +1,176 @@
1
+ # Copyright 2025, 2026 Query Farm LLC - https://query.farm
2
+
3
+ """Number/string formatting scalar fixtures (format_number_*, smart_format_*)."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import Annotated
8
+
9
+ import pyarrow as pa
10
+
11
+ from vgi.arguments import ConstParam, Param, Returns
12
+ from vgi.metadata import FunctionExample
13
+ from vgi.scalar_function import ScalarFunction
14
+
15
+
16
+ class FormatNumberDefaultFunction(ScalarFunction):
17
+ """Format a number with default precision (0 decimal places).
18
+
19
+ Overload with 0 ConstParams: just a column input.
20
+
21
+ Example:
22
+ SQL: SELECT format_number(price) FROM products
23
+ Input: price=[3.14, 2.718, 100.5]
24
+ Output: result=['3', '3', '100']
25
+
26
+ """
27
+
28
+ class Meta:
29
+ """Function metadata."""
30
+
31
+ name = "format_number"
32
+ description = "Format number with default precision (0 decimals)"
33
+ examples = [
34
+ FunctionExample(
35
+ sql="SELECT format_number(price) FROM products",
36
+ description="Format prices with no decimal places",
37
+ ),
38
+ ]
39
+
40
+ @classmethod
41
+ def compute(
42
+ cls,
43
+ value: Annotated[pa.DoubleArray, Param(doc="Number to format")],
44
+ ) -> Annotated[pa.StringArray, Returns()]:
45
+ """Format each value with 0 decimal places."""
46
+ return pa.array(
47
+ [f"{v:.0f}" if v is not None else None for v in value.to_pylist()],
48
+ type=pa.string(),
49
+ )
50
+
51
+
52
+ class FormatNumberPrecisionFunction(ScalarFunction):
53
+ """Format a number with specified precision.
54
+
55
+ Overload with 1 ConstParam: precision.
56
+
57
+ Example:
58
+ SQL: SELECT format_number(2, price) FROM products
59
+ Input: price=[3.14159, 2.718, 100.5]
60
+ Args: precision=2
61
+ Output: result=['3.14', '2.72', '100.50']
62
+
63
+ """
64
+
65
+ class Meta:
66
+ """Function metadata."""
67
+
68
+ name = "format_number"
69
+ description = "Format number with specified precision"
70
+ examples = [
71
+ FunctionExample(
72
+ sql="SELECT format_number(2, price) FROM products",
73
+ description="Format prices with 2 decimal places",
74
+ ),
75
+ ]
76
+
77
+ @classmethod
78
+ def compute(
79
+ cls,
80
+ precision: Annotated[int, ConstParam("Number of decimal places")],
81
+ value: Annotated[pa.DoubleArray, Param(doc="Number to format")],
82
+ ) -> Annotated[pa.StringArray, Returns()]:
83
+ """Format each value with the specified precision."""
84
+ return pa.array(
85
+ [f"{v:.{precision}f}" if v is not None else None for v in value.to_pylist()],
86
+ type=pa.string(),
87
+ )
88
+
89
+
90
+ class FormatNumberFullFunction(ScalarFunction):
91
+ """Format a number with precision and prefix.
92
+
93
+ Overload with 2 ConstParams: precision and prefix.
94
+
95
+ Example:
96
+ SQL: SELECT format_number(2, '$', price) FROM products
97
+ Input: price=[3.14, 2.718, 100.5]
98
+ Args: precision=2, prefix='$'
99
+ Output: result=['$3.14', '$2.72', '$100.50']
100
+
101
+ """
102
+
103
+ class Meta:
104
+ """Function metadata."""
105
+
106
+ name = "format_number"
107
+ description = "Format number with precision and prefix"
108
+ examples = [
109
+ FunctionExample(
110
+ sql="SELECT format_number(2, '$', price) FROM products",
111
+ description="Format prices with dollar sign and 2 decimals",
112
+ ),
113
+ ]
114
+
115
+ @classmethod
116
+ def compute(
117
+ cls,
118
+ precision: Annotated[int, ConstParam("Number of decimal places")],
119
+ prefix: Annotated[str, ConstParam("Prefix string")],
120
+ value: Annotated[pa.DoubleArray, Param(doc="Number to format")],
121
+ ) -> Annotated[pa.StringArray, Returns()]:
122
+ """Format each value with prefix and specified precision."""
123
+ return pa.array(
124
+ [f"{prefix}{v:.{precision}f}" if v is not None else None for v in value.to_pylist()],
125
+ type=pa.string(),
126
+ )
127
+
128
+
129
+ class SmartFormatWidthFunction(ScalarFunction):
130
+ """Right-align a double in a field of given width.
131
+
132
+ Overload with int ConstParam.
133
+ """
134
+
135
+ class Meta:
136
+ """Function metadata."""
137
+
138
+ name = "smart_format"
139
+ description = "Right-align value in field of given width"
140
+
141
+ @classmethod
142
+ def compute(
143
+ cls,
144
+ width: Annotated[int, ConstParam("Field width")],
145
+ value: Annotated[pa.DoubleArray, Param(doc="Value to format")],
146
+ ) -> Annotated[pa.StringArray, Returns()]:
147
+ """Right-align value in field of given width."""
148
+ return pa.array(
149
+ [f"{v:>{width}}" if v is not None else None for v in value.to_pylist()],
150
+ type=pa.string(),
151
+ )
152
+
153
+
154
+ class SmartFormatPrefixFunction(ScalarFunction):
155
+ """Prepend a prefix string to a formatted double.
156
+
157
+ Overload with str ConstParam.
158
+ """
159
+
160
+ class Meta:
161
+ """Function metadata."""
162
+
163
+ name = "smart_format"
164
+ description = "Prepend prefix to formatted value"
165
+
166
+ @classmethod
167
+ def compute(
168
+ cls,
169
+ prefix: Annotated[str, ConstParam("Prefix string")],
170
+ value: Annotated[pa.DoubleArray, Param(doc="Value to format")],
171
+ ) -> Annotated[pa.StringArray, Returns()]:
172
+ """Prepend prefix to formatted value."""
173
+ return pa.array(
174
+ [f"{prefix}{v}" if v is not None else None for v in value.to_pylist()],
175
+ type=pa.string(),
176
+ )