зеркало из
1
0
Форкнуть 0

Add SQL Decorator for V2 model. (#185)

* add sql decorator

* add tests + update param names

* add commandType to tests

* fix indentation

* fix output binding tests

* fix input binding test

* update ProgModelSpec.pyi

* add leases_table_name to sqltrigger

---------

Co-authored-by: gavin-aguiar <80794152+gavin-aguiar@users.noreply.github.com>
This commit is contained in:
Lucy Zhang 2023-08-31 15:57:27 -07:00 коммит произвёл GitHub
Родитель 5fff4fc646
Коммит 71f756bda4
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 585 добавлений и 2 удалений

Просмотреть файл

@ -19,6 +19,8 @@ BLOB = "blob"
EVENT_GRID_TRIGGER = "eventGridTrigger"
EVENT_GRID = "eventGrid"
TABLE = "table"
SQL = "sql"
SQL_TRIGGER = "sqlTrigger"
DAPR_SERVICE_INVOCATION_TRIGGER = "daprServiceInvocationTrigger"
DAPR_BINDING_TRIGGER = "daprBindingTrigger"
DAPR_TOPIC_TRIGGER = "daprTopicTrigger"

Просмотреть файл

@ -23,6 +23,7 @@ from azure.functions.decorators.queue import QueueTrigger, QueueOutput
from azure.functions.decorators.servicebus import ServiceBusQueueTrigger, \
ServiceBusQueueOutput, ServiceBusTopicTrigger, \
ServiceBusTopicOutput
from azure.functions.decorators.sql import SqlTrigger, SqlInput, SqlOutput
from azure.functions.decorators.table import TableInput, TableOutput
from azure.functions.decorators.timer import TimerTrigger
from azure.functions.decorators.utils import parse_singular_param_to_enum, \
@ -1069,6 +1070,61 @@ class TriggerApi(DecoratorApi, ABC):
return wrap
def sql_trigger(self,
arg_name: str,
table_name: str,
connection_string_setting: str,
leases_table_name: Optional[str] = None,
data_type: Optional[DataType] = None,
**kwargs) -> Callable[..., Any]:
"""The sql_trigger decorator adds :class:`SqlTrigger`
to the :class:`FunctionBuilder` object
for building :class:`Function` object used in worker function
indexing model. This decorator will work only with extension bundle 4.x
and above.
This is equivalent to defining SqlTrigger in the function.json which
enables function to be triggered when there are changes in the Sql
table.
All optional fields will be given default value by function host when
they are parsed by function host.
Ref: https://aka.ms/sqlbindings
:param arg_name: The name of the variable that represents a
:class:`SqlRowList` object in the function code
:param table_name: The name of the table monitored by the trigger
:param connection_string_setting: The name of an app setting that
contains the connection string for the database against which the
query or stored procedure is being executed
:param leases_table_name: The name of the table used to store
leases. If not specified, the leases table name will be
Leases_{FunctionId}_{TableId}.
:param data_type: Defines how Functions runtime should treat the
parameter value
:param kwargs: Keyword arguments for specifying additional binding
fields to include in the binding json
:return: Decorator function.
"""
@self._configure_function_builder
def wrap(fb):
def decorator():
fb.add_trigger(
trigger=SqlTrigger(
name=arg_name,
table_name=table_name,
connection_string_setting=connection_string_setting,
leases_table_name=leases_table_name,
data_type=parse_singular_param_to_enum(data_type,
DataType),
**kwargs))
return fb
return decorator()
return wrap
def generic_trigger(self,
arg_name: str,
type: str,
@ -1859,6 +1915,115 @@ class BindingApi(DecoratorApi, ABC):
return wrap
def sql_input(self,
arg_name: str,
command_text: str,
connection_string_setting: str,
command_type: Optional[str] = 'Text',
parameters: Optional[str] = None,
data_type: Optional[DataType] = None,
**kwargs) -> Callable[..., Any]:
"""The sql_input decorator adds
:class:`SqlInput` to the :class:`FunctionBuilder` object
for building :class:`Function` object used in worker function
indexing model. This decorator will work only with extension bundle 4.x
and above.
This is equivalent to defining SqlInput in the function.json which
enables the function to read from a Sql database.
All optional fields will be given default value by function host when
they are parsed by function host.
Ref: https://aka.ms/sqlbindings
:param arg_name: The name of the variable that represents a
:class:`SqlRowList` input object in function code
:param command_text: The Transact-SQL query command or name of the
stored procedure executed by the binding
:param connection_string_setting: The name of an app setting that
contains the connection string for the database against which the
query or stored procedure is being executed
:param command_type: A CommandType value, which is Text for a query
and StoredProcedure for a stored procedure
:param parameters: Zero or more parameter values passed to the
command during execution as a single string. Must follow the format
@param1=param1,@param2=param2
:param data_type: Defines how Functions runtime should treat the
parameter value
:param kwargs: Keyword arguments for specifying additional binding
fields to include in the binding json
:return: Decorator function.
"""
@self._configure_function_builder
def wrap(fb):
def decorator():
fb.add_binding(
binding=SqlInput(
name=arg_name,
command_text=command_text,
connection_string_setting=connection_string_setting,
command_type=command_type,
parameters=parameters,
data_type=parse_singular_param_to_enum(data_type,
DataType),
**kwargs))
return fb
return decorator()
return wrap
def sql_output(self,
arg_name: str,
command_text: str,
connection_string_setting: str,
data_type: Optional[DataType] = None,
**kwargs) -> Callable[..., Any]:
"""The sql_output decorator adds
:class:`SqlOutput` to the :class:`FunctionBuilder` object
for building :class:`Function` object used in worker function
indexing model. This decorator will work only with extension bundle 4.x
and above.
This is equivalent to defining SqlOutput in the function.json which
enables the function to write to a Sql database.
All optional fields will be given default value by function host when
they are parsed by function host.
Ref: https://aka.ms/sqlbindings
:param arg_name: The name of the variable that represents
Sql output object in function code
:param command_text: The Transact-SQL query command or name of the
stored procedure executed by the binding
:param connection_string_setting: The name of an app setting that
contains the connection string for the database against which the
query or stored procedure is being executed
:param data_type: Defines how Functions runtime should treat the
parameter value
:param kwargs: Keyword arguments for specifying additional binding
fields to include in the binding json
:return: Decorator function.
"""
@self._configure_function_builder
def wrap(fb):
def decorator():
fb.add_binding(
binding=SqlOutput(
name=arg_name,
command_text=command_text,
connection_string_setting=connection_string_setting,
data_type=parse_singular_param_to_enum(data_type,
DataType),
**kwargs))
return fb
return decorator()
return wrap
def generic_input_binding(self,
arg_name: str,
type: str,

Просмотреть файл

@ -0,0 +1,61 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
from typing import Optional
from azure.functions.decorators.constants import SQL, SQL_TRIGGER
from azure.functions.decorators.core import DataType, InputBinding, \
OutputBinding, Trigger
class SqlInput(InputBinding):
@staticmethod
def get_binding_name() -> str:
return SQL
def __init__(self,
name: str,
command_text: str,
connection_string_setting: str,
command_type: Optional[str] = 'Text',
parameters: Optional[str] = None,
data_type: Optional[DataType] = None,
**kwargs):
self.command_text = command_text
self.connection_string_setting = connection_string_setting
self.command_type = command_type
self.parameters = parameters
super().__init__(name=name, data_type=data_type)
class SqlOutput(OutputBinding):
@staticmethod
def get_binding_name() -> str:
return SQL
def __init__(self,
name: str,
command_text: str,
connection_string_setting: str,
data_type: Optional[DataType] = None,
**kwargs):
self.command_text = command_text
self.connection_string_setting = connection_string_setting
super().__init__(name=name, data_type=data_type)
class SqlTrigger(Trigger):
@staticmethod
def get_binding_name() -> str:
return SQL_TRIGGER
def __init__(self,
name: str,
table_name: str,
connection_string_setting: str,
leases_table_name: Optional[str] = None,
data_type: Optional[DataType] = None,
**kwargs):
self.table_name = table_name
self.connection_string_setting = connection_string_setting
self.leases_table_name = leases_table_name
super().__init__(name=name, data_type=data_type)

Просмотреть файл

@ -548,7 +548,46 @@ class TriggerApi(DecoratorApi, ABC):
"""
pass
def sql_trigger(self,
arg_name: str,
table_name: str,
connection_string_setting: str,
leases_table_name: Optional[str] = None,
data_type: Optional[DataType] = None,
**kwargs) -> Callable[..., Any]:
"""The sql_trigger decorator adds :class:`SqlTrigger`
to the :class:`FunctionBuilder` object
for building :class:`Function` object used in worker function
indexing model. This decorator will work only with extension bundle 4.x
and above.
This is equivalent to defining SqlTrigger in the function.json which
enables function to be triggered when there are changes in the Sql
table.
All optional fields will be given default value by function host when
they are parsed by function host.
Ref: https://aka.ms/sqlbindings
:param arg_name: The name of the variable that represents a
:class:`SqlRowList` object in the function code
:param table_name: The name of the table monitored by the trigger
:param connection_string_setting: The name of an app setting that
contains the connection string for the database against which the
query or stored procedure is being executed
:param leases_table_name: The name of the table used to store
leases. If not specified, the leases table name will be
Leases_{FunctionId}_{TableId}.
:param data_type: Defines how Functions runtime should treat the
parameter value
:param kwargs: Keyword arguments for specifying additional binding
fields to include in the binding json
:return: Decorator function.
"""
pass
def generic_trigger(self,
arg_name: str,
type: str,
@ -989,6 +1028,83 @@ class BindingApi(DecoratorApi, ABC):
pass
def sql_input(self,
arg_name: str,
command_text: str,
connection_string_setting: str,
command_type: Optional[str] = 'Text',
parameters: Optional[str] = None,
data_type: Optional[DataType] = None,
**kwargs) -> Callable[..., Any]:
"""The sql_input decorator adds
:class:`SqlInput` to the :class:`FunctionBuilder` object
for building :class:`Function` object used in worker function
indexing model. This decorator will work only with extension bundle 4.x
and above.
This is equivalent to defining SqlInput in the function.json which
enables the function to read from a Sql database.
All optional fields will be given default value by function host when
they are parsed by function host.
Ref: https://aka.ms/sqlbindings
:param arg_name: The name of the variable that represents a
:class:`SqlRowList` input object in function code
:param command_text: The Transact-SQL query command or name of the
stored procedure executed by the binding
:param connection_string_setting: The name of an app setting that
contains the connection string for the database against which the
query or stored procedure is being executed
:param command_type: A CommandType value, which is Text for a query
and StoredProcedure for a stored procedure
:param parameters: Zero or more parameter values passed to the
command during execution as a single string. Must follow the format
@param1=param1,@param2=param2
:param data_type: Defines how Functions runtime should treat the
parameter value
:param kwargs: Keyword arguments for specifying additional binding
fields to include in the binding json
:return: Decorator function.
"""
pass
def sql_output(self,
arg_name: str,
command_text: str,
connection_string_setting: str,
data_type: Optional[DataType] = None,
**kwargs) -> Callable[..., Any]:
"""The sql_output decorator adds
:class:`SqlOutput` to the :class:`FunctionBuilder` object
for building :class:`Function` object used in worker function
indexing model. This decorator will work only with extension bundle 4.x
and above.
This is equivalent to defining SqlOutput in the function.json which
enables the function to write to a Sql database.
All optional fields will be given default value by function host when
they are parsed by function host.
Ref: https://aka.ms/sqlbindings
:param arg_name: The name of the variable that represents
Sql output object in function code
:param command_text: The Transact-SQL query command or name of the
stored procedure executed by the binding
:param connection_string_setting: The name of an app setting that
contains the connection string for the database against which the
query or stored procedure is being executed
:param data_type: Defines how Functions runtime should treat the
parameter value
:param kwargs: Keyword arguments for specifying additional binding
fields to include in the binding json
:return: Decorator function.
"""
pass
def generic_input_binding(self,
arg_name: str,
type: str,

Просмотреть файл

@ -5,7 +5,8 @@ import unittest
from azure.functions.decorators.constants import TIMER_TRIGGER, HTTP_TRIGGER, \
HTTP_OUTPUT, QUEUE, QUEUE_TRIGGER, SERVICE_BUS, SERVICE_BUS_TRIGGER, \
EVENT_HUB, EVENT_HUB_TRIGGER, COSMOS_DB, COSMOS_DB_TRIGGER, BLOB, \
BLOB_TRIGGER, EVENT_GRID_TRIGGER, EVENT_GRID, TABLE, WARMUP_TRIGGER
BLOB_TRIGGER, EVENT_GRID_TRIGGER, EVENT_GRID, TABLE, WARMUP_TRIGGER, \
SQL, SQL_TRIGGER
from azure.functions.decorators.core import DataType, AuthLevel, \
BindingDirection, AccessRights, Cardinality
from azure.functions.decorators.function_app import FunctionApp
@ -2054,6 +2055,184 @@ class TestFunctionsApp(unittest.TestCase):
"connection": "dummy_out_conn"
})
def test_sql_default_args(self):
app = self.func_app
@app.sql_trigger(arg_name="trigger",
table_name="dummy_table",
connection_string_setting="dummy_setting")
@app.sql_input(arg_name="in",
command_text="dummy_query",
connection_string_setting="dummy_setting")
@app.sql_output(arg_name="out",
command_text="dummy_table",
connection_string_setting="dummy_setting")
def dummy():
pass
func = self._get_user_function(app)
assert_json(self, func, {
"scriptFile": "function_app.py",
"bindings": [
{
"direction": BindingDirection.OUT,
"type": SQL,
"name": "out",
"commandText": "dummy_table",
"connectionStringSetting": "dummy_setting"
},
{
"direction": BindingDirection.IN,
"type": SQL,
"name": "in",
"commandText": "dummy_query",
"connectionStringSetting": "dummy_setting",
"commandType": "Text"
},
{
"direction": BindingDirection.IN,
"type": SQL_TRIGGER,
"name": "trigger",
"tableName": "dummy_table",
"connectionStringSetting": "dummy_setting"
}
]
})
def test_sql_full_args(self):
app = self.func_app
@app.sql_trigger(arg_name="trigger",
table_name="dummy_table",
connection_string_setting="dummy_setting",
data_type=DataType.STRING,
dummy_field="dummy")
@app.sql_input(arg_name="in",
command_text="dummy_query",
connection_string_setting="dummy_setting",
command_type="Text",
parameters="dummy_parameters",
data_type=DataType.STRING,
dummy_field="dummy")
@app.sql_output(arg_name="out",
command_text="dummy_table",
connection_string_setting="dummy_setting",
data_type=DataType.STRING,
dummy_field="dummy")
def dummy():
pass
func = self._get_user_function(app)
assert_json(self, func, {
"scriptFile": "function_app.py",
"bindings": [
{
"direction": BindingDirection.OUT,
'dummyField': 'dummy',
"dataType": DataType.STRING,
"type": SQL,
"name": "out",
"commandText": "dummy_table",
"connectionStringSetting": "dummy_setting"
},
{
"direction": BindingDirection.IN,
'dummyField': 'dummy',
"dataType": DataType.STRING,
"type": SQL,
"name": "in",
"commandText": "dummy_query",
"connectionStringSetting": "dummy_setting",
"parameters": "dummy_parameters",
"commandType": "Text"
},
{
"direction": BindingDirection.IN,
'dummyField': 'dummy',
"dataType": DataType.STRING,
"type": SQL_TRIGGER,
"name": "trigger",
"tableName": "dummy_table",
"connectionStringSetting": "dummy_setting"
}
]
})
def test_sql_trigger(self):
app = self.func_app
@app.sql_trigger(arg_name="trigger",
table_name="dummy_table",
connection_string_setting="dummy_setting")
def dummy():
pass
func = self._get_user_function(app)
self.assertEqual(len(func.get_bindings()), 1)
output = func.get_bindings()[0]
self.assertEqual(output.get_dict_repr(), {
"direction": BindingDirection.IN,
"type": SQL_TRIGGER,
"name": "trigger",
"tableName": "dummy_table",
"connectionStringSetting": "dummy_setting"
})
def test_sql_input_binding(self):
app = self.func_app
@app.sql_trigger(arg_name="trigger",
table_name="dummy_table",
connection_string_setting="dummy_setting")
@app.sql_input(arg_name="in",
command_text="dummy_query",
connection_string_setting="dummy_setting")
def dummy():
pass
func = self._get_user_function(app)
self.assertEqual(len(func.get_bindings()), 2)
output = func.get_bindings()[0]
self.assertEqual(output.get_dict_repr(), {
"direction": BindingDirection.IN,
"type": SQL,
"name": "in",
"commandText": "dummy_query",
"connectionStringSetting": "dummy_setting",
"commandType": "Text"
})
def test_sql_output_binding(self):
app = self.func_app
@app.sql_trigger(arg_name="trigger",
table_name="dummy_table",
connection_string_setting="dummy_setting")
@app.sql_output(arg_name="out",
command_text="dummy_table",
connection_string_setting="dummy_setting")
def dummy():
pass
func = self._get_user_function(app)
self.assertEqual(len(func.get_bindings()), 2)
output = func.get_bindings()[0]
self.assertEqual(output.get_dict_repr(), {
"direction": BindingDirection.OUT,
"type": SQL,
"name": "out",
"commandText": "dummy_table",
"connectionStringSetting": "dummy_setting",
})
def test_function_app_full_bindings_metadata_key_order(self):
app = self.func_app

Просмотреть файл

@ -0,0 +1,60 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
import unittest
from azure.functions.decorators.constants import SQL_TRIGGER, SQL
from azure.functions.decorators.core import BindingDirection, DataType
from azure.functions.decorators.sql import SqlTrigger, \
SqlInput, SqlOutput
class TestSql(unittest.TestCase):
def test_sql_trigger_valid_creation(self):
trigger = SqlTrigger(name="req",
table_name="dummy_table",
connection_string_setting="dummy_setting",
data_type=DataType.UNDEFINED,
dummy_field="dummy")
self.assertEqual(trigger.get_binding_name(), "sqlTrigger")
self.assertEqual(trigger.get_dict_repr(),
{"connectionStringSetting": "dummy_setting",
"dataType": DataType.UNDEFINED,
"tableName": "dummy_table",
"direction": BindingDirection.IN,
"dummyField": "dummy",
"name": "req",
"type": SQL_TRIGGER})
def test_sql_output_valid_creation(self):
output = SqlOutput(name="req",
command_text="dummy_table",
connection_string_setting="dummy_setting",
data_type=DataType.UNDEFINED,
dummy_field="dummy")
self.assertEqual(output.get_binding_name(), "sql")
self.assertEqual(output.get_dict_repr(),
{"commandText": "dummy_table",
"connectionStringSetting": "dummy_setting",
"dataType": DataType.UNDEFINED,
"direction": BindingDirection.OUT,
"dummyField": "dummy",
"name": "req",
"type": SQL})
def test_sql_input_valid_creation(self):
input = SqlInput(name="req",
command_text="dummy_query",
connection_string_setting="dummy_setting",
data_type=DataType.UNDEFINED,
dummy_field="dummy")
self.assertEqual(input.get_binding_name(), "sql")
self.assertEqual(input.get_dict_repr(),
{"commandText": "dummy_query",
"connectionStringSetting": "dummy_setting",
"commandType": "Text",
"dataType": DataType.UNDEFINED,
"direction": BindingDirection.IN,
"dummyField": "dummy",
"name": "req",
"type": SQL})