Important
New database features are no longer connected to engine versions; features are now enabled independently during scheduled update windows, and “major” and “minor” releases no longer exist in Helios. Please visit the release notes to view the latest features available in your database clusters.
Working with Python UDFs
On this page
Equivalent Data Types
When defining Python UDFs, input parameters and return values must be mapped between Python and SingleStore data types.
Following are the default Python to SingleStore mappings:
|
Python Type |
SingleStore Type |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
For finer control, use the singlestoredb. module to specify types such as SMALLINT, VARCHAR, DECIMAL, or JSON.
Handling NULL Values
By default, Python UDFs and TVFs do not allow NULL values.NULL support depends on whether the Python function is scalar or vectorized.
Scalar Python Functions (UDFs and TVFs)
-
Use
Optional[.(or. . ] type|Nonein Python 3.10+) to allow parameters and return values to accept NULL. -
This makes the entire parameter or return value nullable.
-
If any argument is
NULL, the function can returnNULLas needed.
Vectorized Python Functions (UDFs and TVFs)
-
Optional[.cannot be used for element-level NULLs inside vectors.. . ] -
Instead, use the
Maskedannotation for parameters and return values that must supportNULL. -
A
Maskedvalue consists of:-
A data vector containing the non-NULL values.
-
A boolean mask vector, where
Trueindicates aNULLelement.
-
-
This ensures that
NULLvalues are preserved and propagated correctly across inputs and outputs. -
In Python TVFs, each output column can independently use
Maskedto indicate nullability.
Masked Example
The following example shows a vectorized UDF that doubles non-NULL values and preserves NULLs using Masked:
import numpy as np
import numpy.typing as npt
from singlestoredb.functions import udf
from singlestoredb.functions.dtypes import Masked
@udf
def double_nullable(
x: Masked[npt.NDArray[np.float64]],
) -> Masked[npt.NDArray[np.float64]]:
# x.data contains the values; x.mask is True where the value is NULL
result_data = x.data * 2
# Propagate the mask unchanged — NULLs in, NULLs out
return Masked(data=result_data, mask=x.mask)The generated SQL function is nullable:
CREATE OR REPLACE EXTERNAL FUNCTION `double_nullable`(`x` DOUBLE NULL)RETURNS DOUBLE NULLDEFINER = '<function_owner>'@'%'AS MANAGED SERVICE '<managed_service_url>'FORMAT ROWDAT_1;
Call the function:
SELECT double_nullable(val) FROM my_table;-- If val = 5.0 → returns 10.0-- If val = NULL → returns NULL
Overriding Parameter and Return Value Types
You can specify an output schema using the returns= parameter in the @udf decorator.
-
A list of SQL types with
name=specified, or -
A
NamedTuple,TypedDict, orpydantic..BaseModel
The schema class is automatically inferred and it does not need to be returned explicitly, it is only used to define the output schema.
Function Count Limit
Publishing a Python UDF can expose up to 10 functions.
Cancelling Running Python UDFs
When a query that uses a Python UDF is cancelled or when the connection between the database engine and the Python UDF server is broken, the Python UDF execution continues until it can be safely interrupted.
-
Synchronous Python UDFs
-
Scalar Python UDFs: Cancellation can only occur between row function calls.
-
Vectorized Python UDFs: Cancellation occurs only after all rows in the current batch are processed.
-
-
Asynchronous Python UDFs
-
Cancellation is detected more quickly.
-
When a disconnect is detected, an
asyncio.is raised the next time the Python UDF becomes active.CancelledError -
If synchronous operations are used inside an
asyncPython UDF, they must complete before cancellation occurs. -
Nested async calls are cancelled as soon as they activate again.
-
SingleStore recommends using async before the function definition for better cancellation handling.
Note
Use asynchronous Python UDFs and async-compatible libraries (such as aiohttp or httpx) wherever possible to ensure that Python UDFs can be cancelled promptly.requests) inside async def functions, as they prevent cancellation until the blocking call completes.
Timeouts
A timeout can be applied directly in the @udfdecorator using the timeout= parameter.
-
The value must be specified in seconds.
-
If the timeout period is exceeded, the Python UDF is cancelled automatically.
User Permissions
To allow users other than the organization owner to execute Python UDFs or TVFs, the organization owner must grant the appropriate permissions.
Required Permissions
-
EXECUTE(Object-Level Permission)-
Required to execute a function in a specific database.
-
Can be granted on all functions in a database or a specific function.
-
Following example demonstrates the syntax:
GRANT EXECUTE ON <db_name>.<function_name> TO 'user'@'%';
-
-
OUTBOUND(Global Permission)-
Required for managed Python UDFs and TVFs to make external service calls during execution.
This is an execution prerequisite for managed functions, not merely an application-level network capability. -
Must be granted globally.
-
Following example demonstrates the syntax:
GRANT OUTBOUND ON *.* TO 'user'@'%';
-
Note
Both permissions must be granted to ensure the user can execute Python UDFs or TVFs successfully.
Side Effects and Transactions
Python UDF code runs outside the database engine.
Runtime Dependencies
Python UDFs run in the managed Python UDF runtime after publishing.tensorflow, openai, or instructor), install or configure those dependencies using the Container Services runtime configuration before publishing.
Note
Examples on this page may import third-party packages.
Examples
Example 1: Calculate Sales Metrics with Vectorized Python TVFs
This example demonstrates a vectorized Python TVF that computes total and average sales per row and returns a table with named columns.
import typingimport numpy as npimport pandas as pdfrom singlestoredb.functions import udf, Tableimport numpy.typing as npt# Define output schema using NamedTupleclass SalesOutput(typing.NamedTuple):total_sales: floataverage_sales: floatcategory: str@udf(returns=SalesOutput)async def vector_sales(units_sold: npt.NDArray[np.float64], # Vector of units soldunit_price: npt.NDArray[np.float64], # Vector of unit pricescategory: npt.NDArray[np.str_] # Vector of categories) -> Table[pd.DataFrame]:"""Calculate total and average sales per row with category.Parameters----------units_sold : np.ndarray[np.float64]Number of units soldunit_price : np.ndarray[np.float64]Price per unitcategory : np.ndarray[str]Category name for each rowReturns-------pd.DataFrameTable with columns:- total_sales- average_sales- category"""# Compute vectorized total and averagetotal_sales = units_sold * unit_priceaverage_sales = total_sales / np.maximum(units_sold, 1) # Avoid divide by zero# Return as a DataFrame with named columnsdf = pd.DataFrame({'total_sales': total_sales,'average_sales': average_sales,'category': category})return Table(df)# Start Python UDF serverimport singlestoredb.apps as appsconnection_info = await apps.run_udf_app()
In Helios, publishing the Python UDF registers the generated external function in the selected database:
CREATE OR REPLACE EXTERNAL FUNCTION `vector_sales`(`units_sold` DOUBLE NOT NULL,`unit_price` DOUBLE NOT NULL,`category` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL)RETURNS TABLE(`total_sales` DOUBLE NOT NULL,`average_sales` DOUBLE NOT NULL,`category` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL)DEFINER = '<function_owner>'@'%'AS MANAGED SERVICE '<managed_service_url>'FORMAT ROWDAT_1;
Example 2: Vector Embeddings
This example demonstrates a Python UDF that can connect to any deployed embedding service in SingleStore Aura.
import base64import sysimport httpximport numpy as npimport numpy.typing as nptfrom singlestoredb.functions import udfMODEL_NAME = 'name-of-embedding-service'MODEL_URL = 'url-of-embedding-server'TOKEN = 'api-token-of-embedding-service'HEADERS = {'accept': 'application/json','Content-Type': 'application/json','Authorization': f'Bearer {TOKEN}',}@udfasync def mixedbread_embeddings(text: npt.NDArray[np.str_],) -> npt.NDArray[np.bytes_]:async with httpx.AsyncClient() as client:res = await client.post(MODEL_URL,headers=HEADERS,json=dict(model=MODEL_NAME,input=text.tolist(),encoding_format='base64',),)if res.status_code >= 400:print(res.content.decode('utf8'), file=sys.stderr)raise RuntimeError(res.content.decode('utf8'))return np.array([base64.b64decode(x['embedding']) for x in res.json()['data']],dtype=object,)# Start Python UDF server.import singlestoredb.apps as appsconnection_info = await apps.run_udf_app()
This results in the following external function:
CREATE OR REPLACE EXTERNAL FUNCTION `mixedbread_embeddings`(`text` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL)RETURNS BLOB NOT NULLDEFINER = '<function_owner>'@'%'AS MANAGED SERVICE '<managed_service_url>'FORMAT ROWDAT_1;
Example 3: OpenAI Integration with Structured Output
This example demonstrates a Python table-valued function (TVF) that returns structured output using a pydantic..
Warning
This example calls an external API (OpenAI).
import osfrom typing import Listfrom pydantic import BaseModel, Fieldfrom singlestoredb.functions import udf, Tableimport instructorfrom openai import OpenAI# Create OpenAI client via instructor wrapperclient = instructor.from_openai(OpenAI(api_key=os.getenv("OPENAI_API_KEY")))# Define output schemaclass Synonym(BaseModel):word: str = Field(description='Synonym of the given word')score: float = Field(description='Closeness score from 0.0 to 1.0')# Define the TVF@udfasync def get_synonyms(word: str) -> Table[List[Synonym]]:"""Return a list of synonyms of the given word and a score."""return Table(client.create(model='gpt-4o-mini',messages=[dict(role='system', content='You are a helpful assistant'),dict(role='user',content=f'''* Get a list of synonyms of the word "{word}"* Limit the number of results to 10''')],response_model=List[Synonym],max_retries=0,))# Start Python UDF serverimport singlestoredb.apps as appsconnection_info = await apps.run_udf_app()
This results in the following external function:
CREATE OR REPLACE EXTERNAL FUNCTION `get_synonyms`(`word` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL)RETURNS TABLE(`word` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,`score` DOUBLE NOT NULL)DEFINER = '<function_owner>'@'%'AS MANAGED SERVICE '<managed_service_url>'FORMAT ROWDAT_1;
Run the following command to retrieve the synonyms of the word “danger”.
SELECT * FROM get_synonyms('danger');
The command results the following:
word score0 hazard 0.901 risk 0.852 threat 0.883 peril 0.904 jeopardy 0.875 threatening 0.806 threatening situation 0.707 threatened 0.758 insecurities 0.609 unsafe 0.65
Example 4: Machine Learning Model Scoring
This example demonstrates how to use a pre-trained Keras model stored in Stage to score a collection of input parameters.
Note
This example requires tensorflow to be available in the Python UDF runtime.
import numpy as npimport pandas as pdimport numpy.typing as nptimport singlestoredb.notebook as nbfrom tensorflow.keras.models import load_modelfrom singlestoredb.functions import udf# Download model from Stagenb.stage.download_file('my_model.keras', local_path='my_model.keras')# Load the modelmodel = load_model('my_model.keras')@udfasync def keras_score(param_1: npt.NDArray[np.float32],param_2: npt.NDArray[np.float32],param_3: npt.NDArray[np.float32],) -> npt.NDArray[np.float32]:"""Score rows using a Keras model.Parameters----------param_1 : np.ndarray[np.float32]First input parameter.param_2 : np.ndarray[np.float32]Second input parameter.param_3 : np.ndarray[np.float32]Third input parameter.Returns-------np.ndarray[np.float32]Predicted scores from the model."""X_test = pd.DataFrame({'param_1': param_1,'param_2': param_2,'param_3': param_3})return model.predict(X_test).reshape((-1,))# Start Python UDF serverimport singlestoredb.apps as appsconnection_info = await apps.run_udf_app()
Example 5: Overriding Types with dtypes
By default, Python types map to general SQL types (for example, int maps to BIGINT).singlestoredb. to override these defaults and define precise SQL signatures.
Pass these types to the @udf(args=[. decorator.
-
Use
SMALLINTfor integer inputs and outputsThis example overrides the default
BIGINTmapping for Pythonintby explicitly usingSMALLINT.Use this approach when it is required to match an existing schema that uses smaller integer types. from singlestoredb.functions import udffrom singlestoredb.functions.dtypes import SMALLINT@udf(args=[SMALLINT(nullable=False)],returns=SMALLINT(nullable=False),)def double_small(x: int) -> int:return x * 2 -
Control string length and return structured JSON data
This example defines a bounded string input using
VARCHAR(n)and returns structured data as JSON.Use VARCHAR(n)to enforce input size limits and return a JSON-encoded string when using theJSONtype.from singlestoredb.functions import udffrom singlestoredb.functions.dtypes import JSON, VARCHAR@udf(args=[VARCHAR(128, nullable=False)],returns=JSON(nullable=False),)def wrap_as_json(label: str) -> str:import jsonreturn json.dumps({"label": label})Note
-
Ensure
VARCHAR(n)length matches the expected input size.Values longer than this limit may be truncated or rejected. -
When using
JSONas the return type, return a JSON-encoded string. -
Set
nullableto match whether your Python types allowNone.
-
Last modified: