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

Merge remote-tracking branch 'origin/release/1.21.1'

This commit is contained in:
AzureFunctionsPython 2024-09-30 20:46:35 +00:00
Родитель d83601cfc1 52a5bbe857
Коммит dd4fac4db0
41 изменённых файлов: 2767 добавлений и 277 удалений

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

@ -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

38
.github/workflows/gh-tests-ci.yml поставляемый
Просмотреть файл

@ -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

2
.github/workflows/label.yml поставляемый
Просмотреть файл

@ -1,6 +1,6 @@
name: "Pull Request Labeler"
on:
pull_request:
pull_request_target:
paths:
- '**/__init__.py'
jobs:

20
.github/workflows/pr_title_enforcer.yml поставляемый Normal file
Просмотреть файл

@ -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

18
eng/ci/code-mirror.yml Normal file
Просмотреть файл

@ -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

50
eng/ci/official-build.yml Normal file
Просмотреть файл

@ -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

50
eng/ci/public-build.yml Normal file
Просмотреть файл

@ -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

21
eng/templates/build.yml Normal file
Просмотреть файл

@ -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'

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

@ -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')