Merge remote-tracking branch 'origin/release/1.21.1'
This commit is contained in:
Коммит
dd4fac4db0
|
@ -1,5 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e -x
|
||||
|
||||
python -m pip install -U -e .[dev]
|
|
@ -1,7 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e -x
|
||||
|
||||
coverage run --branch -m pytest tests
|
||||
coverage xml
|
||||
coverage erase
|
|
@ -1,38 +0,0 @@
|
|||
name: Unittest pipeline
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
pull_request:
|
||||
branches: [ dev ]
|
||||
schedule:
|
||||
# Monday to Thursday 1 AM PDT build
|
||||
- cron: "0 8 * * 1,2,3,4"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python_version: [3.7, 3.8, 3.9, "3.10", "3.11"]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python ${{ matrix.python_version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python_version }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install -U -e .[dev]
|
||||
- name: Test with pytest
|
||||
run: |
|
||||
python -m pytest --cache-clear --cov=./azure --cov-report=xml --cov-branch tests
|
||||
- name: Codecov
|
||||
if: ${{ matrix.python-version }} == 3.9
|
||||
uses: codecov/codecov-action@v2
|
||||
with:
|
||||
file: ./coverage.xml
|
||||
flags: unittests
|
||||
name: codecov
|
||||
fail_ci_if_error: false
|
|
@ -1,6 +1,6 @@
|
|||
name: "Pull Request Labeler"
|
||||
on:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
paths:
|
||||
- '**/__init__.py'
|
||||
jobs:
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
name: "PR Title Enforcer"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- synchronize
|
||||
|
||||
permissions:
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
main:
|
||||
name: Validate PR title
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@ -9,4 +9,5 @@
|
|||
# AZURE FUNCTIONS TEAM
|
||||
# For all file changes, github would automatically include the following people in the PRs.
|
||||
#
|
||||
* @vrdmr @gavin-aguiar @YunchuWang @pdthummar @hallvictoria
|
||||
|
||||
* @vrdmr @gavin-aguiar @YunchuWang @pdthummar @hallvictoria
|
||||
|
|
|
@ -10,7 +10,7 @@ from .decorators import (FunctionApp, Function, Blueprint,
|
|||
DecoratorApi, DataType, AuthLevel,
|
||||
Cardinality, AccessRights, HttpMethod,
|
||||
AsgiFunctionApp, WsgiFunctionApp,
|
||||
ExternalHttpFunctionApp)
|
||||
ExternalHttpFunctionApp, BlobSource)
|
||||
from ._durable_functions import OrchestrationContext, EntityContext
|
||||
from .decorators.function_app import (FunctionRegister, TriggerApi,
|
||||
BindingApi, SettingsApi)
|
||||
|
@ -94,7 +94,8 @@ __all__ = (
|
|||
'AuthLevel',
|
||||
'Cardinality',
|
||||
'AccessRights',
|
||||
'HttpMethod'
|
||||
'HttpMethod',
|
||||
'BlobSource'
|
||||
)
|
||||
|
||||
__version__ = '1.19.0b3'
|
||||
__version__ = '1.21.1'
|
||||
|
|
|
@ -5,6 +5,7 @@ from typing import Dict, List, Tuple, Optional, Any, Union
|
|||
import logging
|
||||
import asyncio
|
||||
from asyncio import Event, Queue
|
||||
from urllib.parse import ParseResult, urlparse
|
||||
from warnings import warn
|
||||
from wsgiref.headers import Headers
|
||||
|
||||
|
@ -22,6 +23,8 @@ class AsgiRequest(WsgiRequest):
|
|||
self.asgi_version = ASGI_VERSION
|
||||
self.asgi_spec_version = ASGI_SPEC_VERSION
|
||||
self._headers = func_req.headers
|
||||
url: ParseResult = urlparse(func_req.url)
|
||||
self.asgi_url_scheme = url.scheme
|
||||
super().__init__(func_req, func_ctx)
|
||||
|
||||
def _get_encoded_http_headers(self) -> List[Tuple[bytes, bytes]]:
|
||||
|
@ -49,7 +52,7 @@ class AsgiRequest(WsgiRequest):
|
|||
"asgi.spec_version": self.asgi_spec_version,
|
||||
"http_version": "1.1",
|
||||
"method": self.request_method,
|
||||
"scheme": "https",
|
||||
"scheme": self.asgi_url_scheme,
|
||||
"path": self.path_info,
|
||||
"raw_path": _raw_path,
|
||||
"query_string": _query_string,
|
||||
|
|
|
@ -29,8 +29,11 @@ class WsgiRequest:
|
|||
# Implement interfaces for PEP 3333 environ
|
||||
self.request_method = getattr(func_req, 'method', None)
|
||||
self.script_name = ''
|
||||
self.path_info = unquote_to_bytes(
|
||||
getattr(url, 'path', None)).decode('latin-1') # type: ignore
|
||||
self.path_info = (
|
||||
unquote_to_bytes(getattr(url, 'path', None)) # type: ignore
|
||||
.decode('latin-1' if type(self) is WsgiRequest else 'utf-8')
|
||||
)
|
||||
|
||||
self.query_string = getattr(url, 'query', None)
|
||||
self.content_type = self._lowercased_headers.get('content-type')
|
||||
self.content_length = str(len(func_req_body))
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
from .core import Cardinality, AccessRights
|
||||
from .function_app import FunctionApp, Function, DecoratorApi, DataType, \
|
||||
AuthLevel, Blueprint, ExternalHttpFunctionApp, AsgiFunctionApp, \
|
||||
WsgiFunctionApp, FunctionRegister, TriggerApi, BindingApi, SettingsApi
|
||||
WsgiFunctionApp, FunctionRegister, TriggerApi, BindingApi, \
|
||||
SettingsApi, BlobSource
|
||||
from .http import HttpMethod
|
||||
|
||||
__all__ = [
|
||||
|
@ -22,5 +23,6 @@ __all__ = [
|
|||
'AuthLevel',
|
||||
'Cardinality',
|
||||
'AccessRights',
|
||||
'HttpMethod'
|
||||
'HttpMethod',
|
||||
'BlobSource'
|
||||
]
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
from typing import Optional
|
||||
|
||||
from azure.functions.decorators.constants import BLOB_TRIGGER, BLOB
|
||||
from azure.functions.decorators.core import Trigger, OutputBinding, DataType, \
|
||||
InputBinding
|
||||
from azure.functions.decorators.core import BlobSource, Trigger, \
|
||||
OutputBinding, DataType, InputBinding
|
||||
|
||||
|
||||
class BlobTrigger(Trigger):
|
||||
|
@ -12,10 +12,12 @@ class BlobTrigger(Trigger):
|
|||
name: str,
|
||||
path: str,
|
||||
connection: str,
|
||||
source: Optional[BlobSource] = None,
|
||||
data_type: Optional[DataType] = None,
|
||||
**kwargs):
|
||||
self.path = path
|
||||
self.connection = connection
|
||||
self.source = source.value if source else None
|
||||
super().__init__(name=name, data_type=data_type)
|
||||
|
||||
@staticmethod
|
||||
|
|
|
@ -8,6 +8,8 @@ EVENT_HUB_TRIGGER = "eventHubTrigger"
|
|||
EVENT_HUB = "eventHub"
|
||||
HTTP_TRIGGER = "httpTrigger"
|
||||
HTTP_OUTPUT = "http"
|
||||
KAFKA = "kafka"
|
||||
KAFKA_TRIGGER = "kafkaTrigger"
|
||||
QUEUE = "queue"
|
||||
QUEUE_TRIGGER = "queueTrigger"
|
||||
SERVICE_BUS = "serviceBus"
|
||||
|
@ -28,5 +30,16 @@ DAPR_STATE = "daprState"
|
|||
DAPR_SECRET = "daprSecret"
|
||||
DAPR_PUBLISH = "daprPublish"
|
||||
DAPR_INVOKE = "daprInvoke"
|
||||
DAPR_PUBLISH = "daprPublish"
|
||||
DAPR_BINDING = "daprBinding"
|
||||
ORCHESTRATION_TRIGGER = "orchestrationTrigger"
|
||||
ACTIVITY_TRIGGER = "activityTrigger"
|
||||
ENTITY_TRIGGER = "entityTrigger"
|
||||
DURABLE_CLIENT = "durableClient"
|
||||
ASSISTANT_SKILL_TRIGGER = "assistantSkillTrigger"
|
||||
TEXT_COMPLETION = "textCompletion"
|
||||
ASSISTANT_QUERY = "assistantQuery"
|
||||
EMBEDDINGS = "embeddings"
|
||||
EMBEDDINGS_STORE = "embeddingsStore"
|
||||
ASSISTANT_CREATE = "assistantCreate"
|
||||
ASSISTANT_POST = "assistantPost"
|
||||
SEMANTIC_SEARCH = "semanticSearch"
|
||||
|
|
|
@ -65,6 +65,14 @@ class AccessRights(StringifyEnum):
|
|||
and all related message handling. """
|
||||
|
||||
|
||||
class BlobSource(StringifyEnum):
|
||||
"""Source of the blob trigger."""
|
||||
EVENT_GRID = "EventGrid"
|
||||
"""Event Grid is the source of the blob trigger."""
|
||||
LOGS_AND_CONTAINER_SCAN = "LogsAndContainerScan"
|
||||
"""Standard polling mechanism to detect changes in the container."""
|
||||
|
||||
|
||||
class Binding(ABC):
|
||||
"""Abstract binding class which captures common attributes and
|
||||
functions. :meth:`get_dict_repr` can auto generate the function.json for
|
||||
|
|
|
@ -27,10 +27,23 @@ class EventGridOutput(OutputBinding):
|
|||
|
||||
def __init__(self,
|
||||
name: str,
|
||||
topic_endpoint_uri: str,
|
||||
topic_key_setting: str,
|
||||
topic_endpoint_uri: Optional[str] = None,
|
||||
topic_key_setting: Optional[str] = None,
|
||||
connection: Optional[str] = None,
|
||||
data_type: Optional[DataType] = None,
|
||||
**kwargs):
|
||||
if (connection is not None and (
|
||||
topic_endpoint_uri is not None
|
||||
or topic_key_setting is not None)) or \
|
||||
(connection is None and (
|
||||
topic_endpoint_uri is None
|
||||
or topic_key_setting is None)):
|
||||
raise ValueError(
|
||||
"Specify either the 'Connection' property or both "
|
||||
"'TopicKeySetting' and 'TopicEndpointUri' properties,"
|
||||
" but not both.")
|
||||
|
||||
self.topic_endpoint_uri = topic_endpoint_uri
|
||||
self.topic_key_setting = topic_key_setting
|
||||
self.connection = connection
|
||||
super().__init__(name=name, data_type=data_type)
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,155 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License.
|
||||
from typing import Optional
|
||||
|
||||
from azure.functions.decorators.constants import KAFKA, KAFKA_TRIGGER
|
||||
from azure.functions.decorators.core import Cardinality, DataType, \
|
||||
OutputBinding, Trigger
|
||||
from .utils import StringifyEnum
|
||||
|
||||
|
||||
class BrokerAuthenticationMode(StringifyEnum):
|
||||
NOTSET = -1
|
||||
GSSAPI = 0
|
||||
PLAIN = 1
|
||||
SCRAMSHA256 = 2
|
||||
SCRAMSHA512 = 3
|
||||
|
||||
|
||||
class BrokerProtocol(StringifyEnum):
|
||||
NOTSET = -1
|
||||
PLAINTEXT = 0
|
||||
SSL = 1
|
||||
SASLPLAINTEXT = 2
|
||||
SASLSSL = 3
|
||||
|
||||
|
||||
class OAuthBearerMethod(StringifyEnum):
|
||||
DEFAULT = 0,
|
||||
OIDC = 1
|
||||
|
||||
|
||||
class KafkaOutput(OutputBinding):
|
||||
@staticmethod
|
||||
def get_binding_name() -> str:
|
||||
return KAFKA
|
||||
|
||||
def __init__(self,
|
||||
name: str,
|
||||
topic: str,
|
||||
broker_list: str,
|
||||
avro_schema: Optional[str],
|
||||
username: Optional[str],
|
||||
password: Optional[str],
|
||||
ssl_key_location: Optional[str],
|
||||
ssl_ca_location: Optional[str],
|
||||
ssl_certificate_location: Optional[str],
|
||||
ssl_key_password: Optional[str],
|
||||
schema_registry_url: Optional[str],
|
||||
schema_registry_username: Optional[str],
|
||||
schema_registry_password: Optional[str],
|
||||
o_auth_bearer_method: Optional[OAuthBearerMethod] = None,
|
||||
o_auth_bearer_client_id: Optional[str] = None,
|
||||
o_auth_bearer_client_secret: Optional[str] = None,
|
||||
o_auth_bearer_scope: Optional[str] = None,
|
||||
o_auth_bearer_token_endpoint_url: Optional[str] = None,
|
||||
o_auth_bearer_extensions: Optional[str] = None,
|
||||
max_message_bytes: int = 1_000_000,
|
||||
batch_size: int = 10_000,
|
||||
enable_idempotence: bool = False,
|
||||
message_timeout_ms: int = 300_000,
|
||||
request_timeout_ms: int = 5_000,
|
||||
max_retries: int = 2_147_483_647,
|
||||
authentication_mode: Optional[BrokerAuthenticationMode] = BrokerAuthenticationMode.NOTSET, # noqa: E501
|
||||
protocol: Optional[BrokerProtocol] = BrokerProtocol.NOTSET,
|
||||
linger_ms: int = 5,
|
||||
data_type: Optional[DataType] = None,
|
||||
**kwargs):
|
||||
self.topic = topic
|
||||
self.broker_list = broker_list
|
||||
self.avro_schema = avro_schema
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.ssl_key_location = ssl_key_location
|
||||
self.ssl_ca_location = ssl_ca_location
|
||||
self.ssl_certificate_location = ssl_certificate_location
|
||||
self.ssl_key_password = ssl_key_password
|
||||
self.schema_registry_url = schema_registry_url
|
||||
self.schema_registry_username = schema_registry_username
|
||||
self.schema_registry_password = schema_registry_password
|
||||
self.o_auth_bearer_method = o_auth_bearer_method
|
||||
self.o_auth_bearer_client_id = o_auth_bearer_client_id
|
||||
self.o_auth_bearer_client_secret = o_auth_bearer_client_secret
|
||||
self.o_auth_bearer_scope = o_auth_bearer_scope
|
||||
self.o_auth_bearer_token_endpoint_url = o_auth_bearer_token_endpoint_url # noqa: E501
|
||||
self.o_auth_bearer_extensions = o_auth_bearer_extensions
|
||||
self.max_message_bytes = max_message_bytes
|
||||
self.batch_size = batch_size
|
||||
self.enable_idempotence = enable_idempotence
|
||||
self.message_timeout_ms = message_timeout_ms
|
||||
self.request_timeout_ms = request_timeout_ms
|
||||
self.max_retries = max_retries
|
||||
self.authentication_mode = authentication_mode
|
||||
self.protocol = protocol
|
||||
self.linger_ms = linger_ms
|
||||
super().__init__(name=name, data_type=data_type)
|
||||
|
||||
|
||||
class KafkaTrigger(Trigger):
|
||||
@staticmethod
|
||||
def get_binding_name() -> str:
|
||||
return KAFKA_TRIGGER
|
||||
|
||||
def __init__(self,
|
||||
name: str,
|
||||
topic: str,
|
||||
broker_list: str,
|
||||
event_hub_connection_string: Optional[str],
|
||||
consumer_group: Optional[str],
|
||||
avro_schema: Optional[str],
|
||||
username: Optional[str],
|
||||
password: Optional[str],
|
||||
ssl_key_location: Optional[str],
|
||||
ssl_ca_location: Optional[str],
|
||||
ssl_certificate_location: Optional[str],
|
||||
ssl_key_password: Optional[str],
|
||||
schema_registry_url: Optional[str],
|
||||
schema_registry_username: Optional[str],
|
||||
schema_registry_password: Optional[str],
|
||||
o_auth_bearer_method: Optional[OAuthBearerMethod] = None,
|
||||
o_auth_bearer_client_id: Optional[str] = None,
|
||||
o_auth_bearer_client_secret: Optional[str] = None,
|
||||
o_auth_bearer_scope: Optional[str] = None,
|
||||
o_auth_bearer_token_endpoint_url: Optional[str] = None,
|
||||
o_auth_bearer_extensions: Optional[str] = None,
|
||||
authentication_mode: Optional[BrokerAuthenticationMode] = BrokerAuthenticationMode.NOTSET, # noqa: E501
|
||||
protocol: Optional[BrokerProtocol] = BrokerProtocol.NOTSET,
|
||||
cardinality: Optional[Cardinality] = Cardinality.ONE,
|
||||
lag_threshold: int = 1000,
|
||||
data_type: Optional[DataType] = None,
|
||||
**kwargs):
|
||||
self.topic = topic
|
||||
self.broker_list = broker_list
|
||||
self.event_hub_connection_string = event_hub_connection_string
|
||||
self.consumer_group = consumer_group
|
||||
self.avro_schema = avro_schema
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.ssl_key_location = ssl_key_location
|
||||
self.ssl_ca_location = ssl_ca_location
|
||||
self.ssl_certificate_location = ssl_certificate_location
|
||||
self.ssl_key_password = ssl_key_password
|
||||
self.schema_registry_url = schema_registry_url
|
||||
self.schema_registry_username = schema_registry_username
|
||||
self.schema_registry_password = schema_registry_password
|
||||
self.o_auth_bearer_method = o_auth_bearer_method
|
||||
self.o_auth_bearer_client_id = o_auth_bearer_client_id
|
||||
self.o_auth_bearer_client_secret = o_auth_bearer_client_secret
|
||||
self.o_auth_bearer_scope = o_auth_bearer_scope
|
||||
self.o_auth_bearer_token_endpoint_url = o_auth_bearer_token_endpoint_url # noqa: E501
|
||||
self.o_auth_bearer_extensions = o_auth_bearer_extensions
|
||||
self.authentication_mode = authentication_mode
|
||||
self.protocol = protocol
|
||||
self.cardinality = cardinality
|
||||
self.lag_threshold = lag_threshold
|
||||
super().__init__(name=name, data_type=data_type)
|
|
@ -0,0 +1,216 @@
|
|||
from typing import Optional
|
||||
|
||||
from azure.functions.decorators.constants import (ASSISTANT_SKILL_TRIGGER,
|
||||
TEXT_COMPLETION,
|
||||
ASSISTANT_QUERY,
|
||||
EMBEDDINGS, EMBEDDINGS_STORE,
|
||||
ASSISTANT_CREATE,
|
||||
ASSISTANT_POST,
|
||||
SEMANTIC_SEARCH)
|
||||
from azure.functions.decorators.core import Trigger, DataType, InputBinding, \
|
||||
OutputBinding
|
||||
from azure.functions.decorators.utils import StringifyEnum
|
||||
|
||||
|
||||
class InputType(StringifyEnum):
|
||||
|
||||
RawText = "raw_text",
|
||||
FilePath = "file_path"
|
||||
|
||||
|
||||
class OpenAIModels(StringifyEnum):
|
||||
DefaultChatModel = "gpt-3.5-turbo"
|
||||
DefaultEmbeddingsModel = "text-embedding-ada-002"
|
||||
|
||||
|
||||
class AssistantSkillTrigger(Trigger):
|
||||
|
||||
@staticmethod
|
||||
def get_binding_name() -> str:
|
||||
return ASSISTANT_SKILL_TRIGGER
|
||||
|
||||
def __init__(self,
|
||||
name: str,
|
||||
function_description: str,
|
||||
function_name: Optional[str] = None,
|
||||
parameter_description_json: Optional[str] = None,
|
||||
model: Optional[OpenAIModels] = OpenAIModels.DefaultChatModel,
|
||||
data_type: Optional[DataType] = None,
|
||||
**kwargs):
|
||||
self.function_description = function_description
|
||||
self.function_name = function_name
|
||||
self.parameter_description_json = parameter_description_json
|
||||
self.model = model
|
||||
super().__init__(name=name, data_type=data_type)
|
||||
|
||||
|
||||
class TextCompletionInput(InputBinding):
|
||||
|
||||
@staticmethod
|
||||
def get_binding_name() -> str:
|
||||
return TEXT_COMPLETION
|
||||
|
||||
def __init__(self,
|
||||
name: str,
|
||||
prompt: str,
|
||||
model: Optional[OpenAIModels] = OpenAIModels.DefaultChatModel,
|
||||
temperature: Optional[str] = "0.5",
|
||||
top_p: Optional[str] = None,
|
||||
max_tokens: Optional[str] = "100",
|
||||
data_type: Optional[DataType] = None,
|
||||
**kwargs):
|
||||
self.prompt = prompt
|
||||
self.model = model
|
||||
self.temperature = temperature
|
||||
self.top_p = top_p
|
||||
self.max_tokens = max_tokens
|
||||
super().__init__(name=name, data_type=data_type)
|
||||
|
||||
|
||||
class AssistantQueryInput(InputBinding):
|
||||
|
||||
@staticmethod
|
||||
def get_binding_name():
|
||||
return ASSISTANT_QUERY
|
||||
|
||||
def __init__(self,
|
||||
name: str,
|
||||
id: str,
|
||||
timestamp_utc: str,
|
||||
data_type: Optional[DataType] = None,
|
||||
**kwargs):
|
||||
self.id = id
|
||||
self.timestamp_utc = timestamp_utc
|
||||
super().__init__(name=name, data_type=data_type)
|
||||
|
||||
|
||||
class EmbeddingsInput(InputBinding):
|
||||
|
||||
@staticmethod
|
||||
def get_binding_name() -> str:
|
||||
return EMBEDDINGS
|
||||
|
||||
def __init__(self,
|
||||
name: str,
|
||||
input: str,
|
||||
input_type: InputType,
|
||||
model: Optional[str] = None,
|
||||
max_chunk_length: Optional[int] = 8 * 1024,
|
||||
max_overlap: Optional[int] = 128,
|
||||
data_type: Optional[DataType] = None,
|
||||
**kwargs):
|
||||
self.name = name
|
||||
self.input = input
|
||||
self.input_type = input_type
|
||||
self.model = model
|
||||
self.max_chunk_length = max_chunk_length
|
||||
self.max_overlap = max_overlap
|
||||
super().__init__(name=name, data_type=data_type)
|
||||
|
||||
|
||||
semantic_search_system_prompt = \
|
||||
"""You are a helpful assistant. You are responding to requests
|
||||
from a user about internal emails and documents. You can and
|
||||
should refer to the internal documents to help respond to
|
||||
requests. If a user makes a request that's not covered by the
|
||||
internal emails and documents, explain that you don't know the
|
||||
answer or that you don't have access to the information.
|
||||
|
||||
The following is a list of documents that you can refer to when
|
||||
answering questions. The documents are in the format
|
||||
[filename]: [text] and are separated by newlines. If you answer
|
||||
a question by referencing any of the documents, please cite the
|
||||
document in your answer. For example, if you answer a question
|
||||
by referencing info.txt, you should add "Reference: info.txt"
|
||||
to the end of your answer on a separate line."""
|
||||
|
||||
|
||||
class SemanticSearchInput(InputBinding):
|
||||
|
||||
@staticmethod
|
||||
def get_binding_name() -> str:
|
||||
return SEMANTIC_SEARCH
|
||||
|
||||
def __init__(self,
|
||||
name: str,
|
||||
connection_name: str,
|
||||
collection: str,
|
||||
query: Optional[str] = None,
|
||||
embeddings_model: Optional[
|
||||
OpenAIModels] = OpenAIModels.DefaultEmbeddingsModel,
|
||||
chat_model: Optional[
|
||||
OpenAIModels] = OpenAIModels.DefaultChatModel,
|
||||
system_prompt: Optional[str] = semantic_search_system_prompt,
|
||||
max_knowledge_count: Optional[int] = 1,
|
||||
data_type: Optional[DataType] = None,
|
||||
**kwargs):
|
||||
self.name = name
|
||||
self.connection_name = connection_name
|
||||
self.collection = collection
|
||||
self.query = query
|
||||
self.embeddings_model = embeddings_model
|
||||
self.chat_model = chat_model
|
||||
self.system_prompt = system_prompt
|
||||
self.max_knowledge_count = max_knowledge_count
|
||||
super().__init__(name=name, data_type=data_type)
|
||||
|
||||
|
||||
class AssistantPostInput(InputBinding):
|
||||
|
||||
@staticmethod
|
||||
def get_binding_name():
|
||||
return ASSISTANT_POST
|
||||
|
||||
def __init__(self, name: str,
|
||||
id: str,
|
||||
user_message: str,
|
||||
model: Optional[str] = None,
|
||||
data_type: Optional[DataType] = None,
|
||||
**kwargs):
|
||||
self.name = name
|
||||
self.id = id
|
||||
self.user_message = user_message
|
||||
self.model = model
|
||||
super().__init__(name=name, data_type=data_type)
|
||||
|
||||
|
||||
class EmbeddingsStoreOutput(OutputBinding):
|
||||
|
||||
@staticmethod
|
||||
def get_binding_name() -> str:
|
||||
return EMBEDDINGS_STORE
|
||||
|
||||
def __init__(self,
|
||||
name: str,
|
||||
input: str,
|
||||
input_type: InputType,
|
||||
connection_name: str,
|
||||
collection: str,
|
||||
model: Optional[
|
||||
OpenAIModels] = OpenAIModels.DefaultEmbeddingsModel,
|
||||
max_chunk_length: Optional[int] = 8 * 1024,
|
||||
max_overlap: Optional[int] = 128,
|
||||
data_type: Optional[DataType] = None,
|
||||
**kwargs):
|
||||
self.name = name
|
||||
self.input = input
|
||||
self.input_type = input_type
|
||||
self.connection_name = connection_name
|
||||
self.collection = collection
|
||||
self.model = model
|
||||
self.max_chunk_length = max_chunk_length
|
||||
self.max_overlap = max_overlap
|
||||
super().__init__(name=name, data_type=data_type)
|
||||
|
||||
|
||||
class AssistantCreateOutput(OutputBinding):
|
||||
|
||||
@staticmethod
|
||||
def get_binding_name():
|
||||
return ASSISTANT_CREATE
|
||||
|
||||
def __init__(self,
|
||||
name: str,
|
||||
data_type: Optional[DataType] = None,
|
||||
**kwargs):
|
||||
super().__init__(name=name, data_type=data_type)
|
|
@ -62,12 +62,13 @@ class AppExtensionBase(metaclass=ExtensionMeta):
|
|||
# DO NOT decorate this with @abc.abstractstatismethod
|
||||
# since implementation by subclass is not mandatory
|
||||
@classmethod
|
||||
def pre_invocation_app_level(cls,
|
||||
logger: Logger,
|
||||
context: Context,
|
||||
func_args: typing.Dict[str, object] = {},
|
||||
*args,
|
||||
**kwargs) -> None:
|
||||
def pre_invocation_app_level(
|
||||
cls,
|
||||
logger: Logger,
|
||||
context: Context,
|
||||
func_args: typing.Optional[typing.Dict[str, object]] = None,
|
||||
*args,
|
||||
**kwargs) -> None:
|
||||
"""This must be implemented as a @staticmethod. It will be called right
|
||||
before a customer's function is being executed.
|
||||
|
||||
|
@ -90,13 +91,14 @@ class AppExtensionBase(metaclass=ExtensionMeta):
|
|||
# DO NOT decorate this with @abc.abstractstatismethod
|
||||
# since implementation by subclass is not mandatory
|
||||
@classmethod
|
||||
def post_invocation_app_level(cls,
|
||||
logger: Logger,
|
||||
context: Context,
|
||||
func_args: typing.Dict[str, object] = {},
|
||||
func_ret: typing.Optional[object] = None,
|
||||
*args,
|
||||
**kwargs) -> None:
|
||||
def post_invocation_app_level(
|
||||
cls,
|
||||
logger: Logger,
|
||||
context: Context,
|
||||
func_args: typing.Optional[typing.Dict[str, object]] = None,
|
||||
func_ret: typing.Optional[object] = None,
|
||||
*args,
|
||||
**kwargs) -> None:
|
||||
"""This must be implemented as a @staticmethod. It will be called right
|
||||
after a customer's function is being executed.
|
||||
|
||||
|
|
|
@ -86,12 +86,13 @@ class FuncExtensionBase(metaclass=ExtensionMeta):
|
|||
|
||||
# DO NOT decorate this with @abc.abstractmethod
|
||||
# since implementation by subclass is not mandatory
|
||||
def pre_invocation(self,
|
||||
logger: Logger,
|
||||
context: Context,
|
||||
func_args: typing.Dict[str, object] = {},
|
||||
*args,
|
||||
**kwargs) -> None:
|
||||
def pre_invocation(
|
||||
self,
|
||||
logger: Logger,
|
||||
context: Context,
|
||||
func_args: typing.Optional[typing.Dict[str, object]] = None,
|
||||
*args,
|
||||
**kwargs) -> None:
|
||||
"""This hook will be called right before customer's function
|
||||
is being executed.
|
||||
|
||||
|
@ -113,13 +114,14 @@ class FuncExtensionBase(metaclass=ExtensionMeta):
|
|||
|
||||
# DO NOT decorate this with @abc.abstractmethod
|
||||
# since implementation by subclass is not mandatory
|
||||
def post_invocation(self,
|
||||
logger: Logger,
|
||||
context: Context,
|
||||
func_args: typing.Dict[str, object] = {},
|
||||
func_ret: typing.Optional[object] = None,
|
||||
*args,
|
||||
**kwargs) -> None:
|
||||
def post_invocation(
|
||||
self,
|
||||
logger: Logger,
|
||||
context: Context,
|
||||
func_args: typing.Optional[typing.Dict[str, object]] = None,
|
||||
func_ret: typing.Optional[object] = None,
|
||||
*args,
|
||||
**kwargs) -> None:
|
||||
"""This hook will be called right after a customer's function
|
||||
is executed.
|
||||
|
||||
|
|
|
@ -10,11 +10,12 @@ from . import meta
|
|||
|
||||
class TimerRequest(azf_abc.TimerRequest):
|
||||
|
||||
def __init__(self, *, past_due: bool = False, schedule_status: dict = {},
|
||||
schedule: dict = {}) -> None:
|
||||
def __init__(self, *, past_due: bool = False,
|
||||
schedule_status: typing.Optional[dict] = None,
|
||||
schedule: typing.Optional[dict] = None) -> None:
|
||||
self.__past_due = past_due
|
||||
self.__schedule_status = schedule_status
|
||||
self.__schedule = schedule
|
||||
self.__schedule_status = schedule_status if schedule_status else {}
|
||||
self.__schedule = schedule if schedule else {}
|
||||
|
||||
@property
|
||||
def past_due(self) -> bool:
|
||||
|
|
|
@ -4,7 +4,7 @@ from abc import ABC
|
|||
from typing import Callable, Dict, List, Optional, Union, Iterable
|
||||
|
||||
from azure.functions import AsgiMiddleware, WsgiMiddleware
|
||||
from azure.functions.decorators.core import Binding, Trigger, DataType, \
|
||||
from azure.functions.decorators.core import Binding, BlobSource, Trigger, DataType, \
|
||||
AuthLevel, Cardinality, AccessRights, Setting
|
||||
from azure.functions.decorators.function_app import FunctionBuilder, SettingsApi
|
||||
from azure.functions.decorators.http import HttpMethod
|
||||
|
@ -495,6 +495,7 @@ class TriggerApi(DecoratorApi, ABC):
|
|||
arg_name: str,
|
||||
path: str,
|
||||
connection: str,
|
||||
source: Optional[BlobSource] = None,
|
||||
data_type: Optional[DataType] = None,
|
||||
**kwargs) -> Callable:
|
||||
"""
|
||||
|
@ -512,6 +513,12 @@ class TriggerApi(DecoratorApi, ABC):
|
|||
:param path: The path to the blob.
|
||||
:param connection: The name of an app setting or setting collection
|
||||
that specifies how to connect to Azure Blobs.
|
||||
:param source: Sets the source of the triggering event.
|
||||
Use EventGrid for an Event Grid-based blob trigger,
|
||||
which provides much lower latency.
|
||||
The default is LogsAndContainerScan,
|
||||
which uses the standard polling mechanism to detect changes
|
||||
in the container.
|
||||
:param data_type: Defines how Functions runtime should treat the
|
||||
parameter value.
|
||||
:param kwargs: Keyword arguments for specifying additional binding
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
trigger:
|
||||
branches:
|
||||
include:
|
||||
- dev
|
||||
- release/*
|
||||
|
||||
resources:
|
||||
repositories:
|
||||
- repository: eng
|
||||
type: git
|
||||
name: engineering
|
||||
ref: refs/tags/release
|
||||
|
||||
variables:
|
||||
- template: ci/variables/cfs.yml@eng
|
||||
|
||||
extends:
|
||||
template: ci/code-mirror.yml@eng
|
|
@ -0,0 +1,50 @@
|
|||
trigger:
|
||||
batch: true
|
||||
branches:
|
||||
include:
|
||||
- dev
|
||||
- release/*
|
||||
|
||||
# CI only, does not trigger on PRs.
|
||||
pr: none
|
||||
|
||||
schedules:
|
||||
- cron: '0 0 * * MON'
|
||||
displayName: At 12:00 AM, only on Monday
|
||||
branches:
|
||||
include:
|
||||
- dev
|
||||
always: true
|
||||
|
||||
resources:
|
||||
repositories:
|
||||
- repository: 1es
|
||||
type: git
|
||||
name: 1ESPipelineTemplates/1ESPipelineTemplates
|
||||
ref: refs/tags/release
|
||||
- repository: eng
|
||||
type: git
|
||||
name: engineering
|
||||
ref: refs/tags/release
|
||||
|
||||
variables:
|
||||
- template: ci/variables/build.yml@eng
|
||||
- template: ci/variables/cfs.yml@eng
|
||||
|
||||
extends:
|
||||
template: v1/1ES.Official.PipelineTemplate.yml@1es
|
||||
parameters:
|
||||
pool:
|
||||
name: 1es-pool-azfunc
|
||||
image: 1es-windows-2022
|
||||
os: windows
|
||||
|
||||
stages:
|
||||
- stage: Build
|
||||
jobs:
|
||||
- template: /eng/templates/official/jobs/build-artifacts.yml@self
|
||||
|
||||
- stage: RunTests
|
||||
dependsOn: Build
|
||||
jobs:
|
||||
- template: /eng/templates/jobs/ci-tests.yml@self
|
|
@ -0,0 +1,50 @@
|
|||
trigger:
|
||||
batch: true
|
||||
branches:
|
||||
include:
|
||||
- dev
|
||||
|
||||
pr:
|
||||
branches:
|
||||
include:
|
||||
- dev
|
||||
|
||||
schedules:
|
||||
- cron: '0 0 * * MON'
|
||||
displayName: At 12:00 AM, only on Monday
|
||||
branches:
|
||||
include:
|
||||
- dev
|
||||
always: true
|
||||
|
||||
resources:
|
||||
repositories:
|
||||
- repository: 1es
|
||||
type: git
|
||||
name: 1ESPipelineTemplates/1ESPipelineTemplates
|
||||
ref: refs/tags/release
|
||||
|
||||
extends:
|
||||
template: v1/1ES.Unofficial.PipelineTemplate.yml@1es
|
||||
parameters:
|
||||
pool:
|
||||
name: 1es-pool-azfunc-public
|
||||
image: 1es-windows-2022
|
||||
os: windows
|
||||
sdl:
|
||||
codeql:
|
||||
compiled:
|
||||
enabled: true # still only runs for default branch
|
||||
runSourceLanguagesInSourceAnalysis: true
|
||||
settings:
|
||||
skipBuildTagsForGitHubPullRequests: ${{ variables['System.PullRequest.IsFork'] }}
|
||||
|
||||
stages:
|
||||
- stage: Build
|
||||
jobs:
|
||||
- template: /eng/templates/jobs/build.yml@self
|
||||
|
||||
- stage: RunTests
|
||||
dependsOn: Build
|
||||
jobs:
|
||||
- template: /eng/templates/jobs/ci-tests.yml@self
|
|
@ -0,0 +1,21 @@
|
|||
jobs:
|
||||
- job: "Build"
|
||||
displayName: 'Build Python SDK'
|
||||
|
||||
pool:
|
||||
name: 1es-pool-azfunc
|
||||
image: 1es-ubuntu-22.04
|
||||
os: linux
|
||||
|
||||
steps:
|
||||
- task: UsePythonVersion@0
|
||||
inputs:
|
||||
versionSpec: "3.11"
|
||||
- bash: |
|
||||
python --version
|
||||
displayName: 'Check python version'
|
||||
- bash: |
|
||||
python -m pip install -U pip
|
||||
pip install twine wheel
|
||||
python setup.py sdist bdist_wheel
|
||||
displayName: 'Build Python SDK'
|
|
@ -0,0 +1,27 @@
|
|||
jobs:
|
||||
- job: "TestPython"
|
||||
displayName: "Run Python SDK Unit Tests"
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
python-37:
|
||||
PYTHON_VERSION: '3.7'
|
||||
python-38:
|
||||
PYTHON_VERSION: '3.8'
|
||||
python-39:
|
||||
PYTHON_VERSION: '3.9'
|
||||
python-310:
|
||||
PYTHON_VERSION: '3.10'
|
||||
python-311:
|
||||
PYTHON_VERSION: '3.11'
|
||||
steps:
|
||||
- task: UsePythonVersion@0
|
||||
inputs:
|
||||
versionSpec: $(PYTHON_VERSION)
|
||||
- bash: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install -U -e .[dev]
|
||||
displayName: 'Install dependencies'
|
||||
- bash: |
|
||||
python -m pytest --cache-clear --cov=./azure --cov-report=xml --cov-branch tests
|
||||
displayName: 'Test with pytest'
|
|
@ -0,0 +1,16 @@
|
|||
jobs:
|
||||
- job: "Build"
|
||||
displayName: 'Build Python SDK'
|
||||
|
||||
steps:
|
||||
- task: UsePythonVersion@0
|
||||
inputs:
|
||||
versionSpec: "3.11"
|
||||
- bash: |
|
||||
python --version
|
||||
displayName: 'Check python version'
|
||||
- bash: |
|
||||
python -m pip install -U pip
|
||||
pip install twine wheel
|
||||
python setup.py sdist bdist_wheel
|
||||
displayName: 'Build Python SDK'
|
|
@ -0,0 +1,27 @@
|
|||
jobs:
|
||||
- job: "TestPython"
|
||||
displayName: "Run Python SDK Unit Tests"
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
python-37:
|
||||
PYTHON_VERSION: '3.7'
|
||||
python-38:
|
||||
PYTHON_VERSION: '3.8'
|
||||
python-39:
|
||||
PYTHON_VERSION: '3.9'
|
||||
python-310:
|
||||
PYTHON_VERSION: '3.10'
|
||||
python-311:
|
||||
PYTHON_VERSION: '3.11'
|
||||
steps:
|
||||
- task: UsePythonVersion@0
|
||||
inputs:
|
||||
versionSpec: $(PYTHON_VERSION)
|
||||
- bash: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install -U -e .[dev]
|
||||
displayName: 'Install dependencies'
|
||||
- bash: |
|
||||
python -m pytest --cache-clear --cov=./azure --cov-report=xml --cov-branch tests
|
||||
displayName: 'Test with pytest'
|
|
@ -0,0 +1,28 @@
|
|||
jobs:
|
||||
- job: "Build"
|
||||
displayName: 'Build Python SDK'
|
||||
|
||||
pool:
|
||||
name: 1es-pool-azfunc
|
||||
image: 1es-ubuntu-22.04
|
||||
os: linux
|
||||
|
||||
templateContext:
|
||||
outputParentDirectory: $(Build.ArtifactStagingDirectory)
|
||||
outputs:
|
||||
- output: pipelineArtifact
|
||||
targetPath: $(Build.SourcesDirectory)
|
||||
artifactName: "azure-functions"
|
||||
|
||||
steps:
|
||||
- task: UsePythonVersion@0
|
||||
inputs:
|
||||
versionSpec: "3.11"
|
||||
- bash: |
|
||||
python --version
|
||||
displayName: 'Check python version'
|
||||
- bash: |
|
||||
python -m pip install -U pip
|
||||
pip install twine wheel
|
||||
python setup.py sdist bdist_wheel
|
||||
displayName: 'Build Python SDK'
|
3
setup.py
3
setup.py
|
@ -12,7 +12,8 @@ EXTRA_REQUIRES = {
|
|||
'pytest',
|
||||
'pytest-cov',
|
||||
'requests==2.*',
|
||||
'coverage'
|
||||
'coverage',
|
||||
'azure-functions-durable'
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
@ -3,11 +3,12 @@
|
|||
import unittest
|
||||
|
||||
from azure.functions.decorators.blob import BlobTrigger, BlobOutput, BlobInput
|
||||
from azure.functions.decorators.core import BindingDirection, DataType
|
||||
from azure.functions.decorators.core import BindingDirection, BlobSource, \
|
||||
DataType
|
||||
|
||||
|
||||
class TestBlob(unittest.TestCase):
|
||||
def test_blob_trigger_valid_creation(self):
|
||||
def test_blob_trigger_creation_with_no_source(self):
|
||||
trigger = BlobTrigger(name="req",
|
||||
path="dummy_path",
|
||||
connection="dummy_connection",
|
||||
|
@ -25,6 +26,66 @@ class TestBlob(unittest.TestCase):
|
|||
"connection": "dummy_connection"
|
||||
})
|
||||
|
||||
def test_blob_trigger_creation_with_default_specified_source(self):
|
||||
trigger = BlobTrigger(name="req",
|
||||
path="dummy_path",
|
||||
connection="dummy_connection",
|
||||
source=BlobSource.LOGS_AND_CONTAINER_SCAN,
|
||||
data_type=DataType.UNDEFINED,
|
||||
dummy_field="dummy")
|
||||
|
||||
self.assertEqual(trigger.get_binding_name(), "blobTrigger")
|
||||
self.assertEqual(trigger.get_dict_repr(), {
|
||||
"type": "blobTrigger",
|
||||
"direction": BindingDirection.IN,
|
||||
'dummyField': 'dummy',
|
||||
"name": "req",
|
||||
"dataType": DataType.UNDEFINED,
|
||||
"path": "dummy_path",
|
||||
'source': 'LogsAndContainerScan',
|
||||
"connection": "dummy_connection"
|
||||
})
|
||||
|
||||
def test_blob_trigger_creation_with_source_as_string(self):
|
||||
trigger = BlobTrigger(name="req",
|
||||
path="dummy_path",
|
||||
connection="dummy_connection",
|
||||
source=BlobSource.EVENT_GRID,
|
||||
data_type=DataType.UNDEFINED,
|
||||
dummy_field="dummy")
|
||||
|
||||
self.assertEqual(trigger.get_binding_name(), "blobTrigger")
|
||||
self.assertEqual(trigger.get_dict_repr(), {
|
||||
"type": "blobTrigger",
|
||||
"direction": BindingDirection.IN,
|
||||
'dummyField': 'dummy',
|
||||
"name": "req",
|
||||
"dataType": DataType.UNDEFINED,
|
||||
"path": "dummy_path",
|
||||
'source': 'EventGrid',
|
||||
"connection": "dummy_connection"
|
||||
})
|
||||
|
||||
def test_blob_trigger_creation_with_source_as_enum(self):
|
||||
trigger = BlobTrigger(name="req",
|
||||
path="dummy_path",
|
||||
connection="dummy_connection",
|
||||
source=BlobSource.EVENT_GRID,
|
||||
data_type=DataType.UNDEFINED,
|
||||
dummy_field="dummy")
|
||||
|
||||
self.assertEqual(trigger.get_binding_name(), "blobTrigger")
|
||||
self.assertEqual(trigger.get_dict_repr(), {
|
||||
"type": "blobTrigger",
|
||||
"direction": BindingDirection.IN,
|
||||
'dummyField': 'dummy',
|
||||
"name": "req",
|
||||
"dataType": DataType.UNDEFINED,
|
||||
"path": "dummy_path",
|
||||
'source': 'EventGrid',
|
||||
"connection": "dummy_connection"
|
||||
})
|
||||
|
||||
def test_blob_input_valid_creation(self):
|
||||
blob_input = BlobInput(name="res",
|
||||
path="dummy_path",
|
||||
|
|
|
@ -24,7 +24,7 @@ class TestDapr(unittest.TestCase):
|
|||
|
||||
@app.dapr_service_invocation_trigger(arg_name="req",
|
||||
method_name="dummy_method_name")
|
||||
def dummy():
|
||||
def test_dapr_service_invocation_trigger_default_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -50,7 +50,7 @@ class TestDapr(unittest.TestCase):
|
|||
|
||||
@app.dapr_binding_trigger(arg_name="req",
|
||||
binding_name="dummy_binding_name")
|
||||
def dummy():
|
||||
def test_dapr_binding_trigger_default_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -73,7 +73,7 @@ class TestDapr(unittest.TestCase):
|
|||
pub_sub_name="dummy_pub_sub_name",
|
||||
topic="dummy_topic",
|
||||
route="/dummy_route")
|
||||
def dummy():
|
||||
def test_dapr_topic_trigger_default_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -99,7 +99,7 @@ class TestDapr(unittest.TestCase):
|
|||
@app.dapr_state_input(arg_name="in",
|
||||
state_store="dummy_state_store",
|
||||
key="dummy_key")
|
||||
def dummy():
|
||||
def test_dapr_state_input_binding():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -125,7 +125,7 @@ class TestDapr(unittest.TestCase):
|
|||
secret_store_name="dummy_secret_store_name",
|
||||
key="dummy_key",
|
||||
metadata="dummy_metadata")
|
||||
def dummy():
|
||||
def test_dapr_secret_input_binding():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -151,7 +151,7 @@ class TestDapr(unittest.TestCase):
|
|||
@app.dapr_state_output(arg_name="out",
|
||||
state_store="dummy_state_store",
|
||||
key="dummy_key")
|
||||
def dummy():
|
||||
def test_dapr_state_output_binding():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -177,7 +177,7 @@ class TestDapr(unittest.TestCase):
|
|||
app_id="dummy_app_id",
|
||||
method_name="dummy_method_name",
|
||||
http_verb="dummy_http_verb")
|
||||
def dummy():
|
||||
def test_dapr_invoke_output_binding():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -203,7 +203,7 @@ class TestDapr(unittest.TestCase):
|
|||
@app.dapr_publish_output(arg_name="out",
|
||||
pub_sub_name="dummy_pub_sub_name",
|
||||
topic="dummy_topic")
|
||||
def dummy():
|
||||
def test_dapr_publish_output_binding():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -228,7 +228,7 @@ class TestDapr(unittest.TestCase):
|
|||
@app.dapr_binding_output(arg_name="out",
|
||||
binding_name="dummy_binding_name",
|
||||
operation="dummy_operation")
|
||||
def dummy():
|
||||
def test_dapr_binding_output_binding():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
|
|
@ -6,8 +6,9 @@ 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, \
|
||||
SQL, SQL_TRIGGER
|
||||
from azure.functions.decorators.core import DataType, AuthLevel, \
|
||||
SQL, SQL_TRIGGER, ORCHESTRATION_TRIGGER, ACTIVITY_TRIGGER, \
|
||||
ENTITY_TRIGGER, DURABLE_CLIENT
|
||||
from azure.functions.decorators.core import BlobSource, DataType, AuthLevel, \
|
||||
BindingDirection, AccessRights, Cardinality
|
||||
from azure.functions.decorators.function_app import FunctionApp
|
||||
from azure.functions.decorators.http import HttpTrigger, HttpMethod
|
||||
|
@ -26,11 +27,11 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
|
||||
def test_route_is_function_name(self):
|
||||
app = self.func_app
|
||||
test_func_name = "dummy_function"
|
||||
test_func_name = "test_route_is_function_name"
|
||||
|
||||
@app.function_name(test_func_name)
|
||||
@app.route()
|
||||
def dummy_func():
|
||||
def test_route_is_function_name():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -43,26 +44,28 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
app = self.func_app
|
||||
|
||||
@app.route()
|
||||
def dummy_func():
|
||||
def test_route_is_python_function_name():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
||||
self.assertEqual(func.get_function_name(), "dummy_func")
|
||||
self.assertEqual(func.get_function_name(),
|
||||
"test_route_is_python_function_name")
|
||||
self.assertTrue(isinstance(func.get_trigger(), HttpTrigger))
|
||||
self.assertTrue(func.get_trigger().route, "dummy_func")
|
||||
self.assertTrue(func.get_trigger().route,
|
||||
"test_route_is_python_function_name")
|
||||
|
||||
def test_route_is_custom(self):
|
||||
app = self.func_app
|
||||
|
||||
@app.function_name("dummy_function")
|
||||
@app.function_name("test_route_is_custom")
|
||||
@app.route("dummy")
|
||||
def dummy_func():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
||||
self.assertEqual("dummy_function", func.get_function_name())
|
||||
self.assertEqual("test_route_is_custom", func.get_function_name())
|
||||
self.assertTrue(isinstance(func.get_trigger(), HttpTrigger))
|
||||
self.assertTrue(func.get_trigger().route, "dummy")
|
||||
|
||||
|
@ -70,11 +73,12 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
app = self.func_app
|
||||
|
||||
@app.schedule(arg_name="req", schedule="dummy_schedule")
|
||||
def dummy_func():
|
||||
def test_schedule_trigger_default_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
self.assertEqual(func.get_function_name(), "dummy_func")
|
||||
self.assertEqual(func.get_function_name(),
|
||||
"test_schedule_trigger_default_args")
|
||||
assert_json(self, func, {
|
||||
"scriptFile": "function_app.py",
|
||||
"bindings": [
|
||||
|
@ -93,7 +97,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
@app.schedule(arg_name="req", schedule="dummy_schedule",
|
||||
run_on_startup=False, use_monitor=False,
|
||||
data_type=DataType.STRING, dummy_field='dummy')
|
||||
def dummy():
|
||||
def test_schedule_trigger_full_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -117,11 +121,12 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
app = self.func_app
|
||||
|
||||
@app.timer_trigger(arg_name="req", schedule="dummy_schedule")
|
||||
def dummy_func():
|
||||
def test_timer_trigger_default_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
self.assertEqual(func.get_function_name(), "dummy_func")
|
||||
self.assertEqual(func.get_function_name(),
|
||||
"test_timer_trigger_default_args")
|
||||
assert_json(self, func, {
|
||||
"scriptFile": "function_app.py",
|
||||
"bindings": [
|
||||
|
@ -140,7 +145,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
@app.timer_trigger(arg_name="req", schedule="dummy_schedule",
|
||||
run_on_startup=False, use_monitor=False,
|
||||
data_type=DataType.STRING, dummy_field='dummy')
|
||||
def dummy():
|
||||
def test_timer_trigger_full_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -160,11 +165,89 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
]
|
||||
})
|
||||
|
||||
def test_orchestration_trigger(self):
|
||||
app = self.func_app
|
||||
|
||||
@app.orchestration_trigger("context")
|
||||
def test_orchestration_trigger(context):
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
assert_json(self, func, {
|
||||
"scriptFile": "function_app.py",
|
||||
"bindings": [
|
||||
{
|
||||
"name": "context",
|
||||
"type": ORCHESTRATION_TRIGGER,
|
||||
"direction": BindingDirection.IN
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
def test_activity_trigger(self):
|
||||
app = self.func_app
|
||||
|
||||
@app.activity_trigger("arg")
|
||||
def test_activity_trigger(arg):
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
assert_json(self, func, {
|
||||
"scriptFile": "function_app.py",
|
||||
"bindings": [
|
||||
{
|
||||
"name": "arg",
|
||||
"type": ACTIVITY_TRIGGER,
|
||||
"direction": BindingDirection.IN
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
def test_entity_trigger(self):
|
||||
app = self.func_app
|
||||
|
||||
@app.entity_trigger("context")
|
||||
def test_entity_trigger(context):
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
assert_json(self, func, {
|
||||
"scriptFile": "function_app.py",
|
||||
"bindings": [
|
||||
{
|
||||
"name": "context",
|
||||
"type": ENTITY_TRIGGER,
|
||||
"direction": BindingDirection.IN,
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
def test_durable_client(self):
|
||||
app = self.func_app
|
||||
|
||||
@app.generic_trigger(arg_name="req", type=HTTP_TRIGGER)
|
||||
@app.durable_client_input(client_name="client")
|
||||
def test_durable_client(client):
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
||||
self.assertEqual(len(func.get_bindings()), 2)
|
||||
self.assertTrue(func.is_http_function())
|
||||
|
||||
output = func.get_bindings()[0]
|
||||
|
||||
self.assertEqual(output.get_dict_repr(), {
|
||||
"direction": BindingDirection.IN,
|
||||
"type": DURABLE_CLIENT,
|
||||
"name": "client"
|
||||
})
|
||||
|
||||
def test_route_default_args(self):
|
||||
app = self.func_app
|
||||
|
||||
@app.route()
|
||||
def dummy():
|
||||
def test_route_default_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -176,7 +259,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
"direction": BindingDirection.IN,
|
||||
"type": HTTP_TRIGGER,
|
||||
"name": "req",
|
||||
"route": "dummy"
|
||||
"route": "test_route_default_args"
|
||||
},
|
||||
{
|
||||
"direction": BindingDirection.OUT,
|
||||
|
@ -194,7 +277,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
auth_level=AuthLevel.FUNCTION, route='dummy_route',
|
||||
trigger_extra_fields={"dummy_field": "dummy"},
|
||||
binding_extra_fields={"dummy_field": "dummy"})
|
||||
def dummy():
|
||||
def test_route_with_all_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -225,11 +308,12 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
app = self.func_app
|
||||
|
||||
@app.warm_up_trigger(arg_name="req")
|
||||
def dummy_func():
|
||||
def test_warmup_trigger_default_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
self.assertEqual(func.get_function_name(), "dummy_func")
|
||||
self.assertEqual(func.get_function_name(),
|
||||
"test_warmup_trigger_default_args")
|
||||
assert_json(self, func, {
|
||||
"scriptFile": "function_app.py",
|
||||
"bindings": [
|
||||
|
@ -246,7 +330,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
|
||||
@app.warm_up_trigger(arg_name="req", data_type=DataType.STRING,
|
||||
dummy_field='dummy')
|
||||
def dummy():
|
||||
def test_warmup_trigger_full_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -270,7 +354,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
connection="dummy_conn")
|
||||
@app.queue_output(arg_name="out", queue_name="dummy_out_queue",
|
||||
connection="dummy_out_conn")
|
||||
def dummy():
|
||||
def test_queue_default_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -297,7 +381,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
|
||||
@app.queue_trigger(arg_name="req", queue_name="dummy_queue",
|
||||
connection="dummy_conn")
|
||||
def dummy():
|
||||
def test_queue_trigger():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -320,7 +404,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
connection="dummy_conn")
|
||||
@app.queue_output(arg_name="out", queue_name="dummy_out_queue",
|
||||
connection="dummy_out_conn")
|
||||
def dummy():
|
||||
def test_queue_output_binding():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -345,7 +429,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
@app.queue_output(arg_name="out", queue_name="dummy_out_queue",
|
||||
connection="dummy_out_conn",
|
||||
data_type=DataType.STRING, dummy_field="dummy")
|
||||
def dummy():
|
||||
def test_queue_full_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -380,7 +464,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
@app.service_bus_queue_output(arg_name='res',
|
||||
connection='dummy_out_conn',
|
||||
queue_name='dummy_out_queue')
|
||||
def dummy():
|
||||
def test_service_bus_queue_default_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -410,7 +494,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
@app.service_bus_queue_trigger(arg_name="req",
|
||||
connection="dummy_conn",
|
||||
queue_name="dummy_queue")
|
||||
def dummy():
|
||||
def test_service_bus_queue_trigger():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -435,7 +519,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
@app.service_bus_queue_output(arg_name='res',
|
||||
connection='dummy_out_conn',
|
||||
queue_name='dummy_out_queue')
|
||||
def dummy():
|
||||
def test_service_bus_queue_output_binding():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -468,7 +552,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
data_type=DataType.STREAM,
|
||||
access_rights=AccessRights.MANAGE,
|
||||
dummy_field="dummy")
|
||||
def dummy():
|
||||
def test_service_bus_queue_full_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -510,7 +594,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
@app.service_bus_topic_output(arg_name='res', connection='dummy_conn',
|
||||
topic_name='dummy_topic',
|
||||
subscription_name='dummy_sub')
|
||||
def dummy():
|
||||
def test_service_bus_topic_default_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -543,7 +627,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
connection='dummy_conn',
|
||||
topic_name='dummy_topic',
|
||||
subscription_name='dummy_sub')
|
||||
def dummy():
|
||||
def test_service_bus_topic_trigger():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -570,7 +654,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
@app.service_bus_topic_output(arg_name='res', connection='dummy_conn',
|
||||
topic_name='dummy_topic',
|
||||
subscription_name='dummy_sub')
|
||||
def dummy():
|
||||
def test_service_bus_topic_output_binding():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -605,7 +689,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
data_type=DataType.STRING,
|
||||
access_rights=AccessRights.LISTEN,
|
||||
dummy_field="dummy")
|
||||
def dummy():
|
||||
def test_service_bus_topic_full_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -648,7 +732,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
@app.event_hub_output(arg_name="res",
|
||||
event_hub_name="dummy_event_hub",
|
||||
connection="dummy_connection")
|
||||
def dummy():
|
||||
def test_event_hub_default_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -678,7 +762,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
@app.event_hub_message_trigger(arg_name="req",
|
||||
connection="dummy_connection",
|
||||
event_hub_name="dummy_event_hub")
|
||||
def dummy():
|
||||
def test_event_hub_trigger():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -703,7 +787,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
@app.event_hub_output(arg_name="res",
|
||||
event_hub_name="dummy_event_hub",
|
||||
connection="dummy_connection")
|
||||
def dummy():
|
||||
def test_event_hub_output_binding():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -734,7 +818,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
connection="dummy_connection",
|
||||
data_type=DataType.UNDEFINED,
|
||||
dummy_field="dummy")
|
||||
def dummy():
|
||||
def test_event_hub_full_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -810,7 +894,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
preferred_locations="dummy_location",
|
||||
data_type=DataType.STRING,
|
||||
dummy_field="dummy")
|
||||
def dummy():
|
||||
def test_cosmosdb_v3_full_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -927,7 +1011,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
preferred_locations="dummy_location",
|
||||
data_type=DataType.STRING,
|
||||
dummy_field="dummy")
|
||||
def dummy():
|
||||
def test_cosmosdb_full_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -1011,7 +1095,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
database_name="dummy_out_db",
|
||||
collection_name="dummy_out_collection",
|
||||
connection_string_setting="dummy_str")
|
||||
def dummy():
|
||||
def test_cosmosdb_v3_default_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -1061,7 +1145,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
database_name="dummy_out_db",
|
||||
container_name="dummy_out_container",
|
||||
connection="dummy_str")
|
||||
def dummy():
|
||||
def test_cosmosdb_default_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -1104,7 +1188,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
database_name="dummy_db",
|
||||
collection_name="dummy_collection",
|
||||
connection_string_setting="dummy_str")
|
||||
def dummy():
|
||||
def test_cosmosdb_v3_trigger():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -1128,7 +1212,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
database_name="dummy_db",
|
||||
container_name="dummy_container",
|
||||
connection="dummy_str")
|
||||
def dummy():
|
||||
def test_cosmosdb_trigger():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -1152,7 +1236,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
database_name="dummy_db",
|
||||
container_name="dummy_container",
|
||||
connection="dummy_str")
|
||||
def dummy():
|
||||
def test_not_http_function():
|
||||
pass
|
||||
|
||||
funcs = app.get_functions()
|
||||
|
@ -1171,7 +1255,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
database_name="dummy_in_db",
|
||||
collection_name="dummy_in_collection",
|
||||
connection_string_setting="dummy_str")
|
||||
def dummy():
|
||||
def test_cosmosdb_v3_input_binding():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -1200,7 +1284,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
database_name="dummy_in_db",
|
||||
container_name="dummy_in_container",
|
||||
connection="dummy_str")
|
||||
def dummy():
|
||||
def test_cosmosdb_input_binding():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -1229,7 +1313,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
database_name="dummy_out_db",
|
||||
collection_name="dummy_out_collection",
|
||||
connection_string_setting="dummy_str")
|
||||
def dummy():
|
||||
def test_cosmosdb_v3_output_binding():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -1258,7 +1342,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
database_name="dummy_out_db",
|
||||
container_name="dummy_out_container",
|
||||
connection="dummy_str")
|
||||
def dummy():
|
||||
def test_cosmosdb_output_binding():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -1284,7 +1368,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
|
||||
@app.schedule(arg_name="req1", schedule="dummy_schedule")
|
||||
@app.schedule(arg_name="req2", schedule="dummy_schedule")
|
||||
def dummy():
|
||||
def test_multiple_triggers():
|
||||
pass
|
||||
self.assertEqual(err.exception.args[0],
|
||||
"A trigger was already registered to this "
|
||||
|
@ -1299,15 +1383,15 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
with self.assertRaises(ValueError) as err:
|
||||
@app.queue_output(arg_name="out", queue_name="dummy_out_queue",
|
||||
connection="dummy_out_conn")
|
||||
def dummy():
|
||||
def test_no_trigger():
|
||||
pass
|
||||
|
||||
app.get_functions()
|
||||
|
||||
self.assertEqual(err.exception.args[0],
|
||||
"Function dummy does not have a trigger. A valid "
|
||||
"function must have one and only one trigger "
|
||||
"registered.")
|
||||
"Function test_no_trigger does not have a trigger."
|
||||
" A valid function must have one and only one"
|
||||
" trigger registered.")
|
||||
|
||||
def test_multiple_input_bindings(self):
|
||||
app = self.func_app
|
||||
|
@ -1337,7 +1421,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
arg_name="res",
|
||||
event_hub_name="dummy_event_hub",
|
||||
connection="dummy_connection")
|
||||
def dummy():
|
||||
def test_multiple_input_bindings():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -1464,7 +1548,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
connection="dummy_conn")
|
||||
@app.blob_output(arg_name="out", path="dummy_out_path",
|
||||
connection="dummy_out_conn")
|
||||
def dummy():
|
||||
def test_blob_default_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -1499,7 +1583,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
@app.blob_trigger(arg_name="req", path="dummy_path",
|
||||
data_type=DataType.STRING,
|
||||
connection="dummy_conn")
|
||||
def dummy():
|
||||
def test_blob_trigger():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -1522,11 +1606,12 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
|
||||
@app.blob_trigger(arg_name="req", path="dummy_path",
|
||||
data_type=DataType.STRING,
|
||||
source=BlobSource.EVENT_GRID,
|
||||
connection="dummy_conn")
|
||||
@app.blob_input(arg_name="file", path="dummy_in_path",
|
||||
connection="dummy_in_conn",
|
||||
data_type=DataType.STRING)
|
||||
def dummy():
|
||||
def test_blob_input_binding():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -1543,6 +1628,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
"type": BLOB_TRIGGER,
|
||||
"name": "req",
|
||||
"path": "dummy_path",
|
||||
"source": 'EventGrid',
|
||||
"connection": "dummy_conn"
|
||||
})
|
||||
|
||||
|
@ -1564,7 +1650,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
@app.blob_output(arg_name="out", path="dummy_out_path",
|
||||
connection="dummy_out_conn",
|
||||
data_type=DataType.STRING)
|
||||
def dummy():
|
||||
def test_blob_output_binding():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -1600,7 +1686,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
data_type=DataType.BINARY,
|
||||
connection="dummy_conn",
|
||||
path="dummy_path")
|
||||
def dummy():
|
||||
def test_custom_trigger():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -1629,7 +1715,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
path="dummy_in_path",
|
||||
connection="dummy_in_conn",
|
||||
data_type=DataType.STRING)
|
||||
def dummy():
|
||||
def test_custom_input_binding():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -1667,7 +1753,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
path="dummy_out_path",
|
||||
connection="dummy_out_conn",
|
||||
data_type=DataType.STRING)
|
||||
def dummy():
|
||||
def test_custom_output_binding():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -1696,7 +1782,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
app = self.func_app
|
||||
|
||||
@app.generic_trigger(arg_name="req", type=HTTP_TRIGGER)
|
||||
def dummy():
|
||||
def test_custom_http_trigger():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -1710,7 +1796,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
"direction": BindingDirection.IN,
|
||||
"type": HTTP_TRIGGER,
|
||||
"name": "req",
|
||||
"route": "dummy",
|
||||
"route": "test_custom_http_trigger",
|
||||
"authLevel": AuthLevel.FUNCTION
|
||||
})
|
||||
|
||||
|
@ -1719,7 +1805,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
|
||||
@app.generic_trigger(arg_name="req", type=QUEUE_TRIGGER,
|
||||
direction=BindingDirection.INOUT)
|
||||
def dummy():
|
||||
def test_custom_binding_with_excluded_params():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -1741,7 +1827,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
path="dummy_out_path",
|
||||
connection="dummy_out_conn",
|
||||
data_type=DataType.STRING)
|
||||
def dummy():
|
||||
def test_mixed_custom_and_supported_binding():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -1774,7 +1860,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
arg_name="res",
|
||||
topic_endpoint_uri="dummy_topic_endpoint_uri",
|
||||
topic_key_setting="dummy_topic_key_setting")
|
||||
def dummy():
|
||||
def test_event_grid_default_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -1810,7 +1896,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
data_type=DataType.UNDEFINED,
|
||||
dummy_field="dummy"
|
||||
)
|
||||
def dummy():
|
||||
def test_event_grid_full_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -1841,7 +1927,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
app = self.func_app
|
||||
|
||||
@app.event_grid_trigger(arg_name="req")
|
||||
def dummy():
|
||||
def test_event_grid_trigger():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -1863,7 +1949,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
arg_name="res",
|
||||
topic_endpoint_uri="dummy_topic_endpoint_uri",
|
||||
topic_key_setting="dummy_topic_key_setting")
|
||||
def dummy():
|
||||
def test_event_grid_output_binding():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -1889,7 +1975,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
connection="dummy_out_conn",
|
||||
row_key="dummy_key",
|
||||
partition_key="dummy_partition_key")
|
||||
def dummy():
|
||||
def test_table_default_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -1918,7 +2004,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
"type": HTTP_TRIGGER,
|
||||
"name": "req",
|
||||
"authLevel": AuthLevel.FUNCTION,
|
||||
"route": "dummy"
|
||||
"route": "test_table_default_args"
|
||||
},
|
||||
{
|
||||
"direction": BindingDirection.OUT,
|
||||
|
@ -1946,7 +2032,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
connection="dummy_out_conn",
|
||||
row_key="dummy_key",
|
||||
partition_key="dummy_partition_key")
|
||||
def dummy():
|
||||
def test_table_with_all_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -2004,7 +2090,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
take=1,
|
||||
filter="dummy_filter",
|
||||
data_type=DataType.STRING)
|
||||
def dummy():
|
||||
def test_table_input_binding():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -2035,7 +2121,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
row_key="dummy_key",
|
||||
partition_key="dummy_partition_key",
|
||||
data_type=DataType.STRING)
|
||||
def dummy():
|
||||
def test_table_output_binding():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -2067,7 +2153,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
@app.sql_output(arg_name="out",
|
||||
command_text="dummy_table",
|
||||
connection_string_setting="dummy_setting")
|
||||
def dummy():
|
||||
def test_sql_default_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -2120,7 +2206,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
connection_string_setting="dummy_setting",
|
||||
data_type=DataType.STRING,
|
||||
dummy_field="dummy")
|
||||
def dummy():
|
||||
def test_sql_full_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -2166,7 +2252,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
@app.sql_trigger(arg_name="trigger",
|
||||
table_name="dummy_table",
|
||||
connection_string_setting="dummy_setting")
|
||||
def dummy():
|
||||
def test_sql_trigger():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -2191,7 +2277,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
@app.sql_input(arg_name="in",
|
||||
command_text="dummy_query",
|
||||
connection_string_setting="dummy_setting")
|
||||
def dummy():
|
||||
def test_sql_input_binding():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -2217,7 +2303,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
@app.sql_output(arg_name="out",
|
||||
command_text="dummy_table",
|
||||
connection_string_setting="dummy_setting")
|
||||
def dummy():
|
||||
def test_sql_output_binding():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
|
@ -2251,7 +2337,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
connection="dummy_out_conn",
|
||||
row_key="dummy_key",
|
||||
partition_key="dummy_partition_key")
|
||||
def dummy():
|
||||
def test_function_app_full_bindings_metadata_key_order():
|
||||
pass
|
||||
|
||||
self._test_function_metadata_order(app)
|
||||
|
@ -2260,7 +2346,7 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
app = self.func_app
|
||||
|
||||
@app.generic_trigger(arg_name="req", type=HTTP_TRIGGER)
|
||||
def dummy():
|
||||
def test_function_app_generic_http_trigger_metadata_key_order():
|
||||
pass
|
||||
|
||||
self._test_function_metadata_order(app)
|
||||
|
@ -2278,11 +2364,12 @@ class TestFunctionsApp(unittest.TestCase):
|
|||
|
||||
@app.schedule(arg_name="req", schedule="dummy_schedule")
|
||||
@app.retry(strategy="fixed", max_retry_count="2", delay_interval="4")
|
||||
def dummy_func():
|
||||
def test_function_app_retry_default_args():
|
||||
pass
|
||||
|
||||
func = self._get_user_function(app)
|
||||
self.assertEqual(func.get_function_name(), "dummy_func")
|
||||
self.assertEqual(func.get_function_name(),
|
||||
"test_function_app_retry_default_args")
|
||||
self.assertEqual(func.get_setting("retry_policy").get_dict_repr(), {
|
||||
'setting_name': 'retry_policy',
|
||||
'strategy': 'fixed',
|
||||
|
|
|
@ -27,17 +27,46 @@ class TestEventGrid(unittest.TestCase):
|
|||
output = EventGridOutput(name="res",
|
||||
topic_endpoint_uri="dummy_topic_endpoint_uri",
|
||||
topic_key_setting="dummy_topic_key_setting",
|
||||
connection="dummy_connection",
|
||||
data_type=DataType.UNDEFINED,
|
||||
dummy_field="dummy")
|
||||
|
||||
self.assertEqual(output.get_binding_name(), "eventGrid")
|
||||
self.assertEqual(output.get_dict_repr(),
|
||||
{'connection': 'dummy_connection',
|
||||
'dataType': DataType.UNDEFINED,
|
||||
'direction': BindingDirection.OUT,
|
||||
'dummyField': 'dummy',
|
||||
'topicEndpointUri': 'dummy_topic_endpoint_uri',
|
||||
'topicKeySetting': 'dummy_topic_key_setting',
|
||||
'name': 'res',
|
||||
'type': EVENT_GRID})
|
||||
{'dataType': DataType.UNDEFINED,
|
||||
'direction': BindingDirection.OUT,
|
||||
'dummyField': 'dummy',
|
||||
'topicEndpointUri': 'dummy_topic_endpoint_uri',
|
||||
'topicKeySetting': 'dummy_topic_key_setting',
|
||||
'name': 'res',
|
||||
'type': EVENT_GRID})
|
||||
|
||||
def test_event_grid_output_valid_creation_with_connection(self):
|
||||
output = EventGridOutput(name="res",
|
||||
connection="dummy_connection",
|
||||
data_type=DataType.UNDEFINED,
|
||||
dummy_field="dummy")
|
||||
|
||||
self.assertEqual(output.connection, "dummy_connection")
|
||||
self.assertIsNone(output.topic_endpoint_uri)
|
||||
self.assertIsNone(output.topic_key_setting)
|
||||
|
||||
def test_event_grid_output_invalid_creation_with_both(self):
|
||||
with self.assertRaises(ValueError) as context:
|
||||
EventGridOutput(name="res",
|
||||
connection="dummy_connection",
|
||||
topic_endpoint_uri="dummy_topic_endpoint_uri",
|
||||
topic_key_setting="dummy_topic_key_setting")
|
||||
|
||||
self.assertTrue("Specify either the 'Connection' property or both "
|
||||
"'TopicKeySetting' and 'TopicEndpointUri' properties, "
|
||||
"but not both." in str(context.exception))
|
||||
|
||||
def test_event_grid_output_invalid_creation_with_none(self):
|
||||
with self.assertRaises(ValueError) as context:
|
||||
EventGridOutput(name="res",
|
||||
data_type=DataType.UNDEFINED,
|
||||
dummy_field="dummy")
|
||||
|
||||
self.assertTrue("Specify either the 'Connection' property or both "
|
||||
"'TopicKeySetting' and 'TopicEndpointUri' properties,"
|
||||
" but not both." in str(context.exception))
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License.
|
||||
from abc import ABC
|
||||
import inspect
|
||||
import json
|
||||
import unittest
|
||||
|
@ -11,10 +12,12 @@ from azure.functions.decorators.constants import HTTP_OUTPUT, HTTP_TRIGGER, \
|
|||
TIMER_TRIGGER
|
||||
from azure.functions.decorators.core import DataType, AuthLevel, \
|
||||
BindingDirection, SCRIPT_FILE_NAME
|
||||
from azure.functions.decorators.function_app import BindingApi, \
|
||||
FunctionBuilder, FunctionApp, Function, Blueprint, DecoratorApi, \
|
||||
AsgiFunctionApp, WsgiFunctionApp, HttpFunctionsAuthLevelMixin, \
|
||||
FunctionRegister, TriggerApi, ExternalHttpFunctionApp
|
||||
from azure.functions.decorators.function_app import (
|
||||
BindingApi, FunctionBuilder, FunctionApp, Function, Blueprint,
|
||||
DecoratorApi, AsgiFunctionApp, SettingsApi, WsgiFunctionApp,
|
||||
HttpFunctionsAuthLevelMixin, FunctionRegister, TriggerApi,
|
||||
ExternalHttpFunctionApp
|
||||
)
|
||||
from azure.functions.decorators.http import HttpTrigger, HttpOutput, \
|
||||
HttpMethod
|
||||
from azure.functions.decorators.retry_policy import RetryPolicy
|
||||
|
@ -111,30 +114,42 @@ class TestFunction(unittest.TestCase):
|
|||
|
||||
|
||||
class TestFunctionBuilder(unittest.TestCase):
|
||||
def setUp(self):
|
||||
def dummy():
|
||||
return "dummy"
|
||||
|
||||
self.dummy = dummy
|
||||
self.fb = FunctionBuilder(self.dummy, "dummy.py")
|
||||
|
||||
def test_function_builder_creation(self):
|
||||
def test_function_builder_creation():
|
||||
return "dummy"
|
||||
|
||||
self.dummy = test_function_builder_creation
|
||||
self.fb = FunctionBuilder(self.dummy, "dummy.py")
|
||||
|
||||
self.assertTrue(callable(self.fb))
|
||||
func = getattr(self.fb, "_function")
|
||||
self.assertEqual(self.fb._function.function_script_file, "dummy.py")
|
||||
self.assertEqual(func.get_user_function(), self.dummy)
|
||||
|
||||
def test_validate_function_missing_trigger(self):
|
||||
def test_validate_function_missing_trigger():
|
||||
return "dummy"
|
||||
|
||||
self.dummy = test_validate_function_missing_trigger
|
||||
self.fb = FunctionBuilder(self.dummy, "dummy.py")
|
||||
|
||||
with self.assertRaises(ValueError) as err:
|
||||
# self.fb.configure_function_name('dummy').build()
|
||||
self.fb.build()
|
||||
|
||||
self.assertEqual(err.exception.args[0],
|
||||
"Function dummy does not have a trigger. A valid "
|
||||
"function must have one and only one trigger "
|
||||
"registered.")
|
||||
"Function test_validate_function_missing_trigger"
|
||||
" does not have a trigger. A valid function must have"
|
||||
" one and only one trigger registered.")
|
||||
|
||||
def test_validate_function_trigger_not_in_bindings(self):
|
||||
def test_validate_function_trigger_not_in_bindings():
|
||||
return "dummy"
|
||||
|
||||
self.dummy = test_validate_function_trigger_not_in_bindings
|
||||
self.fb = FunctionBuilder(self.dummy, "dummy.py")
|
||||
|
||||
trigger = HttpTrigger(name='req', methods=(HttpMethod.GET,),
|
||||
data_type=DataType.UNDEFINED,
|
||||
auth_level=AuthLevel.ANONYMOUS,
|
||||
|
@ -144,37 +159,59 @@ class TestFunctionBuilder(unittest.TestCase):
|
|||
getattr(self.fb, "_function").get_bindings().clear()
|
||||
self.fb.build()
|
||||
|
||||
self.assertEqual(err.exception.args[0],
|
||||
f"Function dummy trigger {trigger} not present"
|
||||
f" in bindings {[]}")
|
||||
self.assertEqual(
|
||||
err.exception.args[0],
|
||||
f"Function test_validate_function_trigger_not_in_bindings"
|
||||
f" trigger {trigger} not present"
|
||||
f" in bindings {[]}")
|
||||
|
||||
def test_validate_function_working(self):
|
||||
def test_validate_function_working():
|
||||
return "dummy"
|
||||
|
||||
self.dummy = test_validate_function_working
|
||||
self.fb = FunctionBuilder(self.dummy, "dummy.py")
|
||||
|
||||
trigger = HttpTrigger(name='req', methods=(HttpMethod.GET,),
|
||||
data_type=DataType.UNDEFINED,
|
||||
auth_level=AuthLevel.ANONYMOUS)
|
||||
auth_level=AuthLevel.ANONYMOUS,
|
||||
route='test_validate_function_working')
|
||||
self.fb.add_trigger(trigger)
|
||||
self.fb.build()
|
||||
|
||||
def test_build_function_http_route_default(self):
|
||||
def test_build_function_http_route_default():
|
||||
return "dummy"
|
||||
|
||||
self.dummy = test_build_function_http_route_default
|
||||
self.fb = FunctionBuilder(self.dummy, "dummy.py")
|
||||
|
||||
trigger = HttpTrigger(name='req', methods=(HttpMethod.GET,),
|
||||
data_type=DataType.UNDEFINED,
|
||||
auth_level=AuthLevel.ANONYMOUS)
|
||||
self.fb.add_trigger(trigger)
|
||||
func = self.fb.build()
|
||||
|
||||
self.assertEqual(func.get_trigger().route, "dummy")
|
||||
self.assertEqual(func.get_trigger().route,
|
||||
"test_build_function_http_route_default")
|
||||
|
||||
def test_build_function_with_bindings(self):
|
||||
def test_build_function_with_bindings():
|
||||
return "dummy"
|
||||
|
||||
self.dummy = test_build_function_with_bindings
|
||||
self.fb = FunctionBuilder(self.dummy, "dummy.py")
|
||||
|
||||
test_trigger = HttpTrigger(name='req', methods=(HttpMethod.GET,),
|
||||
data_type=DataType.UNDEFINED,
|
||||
auth_level=AuthLevel.ANONYMOUS,
|
||||
route='dummy')
|
||||
auth_level=AuthLevel.ANONYMOUS)
|
||||
test_input = HttpOutput(name='out', data_type=DataType.UNDEFINED)
|
||||
|
||||
func = self.fb.add_trigger(
|
||||
test_trigger).add_binding(test_input).build()
|
||||
|
||||
self.assertEqual(func.get_function_name(), "dummy")
|
||||
self.assertEqual(func.get_function_name(),
|
||||
"test_build_function_with_bindings")
|
||||
assert_json(self, func, {
|
||||
"scriptFile": "dummy.py",
|
||||
"bindings": [
|
||||
|
@ -184,7 +221,7 @@ class TestFunctionBuilder(unittest.TestCase):
|
|||
"direction": BindingDirection.IN,
|
||||
"name": "req",
|
||||
"dataType": DataType.UNDEFINED,
|
||||
"route": "dummy",
|
||||
"route": "test_build_function_with_bindings",
|
||||
"methods": [
|
||||
HttpMethod.GET
|
||||
]
|
||||
|
@ -199,6 +236,12 @@ class TestFunctionBuilder(unittest.TestCase):
|
|||
})
|
||||
|
||||
def test_build_function_with_function_app_auth_level(self):
|
||||
def test_build_function_with_function_app_auth_level():
|
||||
return "dummy"
|
||||
|
||||
self.dummy = test_build_function_with_function_app_auth_level
|
||||
self.fb = FunctionBuilder(self.dummy, "dummy.py")
|
||||
|
||||
trigger = HttpTrigger(name='req', methods=(HttpMethod.GET,),
|
||||
data_type=DataType.UNDEFINED)
|
||||
self.fb.add_trigger(trigger)
|
||||
|
@ -207,11 +250,19 @@ class TestFunctionBuilder(unittest.TestCase):
|
|||
self.assertEqual(func.get_trigger().auth_level, AuthLevel.ANONYMOUS)
|
||||
|
||||
def test_build_function_with_retry_policy_setting(self):
|
||||
def test_build_function_with_retry_policy_setting():
|
||||
return "dummy"
|
||||
|
||||
self.dummy = test_build_function_with_retry_policy_setting
|
||||
self.fb = FunctionBuilder(self.dummy, "dummy.py")
|
||||
|
||||
setting = RetryPolicy(strategy="exponential", max_retry_count="2",
|
||||
minimum_interval="1", maximum_interval="5")
|
||||
trigger = HttpTrigger(name='req', methods=(HttpMethod.GET,),
|
||||
data_type=DataType.UNDEFINED,
|
||||
auth_level=AuthLevel.ANONYMOUS)
|
||||
trigger = HttpTrigger(
|
||||
name='req', methods=(HttpMethod.GET,),
|
||||
data_type=DataType.UNDEFINED,
|
||||
auth_level=AuthLevel.ANONYMOUS,
|
||||
route='test_build_function_with_retry_policy_setting')
|
||||
self.fb.add_trigger(trigger)
|
||||
self.fb.add_setting(setting)
|
||||
func = self.fb.build()
|
||||
|
@ -221,6 +272,293 @@ class TestFunctionBuilder(unittest.TestCase):
|
|||
'strategy': 'exponential', 'max_retry_count': '2',
|
||||
'minimum_interval': '1', 'maximum_interval': '5'})
|
||||
|
||||
def test_unique_method_names(self):
|
||||
app = FunctionApp()
|
||||
|
||||
@app.schedule(arg_name="name", schedule="10****")
|
||||
def test_unique_method_names(name: str):
|
||||
return name
|
||||
|
||||
@app.schedule(arg_name="name", schedule="10****")
|
||||
def test_unique_method_names2(name: str):
|
||||
return name
|
||||
|
||||
functions = app.get_functions()
|
||||
self.assertEqual(len(functions), 2)
|
||||
|
||||
self.assertEqual(functions[0].get_function_name(),
|
||||
"test_unique_method_names")
|
||||
self.assertEqual(functions[1].get_function_name(),
|
||||
"test_unique_method_names2")
|
||||
|
||||
def test_unique_function_names(self):
|
||||
app = FunctionApp()
|
||||
|
||||
@app.function_name("test_unique_function_names")
|
||||
@app.schedule(arg_name="name", schedule="10****")
|
||||
def test_unique_function_names(name: str):
|
||||
return name
|
||||
|
||||
@app.function_name("test_unique_function_names2")
|
||||
@app.schedule(arg_name="name", schedule="10****")
|
||||
def test_unique_function_names2(name: str):
|
||||
return name
|
||||
|
||||
functions = app.get_functions()
|
||||
self.assertEqual(len(functions), 2)
|
||||
|
||||
self.assertEqual(functions[0].get_function_name(),
|
||||
"test_unique_function_names")
|
||||
self.assertEqual(functions[1].get_function_name(),
|
||||
"test_unique_function_names2")
|
||||
|
||||
def test_same_method_names(self):
|
||||
app = FunctionApp()
|
||||
|
||||
@app.schedule(arg_name="name", schedule="10****") # NoQA
|
||||
def test_same_method_names(name: str): # NoQA
|
||||
return name
|
||||
|
||||
@app.schedule(arg_name="name", schedule="10****") # NoQA
|
||||
def test_same_method_names(name: str): # NoQA
|
||||
return name
|
||||
|
||||
with self.assertRaises(ValueError) as err:
|
||||
app.get_functions()
|
||||
self.assertEqual(err.exception.args[0],
|
||||
"Function test_same_method_names does not have"
|
||||
" a unique function name."
|
||||
" Please change @app.function_name()"
|
||||
" or the function method name to be unique.")
|
||||
|
||||
def test_same_function_names(self):
|
||||
app = FunctionApp()
|
||||
|
||||
@app.function_name("test_same_function_names") # NoQA
|
||||
@app.schedule(arg_name="name", schedule="10****")
|
||||
def test_same_function_names(name: str):
|
||||
return name
|
||||
|
||||
@app.function_name("test_same_function_names") # NoQA
|
||||
@app.schedule(arg_name="name", schedule="10****")
|
||||
def test_same_function_names(name: str): # NoQA
|
||||
return name
|
||||
|
||||
with self.assertRaises(ValueError) as err:
|
||||
app.get_functions()
|
||||
self.assertEqual(err.exception.args[0],
|
||||
"Function test_same_function_names does not have"
|
||||
" a unique function name."
|
||||
" Please change @app.function_name()"
|
||||
" or the function method name to be unique.")
|
||||
|
||||
def test_same_function_name_different_method_name(self):
|
||||
app = FunctionApp()
|
||||
|
||||
@app.function_name("test_same_function_name_different_method_name")
|
||||
@app.schedule(arg_name="name", schedule="10****")
|
||||
def test_same_function_name_different_method_name(name: str):
|
||||
return name
|
||||
|
||||
@app.function_name("test_same_function_name_different_method_name")
|
||||
@app.schedule(arg_name="name", schedule="10****")
|
||||
def test_same_function_name_different_method_name2(name: str):
|
||||
return name
|
||||
|
||||
with self.assertRaises(ValueError) as err:
|
||||
app.get_functions()
|
||||
self.assertEqual(
|
||||
err.exception.args[0],
|
||||
"Function test_same_function_name_different_method_name"
|
||||
" does not have a unique function name."
|
||||
" Please change @app.function_name()"
|
||||
" or the function method name to be unique.")
|
||||
|
||||
def test_same_function_and_method_name(self):
|
||||
app = FunctionApp()
|
||||
|
||||
@app.function_name("test_same_function_and_method_name")
|
||||
@app.schedule(arg_name="name", schedule="10****")
|
||||
def test_same_function_and_method_name2(name: str):
|
||||
return name
|
||||
|
||||
@app.schedule(arg_name="name", schedule="10****")
|
||||
def test_same_function_and_method_name(name: str):
|
||||
return name
|
||||
|
||||
with self.assertRaises(ValueError) as err:
|
||||
app.get_functions()
|
||||
self.assertEqual(err.exception.args[0],
|
||||
"Function test_same_function_and_method_name"
|
||||
" does not have a unique function name."
|
||||
" Please change @app.function_name()"
|
||||
" or the function method name to be unique.")
|
||||
|
||||
def test_blueprint_unique_method_names(self):
|
||||
app = FunctionApp()
|
||||
|
||||
@app.schedule(arg_name="name", schedule="10****")
|
||||
def test_blueprint_unique_method_names(name: str):
|
||||
return name
|
||||
|
||||
bp = Blueprint()
|
||||
|
||||
@bp.schedule(arg_name="name", schedule="10****")
|
||||
def test_blueprint_unique_method_names2(name: str):
|
||||
return name
|
||||
|
||||
app.register_blueprint(bp)
|
||||
|
||||
functions = app.get_functions()
|
||||
self.assertEqual(len(functions), 2)
|
||||
|
||||
self.assertEqual(functions[0].get_function_name(),
|
||||
"test_blueprint_unique_method_names")
|
||||
self.assertEqual(functions[1].get_function_name(),
|
||||
"test_blueprint_unique_method_names2")
|
||||
|
||||
def test_blueprint_unique_function_names(self):
|
||||
app = FunctionApp()
|
||||
|
||||
@app.function_name("test_blueprint_unique_function_names")
|
||||
@app.schedule(arg_name="name", schedule="10****")
|
||||
def test_blueprint_unique_function_names(name: str):
|
||||
return name
|
||||
|
||||
bp = Blueprint()
|
||||
|
||||
@bp.function_name("test_blueprint_unique_function_names2")
|
||||
@bp.schedule(arg_name="name", schedule="10****")
|
||||
def test_blueprint_unique_function_names2(name: str):
|
||||
return name
|
||||
|
||||
app.register_blueprint(bp)
|
||||
|
||||
functions = app.get_functions()
|
||||
self.assertEqual(len(functions), 2)
|
||||
|
||||
self.assertEqual(functions[0].get_function_name(),
|
||||
"test_blueprint_unique_function_names")
|
||||
self.assertEqual(functions[1].get_function_name(),
|
||||
"test_blueprint_unique_function_names2")
|
||||
|
||||
def test_blueprint_same_method_names(self):
|
||||
app = FunctionApp()
|
||||
|
||||
@app.schedule(arg_name="name", schedule="10****") # NoQA
|
||||
def test_blueprint_same_method_names(name: str): # NoQA
|
||||
return name
|
||||
|
||||
bp = Blueprint()
|
||||
|
||||
@bp.schedule(arg_name="name", schedule="10****") # NoQA
|
||||
def test_blueprint_same_method_names(name: str): # NoQA
|
||||
return name
|
||||
|
||||
app.register_blueprint(bp)
|
||||
|
||||
with self.assertRaises(ValueError) as err:
|
||||
app.get_functions()
|
||||
self.assertEqual(err.exception.args[0],
|
||||
"Function test_blueprint_same_method_names"
|
||||
" does not have a unique function name."
|
||||
" Please change @app.function_name()"
|
||||
" or the function method name to be unique.")
|
||||
|
||||
def test_blueprint_same_function_names(self):
|
||||
app = FunctionApp()
|
||||
|
||||
@app.function_name("test_blueprint_same_function_names") # NoQA
|
||||
@app.schedule(arg_name="name", schedule="10****")
|
||||
def test_blueprint_same_function_names(name: str): # NoQA
|
||||
return name
|
||||
|
||||
bp = Blueprint()
|
||||
|
||||
@bp.function_name("test_blueprint_same_function_names") # NoQA
|
||||
@bp.schedule(arg_name="name", schedule="10****")
|
||||
def test_blueprint_same_function_names(name: str): # NoQA
|
||||
return name
|
||||
|
||||
app.register_blueprint(bp)
|
||||
|
||||
with self.assertRaises(ValueError) as err:
|
||||
app.get_functions()
|
||||
self.assertEqual(err.exception.args[0],
|
||||
"Function test_blueprint_same_function_names"
|
||||
" does not have a unique function name. Please change"
|
||||
" @app.function_name() or the function method name"
|
||||
" to be unique.")
|
||||
|
||||
def test_blueprint_same_function_name_different_method_name(self):
|
||||
app = FunctionApp()
|
||||
|
||||
@app.function_name(
|
||||
"test_blueprint_same_function_name_different_method_name")
|
||||
@app.schedule(arg_name="name", schedule="10****")
|
||||
def test_blueprint_same_function_name_different_method_name(name: str):
|
||||
return name
|
||||
|
||||
bp = Blueprint()
|
||||
|
||||
@bp.function_name(
|
||||
"test_blueprint_same_function_name_different_method_name")
|
||||
@bp.schedule(arg_name="name", schedule="10****")
|
||||
def test_blueprint_same_function_name_different_method_name2(
|
||||
name: str):
|
||||
return name
|
||||
|
||||
app.register_blueprint(bp)
|
||||
|
||||
with self.assertRaises(ValueError) as err:
|
||||
app.get_functions()
|
||||
self.assertEqual(
|
||||
err.exception.args[0],
|
||||
"Function test_blueprint_same_function_name_different_method_name"
|
||||
" does not have a unique function name. Please change"
|
||||
" @app.function_name() or the function method name to be unique.")
|
||||
|
||||
def test_blueprint_same_function_and_method_name(self):
|
||||
app = FunctionApp()
|
||||
|
||||
@app.function_name("test_blueprint_same_function_and_method_name")
|
||||
@app.schedule(arg_name="name", schedule="10****")
|
||||
def test_blueprint_same_function_and_method_name2(name: str):
|
||||
return name
|
||||
|
||||
bp = Blueprint()
|
||||
|
||||
@bp.schedule(arg_name="name", schedule="10****")
|
||||
def test_blueprint_same_function_and_method_name(name: str):
|
||||
return name
|
||||
|
||||
app.register_blueprint(bp)
|
||||
|
||||
with self.assertRaises(ValueError) as err:
|
||||
app.get_functions()
|
||||
self.assertEqual(
|
||||
err.exception.args[0],
|
||||
"Function test_blueprint_same_function_and_method_name"
|
||||
" does not have a unique function name."
|
||||
" Please change @app.function_name() or the function"
|
||||
" method name to be unique.")
|
||||
|
||||
def test_user_function_is_directly_callable_no_args(self):
|
||||
def test_validate_function_working_no_args():
|
||||
return "dummy"
|
||||
|
||||
self.dummy = test_validate_function_working_no_args
|
||||
self.fb = FunctionBuilder(self.dummy, "dummy.py")
|
||||
self.assertEqual(self.fb(), "dummy")
|
||||
|
||||
def test_user_function_is_directly_callable_args(self):
|
||||
def test_validate_function_working_sum_args(arg1: int, arg2: int):
|
||||
return arg1 + arg2
|
||||
|
||||
self.dummy = test_validate_function_working_sum_args
|
||||
self.fb = FunctionBuilder(self.dummy, "dummy.py")
|
||||
self.assertEqual(self.fb(1, 2), 3)
|
||||
|
||||
|
||||
class TestScaffold(unittest.TestCase):
|
||||
def setUp(self):
|
||||
|
@ -246,12 +584,12 @@ class TestScaffold(unittest.TestCase):
|
|||
|
||||
def test_dummy_app_trigger(self):
|
||||
@self.dummy.dummy_trigger(name="dummy")
|
||||
def dummy():
|
||||
def test_dummy_app_trigger():
|
||||
return "dummy"
|
||||
|
||||
self.assertEqual(len(self.dummy._function_builders), 1)
|
||||
func = self.dummy._function_builders[0].build()
|
||||
self.assertEqual(func.get_function_name(), "dummy")
|
||||
self.assertEqual(func.get_function_name(), "test_dummy_app_trigger")
|
||||
self.assertEqual(func.get_function_json(),
|
||||
'{"scriptFile": "function_app.py", "bindings": [{'
|
||||
'"direction": "IN", "dataType": "UNDEFINED", '
|
||||
|
@ -305,7 +643,7 @@ class TestFunctionApp(unittest.TestCase):
|
|||
'._add_http_app')
|
||||
def test_add_asgi(self, add_http_app_mock):
|
||||
mock_asgi_app = object()
|
||||
AsgiFunctionApp(app=mock_asgi_app)
|
||||
AsgiFunctionApp(app=mock_asgi_app, function_name='test_add_asgi')
|
||||
|
||||
add_http_app_mock.assert_called_once()
|
||||
|
||||
|
@ -316,17 +654,34 @@ class TestFunctionApp(unittest.TestCase):
|
|||
'._add_http_app')
|
||||
def test_add_wsgi(self, add_http_app_mock):
|
||||
mock_wsgi_app = object()
|
||||
WsgiFunctionApp(app=mock_wsgi_app)
|
||||
WsgiFunctionApp(app=mock_wsgi_app, function_name='test_add_wsgi')
|
||||
|
||||
add_http_app_mock.assert_called_once()
|
||||
self.assertIsInstance(add_http_app_mock.call_args[0][0],
|
||||
WsgiMiddleware)
|
||||
|
||||
def test_extends_required_classes(self):
|
||||
self.assertTrue(issubclass(ExternalHttpFunctionApp, FunctionRegister))
|
||||
self.assertTrue(issubclass(ExternalHttpFunctionApp, TriggerApi))
|
||||
self.assertTrue(issubclass(ExternalHttpFunctionApp, SettingsApi))
|
||||
self.assertTrue(issubclass(ExternalHttpFunctionApp, BindingApi))
|
||||
self.assertTrue(issubclass(ExternalHttpFunctionApp, ABC))
|
||||
self.assertTrue(issubclass(AsgiFunctionApp, ExternalHttpFunctionApp))
|
||||
self.assertTrue(issubclass(WsgiFunctionApp, ExternalHttpFunctionApp))
|
||||
|
||||
def test_add_asgi_app(self):
|
||||
self._test_http_external_app(AsgiFunctionApp(app=object()), True)
|
||||
self._test_http_external_app(AsgiFunctionApp(
|
||||
app=object(),
|
||||
function_name='test_add_asgi_app'),
|
||||
True,
|
||||
function_name='test_add_asgi_app')
|
||||
|
||||
def test_add_wsgi_app(self):
|
||||
self._test_http_external_app(WsgiFunctionApp(app=object()), False)
|
||||
self._test_http_external_app(WsgiFunctionApp(
|
||||
app=object(),
|
||||
function_name='test_add_wsgi_app'),
|
||||
False,
|
||||
function_name='test_add_wsgi_app')
|
||||
|
||||
def test_register_function_app_error(self):
|
||||
with self.assertRaises(TypeError) as err:
|
||||
|
@ -339,7 +694,7 @@ class TestFunctionApp(unittest.TestCase):
|
|||
bp = Blueprint()
|
||||
|
||||
@bp.schedule(arg_name="name", schedule="10****")
|
||||
def hello(name: str):
|
||||
def test_register_blueprint(name: str):
|
||||
return "hello"
|
||||
|
||||
app = FunctionApp()
|
||||
|
@ -353,14 +708,15 @@ class TestFunctionApp(unittest.TestCase):
|
|||
bp = Blueprint()
|
||||
|
||||
@bp.route("name")
|
||||
def hello(name: str):
|
||||
def test_register_app_auth_level(name: str):
|
||||
return "hello"
|
||||
|
||||
app = FunctionApp(http_auth_level=AuthLevel.ANONYMOUS)
|
||||
app.register_blueprint(bp)
|
||||
|
||||
self.assertEqual(len(app.get_functions()), 1)
|
||||
self.assertEqual(app.get_functions()[0].get_trigger().auth_level,
|
||||
functions = app.get_functions()
|
||||
self.assertEqual(len(functions), 1)
|
||||
self.assertEqual(functions[0].get_trigger().auth_level,
|
||||
AuthLevel.ANONYMOUS)
|
||||
|
||||
def test_default_function_http_type(self):
|
||||
|
@ -381,12 +737,12 @@ class TestFunctionApp(unittest.TestCase):
|
|||
|
||||
@app.route("name1")
|
||||
@app.http_type("dummy1")
|
||||
def hello(name: str):
|
||||
def test_set_http_type(name: str):
|
||||
return "hello"
|
||||
|
||||
@app.route("name2")
|
||||
@app.http_type("dummy2")
|
||||
def hello2(name: str):
|
||||
def test_set_http_type2(name: str):
|
||||
return "hello"
|
||||
|
||||
funcs = app.get_functions()
|
||||
|
@ -459,7 +815,7 @@ class TestFunctionApp(unittest.TestCase):
|
|||
blueprint = Blueprint()
|
||||
|
||||
@blueprint.schedule(arg_name="name", schedule="10****")
|
||||
def hello(name: str):
|
||||
def test_function_register_non_http_function_app(name: str):
|
||||
return name
|
||||
|
||||
app.register_blueprint(blueprint)
|
||||
|
@ -499,7 +855,7 @@ class TestFunctionApp(unittest.TestCase):
|
|||
app = DummyFunctionApp(auth_level=AuthLevel.ANONYMOUS)
|
||||
blueprint = LegacyBluePrint()
|
||||
|
||||
@blueprint.function_name("timer_function")
|
||||
@blueprint.function_name("test_legacy_blueprints_with_function_name")
|
||||
@blueprint.schedule(arg_name="name", schedule="10****")
|
||||
def hello(name: str):
|
||||
return name
|
||||
|
@ -512,7 +868,7 @@ class TestFunctionApp(unittest.TestCase):
|
|||
setting = functions[0].get_setting("function_name")
|
||||
|
||||
self.assertEqual(setting.get_settings_value("function_name"),
|
||||
"timer_function")
|
||||
"test_legacy_blueprints_with_function_name")
|
||||
|
||||
def test_function_register_register_function_register_error(self):
|
||||
class DummyFunctionApp(FunctionRegister):
|
||||
|
@ -533,7 +889,8 @@ class TestFunctionApp(unittest.TestCase):
|
|||
blueprint = Blueprint()
|
||||
|
||||
@blueprint.schedule(arg_name="name", schedule="10****")
|
||||
def hello(name: str):
|
||||
def test_function_register_register_functions_from_blueprint(
|
||||
name: str):
|
||||
return name
|
||||
|
||||
app.register_blueprint(blueprint)
|
||||
|
@ -546,7 +903,9 @@ class TestFunctionApp(unittest.TestCase):
|
|||
self.assertEqual(trigger.type, TIMER_TRIGGER)
|
||||
self.assertEqual(trigger.schedule, "10****")
|
||||
self.assertEqual(trigger.name, "name")
|
||||
self.assertEqual(functions[0].get_function_name(), "hello")
|
||||
self.assertEqual(
|
||||
functions[0].get_function_name(),
|
||||
"test_function_register_register_functions_from_blueprint")
|
||||
self.assertEqual(functions[0].get_user_function()("timer"), "timer")
|
||||
|
||||
def test_asgi_function_app_default(self):
|
||||
|
@ -575,7 +934,9 @@ class TestFunctionApp(unittest.TestCase):
|
|||
self.assertEqual(app.auth_level, AuthLevel.ANONYMOUS)
|
||||
|
||||
def test_wsgi_function_app_is_http_function(self):
|
||||
app = WsgiFunctionApp(app=object())
|
||||
app = WsgiFunctionApp(
|
||||
app=object(),
|
||||
function_name='test_wsgi_function_app_is_http_function')
|
||||
funcs = app.get_functions()
|
||||
|
||||
self.assertEqual(len(funcs), 1)
|
||||
|
@ -606,11 +967,11 @@ class TestFunctionApp(unittest.TestCase):
|
|||
app = ExternalHttpFunctionApp(auth_level=AuthLevel.ANONYMOUS)
|
||||
app._add_http_app(AsgiMiddleware(object()))
|
||||
|
||||
def _test_http_external_app(self, app, is_async):
|
||||
def _test_http_external_app(self, app, is_async, function_name):
|
||||
funcs = app.get_functions()
|
||||
self.assertEqual(len(funcs), 1)
|
||||
func = funcs[0]
|
||||
self.assertEqual(func.get_function_name(), "http_app_func")
|
||||
self.assertEqual(func.get_function_name(), function_name)
|
||||
raw_bindings = func.get_raw_bindings()
|
||||
raw_trigger = raw_bindings[0]
|
||||
raw_output_binding = raw_bindings[0]
|
||||
|
@ -647,3 +1008,43 @@ class TestFunctionApp(unittest.TestCase):
|
|||
"type": HTTP_OUTPUT
|
||||
}
|
||||
]})
|
||||
|
||||
|
||||
class TestFunctionRegister(unittest.TestCase):
|
||||
def test_validate_empty_dict(self):
|
||||
def dummy():
|
||||
return "dummy"
|
||||
|
||||
test_func = Function(dummy, "dummy.py")
|
||||
fr = FunctionRegister(auth_level="ANONYMOUS")
|
||||
fr.validate_function_names(functions=[test_func])
|
||||
|
||||
def test_validate_unique_names(self):
|
||||
def dummy():
|
||||
return "dummy"
|
||||
|
||||
def dummy2():
|
||||
return "dummy"
|
||||
|
||||
test_func = Function(dummy, "dummy.py")
|
||||
test_func2 = Function(dummy2, "dummy.py")
|
||||
|
||||
fr = FunctionRegister(auth_level="ANONYMOUS")
|
||||
fr.validate_function_names(
|
||||
functions=[test_func, test_func2])
|
||||
|
||||
def test_validate_non_unique_names(self):
|
||||
def dummy():
|
||||
return "dummy"
|
||||
|
||||
test_func = Function(dummy, "dummy.py")
|
||||
test_func2 = Function(dummy, "dummy.py")
|
||||
|
||||
fr = FunctionRegister(auth_level="ANONYMOUS")
|
||||
with self.assertRaises(ValueError) as err:
|
||||
fr.validate_function_names(functions=[test_func, test_func2])
|
||||
self.assertEqual(err.exception.args[0],
|
||||
"Function dummy does not have"
|
||||
" a unique function name."
|
||||
" Please change @app.function_name()"
|
||||
" or the function method name to be unique.")
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License.
|
||||
import unittest
|
||||
|
||||
from azure.functions.decorators.constants import KAFKA_TRIGGER, KAFKA
|
||||
from azure.functions.decorators.core import BindingDirection, Cardinality, \
|
||||
DataType
|
||||
from azure.functions.decorators.kafka import KafkaTrigger, KafkaOutput, \
|
||||
BrokerAuthenticationMode, BrokerProtocol
|
||||
|
||||
|
||||
class TestKafka(unittest.TestCase):
|
||||
def test_kafka_trigger_valid_creation(self):
|
||||
trigger = KafkaTrigger(name="arg_name",
|
||||
topic="topic",
|
||||
broker_list="broker_list",
|
||||
event_hub_connection_string="ehcs",
|
||||
consumer_group="consumer_group",
|
||||
avro_schema="avro_schema",
|
||||
username="username",
|
||||
password="password",
|
||||
ssl_key_location="ssl_key_location",
|
||||
ssl_ca_location="ssl_ca_location",
|
||||
ssl_certificate_location="scl",
|
||||
ssl_key_password="ssl_key_password",
|
||||
schema_registry_url="srurl",
|
||||
schema_registry_username="",
|
||||
schema_registry_password="srp",
|
||||
authentication_mode=BrokerAuthenticationMode.PLAIN, # noqa: E501
|
||||
data_type=DataType.UNDEFINED,
|
||||
dummy_field="dummy")
|
||||
|
||||
self.assertEqual(trigger.get_binding_name(), "kafkaTrigger")
|
||||
self.assertEqual(trigger.get_dict_repr(),
|
||||
{"authenticationMode": BrokerAuthenticationMode.PLAIN,
|
||||
"avroSchema": "avro_schema",
|
||||
"brokerList": "broker_list",
|
||||
"consumerGroup": "consumer_group",
|
||||
"dataType": DataType.UNDEFINED,
|
||||
"direction": BindingDirection.IN,
|
||||
"dummyField": "dummy",
|
||||
"eventHubConnectionString": "ehcs",
|
||||
"lagThreshold": 1000,
|
||||
"name": "arg_name",
|
||||
"password": "password",
|
||||
"protocol": BrokerProtocol.NOTSET,
|
||||
"schemaRegistryPassword": "srp",
|
||||
"schemaRegistryUrl": "srurl",
|
||||
"schemaRegistryUsername": "",
|
||||
"sslCaLocation": "ssl_ca_location",
|
||||
"sslCertificateLocation": "scl",
|
||||
"sslKeyLocation": "ssl_key_location",
|
||||
"sslKeyPassword": "ssl_key_password",
|
||||
"topic": "topic",
|
||||
"cardinality": Cardinality.ONE,
|
||||
"type": KAFKA_TRIGGER,
|
||||
"username": "username"})
|
||||
|
||||
def test_kafka_output_valid_creation(self):
|
||||
output = KafkaOutput(name="arg_name",
|
||||
topic="topic",
|
||||
broker_list="broker_list",
|
||||
avro_schema="avro_schema",
|
||||
username="username",
|
||||
password="password",
|
||||
ssl_key_location="ssl_key_location",
|
||||
ssl_ca_location="ssl_ca_location",
|
||||
ssl_certificate_location="scl",
|
||||
ssl_key_password="ssl_key_password",
|
||||
schema_registry_url="schema_registry_url",
|
||||
schema_registry_username="",
|
||||
schema_registry_password="srp",
|
||||
max_retries=10,
|
||||
data_type=DataType.UNDEFINED,
|
||||
dummy_field="dummy")
|
||||
|
||||
self.assertEqual(output.get_binding_name(), "kafka")
|
||||
self.assertEqual(output.get_dict_repr(),
|
||||
{'authenticationMode': BrokerAuthenticationMode.NOTSET, # noqa: E501
|
||||
'avroSchema': 'avro_schema',
|
||||
'batchSize': 10000,
|
||||
'brokerList': 'broker_list',
|
||||
'dataType': DataType.UNDEFINED,
|
||||
'direction': BindingDirection.OUT,
|
||||
'dummyField': 'dummy',
|
||||
'enableIdempotence': False,
|
||||
'lingerMs': 5,
|
||||
'maxMessageBytes': 1000000,
|
||||
'maxRetries': 10,
|
||||
'messageTimeoutMs': 300000,
|
||||
'name': 'arg_name',
|
||||
'password': 'password',
|
||||
'protocol': BrokerProtocol.NOTSET,
|
||||
'requestTimeoutMs': 5000,
|
||||
'schemaRegistryPassword': 'srp',
|
||||
'schemaRegistryUrl': 'schema_registry_url',
|
||||
'schemaRegistryUsername': '',
|
||||
'sslCaLocation': 'ssl_ca_location',
|
||||
'sslCertificateLocation': 'scl',
|
||||
'sslKeyLocation': 'ssl_key_location',
|
||||
'sslKeyPassword': 'ssl_key_password',
|
||||
'topic': 'topic',
|
||||
'type': KAFKA,
|
||||
'username': 'username'})
|
|
@ -0,0 +1,181 @@
|
|||
import unittest
|
||||
|
||||
from azure.functions import DataType
|
||||
from azure.functions.decorators.core import BindingDirection
|
||||
from azure.functions.decorators.openai import AssistantSkillTrigger, \
|
||||
TextCompletionInput, OpenAIModels, AssistantQueryInput, EmbeddingsInput, \
|
||||
AssistantCreateOutput, SemanticSearchInput, EmbeddingsStoreOutput, \
|
||||
AssistantPostInput
|
||||
|
||||
|
||||
class TestOpenAI(unittest.TestCase):
|
||||
|
||||
def test_assistant_skill_trigger_valid_creation(self):
|
||||
trigger = AssistantSkillTrigger(name="test",
|
||||
function_description="description",
|
||||
function_name="test_function_name",
|
||||
parameter_description_json="test_json",
|
||||
model=OpenAIModels.DefaultChatModel,
|
||||
data_type=DataType.UNDEFINED,
|
||||
dummy_field="dummy")
|
||||
self.assertEqual(trigger.get_binding_name(),
|
||||
"assistantSkillTrigger")
|
||||
self.assertEqual(
|
||||
trigger.get_dict_repr(), {"name": "test",
|
||||
"functionDescription": "description",
|
||||
"functionName": "test_function_name",
|
||||
"parameterDescriptionJson": "test_json",
|
||||
"model": OpenAIModels.DefaultChatModel,
|
||||
"dataType": DataType.UNDEFINED,
|
||||
'type': 'assistantSkillTrigger',
|
||||
'dummyField': 'dummy',
|
||||
"direction": BindingDirection.IN,
|
||||
})
|
||||
|
||||
def test_text_completion_input_valid_creation(self):
|
||||
input = TextCompletionInput(name="test",
|
||||
prompt="test_prompt",
|
||||
temperature="1",
|
||||
max_tokens="1",
|
||||
data_type=DataType.UNDEFINED,
|
||||
model=OpenAIModels.DefaultChatModel,
|
||||
dummy_field="dummy")
|
||||
self.assertEqual(input.get_binding_name(),
|
||||
"textCompletion")
|
||||
self.assertEqual(input.get_dict_repr(),
|
||||
{"name": "test",
|
||||
"temperature": "1",
|
||||
"maxTokens": "1",
|
||||
'type': 'textCompletion',
|
||||
"dataType": DataType.UNDEFINED,
|
||||
"dummyField": "dummy",
|
||||
"prompt": "test_prompt",
|
||||
"direction": BindingDirection.IN,
|
||||
"model": OpenAIModels.DefaultChatModel
|
||||
})
|
||||
|
||||
def test_assistant_query_input_valid_creation(self):
|
||||
input = AssistantQueryInput(name="test",
|
||||
timestamp_utc="timestamp_utc",
|
||||
data_type=DataType.UNDEFINED,
|
||||
id="test_id",
|
||||
type="assistantQueryInput",
|
||||
dummy_field="dummy")
|
||||
self.assertEqual(input.get_binding_name(),
|
||||
"assistantQuery")
|
||||
self.assertEqual(input.get_dict_repr(),
|
||||
{"name": "test",
|
||||
"timestampUtc": "timestamp_utc",
|
||||
"dataType": DataType.UNDEFINED,
|
||||
"direction": BindingDirection.IN,
|
||||
"type": "assistantQuery",
|
||||
"id": "test_id",
|
||||
"dummyField": "dummy"
|
||||
})
|
||||
|
||||
def test_embeddings_input_valid_creation(self):
|
||||
input = EmbeddingsInput(name="test",
|
||||
data_type=DataType.UNDEFINED,
|
||||
input="test_input",
|
||||
input_type="test_input_type",
|
||||
model="test_model",
|
||||
max_overlap=1,
|
||||
max_chunk_length=1,
|
||||
dummy_field="dummy")
|
||||
self.assertEqual(input.get_binding_name(),
|
||||
"embeddings")
|
||||
self.assertEqual(input.get_dict_repr(),
|
||||
{"name": "test",
|
||||
"type": "embeddings",
|
||||
"dataType": DataType.UNDEFINED,
|
||||
"input": "test_input",
|
||||
"inputType": "test_input_type",
|
||||
"model": "test_model",
|
||||
"maxOverlap": 1,
|
||||
"maxChunkLength": 1,
|
||||
"direction": BindingDirection.IN,
|
||||
"dummyField": "dummy"})
|
||||
|
||||
def test_assistant_create_output_valid_creation(self):
|
||||
output = AssistantCreateOutput(name="test",
|
||||
data_type=DataType.UNDEFINED)
|
||||
self.assertEqual(output.get_binding_name(),
|
||||
"assistantCreate")
|
||||
self.assertEqual(output.get_dict_repr(),
|
||||
{"name": "test",
|
||||
"dataType": DataType.UNDEFINED,
|
||||
"direction": BindingDirection.OUT,
|
||||
"type": "assistantCreate"})
|
||||
|
||||
def test_assistant_post_input_valid_creation(self):
|
||||
input = AssistantPostInput(name="test",
|
||||
id="test_id",
|
||||
model="test_model",
|
||||
user_message="test_message",
|
||||
data_type=DataType.UNDEFINED,
|
||||
dummy_field="dummy")
|
||||
self.assertEqual(input.get_binding_name(),
|
||||
"assistantPost")
|
||||
self.assertEqual(input.get_dict_repr(),
|
||||
{"name": "test",
|
||||
"id": "test_id",
|
||||
"model": "test_model",
|
||||
"userMessage": "test_message",
|
||||
"dataType": DataType.UNDEFINED,
|
||||
"direction": BindingDirection.IN,
|
||||
"dummyField": "dummy",
|
||||
"type": "assistantPost"})
|
||||
|
||||
def test_semantic_search_input_valid_creation(self):
|
||||
input = SemanticSearchInput(name="test",
|
||||
data_type=DataType.UNDEFINED,
|
||||
chat_model=OpenAIModels.DefaultChatModel,
|
||||
embeddings_model=OpenAIModels.DefaultEmbeddingsModel, # NoQA
|
||||
collection="test_collection",
|
||||
connection_name="test_connection",
|
||||
system_prompt="test_prompt",
|
||||
query="test_query",
|
||||
max_knowledge_count=1,
|
||||
dummy_field="dummy_field")
|
||||
self.assertEqual(input.get_binding_name(),
|
||||
"semanticSearch")
|
||||
self.assertEqual(input.get_dict_repr(),
|
||||
{"name": "test",
|
||||
"dataType": DataType.UNDEFINED,
|
||||
"direction": BindingDirection.IN,
|
||||
"dummyField": "dummy_field",
|
||||
"chatModel": OpenAIModels.DefaultChatModel,
|
||||
"embeddingsModel": OpenAIModels.DefaultEmbeddingsModel, # NoQA
|
||||
"type": "semanticSearch",
|
||||
"collection": "test_collection",
|
||||
"connectionName": "test_connection",
|
||||
"systemPrompt": "test_prompt",
|
||||
"maxKnowledgeCount": 1,
|
||||
"query": "test_query"})
|
||||
|
||||
def test_embeddings_store_output_valid_creation(self):
|
||||
output = EmbeddingsStoreOutput(name="test",
|
||||
data_type=DataType.UNDEFINED,
|
||||
input="test_input",
|
||||
input_type="test_input_type",
|
||||
connection_name="test_connection",
|
||||
max_overlap=1,
|
||||
max_chunk_length=1,
|
||||
collection="test_collection",
|
||||
model=OpenAIModels.DefaultChatModel,
|
||||
dummy_field="dummy_field")
|
||||
self.assertEqual(output.get_binding_name(),
|
||||
"embeddingsStore")
|
||||
self.assertEqual(output.get_dict_repr(),
|
||||
{"name": "test",
|
||||
"dataType": DataType.UNDEFINED,
|
||||
"direction": BindingDirection.OUT,
|
||||
"dummyField": "dummy_field",
|
||||
"input": "test_input",
|
||||
"inputType": "test_input_type",
|
||||
"collection": "test_collection",
|
||||
"model": OpenAIModels.DefaultChatModel,
|
||||
"connectionName": "test_connection",
|
||||
"maxOverlap": 1,
|
||||
"maxChunkLength": 1,
|
||||
"type": "embeddingsStore"})
|
|
@ -181,9 +181,10 @@ class TestHTTP(unittest.TestCase):
|
|||
|
||||
def test_http_response_accepts_http_enums(self):
|
||||
response = func.HttpResponse(status_code=404)
|
||||
self.assertEquals(response.status_code, 404)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
response = func.HttpResponse(status_code=HTTPStatus.ACCEPTED)
|
||||
self.assertEquals(response.status_code, 202)
|
||||
self.assertEqual(response.status_code, HTTPStatus.ACCEPTED.value)
|
||||
|
||||
def test_http_request_converter_decode(self):
|
||||
data = {
|
||||
|
|
|
@ -198,7 +198,19 @@ class TestHttpAsgiMiddleware(unittest.TestCase):
|
|||
test_body = b'Hello world!'
|
||||
app.response_body = test_body
|
||||
app.response_code = 200
|
||||
req = func.HttpRequest(method='get', url='/test', body=b'')
|
||||
req = self._generate_func_request()
|
||||
response = AsgiMiddleware(app).handle(req)
|
||||
|
||||
# Verify asserted
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.get_body(), test_body)
|
||||
|
||||
def test_middleware_calls_app_http(self):
|
||||
app = MockAsgiApplication()
|
||||
test_body = b'Hello world!'
|
||||
app.response_body = test_body
|
||||
app.response_code = 200
|
||||
req = self._generate_func_request(url="http://a.b.com")
|
||||
response = AsgiMiddleware(app).handle(req)
|
||||
|
||||
# Verify asserted
|
||||
|
|
|
@ -14,6 +14,7 @@ from azure.functions._http_wsgi import (
|
|||
WsgiResponse,
|
||||
WsgiMiddleware
|
||||
)
|
||||
from azure.functions._http_asgi import AsgiRequest
|
||||
|
||||
|
||||
class WsgiException(Exception):
|
||||
|
@ -211,6 +212,18 @@ class TestHttpWsgi(unittest.TestCase):
|
|||
self.assertEqual(func_response.status_code, 200)
|
||||
self.assertEqual(func_response.get_body(), b'sample string')
|
||||
|
||||
def test_path_encoding_utf8(self):
|
||||
url = 'http://example.com/Pippi%20L%C3%A5ngstrump'
|
||||
request = AsgiRequest(self._generate_func_request(url=url))
|
||||
|
||||
self.assertEqual(request.path_info, u'/Pippi L\u00e5ngstrump')
|
||||
|
||||
def test_path_encoding_latin1(self):
|
||||
url = 'http://example.com/Pippi%20L%C3%A5ngstrump'
|
||||
request = WsgiRequest(self._generate_func_request(url=url))
|
||||
|
||||
self.assertEqual(request.path_info, u'/Pippi L\u00c3\u00a5ngstrump')
|
||||
|
||||
def _generate_func_request(
|
||||
self,
|
||||
method="POST",
|
||||
|
|
|
@ -44,6 +44,18 @@ class TestTimer(unittest.TestCase):
|
|||
self.assertEqual(schedule_status, test_timer.schedule_status)
|
||||
self.assertEqual(schedule, test_timer.schedule)
|
||||
|
||||
def test_timer_initialize_empty_dicts(self):
|
||||
# given
|
||||
past_due = False
|
||||
|
||||
# when
|
||||
test_timer = timer.TimerRequest()
|
||||
|
||||
# then
|
||||
self.assertEqual(past_due, test_timer.past_due)
|
||||
self.assertEqual({}, test_timer.schedule_status)
|
||||
self.assertEqual({}, test_timer.schedule)
|
||||
|
||||
def test_timer_no_implementation_exception(self):
|
||||
# given
|
||||
datum: Datum = Datum(value="test", type='string')
|
||||
|
|
Загрузка…
Ссылка в новой задаче