├── azure
├── functions_worker
│ ├── __init__.py
│ ├── protos
│ │ ├── .gitignore
│ │ ├── __init__.py
│ │ └── _src
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ └── src
│ │ │ └── proto
│ │ │ └── google
│ │ │ └── protobuf
│ │ │ └── duration.proto
│ ├── __main__.py
│ ├── bindings
│ │ ├── out.py
│ │ ├── context.py
│ │ ├── __init__.py
│ │ ├── timer.py
│ │ ├── eventgrid.py
│ │ ├── cosmosdb.py
│ │ ├── eventhub.py
│ │ ├── blob.py
│ │ └── queue.py
│ ├── logging.py
│ ├── main.py
│ ├── loader.py
│ └── aio_compat.py
└── __init__.py
├── docs
├── .gitignore
├── usage.rst
├── Makefile
├── make.bat
└── api.rst
├── tests
├── load_functions
│ ├── relimport
│ │ ├── relative.py
│ │ ├── main.py
│ │ └── function.json
│ ├── simple
│ │ ├── main.py
│ │ └── function.json
│ ├── subdir
│ │ ├── sub
│ │ │ └── main.py
│ │ └── function.json
│ └── entrypoint
│ │ ├── main.py
│ │ └── function.json
├── ping
│ ├── main.py
│ ├── README.md
│ └── function.json
├── broken_functions
│ ├── syntax_error
│ │ ├── main.py
│ │ └── function.json
│ ├── inout_param
│ │ ├── main.py
│ │ └── function.json
│ ├── missing_py_param
│ │ ├── main.py
│ │ └── function.json
│ ├── return_param_in
│ │ ├── main.py
│ │ └── function.json
│ ├── unsupported_ret_type
│ │ ├── main.py
│ │ └── function.json
│ ├── invalid_return_anno
│ │ ├── main.py
│ │ └── function.json
│ ├── missing_json_param
│ │ ├── main.py
│ │ └── function.json
│ ├── unsupported_bind_type
│ │ ├── main.py
│ │ └── function.json
│ ├── wrong_param_dir
│ │ ├── main.py
│ │ └── function.json
│ ├── invalid_http_trigger_anno
│ │ ├── main.py
│ │ └── function.json
│ ├── invalid_context_param
│ │ ├── main.py
│ │ └── function.json
│ ├── invalid_return_anno_non_type
│ │ ├── main.py
│ │ └── function.json
│ ├── invalid_in_anno_non_type
│ │ ├── main.py
│ │ └── function.json
│ ├── import_error
│ │ ├── main.py
│ │ └── function.json
│ ├── bad_out_annotation
│ │ ├── main.py
│ │ └── function.json
│ ├── module_not_found_error
│ │ ├── main.py
│ │ └── function.json
│ ├── wrong_binding_dir
│ │ ├── main.py
│ │ └── function.json
│ ├── invalid_out_anno
│ │ ├── main.py
│ │ └── function.json
│ ├── invalid_in_anno
│ │ ├── main.py
│ │ └── function.json
│ └── README.md
├── .gitignore
├── http_functions
│ ├── no_return_returns
│ │ ├── main.py
│ │ └── function.json
│ ├── remapped_context
│ │ ├── main.py
│ │ └── function.json
│ ├── unhandled_error
│ │ ├── main.py
│ │ └── function.json
│ ├── no_return
│ │ ├── main.py
│ │ └── function.json
│ ├── return_http_no_body
│ │ ├── main.py
│ │ └── function.json
│ ├── return_bytes
│ │ ├── main.py
│ │ └── function.json
│ ├── return_str
│ │ ├── main.py
│ │ └── function.json
│ ├── return_http_404
│ │ ├── main.py
│ │ └── function.json
│ ├── return_route_params
│ │ ├── main.py
│ │ └── function.json
│ ├── return_out
│ │ ├── main.py
│ │ └── function.json
│ ├── async_return_str
│ │ ├── main.py
│ │ └── function.json
│ ├── return_http
│ │ ├── main.py
│ │ └── function.json
│ ├── return_http_auth_admin
│ │ ├── main.py
│ │ └── function.json
│ ├── unhandled_urllib_error
│ │ ├── main.py
│ │ └── function.json
│ ├── return_http_redirect
│ │ ├── main.py
│ │ └── function.json
│ ├── unhandled_unserializable_error
│ │ ├── main.py
│ │ └── function.json
│ ├── accept_json
│ │ ├── function.json
│ │ └── main.py
│ ├── async_logging
│ │ ├── function.json
│ │ └── main.py
│ ├── return_context
│ │ ├── function.json
│ │ └── main.py
│ ├── return_request
│ │ ├── function.json
│ │ └── main.py
│ └── sync_logging
│ │ ├── function.json
│ │ └── main.py
├── blob_functions
│ ├── put_blob_return
│ │ ├── main.py
│ │ └── function.json
│ ├── get_blob_bytes
│ │ ├── main.py
│ │ └── function.json
│ ├── get_blob_return
│ │ ├── main.py
│ │ └── function.json
│ ├── get_blob_str
│ │ ├── main.py
│ │ └── function.json
│ ├── get_blob_as_str
│ │ ├── main.py
│ │ └── function.json
│ ├── get_blob_filelike
│ │ ├── main.py
│ │ └── function.json
│ ├── get_blob_triggered
│ │ ├── main.py
│ │ └── function.json
│ ├── put_blob_bytes
│ │ ├── main.py
│ │ └── function.json
│ ├── put_blob_str
│ │ ├── main.py
│ │ └── function.json
│ ├── put_blob_trigger
│ │ ├── main.py
│ │ └── function.json
│ ├── get_blob_as_bytes
│ │ ├── main.py
│ │ └── function.json
│ ├── put_blob_filelike
│ │ ├── main.py
│ │ └── function.json
│ └── blob_trigger
│ │ ├── main.py
│ │ └── function.json
├── queue_functions
│ ├── put_queue_return
│ │ ├── main.py
│ │ └── function.json
│ ├── queue_trigger_return
│ │ ├── main.py
│ │ └── function.json
│ ├── queue_trigger_message_return
│ │ ├── main.py
│ │ └── function.json
│ ├── put_queue
│ │ ├── main.py
│ │ └── function.json
│ ├── put_queue_message_return
│ │ ├── main.py
│ │ └── function.json
│ ├── get_queue_blob_return
│ │ ├── main.py
│ │ └── function.json
│ ├── get_queue_blob_message_return
│ │ ├── main.py
│ │ └── function.json
│ ├── put_queue_return_multiple
│ │ ├── main.py
│ │ └── function.json
│ ├── get_queue_blob
│ │ ├── main.py
│ │ └── function.json
│ ├── queue_trigger_return_multiple
│ │ ├── main.py
│ │ └── function.json
│ ├── put_queue_multiple_out
│ │ ├── main.py
│ │ └── function.json
│ └── queue_trigger
│ │ ├── function.json
│ │ └── main.py
├── cosmosdb_functions
│ ├── cosmosdb_trigger
│ │ ├── __init__.py
│ │ └── function.json
│ ├── get_cosmosdb_triggered
│ │ ├── main.py
│ │ └── function.json
│ ├── put_document
│ │ ├── __init__.py
│ │ └── function.json
│ └── cosmosdb_input
│ │ ├── __init__.py
│ │ └── function.json
├── servicebus_functions
│ ├── put_message_return
│ │ ├── __init__.py
│ │ └── function.json
│ ├── put_message
│ │ ├── __init__.py
│ │ └── function.json
│ ├── get_servicebus_triggered
│ │ ├── __init__.py
│ │ └── function.json
│ └── servicebus_trigger
│ │ ├── function.json
│ │ └── __init__.py
├── eventhub_functions
│ ├── eventhub_trigger
│ │ ├── __init__.py
│ │ └── function.json
│ ├── get_eventhub_triggered
│ │ ├── main.py
│ │ └── function.json
│ └── eventhub_output
│ │ ├── __init__.py
│ │ └── function.json
├── timer_functions
│ └── return_pastdue
│ │ ├── main.py
│ │ └── function.json
├── eventgrid_functions
│ ├── get_eventgrid_triggered
│ │ ├── main.py
│ │ └── function.json
│ └── eventgrid_trigger
│ │ ├── __init__.py
│ │ └── function.json
├── __init__.py
├── test_loader.py
├── test_servicebus_functions.py
├── test_eventhub_functions.py
├── test_mock_timer_functions.py
├── test_code_quality.py
├── test_cosmosdb_functions.py
├── test_queue_functions.py
├── test_eventgrid_functions.py
├── test_mock_http_functions.py
└── test_blob_functions.py
├── MANIFEST.in
├── requirements.txt
├── .ci
├── linux_devops_build.sh
├── linux_devops_tools.sh
├── linux_devops_tests.sh
└── e2e
│ ├── publish_tests
│ ├── dev_docker_setup
│ │ ├── dev.Dockerfile
│ │ └── setup.sh
│ ├── test_runners
│ │ ├── prod_func_prod_docker
│ │ │ ├── new_packapp.sh
│ │ │ ├── new_no_bundler.sh
│ │ │ ├── customer_no_bundler.sh
│ │ │ ├── new_build_native_deps.sh
│ │ │ └── customer_build_native_deps.sh
│ │ ├── prod_func_dev_docker
│ │ │ ├── new_packapp.sh
│ │ │ ├── new_no_bundler.sh
│ │ │ ├── customer_no_bundler.sh
│ │ │ ├── new_build_native_deps.sh
│ │ │ └── customer_build_native_deps.sh
│ │ ├── dev_func_dev_docker
│ │ │ ├── new_packapp.sh
│ │ │ ├── new_no_bundler.sh
│ │ │ ├── customer_no_bundler.sh
│ │ │ ├── new_build_native_deps.sh
│ │ │ └── customer_build_native_deps.sh
│ │ ├── dev_func_prod_docker
│ │ │ ├── new_packapp.sh
│ │ │ ├── new_no_bundler.sh
│ │ │ ├── customer_no_bundler.sh
│ │ │ ├── new_build_native_deps.sh
│ │ │ └── customer_build_native_deps.sh
│ │ ├── setup_container_environment.sh
│ │ ├── setup_test_environment.sh
│ │ ├── helpers
│ │ │ ├── helper.sh
│ │ │ └── get_config_variables.sh
│ │ ├── run_all_parallel.sh
│ │ └── run_all_serial.sh
│ ├── dev_func_setup
│ │ └── setup.sh
│ ├── func_tests_core
│ │ ├── customer_churn_app
│ │ │ ├── build_native_deps_test.sh
│ │ │ └── no_bundler_test.sh
│ │ ├── helpers
│ │ │ └── helper.sh
│ │ └── new_functionapp
│ │ │ ├── packapp_test.sh
│ │ │ ├── build_native_deps_test.sh
│ │ │ └── no_bundler_test.sh
│ └── publish_config.json
│ └── running-environments
│ └── Docker
│ ├── start_tests_docker.sh
│ └── test_runner.Dockerfile
├── pack
├── scripts
│ ├── nix_deps.sh
│ └── win_deps.ps1
├── Microsoft.Azure.Functions.PythonWorkerRunEnvironments.targets
├── Microsoft.Azure.Functions.PythonWorkerRunEnvironments.nuspec
└── templates
│ ├── win_env_gen.yml
│ └── nix_env_gen.yml
├── python
├── worker.config.json
└── worker.py
├── .flake8
├── setup.cfg
├── LICENSE
├── ISSUE_TEMPLATE.md
├── .gitignore
├── azure-pipelines-nightly.yml
├── azure-pipelines.yml
└── README.md
/azure/functions_worker/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | _build
2 | _templates
3 |
--------------------------------------------------------------------------------
/tests/load_functions/relimport/relative.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/ping/main.py:
--------------------------------------------------------------------------------
1 | def main(req):
2 | return
3 |
--------------------------------------------------------------------------------
/azure/functions_worker/protos/.gitignore:
--------------------------------------------------------------------------------
1 | /_src
2 | *_pb2.py
3 | *_pb2_grpc.py
4 |
--------------------------------------------------------------------------------
/tests/broken_functions/syntax_error/main.py:
--------------------------------------------------------------------------------
1 | def main(req):
2 | 1 / # noqa
3 |
--------------------------------------------------------------------------------
/tests/.gitignore:
--------------------------------------------------------------------------------
1 | *_functions/bin/
2 | *_functions/host.json
3 | *_functions/ping/
4 |
--------------------------------------------------------------------------------
/tests/http_functions/no_return_returns/main.py:
--------------------------------------------------------------------------------
1 | def main(req):
2 | return 'ABC'
3 |
--------------------------------------------------------------------------------
/tests/load_functions/simple/main.py:
--------------------------------------------------------------------------------
1 | def main(req) -> str:
2 | return __name__
3 |
--------------------------------------------------------------------------------
/tests/load_functions/subdir/sub/main.py:
--------------------------------------------------------------------------------
1 | def main(req) -> str:
2 | return __name__
3 |
--------------------------------------------------------------------------------
/tests/http_functions/remapped_context/main.py:
--------------------------------------------------------------------------------
1 | def main(context):
2 | return context.method
3 |
--------------------------------------------------------------------------------
/tests/load_functions/entrypoint/main.py:
--------------------------------------------------------------------------------
1 | def customentry(req) -> str:
2 | return __name__
3 |
--------------------------------------------------------------------------------
/tests/broken_functions/inout_param/main.py:
--------------------------------------------------------------------------------
1 | def main(req, abc):
2 | return 'trust me, it is OK!'
3 |
--------------------------------------------------------------------------------
/tests/broken_functions/missing_py_param/main.py:
--------------------------------------------------------------------------------
1 | def main():
2 | return 'trust me, it is OK!'
3 |
--------------------------------------------------------------------------------
/tests/broken_functions/return_param_in/main.py:
--------------------------------------------------------------------------------
1 | def main(req):
2 | return 'trust me, it is OK!'
3 |
--------------------------------------------------------------------------------
/docs/usage.rst:
--------------------------------------------------------------------------------
1 | .. _azure-functions-usage:
2 |
3 |
4 | Azure Functions Usage
5 | =====================
6 |
--------------------------------------------------------------------------------
/tests/broken_functions/unsupported_ret_type/main.py:
--------------------------------------------------------------------------------
1 | def main(req):
2 | return 'trust me, it is OK!'
3 |
--------------------------------------------------------------------------------
/tests/broken_functions/invalid_return_anno/main.py:
--------------------------------------------------------------------------------
1 | def main(req) -> int:
2 | return 'trust me, it is OK!'
3 |
--------------------------------------------------------------------------------
/tests/broken_functions/missing_json_param/main.py:
--------------------------------------------------------------------------------
1 | def main(req, spam):
2 | return 'trust me, it is OK!'
3 |
--------------------------------------------------------------------------------
/tests/broken_functions/unsupported_bind_type/main.py:
--------------------------------------------------------------------------------
1 | def main(req, ret):
2 | return 'trust me, it is OK!'
3 |
--------------------------------------------------------------------------------
/tests/broken_functions/wrong_param_dir/main.py:
--------------------------------------------------------------------------------
1 | def main(req, foo: int):
2 | return 'trust me, it is OK!'
3 |
--------------------------------------------------------------------------------
/tests/ping/README.md:
--------------------------------------------------------------------------------
1 | This function is used to check the host availability in tests.
2 | Please do not remove.
3 |
--------------------------------------------------------------------------------
/tests/broken_functions/invalid_http_trigger_anno/main.py:
--------------------------------------------------------------------------------
1 | def main(req: int):
2 | return 'trust me, it is OK!'
3 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | recursive-include azure *.py *.pyi
2 | recursive-include tests *.py *.json
3 | include LICENSE README.md
4 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | # Please list runtime dependencies in setup.py 'install_requires' and
2 | # 'extras_require'.
3 | .
4 |
--------------------------------------------------------------------------------
/tests/broken_functions/invalid_context_param/main.py:
--------------------------------------------------------------------------------
1 | def main(req, context: int):
2 | return 'trust me, it is OK!'
3 |
--------------------------------------------------------------------------------
/tests/broken_functions/invalid_return_anno_non_type/main.py:
--------------------------------------------------------------------------------
1 | def main(req) -> 123:
2 | return 'trust me, it is OK!'
3 |
--------------------------------------------------------------------------------
/.ci/linux_devops_build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e -x
4 |
5 | python -m pip install -U -e .[dev]
6 | python setup.py webhost
--------------------------------------------------------------------------------
/azure/functions_worker/__main__.py:
--------------------------------------------------------------------------------
1 | from azure.functions_worker import main
2 |
3 | if __name__ == '__main__':
4 | main.main()
5 |
--------------------------------------------------------------------------------
/tests/http_functions/unhandled_error/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req: azf.HttpRequest):
5 | 1 / 0
6 |
--------------------------------------------------------------------------------
/tests/load_functions/relimport/main.py:
--------------------------------------------------------------------------------
1 | from . import relative
2 |
3 |
4 | def main(req) -> str:
5 | return relative.__name__
6 |
--------------------------------------------------------------------------------
/azure/__init__.py:
--------------------------------------------------------------------------------
1 | from pkgutil import extend_path
2 | import typing
3 | __path__: typing.Iterable[str] = extend_path(__path__, __name__)
4 |
--------------------------------------------------------------------------------
/tests/broken_functions/invalid_in_anno_non_type/main.py:
--------------------------------------------------------------------------------
1 | def main(req: 123): # annotations must be types!
2 | return 'trust me, it is OK!'
3 |
--------------------------------------------------------------------------------
/tests/broken_functions/import_error/main.py:
--------------------------------------------------------------------------------
1 | from sys import __nonexistent # should raise ImportError
2 |
3 |
4 | def main(req):
5 | __nonexistent()
6 |
--------------------------------------------------------------------------------
/tests/blob_functions/put_blob_return/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req: azf.HttpRequest) -> str:
5 | return 'FROM RETURN'
6 |
--------------------------------------------------------------------------------
/tests/broken_functions/bad_out_annotation/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req, foo: azf.Out):
5 | return 'trust me, it is OK!'
6 |
--------------------------------------------------------------------------------
/tests/http_functions/no_return/main.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 |
4 | logger = logging.getLogger('test')
5 |
6 |
7 | def main(req):
8 | logger.error('hi')
9 |
--------------------------------------------------------------------------------
/tests/http_functions/return_http_no_body/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req: azf.HttpRequest):
5 | return azf.HttpResponse()
6 |
--------------------------------------------------------------------------------
/tests/broken_functions/module_not_found_error/main.py:
--------------------------------------------------------------------------------
1 | from __nonexistent import foo # should raise ModuleNotFoundError
2 |
3 |
4 | def main(req):
5 | foo()
6 |
--------------------------------------------------------------------------------
/tests/broken_functions/wrong_binding_dir/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req, foo: azf.Out[str]):
5 | return 'trust me, it is OK!'
6 |
--------------------------------------------------------------------------------
/tests/queue_functions/put_queue_return/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req: azf.HttpRequest) -> bytes:
5 | return req.get_body()
6 |
--------------------------------------------------------------------------------
/tests/http_functions/return_bytes/main.py:
--------------------------------------------------------------------------------
1 | def main(req):
2 | # This function will fail, as we don't auto-convert "bytes" to "http".
3 | return b'Hello World!'
4 |
--------------------------------------------------------------------------------
/tests/http_functions/return_str/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions
2 |
3 |
4 | def main(req: azure.functions.HttpRequest, context) -> str:
5 | return 'Hello World!'
6 |
--------------------------------------------------------------------------------
/tests/queue_functions/queue_trigger_return/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(msg: azf.QueueMessage) -> bytes:
5 | return msg.get_body()
6 |
--------------------------------------------------------------------------------
/tests/broken_functions/invalid_out_anno/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req, ret: azf.Out[azf.HttpRequest]):
5 | return 'trust me, it is OK!'
6 |
--------------------------------------------------------------------------------
/tests/cosmosdb_functions/cosmosdb_trigger/__init__.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(docs: azf.DocumentList) -> str:
5 | return docs[0].to_json()
6 |
--------------------------------------------------------------------------------
/tests/queue_functions/queue_trigger_message_return/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(msg: azf.QueueMessage) -> bytes:
5 | return msg.get_body()
6 |
--------------------------------------------------------------------------------
/tests/servicebus_functions/put_message_return/__init__.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req: azf.HttpRequest) -> bytes:
5 | return req.get_body()
6 |
--------------------------------------------------------------------------------
/tests/eventhub_functions/eventhub_trigger/__init__.py:
--------------------------------------------------------------------------------
1 | import azure.functions as func
2 |
3 |
4 | def main(event: func.EventHubEvent) -> bytes:
5 | return event.get_body()
6 |
--------------------------------------------------------------------------------
/tests/http_functions/return_http_404/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req: azf.HttpRequest):
5 | return azf.HttpResponse('bye', status_code=404)
6 |
--------------------------------------------------------------------------------
/tests/broken_functions/invalid_in_anno/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req: azf.HttpResponse): # should be azf.HttpRequest
5 | return 'trust me, it is OK!'
6 |
--------------------------------------------------------------------------------
/tests/timer_functions/return_pastdue/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(timer: azf.TimerRequest, pastdue: azf.Out[str]):
5 | pastdue.set(str(timer.past_due))
6 |
--------------------------------------------------------------------------------
/tests/blob_functions/get_blob_bytes/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req: azf.HttpRequest, file: azf.InputStream) -> str:
5 | return file.read().decode('utf-8')
6 |
--------------------------------------------------------------------------------
/tests/blob_functions/get_blob_return/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req: azf.HttpRequest, file: azf.InputStream) -> str:
5 | return file.read().decode('utf-8')
6 |
--------------------------------------------------------------------------------
/tests/blob_functions/get_blob_str/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req: azf.HttpRequest, file: azf.InputStream) -> str:
5 | return file.read().decode('utf-8')
6 |
--------------------------------------------------------------------------------
/tests/queue_functions/put_queue/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req: azf.HttpRequest, msg: azf.Out[str]):
5 | msg.set(req.get_body())
6 |
7 | return 'OK'
8 |
--------------------------------------------------------------------------------
/tests/queue_functions/put_queue_message_return/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req: azf.HttpRequest) -> bytes:
5 | return azf.QueueMessage(body=req.get_body())
6 |
--------------------------------------------------------------------------------
/tests/blob_functions/get_blob_as_str/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req: azf.HttpRequest, file: str) -> str:
5 | assert isinstance(file, str)
6 | return file
7 |
--------------------------------------------------------------------------------
/tests/blob_functions/get_blob_filelike/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req: azf.HttpRequest, file: azf.InputStream) -> str:
5 | return file.read().decode('utf-8')
6 |
--------------------------------------------------------------------------------
/tests/blob_functions/get_blob_triggered/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req: azf.HttpRequest, file: azf.InputStream) -> str:
5 | return file.read().decode('utf-8')
6 |
--------------------------------------------------------------------------------
/tests/blob_functions/put_blob_bytes/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req: azf.HttpRequest, file: azf.Out[str]) -> str:
5 | file.set(req.get_body())
6 | return 'OK'
7 |
--------------------------------------------------------------------------------
/tests/blob_functions/put_blob_str/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req: azf.HttpRequest, file: azf.Out[str]) -> str:
5 | file.set(req.get_body())
6 | return 'OK'
7 |
--------------------------------------------------------------------------------
/pack/scripts/nix_deps.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | python -m venv .env
4 | source .env/bin/activate
5 | python -m pip install .
6 |
7 | python -m pip install . --no-compile --target "$BUILD_SOURCESDIRECTORY/deps"
--------------------------------------------------------------------------------
/tests/blob_functions/put_blob_trigger/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req: azf.HttpRequest, file: azf.Out[str]) -> str:
5 | file.set(req.get_body())
6 | return 'OK'
7 |
--------------------------------------------------------------------------------
/tests/http_functions/return_route_params/main.py:
--------------------------------------------------------------------------------
1 | import json
2 | import azure.functions
3 |
4 |
5 | def main(req: azure.functions.HttpRequest) -> str:
6 | return json.dumps(dict(req.route_params))
7 |
--------------------------------------------------------------------------------
/tests/queue_functions/get_queue_blob_return/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req: azf.HttpRequest, file: azf.InputStream) -> str:
5 | return file.read().decode('utf-8')
6 |
--------------------------------------------------------------------------------
/tests/cosmosdb_functions/get_cosmosdb_triggered/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as func
2 |
3 |
4 | def main(req: func.HttpRequest, file: func.InputStream) -> str:
5 | return file.read().decode('utf-8')
6 |
--------------------------------------------------------------------------------
/tests/eventgrid_functions/get_eventgrid_triggered/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as func
2 |
3 |
4 | def main(req: func.HttpRequest, file: func.InputStream) -> str:
5 | return file.read().decode('utf-8')
6 |
--------------------------------------------------------------------------------
/tests/eventhub_functions/get_eventhub_triggered/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as func
2 |
3 |
4 | def main(req: func.HttpRequest, file: func.InputStream) -> str:
5 | return file.read().decode('utf-8')
6 |
--------------------------------------------------------------------------------
/tests/queue_functions/get_queue_blob_message_return/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req: azf.HttpRequest, file: azf.InputStream) -> str:
5 | return file.read().decode('utf-8')
6 |
--------------------------------------------------------------------------------
/tests/http_functions/return_out/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req: azf.HttpRequest, foo: azf.Out[azf.HttpResponse]):
5 | foo.set(azf.HttpResponse(body='hello', status_code=201))
6 |
--------------------------------------------------------------------------------
/tests/blob_functions/get_blob_as_bytes/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req: azf.HttpRequest, file: bytes) -> str:
5 | assert isinstance(file, bytes)
6 | return file.decode('utf-8')
7 |
--------------------------------------------------------------------------------
/tests/queue_functions/put_queue_return_multiple/main.py:
--------------------------------------------------------------------------------
1 | import typing
2 | import azure.functions as azf
3 |
4 |
5 | def main(req: azf.HttpRequest, msgs: azf.Out[typing.List[str]]):
6 | msgs.set(['one', 'two'])
7 |
--------------------------------------------------------------------------------
/tests/servicebus_functions/put_message/__init__.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req: azf.HttpRequest, msg: azf.Out[str]):
5 | msg.set(req.get_body().decode('utf-8'))
6 |
7 | return 'OK'
8 |
--------------------------------------------------------------------------------
/python/worker.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "description":{
3 | "language":"python",
4 | "extensions":[".py"],
5 | "defaultExecutablePath":"python",
6 | "defaultWorkerPath":"worker.py"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/tests/eventhub_functions/eventhub_output/__init__.py:
--------------------------------------------------------------------------------
1 | import azure.functions as func
2 |
3 |
4 | def main(req: func.HttpRequest, event: func.Out[str]):
5 | event.set(req.get_body().decode('utf-8'))
6 |
7 | return 'OK'
8 |
--------------------------------------------------------------------------------
/tests/http_functions/async_return_str/main.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import azure.functions
3 |
4 |
5 | async def main(req: azure.functions.HttpRequest, context):
6 | await asyncio.sleep(0.1)
7 | return 'Hello Async World!'
8 |
--------------------------------------------------------------------------------
/tests/http_functions/return_http/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req: azf.HttpRequest):
5 | return azf.HttpResponse('
Hello World™
',
6 | mimetype='text/html')
7 |
--------------------------------------------------------------------------------
/tests/blob_functions/put_blob_filelike/main.py:
--------------------------------------------------------------------------------
1 | import io
2 |
3 | import azure.functions as azf
4 |
5 |
6 | def main(req: azf.HttpRequest, file: azf.Out[str]) -> str:
7 | file.set(io.StringIO('filelike'))
8 | return 'OK'
9 |
--------------------------------------------------------------------------------
/tests/ping/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/tests/cosmosdb_functions/put_document/__init__.py:
--------------------------------------------------------------------------------
1 | import azure.functions as func
2 |
3 |
4 | def main(req: func.HttpRequest, doc: func.Out[func.Document]):
5 | doc.set(func.Document.from_json(req.get_body()))
6 |
7 | return 'OK'
8 |
--------------------------------------------------------------------------------
/tests/http_functions/return_http_auth_admin/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req: azf.HttpRequest):
5 | return azf.HttpResponse('Hello World™
',
6 | mimetype='text/html')
7 |
--------------------------------------------------------------------------------
/tests/cosmosdb_functions/cosmosdb_input/__init__.py:
--------------------------------------------------------------------------------
1 | import azure.functions as func
2 |
3 |
4 | def main(req: func.HttpRequest, docs: func.DocumentList) -> str:
5 | return func.HttpResponse(docs[0].to_json(), mimetype='application/json')
6 |
--------------------------------------------------------------------------------
/tests/broken_functions/README.md:
--------------------------------------------------------------------------------
1 | Functions in this directory are purposefully "broken". They either have
2 | missing information in `function.json`, or invalid signatures, or even
3 | syntax errors. They are tested in "test_broken_functions.py".
4 |
--------------------------------------------------------------------------------
/pack/scripts/win_deps.ps1:
--------------------------------------------------------------------------------
1 | py -3.6 -m venv .env
2 | .env\scripts\activate
3 | python -m pip install .
4 |
5 | $depsPath = Join-Path -Path $env:BUILD_SOURCESDIRECTORY -ChildPath "deps"
6 |
7 | python -m pip install . --no-compile --target $depsPath.ToString()
--------------------------------------------------------------------------------
/tests/http_functions/no_return/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/tests/http_functions/unhandled_urllib_error/main.py:
--------------------------------------------------------------------------------
1 | from urllib.request import urlopen
2 |
3 | import azure.functions as func
4 |
5 |
6 | def main(req: func.HttpRequest) -> str:
7 | image_url = req.params.get('img')
8 | urlopen(image_url).read()
9 |
--------------------------------------------------------------------------------
/tests/broken_functions/invalid_in_anno/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/tests/http_functions/no_return_returns/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/.ci/linux_devops_tools.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | sudo add-apt-repository -y \
4 | 'deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-xenial-prod xenial main' \
5 | && sudo apt-get update \
6 | && sudo apt-get install -y \
7 | azure-functions-core-tools
--------------------------------------------------------------------------------
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | ignore = W503,E402,E731
3 | exclude =
4 | .git, __pycache__, build, dist, .eggs, .github, .local, docs/,
5 | Samples, azure/functions_worker/protos/,
6 | azure/functions_worker/typing_inspect.py,
7 | tests/test_typing_inspect.py
8 |
--------------------------------------------------------------------------------
/tests/broken_functions/invalid_in_anno_non_type/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/tests/servicebus_functions/get_servicebus_triggered/__init__.py:
--------------------------------------------------------------------------------
1 | import azure.functions as func
2 |
3 |
4 | def main(req: func.HttpRequest, file: func.InputStream) -> str:
5 | return func.HttpResponse(
6 | file.read().decode('utf-8'), mimetype='application/json')
7 |
--------------------------------------------------------------------------------
/tests/queue_functions/get_queue_blob/main.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import azure.functions as azf
4 |
5 |
6 | def main(req: azf.HttpRequest, file: azf.InputStream) -> str:
7 | return json.dumps({
8 | 'queue': json.loads(file.read().decode('utf-8'))
9 | })
10 |
--------------------------------------------------------------------------------
/tests/http_functions/return_http_redirect/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as azf
2 |
3 |
4 | def main(req: azf.HttpRequest):
5 | location = 'return_http?code={}'.format(req.params['code'])
6 | return azf.HttpResponse(
7 | status_code=302,
8 | headers={'location': location})
9 |
--------------------------------------------------------------------------------
/tests/queue_functions/queue_trigger_return_multiple/main.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import azure.functions as azf
3 |
4 |
5 | logger = logging.getLogger(__name__)
6 |
7 |
8 | def main(msg: azf.QueueMessage) -> None:
9 | logging.info('trigger on message: %s', msg.get_body().decode('utf-8'))
10 |
--------------------------------------------------------------------------------
/tests/blob_functions/blob_trigger/main.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import azure.functions as azf
4 |
5 |
6 | def main(file: azf.InputStream) -> str:
7 | return json.dumps({
8 | 'name': file.name,
9 | 'length': file.length,
10 | 'content': file.read().decode('utf-8')
11 | })
12 |
--------------------------------------------------------------------------------
/azure/functions_worker/bindings/out.py:
--------------------------------------------------------------------------------
1 | from azure.functions import _abc as azf_abc
2 |
3 |
4 | class Out(azf_abc.Out):
5 |
6 | def __init__(self):
7 | self.__value = None
8 |
9 | def set(self, val):
10 | self.__value = val
11 |
12 | def get(self):
13 | return self.__value
14 |
--------------------------------------------------------------------------------
/tests/http_functions/unhandled_unserializable_error/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as func
2 |
3 |
4 | class UnserializableException(Exception):
5 | def __str__(self):
6 | raise RuntimeError('cannot serialize me')
7 |
8 |
9 | def main(req: func.HttpRequest) -> str:
10 | raise UnserializableException('foo')
11 |
--------------------------------------------------------------------------------
/tests/http_functions/return_out/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "direction": "out",
12 | "name": "foo",
13 | "type": "http"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/load_functions/relimport/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/load_functions/simple/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/broken_functions/import_error/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/broken_functions/inout_param/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "inout",
13 | "name": "abc"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/broken_functions/invalid_out_anno/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "ret"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/broken_functions/syntax_error/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/http_functions/accept_json/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/http_functions/async_logging/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/http_functions/return_bytes/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/http_functions/return_context/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/http_functions/return_http/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/http_functions/return_request/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/http_functions/return_str/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/http_functions/sync_logging/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/load_functions/subdir/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "sub/main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/broken_functions/missing_py_param/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/broken_functions/return_param_in/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "in",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/broken_functions/unsupported_bind_type/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "yolo",
12 | "direction": "out",
13 | "name": "ret"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/http_functions/async_return_str/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/http_functions/return_http_404/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/http_functions/return_http_no_body/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/http_functions/unhandled_error/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/.ci/linux_devops_tests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e -x
4 | export AzureWebJobsStorage=$LINUXSTORAGECONNECTIONSTRING
5 | export AzureWebJobsCosmosDBConnectionString=$LINUXCOSMOSDBCONNECTIONSTRING
6 | export AzureWebJobsEventHubConnectionString=$LINUXEVENTHUBCONNECTIONSTRING
7 | export AzureWebJobsServiceBusConnectionString=$LINUXSERVICEBUSCONNECTIONSTRING
8 | python setup.py test
--------------------------------------------------------------------------------
/tests/broken_functions/invalid_context_param/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/broken_functions/invalid_return_anno/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/broken_functions/missing_json_param/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/broken_functions/module_not_found_error/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/broken_functions/unsupported_ret_type/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "yolo",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/http_functions/remapped_context/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "context"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/http_functions/return_http_redirect/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/http_functions/unhandled_urllib_error/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/broken_functions/invalid_http_trigger_anno/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/queue_functions/put_queue_multiple_out/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as func
2 |
3 |
4 | def main(req: func.HttpRequest, resp: func.Out[func.HttpResponse],
5 | msg: func.Out[func.QueueMessage]) -> None:
6 | data = req.get_body().decode()
7 | msg.set(func.QueueMessage(body=data))
8 | resp.set(func.HttpResponse(body='HTTP response: {}'.format(data)))
9 |
--------------------------------------------------------------------------------
/tests/queue_functions/queue_trigger_return_multiple/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 |
5 | "bindings": [
6 | {
7 | "type": "queueTrigger",
8 | "direction": "in",
9 | "name": "msg",
10 | "queueName": "testqueue-return-multiple",
11 | "connection": "AzureWebJobsStorage",
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/tests/broken_functions/invalid_return_anno_non_type/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/http_functions/unhandled_unserializable_error/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "$return"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/load_functions/entrypoint/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "entryPoint": "customentry",
4 | "disabled": false,
5 | "bindings": [
6 | {
7 | "type": "httpTrigger",
8 | "direction": "in",
9 | "name": "req"
10 | },
11 | {
12 | "type": "http",
13 | "direction": "out",
14 | "name": "$return"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/tests/http_functions/return_http_auth_admin/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "authLevel": "admin",
7 | "type": "httpTrigger",
8 | "direction": "in",
9 | "name": "req"
10 | },
11 | {
12 | "type": "http",
13 | "direction": "out",
14 | "name": "$return"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/tests/timer_functions/return_pastdue/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "timerTrigger",
7 | "direction": "in",
8 | "name": "timer",
9 | "schedule": "*/5 * * * * *"
10 | },
11 | {
12 | "direction": "out",
13 | "name": "pastdue",
14 | "type": "http"
15 | }
16 | ]
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/tests/http_functions/sync_logging/main.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import time
3 |
4 | import azure.functions
5 |
6 |
7 | logger = logging.getLogger('my function')
8 |
9 |
10 | def main(req: azure.functions.HttpRequest):
11 | try:
12 | 1 / 0
13 | except ZeroDivisionError:
14 | logger.error('a gracefully handled error', exc_info=True)
15 | time.sleep(0.05)
16 | return 'OK-sync'
17 |
--------------------------------------------------------------------------------
/tests/http_functions/accept_json/main.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import azure.functions
4 |
5 |
6 | def main(req: azure.functions.HttpRequest):
7 | return json.dumps({
8 | 'method': req.method,
9 | 'url': req.url,
10 | 'headers': dict(req.headers),
11 | 'params': dict(req.params),
12 | 'get_body': req.get_body().decode(),
13 | 'get_json': req.get_json()
14 | })
15 |
--------------------------------------------------------------------------------
/tests/http_functions/return_context/main.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import azure.functions
4 |
5 |
6 | def main(req: azure.functions.HttpRequest, context: azure.functions.Context):
7 | return json.dumps({
8 | 'method': req.method,
9 | 'ctx_func_name': context.function_name,
10 | 'ctx_func_dir': context.function_directory,
11 | 'ctx_invocation_id': context.invocation_id,
12 | })
13 |
--------------------------------------------------------------------------------
/tests/eventgrid_functions/eventgrid_trigger/__init__.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import azure.functions as func
4 |
5 |
6 | def main(event: func.EventGridEvent) -> str:
7 | result = json.dumps({
8 | 'id': event.id,
9 | 'data': event.get_json(),
10 | 'topic': event.topic,
11 | 'subject': event.subject,
12 | 'event_type': event.event_type,
13 | })
14 |
15 | return result
16 |
--------------------------------------------------------------------------------
/tests/http_functions/return_route_params/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req",
9 | "route": "return_route_params/{param1}/{param2}"
10 | },
11 | {
12 | "type": "http",
13 | "direction": "out",
14 | "name": "$return"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/dev_docker_setup/dev.Dockerfile:
--------------------------------------------------------------------------------
1 | ARG BASE_IMAGE=mcr.microsoft.com/azure-functions/python:2.0
2 | ARG PYTHON_WORKER_LOCATION=.
3 | FROM ${BASE_IMAGE}
4 |
5 | COPY ${PYTHON_WORKER_LOCATION} /python-worker-dev/
6 |
7 | RUN pip install -e /python-worker-dev/ && \
8 | rm -rf /azure-functions-host/workers/python/worker.py && \
9 | mv /python-worker-dev/python/worker.py /azure-functions-host/workers/python/
10 |
--------------------------------------------------------------------------------
/tests/queue_functions/put_queue_return/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 |
5 | "bindings": [
6 | {
7 | "type": "httpTrigger",
8 | "direction": "in",
9 | "name": "req"
10 | },
11 | {
12 | "direction": "out",
13 | "name": "$return",
14 | "queueName": "testqueue-return",
15 | "connection": "AzureWebJobsStorage",
16 | "type": "queue"
17 | }
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/tests/blob_functions/put_blob_return/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "blob",
12 | "direction": "out",
13 | "name": "$return",
14 | "connection": "AzureWebJobsStorage",
15 | "path": "python-worker-tests/test-return.txt"
16 | }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/tests/broken_functions/wrong_binding_dir/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "direction": "in",
12 | "name": "foo",
13 | "type": "int"
14 | },
15 | {
16 | "type": "http",
17 | "direction": "out",
18 | "name": "$return"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/tests/broken_functions/wrong_param_dir/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "direction": "out",
12 | "name": "foo",
13 | "type": "int"
14 | },
15 | {
16 | "type": "http",
17 | "direction": "out",
18 | "name": "$return"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/tests/broken_functions/bad_out_annotation/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "direction": "out",
12 | "name": "foo",
13 | "type": "int"
14 | },
15 | {
16 | "type": "http",
17 | "direction": "out",
18 | "name": "$return"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/tests/queue_functions/put_queue_message_return/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 |
5 | "bindings": [
6 | {
7 | "type": "httpTrigger",
8 | "direction": "in",
9 | "name": "req"
10 | },
11 | {
12 | "direction": "out",
13 | "name": "$return",
14 | "queueName": "testqueue-message-return",
15 | "connection": "AzureWebJobsStorage",
16 | "type": "queue"
17 | }
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/tests/queue_functions/put_queue_return_multiple/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 |
5 | "bindings": [
6 | {
7 | "type": "httpTrigger",
8 | "direction": "in",
9 | "name": "req"
10 | },
11 | {
12 | "direction": "out",
13 | "name": "msgs",
14 | "queueName": "testqueue-return-multiple",
15 | "connection": "AzureWebJobsStorage",
16 | "type": "queue"
17 | }
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/test_runners/prod_func_prod_docker/new_packapp.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Get the configuration variables
4 | source "$(dirname "${BASH_SOURCE[0]}")/../helpers/get_config_variables.sh"
5 |
6 | FUNCTION_APP="$(cat "${GLOBAL_CONFIG}" | jq -r '.publish_function_app.prod_func_prod_docker.new_function.packapp')"
7 |
8 | "$(dirname "${BASH_SOURCE[0]}")/../../func_tests_core/new_functionapp/packapp_test.sh" ${WORKING_DIR}/pfpdnpa ${FUNCTION_APP} func ${PROD_DOCKER_IMAGE}
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/test_runners/prod_func_dev_docker/new_packapp.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Get the configuration variables
4 | source "$(dirname "${BASH_SOURCE[0]}")/../helpers/get_config_variables.sh"
5 |
6 | FUNCTION_APP="$(cat "${GLOBAL_CONFIG}" | jq -r '.publish_function_app.prod_func_dev_docker.new_function.packapp')"
7 |
8 | "$(dirname "${BASH_SOURCE[0]}")/../../func_tests_core/new_functionapp/packapp_test.sh" ${WORKING_DIR}/pfddnpa ${FUNCTION_APP} func ${DEV_DOCKER_IMAGE}
9 |
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/test_runners/prod_func_dev_docker/new_no_bundler.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Get the configuration variables
4 | source "$(dirname "${BASH_SOURCE[0]}")/../helpers/get_config_variables.sh"
5 |
6 | FUNCTION_APP="$(cat "${GLOBAL_CONFIG}" | jq -r '.publish_function_app.prod_func_dev_docker.new_function.no_bundler')"
7 |
8 | "$(dirname "${BASH_SOURCE[0]}")/../../func_tests_core/new_functionapp/no_bundler_test.sh" ${WORKING_DIR}/pfddnnb ${FUNCTION_APP} func ${DEV_DOCKER_IMAGE}
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/test_runners/prod_func_prod_docker/new_no_bundler.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Get the configuration variables
4 | source "$(dirname "${BASH_SOURCE[0]}")/../helpers/get_config_variables.sh"
5 |
6 | FUNCTION_APP="$(cat "${GLOBAL_CONFIG}" | jq -r '.publish_function_app.prod_func_prod_docker.new_function.no_bundler')"
7 |
8 | "$(dirname "${BASH_SOURCE[0]}")/../../func_tests_core/new_functionapp/no_bundler_test.sh" ${WORKING_DIR}/pfpdnnb ${FUNCTION_APP} func ${PROD_DOCKER_IMAGE}
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/test_runners/dev_func_dev_docker/new_packapp.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Get the configuration variables
4 | source "$(dirname "${BASH_SOURCE[0]}")/../helpers/get_config_variables.sh"
5 |
6 | FUNCTION_APP="$(cat "${GLOBAL_CONFIG}" | jq -r '.publish_function_app.dev_func_dev_docker.new_function.packapp')"
7 |
8 | "$(dirname "${BASH_SOURCE[0]}")/../../func_tests_core/new_functionapp/packapp_test.sh" ${WORKING_DIR}/dfddnpa ${FUNCTION_APP} ${FUNC_DEV_DIR}/func ${DEV_DOCKER_IMAGE}
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/test_runners/dev_func_prod_docker/new_packapp.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Get the configuration variables
4 | source "$(dirname "${BASH_SOURCE[0]}")/../helpers/get_config_variables.sh"
5 |
6 | FUNCTION_APP="$(cat "${GLOBAL_CONFIG}" | jq -r '.publish_function_app.dev_func_prod_docker.new_function.packapp')"
7 |
8 | "$(dirname "${BASH_SOURCE[0]}")/../../func_tests_core/new_functionapp/packapp_test.sh" ${WORKING_DIR}/dfpdnpa ${FUNCTION_APP} ${FUNC_DEV_DIR}/func ${PROD_DOCKER_IMAGE}
--------------------------------------------------------------------------------
/tests/servicebus_functions/put_message_return/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "__init__.py",
3 | "disabled": false,
4 |
5 | "bindings": [
6 | {
7 | "type": "httpTrigger",
8 | "direction": "in",
9 | "name": "req"
10 | },
11 | {
12 | "direction": "out",
13 | "name": "$return",
14 | "queueName": "testqueue-return",
15 | "connection": "AzureWebJobsServiceBusConnectionString",
16 | "type": "serviceBus"
17 | }
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/test_runners/dev_func_dev_docker/new_no_bundler.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Get the configuration variables
4 | source "$(dirname "${BASH_SOURCE[0]}")/../helpers/get_config_variables.sh"
5 |
6 | FUNCTION_APP="$(cat "${GLOBAL_CONFIG}" | jq -r '.publish_function_app.dev_func_dev_docker.new_function.no_bundler')"
7 |
8 | "$(dirname "${BASH_SOURCE[0]}")/../../func_tests_core/new_functionapp/no_bundler_test.sh" ${WORKING_DIR}/dfddnnb ${FUNCTION_APP} ${FUNC_DEV_DIR}/func ${DEV_DOCKER_IMAGE}
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/test_runners/prod_func_dev_docker/customer_no_bundler.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Get the configuration variables
4 | source "$(dirname "${BASH_SOURCE[0]}")/../helpers/get_config_variables.sh"
5 |
6 | FUNCTION_APP="$(cat "${GLOBAL_CONFIG}" | jq -r '.publish_function_app.prod_func_dev_docker.customer_churn.no_bundler')"
7 |
8 | "$(dirname "${BASH_SOURCE[0]}")/../../func_tests_core/customer_churn_app/no_bundler_test.sh" ${WORKING_DIR}/pfddcnb ${FUNCTION_APP} func ${DEV_DOCKER_IMAGE}
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/test_runners/prod_func_prod_docker/customer_no_bundler.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Get the configuration variables
4 | source "$(dirname "${BASH_SOURCE[0]}")/../helpers/get_config_variables.sh"
5 |
6 | FUNCTION_APP="$(cat "${GLOBAL_CONFIG}" | jq -r '.publish_function_app.prod_func_prod_docker.customer_churn.no_bundler')"
7 |
8 | "$(dirname "${BASH_SOURCE[0]}")/../../func_tests_core/customer_churn_app/no_bundler_test.sh" ${WORKING_DIR}/pfpdcnb ${FUNCTION_APP} func ${PROD_DOCKER_IMAGE}
--------------------------------------------------------------------------------
/tests/eventgrid_functions/eventgrid_trigger/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "__init__.py",
3 | "disabled": false,
4 |
5 | "bindings": [
6 | {
7 | "type": "eventGridTrigger",
8 | "direction": "in",
9 | "name": "event",
10 | },
11 | {
12 | "type": "blob",
13 | "direction": "out",
14 | "name": "$return",
15 | "connection": "AzureWebJobsStorage",
16 | "path": "python-worker-tests/test-eventgrid-triggered.txt"
17 | }
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/test_runners/dev_func_prod_docker/new_no_bundler.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Get the configuration variables
4 | source "$(dirname "${BASH_SOURCE[0]}")/../helpers/get_config_variables.sh"
5 |
6 | FUNCTION_APP="$(cat "${GLOBAL_CONFIG}" | jq -r '.publish_function_app.dev_func_prod_docker.new_function.no_bundler')"
7 |
8 | "$(dirname "${BASH_SOURCE[0]}")/../../func_tests_core/new_functionapp/no_bundler_test.sh" ${WORKING_DIR}/dfpdnnb ${FUNCTION_APP} ${FUNC_DEV_DIR}/func ${PROD_DOCKER_IMAGE}
--------------------------------------------------------------------------------
/.ci/e2e/running-environments/Docker/start_tests_docker.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # This needs to happen after the docker container is built. This is because the setup uses Docker deamon
4 | # And the /var/run/docker.sock needs to be mounted for the setup script to use Docker.
5 | # This is done when the Docker container is run.
6 | /azure-functions-python-worker/.ci/e2e/publish_tests/test_runners/setup_test_environment.sh
7 |
8 | /azure-functions-python-worker/.ci/e2e/publish_tests/test_runners/run_all_serial.sh
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/test_runners/dev_func_dev_docker/customer_no_bundler.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Get the configuration variables
4 | source "$(dirname "${BASH_SOURCE[0]}")/../helpers/get_config_variables.sh"
5 |
6 | FUNCTION_APP="$(cat "${GLOBAL_CONFIG}" | jq -r '.publish_function_app.dev_func_dev_docker.customer_churn.no_bundler')"
7 |
8 | "$(dirname "${BASH_SOURCE[0]}")/../../func_tests_core/customer_churn_app/no_bundler_test.sh" ${WORKING_DIR}/dfddcnb ${FUNCTION_APP} ${FUNC_DEV_DIR}/func ${DEV_DOCKER_IMAGE}
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/test_runners/prod_func_dev_docker/new_build_native_deps.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Get the configuration variables
4 | source "$(dirname "${BASH_SOURCE[0]}")/../helpers/get_config_variables.sh"
5 |
6 | FUNCTION_APP="$(cat "${GLOBAL_CONFIG}" | jq -r '.publish_function_app.prod_func_dev_docker.new_function.build_native_deps')"
7 |
8 | "$(dirname "${BASH_SOURCE[0]}")/../../func_tests_core/new_functionapp/build_native_deps_test.sh" ${WORKING_DIR}/pfddnbnd ${FUNCTION_APP} func ${DEV_DOCKER_IMAGE}
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/test_runners/dev_func_prod_docker/customer_no_bundler.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Get the configuration variables
4 | source "$(dirname "${BASH_SOURCE[0]}")/../helpers/get_config_variables.sh"
5 |
6 | FUNCTION_APP="$(cat "${GLOBAL_CONFIG}" | jq -r '.publish_function_app.dev_func_prod_docker.customer_churn.no_bundler')"
7 |
8 | "$(dirname "${BASH_SOURCE[0]}")/../../func_tests_core/customer_churn_app/no_bundler_test.sh" ${WORKING_DIR}/dfpdcnb ${FUNCTION_APP} ${FUNC_DEV_DIR}/func ${PROD_DOCKER_IMAGE}
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/test_runners/prod_func_prod_docker/new_build_native_deps.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Get the configuration variables
4 | source "$(dirname "${BASH_SOURCE[0]}")/../helpers/get_config_variables.sh"
5 |
6 | FUNCTION_APP="$(cat "${GLOBAL_CONFIG}" | jq -r '.publish_function_app.prod_func_prod_docker.new_function.build_native_deps')"
7 |
8 | "$(dirname "${BASH_SOURCE[0]}")/../../func_tests_core/new_functionapp/build_native_deps_test.sh" ${WORKING_DIR}/pfpdnbnd ${FUNCTION_APP} func ${PROD_DOCKER_IMAGE}
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/test_runners/prod_func_dev_docker/customer_build_native_deps.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Get the configuration variables
4 | source "$(dirname "${BASH_SOURCE[0]}")/../helpers/get_config_variables.sh"
5 |
6 | FUNCTION_APP="$(cat "${GLOBAL_CONFIG}" | jq -r '.publish_function_app.prod_func_dev_docker.customer_churn.build_native_deps')"
7 |
8 | "$(dirname "${BASH_SOURCE[0]}")/../../func_tests_core/customer_churn_app/build_native_deps_test.sh" ${WORKING_DIR}/pfddcbnd ${FUNCTION_APP} func ${DEV_DOCKER_IMAGE}
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/test_runners/dev_func_dev_docker/new_build_native_deps.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Get the configuration variables
4 | source "$(dirname "${BASH_SOURCE[0]}")/../helpers/get_config_variables.sh"
5 |
6 | FUNCTION_APP="$(cat "${GLOBAL_CONFIG}" | jq -r '.publish_function_app.dev_func_dev_docker.new_function.build_native_deps')"
7 |
8 | "$(dirname "${BASH_SOURCE[0]}")/../../func_tests_core/new_functionapp/build_native_deps_test.sh" ${WORKING_DIR}/dfddnbnd ${FUNCTION_APP} ${FUNC_DEV_DIR}/func ${DEV_DOCKER_IMAGE}
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/test_runners/dev_func_prod_docker/new_build_native_deps.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Get the configuration variables
4 | source "$(dirname "${BASH_SOURCE[0]}")/../helpers/get_config_variables.sh"
5 |
6 | FUNCTION_APP="$(cat "${GLOBAL_CONFIG}" | jq -r '.publish_function_app.dev_func_prod_docker.new_function.build_native_deps')"
7 |
8 | "$(dirname "${BASH_SOURCE[0]}")/../../func_tests_core/new_functionapp/build_native_deps_test.sh" ${WORKING_DIR}/dfpdnbnd ${FUNCTION_APP} ${FUNC_DEV_DIR}/func ${PROD_DOCKER_IMAGE}
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/test_runners/prod_func_prod_docker/customer_build_native_deps.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Get the configuration variables
4 | source "$(dirname "${BASH_SOURCE[0]}")/../helpers/get_config_variables.sh"
5 |
6 | FUNCTION_APP="$(cat "${GLOBAL_CONFIG}" | jq -r '.publish_function_app.prod_func_prod_docker.customer_churn.build_native_deps')"
7 |
8 | "$(dirname "${BASH_SOURCE[0]}")/../../func_tests_core/customer_churn_app/build_native_deps_test.sh" ${WORKING_DIR}/pfpdcbnd ${FUNCTION_APP} func ${PROD_DOCKER_IMAGE}
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/test_runners/dev_func_dev_docker/customer_build_native_deps.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Get the configuration variables
4 | source "$(dirname "${BASH_SOURCE[0]}")/../helpers/get_config_variables.sh"
5 |
6 | FUNCTION_APP="$(cat "${GLOBAL_CONFIG}" | jq -r '.publish_function_app.dev_func_dev_docker.customer_churn.build_native_deps')"
7 |
8 | "$(dirname "${BASH_SOURCE[0]}")/../../func_tests_core/customer_churn_app/build_native_deps_test.sh" ${WORKING_DIR}/dfddcbnd ${FUNCTION_APP} ${FUNC_DEV_DIR}/func ${DEV_DOCKER_IMAGE}
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/test_runners/dev_func_prod_docker/customer_build_native_deps.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Get the configuration variables
4 | source "$(dirname "${BASH_SOURCE[0]}")/../helpers/get_config_variables.sh"
5 |
6 | FUNCTION_APP="$(cat "${GLOBAL_CONFIG}" | jq -r '.publish_function_app.dev_func_prod_docker.customer_churn.build_native_deps')"
7 |
8 | "$(dirname "${BASH_SOURCE[0]}")/../../func_tests_core/customer_churn_app/build_native_deps_test.sh" ${WORKING_DIR}/dfpdcbnd ${FUNCTION_APP} ${FUNC_DEV_DIR}/func ${PROD_DOCKER_IMAGE}
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | """Bootstrap for '$ python setup.py test' command."""
2 |
3 | import os.path
4 | import sys
5 | import unittest
6 | import unittest.runner
7 |
8 |
9 | def suite():
10 | test_loader = unittest.TestLoader()
11 | test_suite = test_loader.discover(
12 | os.path.dirname(__file__), pattern='test_*.py')
13 | return test_suite
14 |
15 |
16 | if __name__ == '__main__':
17 | runner = unittest.runner.TextTestRunner()
18 | result = runner.run(suite())
19 | sys.exit(not result.wasSuccessful())
20 |
--------------------------------------------------------------------------------
/tests/http_functions/return_request/main.py:
--------------------------------------------------------------------------------
1 | import json
2 | import hashlib
3 |
4 | import azure.functions
5 |
6 |
7 | def main(req: azure.functions.HttpRequest):
8 | params = dict(req.params)
9 | params.pop('code', None)
10 | body = req.get_body()
11 | return json.dumps({
12 | 'method': req.method,
13 | 'url': req.url,
14 | 'headers': dict(req.headers),
15 | 'params': params,
16 | 'get_body': body.decode(),
17 | 'body_hash': hashlib.sha256(body).hexdigest(),
18 | })
19 |
--------------------------------------------------------------------------------
/tests/queue_functions/put_queue/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 |
5 | "bindings": [
6 | {
7 | "type": "httpTrigger",
8 | "direction": "in",
9 | "name": "req"
10 | },
11 | {
12 | "direction": "out",
13 | "name": "msg",
14 | "queueName": "testqueue",
15 | "connection": "AzureWebJobsStorage",
16 | "type": "queue"
17 | },
18 | {
19 | "direction": "out",
20 | "name": "$return",
21 | "type": "http"
22 | }
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/tests/queue_functions/queue_trigger/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 |
5 | "bindings": [
6 | {
7 | "type": "queueTrigger",
8 | "direction": "in",
9 | "name": "msg",
10 | "queueName": "testqueue",
11 | "connection": "AzureWebJobsStorage",
12 | },
13 | {
14 | "type": "blob",
15 | "direction": "out",
16 | "name": "$return",
17 | "connection": "AzureWebJobsStorage",
18 | "path": "python-worker-tests/test-queue-blob.txt"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/dev_func_setup/setup.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 | if [[ -z "$1" ]]
5 | then
6 | echo "Missing name of the directory to install func CLI"
7 | echo "usage ./script "
8 | exit 1
9 | fi
10 |
11 | if [[ -z "$2" ]]
12 | then
13 | echo "Missing the URL to download functions"
14 | fi
15 |
16 | wget $2
17 | echo "Retrieving func CLI version: "
18 | curl https://functionsclibuilds.blob.core.windows.net/builds/2/latest/version.txt
19 | echo ""
20 | unzip *.zip -d "$1"
21 | rm -rf *.zip
22 | chmod a+x "$1"/func
--------------------------------------------------------------------------------
/tests/blob_functions/get_blob_str/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "blob",
12 | "direction": "in",
13 | "name": "file",
14 | "connection": "AzureWebJobsStorage",
15 | "path": "python-worker-tests/test-str.txt"
16 | },
17 | {
18 | "type": "http",
19 | "direction": "out",
20 | "name": "$return",
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/tests/blob_functions/put_blob_str/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "blob",
12 | "direction": "out",
13 | "name": "file",
14 | "connection": "AzureWebJobsStorage",
15 | "path": "python-worker-tests/test-str.txt"
16 | },
17 | {
18 | "type": "http",
19 | "direction": "out",
20 | "name": "$return",
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/tests/blob_functions/get_blob_bytes/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "blob",
12 | "direction": "in",
13 | "name": "file",
14 | "connection": "AzureWebJobsStorage",
15 | "path": "python-worker-tests/test-bytes.txt"
16 | },
17 | {
18 | "type": "http",
19 | "direction": "out",
20 | "name": "$return",
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/tests/blob_functions/get_blob_return/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "blob",
12 | "direction": "in",
13 | "name": "file",
14 | "connection": "AzureWebJobsStorage",
15 | "path": "python-worker-tests/test-return.txt"
16 | },
17 | {
18 | "type": "http",
19 | "direction": "out",
20 | "name": "$return",
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/tests/blob_functions/put_blob_bytes/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "blob",
12 | "direction": "out",
13 | "name": "file",
14 | "connection": "AzureWebJobsStorage",
15 | "path": "python-worker-tests/test-bytes.txt"
16 | },
17 | {
18 | "type": "http",
19 | "direction": "out",
20 | "name": "$return",
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/tests/blob_functions/get_blob_filelike/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "blob",
12 | "direction": "in",
13 | "name": "file",
14 | "connection": "AzureWebJobsStorage",
15 | "path": "python-worker-tests/test-filelike.txt"
16 | },
17 | {
18 | "type": "http",
19 | "direction": "out",
20 | "name": "$return",
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/tests/blob_functions/put_blob_filelike/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "blob",
12 | "direction": "out",
13 | "name": "file",
14 | "connection": "AzureWebJobsStorage",
15 | "path": "python-worker-tests/test-filelike.txt"
16 | },
17 | {
18 | "type": "http",
19 | "direction": "out",
20 | "name": "$return",
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/tests/queue_functions/get_queue_blob/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "blob",
12 | "direction": "in",
13 | "name": "file",
14 | "connection": "AzureWebJobsStorage",
15 | "path": "python-worker-tests/test-queue-blob.txt"
16 | },
17 | {
18 | "type": "http",
19 | "direction": "out",
20 | "name": "$return",
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/tests/blob_functions/get_blob_triggered/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "blob",
12 | "direction": "in",
13 | "name": "file",
14 | "connection": "AzureWebJobsStorage",
15 | "path": "python-worker-tests/test-blob-triggered.txt"
16 | },
17 | {
18 | "type": "http",
19 | "direction": "out",
20 | "name": "$return",
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/tests/blob_functions/put_blob_trigger/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "blob",
12 | "direction": "out",
13 | "name": "file",
14 | "connection": "AzureWebJobsStorage",
15 | "path": "python-worker-tests/test-blob-trigger.txt"
16 | },
17 | {
18 | "type": "http",
19 | "direction": "out",
20 | "name": "$return",
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/tests/queue_functions/queue_trigger_return/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 |
5 | "bindings": [
6 | {
7 | "type": "queueTrigger",
8 | "direction": "in",
9 | "name": "msg",
10 | "queueName": "testqueue-return",
11 | "connection": "AzureWebJobsStorage",
12 | },
13 | {
14 | "type": "blob",
15 | "direction": "out",
16 | "name": "$return",
17 | "connection": "AzureWebJobsStorage",
18 | "path": "python-worker-tests/test-queue-blob-return.txt"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/tests/blob_functions/blob_trigger/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "blobTrigger",
7 | "direction": "in",
8 | "name": "file",
9 | "connection": "AzureWebJobsStorage",
10 | "path": "python-worker-tests/test-blob-trigger.txt"
11 | },
12 | {
13 | "type": "blob",
14 | "direction": "out",
15 | "name": "$return",
16 | "connection": "AzureWebJobsStorage",
17 | "path": "python-worker-tests/test-blob-triggered.txt"
18 | },
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/tests/queue_functions/put_queue_multiple_out/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 |
5 | "bindings": [
6 | {
7 | "type": "httpTrigger",
8 | "direction": "in",
9 | "name": "req"
10 | },
11 | {
12 | "name": "resp",
13 | "type": "http",
14 | "direction": "out"
15 | },
16 | {
17 | "direction": "out",
18 | "name": "msg",
19 | "queueName": "testqueue-return-multiple-outparam",
20 | "connection": "AzureWebJobsStorage",
21 | "type": "queue"
22 | }
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/tests/servicebus_functions/put_message/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "__init__.py",
3 | "disabled": false,
4 |
5 | "bindings": [
6 | {
7 | "type": "httpTrigger",
8 | "direction": "in",
9 | "name": "req"
10 | },
11 | {
12 | "direction": "out",
13 | "name": "msg",
14 | "queueName": "testqueue",
15 | "connection": "AzureWebJobsServiceBusConnectionString",
16 | "type": "serviceBus"
17 | },
18 | {
19 | "direction": "out",
20 | "name": "$return",
21 | "type": "http"
22 | }
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/tests/queue_functions/get_queue_blob_return/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "blob",
12 | "direction": "in",
13 | "name": "file",
14 | "connection": "AzureWebJobsStorage",
15 | "path": "python-worker-tests/test-queue-blob-return.txt"
16 | },
17 | {
18 | "type": "http",
19 | "direction": "out",
20 | "name": "$return",
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/tests/cosmosdb_functions/get_cosmosdb_triggered/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "blob",
12 | "direction": "in",
13 | "name": "file",
14 | "connection": "AzureWebJobsStorage",
15 | "path": "python-worker-tests/test-cosmosdb-triggered.txt"
16 | },
17 | {
18 | "type": "http",
19 | "direction": "out",
20 | "name": "$return",
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/tests/eventgrid_functions/get_eventgrid_triggered/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "blob",
12 | "direction": "in",
13 | "name": "file",
14 | "connection": "AzureWebJobsStorage",
15 | "path": "python-worker-tests/test-eventgrid-triggered.txt"
16 | },
17 | {
18 | "type": "http",
19 | "direction": "out",
20 | "name": "$return",
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/tests/eventhub_functions/eventhub_output/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "__init__.py",
3 | "disabled": false,
4 |
5 | "bindings": [
6 | {
7 | "type": "httpTrigger",
8 | "direction": "in",
9 | "name": "req"
10 | },
11 | {
12 | "type": "eventHub",
13 | "name": "event",
14 | "direction": "out",
15 | "eventHubName": "python-worker-ci",
16 | "connection": "AzureWebJobsEventHubConnectionString",
17 | },
18 | {
19 | "direction": "out",
20 | "name": "$return",
21 | "type": "http"
22 | }
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/tests/eventhub_functions/get_eventhub_triggered/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "blob",
12 | "direction": "in",
13 | "name": "file",
14 | "connection": "AzureWebJobsStorage",
15 | "path": "python-worker-tests/test-eventhub-triggered.txt"
16 | },
17 | {
18 | "type": "http",
19 | "direction": "out",
20 | "name": "$return",
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/tests/blob_functions/get_blob_as_str/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "blob",
12 | "direction": "in",
13 | "name": "file",
14 | "dataType": "string",
15 | "connection": "AzureWebJobsStorage",
16 | "path": "python-worker-tests/test-str.txt"
17 | },
18 | {
19 | "type": "http",
20 | "direction": "out",
21 | "name": "$return"
22 | }
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/tests/blob_functions/get_blob_as_bytes/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "blob",
12 | "direction": "in",
13 | "name": "file",
14 | "dataType": "binary",
15 | "connection": "AzureWebJobsStorage",
16 | "path": "python-worker-tests/test-bytes.txt"
17 | },
18 | {
19 | "type": "http",
20 | "direction": "out",
21 | "name": "$return"
22 | }
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/tests/queue_functions/get_queue_blob_message_return/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "blob",
12 | "direction": "in",
13 | "name": "file",
14 | "connection": "AzureWebJobsStorage",
15 | "path": "python-worker-tests/test-queue-blob-message-return.txt"
16 | },
17 | {
18 | "type": "http",
19 | "direction": "out",
20 | "name": "$return",
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/tests/queue_functions/queue_trigger_message_return/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "disabled": false,
4 |
5 | "bindings": [
6 | {
7 | "type": "queueTrigger",
8 | "direction": "in",
9 | "name": "msg",
10 | "queueName": "testqueue-message-return",
11 | "connection": "AzureWebJobsStorage",
12 | },
13 | {
14 | "type": "blob",
15 | "direction": "out",
16 | "name": "$return",
17 | "connection": "AzureWebJobsStorage",
18 | "path": "python-worker-tests/test-queue-blob-message-return.txt"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/tests/servicebus_functions/get_servicebus_triggered/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "__init__.py",
3 | "disabled": false,
4 | "bindings": [
5 | {
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req"
9 | },
10 | {
11 | "type": "blob",
12 | "direction": "in",
13 | "name": "file",
14 | "connection": "AzureWebJobsStorage",
15 | "path": "python-worker-tests/test-servicebus-triggered.txt"
16 | },
17 | {
18 | "type": "http",
19 | "direction": "out",
20 | "name": "$return",
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/tests/eventhub_functions/eventhub_trigger/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "__init__.py",
3 | "disabled": false,
4 |
5 | "bindings": [
6 | {
7 | "type": "eventHubTrigger",
8 | "name": "event",
9 | "direction": "in",
10 | "eventHubName": "python-worker-ci",
11 | "connection": "AzureWebJobsEventHubConnectionString",
12 | },
13 | {
14 | "type": "blob",
15 | "direction": "out",
16 | "name": "$return",
17 | "connection": "AzureWebJobsStorage",
18 | "path": "python-worker-tests/test-eventhub-triggered.txt"
19 | },
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/tests/servicebus_functions/servicebus_trigger/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "__init__.py",
3 | "disabled": false,
4 |
5 | "bindings": [
6 | {
7 | "type": "serviceBusTrigger",
8 | "direction": "in",
9 | "name": "msg",
10 | "queueName": "testqueue",
11 | "connection": "AzureWebJobsServiceBusConnectionString",
12 | },
13 | {
14 | "type": "blob",
15 | "direction": "out",
16 | "name": "$return",
17 | "connection": "AzureWebJobsStorage",
18 | "path": "python-worker-tests/test-servicebus-triggered.txt"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [tool:pytest]
2 | addopts = --capture=no --assert=plain --strict --tb native
3 | testpaths = tests
4 |
5 | [mypy]
6 | python_version = 3.6
7 | check_untyped_defs = True
8 | warn_redundant_casts = True
9 | warn_unused_ignores = True
10 | warn_unused_configs = True
11 | strict_optional = True
12 | warn_return_any = True
13 | disallow_subclassing_any = False
14 | ignore_missing_imports = True
15 |
16 | [mypy-azure.functions_worker.aio_compat]
17 | ignore_errors = True
18 |
19 | [mypy-azure.functions_worker.protos.*]
20 | ignore_errors = True
21 |
22 | [mypy-azure.functions_worker.typing_inspect]
23 | ignore_errors = True
24 |
--------------------------------------------------------------------------------
/azure/functions_worker/protos/__init__.py:
--------------------------------------------------------------------------------
1 | from .FunctionRpc_pb2_grpc import ( # NoQA
2 | FunctionRpcStub,
3 | FunctionRpcServicer,
4 | add_FunctionRpcServicer_to_server)
5 |
6 | from .FunctionRpc_pb2 import ( # NoQA
7 | StreamingMessage,
8 | StartStream,
9 | WorkerInitRequest,
10 | WorkerInitResponse,
11 | RpcFunctionMetadata,
12 | FunctionLoadRequest,
13 | FunctionLoadResponse,
14 | InvocationRequest,
15 | InvocationResponse,
16 | WorkerHeartbeat,
17 | BindingInfo,
18 | StatusResult,
19 | RpcException,
20 | ParameterBinding,
21 | TypedData,
22 | RpcHttp,
23 | RpcLog)
24 |
--------------------------------------------------------------------------------
/azure/functions_worker/bindings/context.py:
--------------------------------------------------------------------------------
1 | from azure.functions import _abc as azf_abc
2 |
3 |
4 | class Context(azf_abc.Context):
5 |
6 | def __init__(self, func_name: str, func_dir: str,
7 | invocation_id: str) -> None:
8 | self.__func_name = func_name
9 | self.__func_dir = func_dir
10 | self.__invocation_id = invocation_id
11 |
12 | @property
13 | def invocation_id(self) -> str:
14 | return self.__invocation_id
15 |
16 | @property
17 | def function_name(self) -> str:
18 | return self.__func_name
19 |
20 | @property
21 | def function_directory(self) -> str:
22 | return self.__func_dir
23 |
--------------------------------------------------------------------------------
/tests/http_functions/async_logging/main.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import logging
3 |
4 | import azure.functions
5 |
6 |
7 | logger = logging.getLogger('my function')
8 |
9 |
10 | async def main(req: azure.functions.HttpRequest):
11 | logger.info('hello %s', 'info')
12 |
13 | await asyncio.sleep(0.1)
14 |
15 | # Create a nested task to check if invocation_id is still
16 | # logged correctly.
17 | await asyncio.ensure_future(nested())
18 |
19 | await asyncio.sleep(0.1)
20 |
21 | return 'OK-async'
22 |
23 |
24 | async def nested():
25 | try:
26 | 1 / 0
27 | except ZeroDivisionError:
28 | logger.error('and another error', exc_info=True)
29 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | SPHINXPROJ = AzureFunctionsforPython
8 | SOURCEDIR = .
9 | BUILDDIR = _build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/test_runners/setup_container_environment.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 | apt-get update
5 | apt-get install azure-functions-core-tools jq git unzip gettext -y
6 | curl -sSL https://get.docker.com/ | sh
7 |
8 | # Install azure CLI
9 | apt-get install apt-transport-https lsb-release software-properties-common dirmngr -y
10 | AZ_REPO=$(lsb_release -cs)
11 | echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ $AZ_REPO main" | tee /etc/apt/sources.list.d/azure-cli.list
12 | apt-key --keyring /etc/apt/trusted.gpg.d/Microsoft.gpg adv --keyserver packages.microsoft.com --recv-keys BC528686B50D79E339D3721CEB3E94ADBE1229CF
13 | apt-get update
14 | apt-get install azure-cli -y
--------------------------------------------------------------------------------
/tests/cosmosdb_functions/put_document/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "__init__.py",
3 | "disabled": false,
4 |
5 | "bindings": [
6 | {
7 | "type": "httpTrigger",
8 | "direction": "in",
9 | "name": "req"
10 | },
11 | {
12 | "direction": "out",
13 | "type": "cosmosDB",
14 | "name": "doc",
15 | "databaseName": "test",
16 | "collectionName": "items",
17 | "leaseCollectionName": "leases",
18 | "createLeaseCollectionIfNotExists": true,
19 | "connectionStringSetting": "AzureWebJobsCosmosDBConnectionString",
20 | "createIfNotExists": true
21 | },
22 | {
23 | "direction": "out",
24 | "name": "$return",
25 | "type": "http"
26 | }
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/tests/cosmosdb_functions/cosmosdb_input/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "__init__.py",
3 | "disabled": false,
4 |
5 | "bindings": [
6 | {
7 | "type": "httpTrigger",
8 | "direction": "in",
9 | "name": "req"
10 | },
11 | {
12 | "direction": "in",
13 | "type": "cosmosDB",
14 | "name": "docs",
15 | "databaseName": "test",
16 | "collectionName": "items",
17 | "id": "cosmosdb-input-test",
18 | "leaseCollectionName": "leases",
19 | "connectionStringSetting": "AzureWebJobsCosmosDBConnectionString",
20 | "createLeaseCollectionIfNotExists": true
21 | },
22 | {
23 | "type": "http",
24 | "direction": "out",
25 | "name": "$return"
26 | }
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/tests/cosmosdb_functions/cosmosdb_trigger/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "__init__.py",
3 | "disabled": false,
4 |
5 | "bindings": [
6 | {
7 | "direction": "in",
8 | "type": "cosmosDBTrigger",
9 | "name": "docs",
10 | "databaseName": "test",
11 | "collectionName": "items",
12 | "id": "cosmosdb-trigger-test",
13 | "leaseCollectionName": "leases",
14 | "connectionStringSetting": "AzureWebJobsCosmosDBConnectionString",
15 | "createLeaseCollectionIfNotExists": true
16 | },
17 | {
18 | "type": "blob",
19 | "direction": "out",
20 | "name": "$return",
21 | "connection": "AzureWebJobsStorage",
22 | "path": "python-worker-tests/test-cosmosdb-triggered.txt"
23 | }
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/tests/queue_functions/queue_trigger/main.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import azure.functions as azf
4 |
5 |
6 | def main(msg: azf.QueueMessage) -> str:
7 | result = json.dumps({
8 | 'id': msg.id,
9 | 'body': msg.get_body().decode('utf-8'),
10 | 'expiration_time': (msg.expiration_time.isoformat()
11 | if msg.expiration_time else None),
12 | 'insertion_time': (msg.insertion_time.isoformat()
13 | if msg.insertion_time else None),
14 | 'time_next_visible': (msg.time_next_visible.isoformat()
15 | if msg.time_next_visible else None),
16 | 'pop_receipt': msg.pop_receipt,
17 | 'dequeue_count': msg.dequeue_count
18 | })
19 |
20 | return result
21 |
--------------------------------------------------------------------------------
/pack/Microsoft.Azure.Functions.PythonWorkerRunEnvironments.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
17 |
18 |
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/test_runners/setup_test_environment.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 | # Get the configuration variables
5 | source "$(dirname "${BASH_SOURCE[0]}")/helpers/get_config_variables.sh"
6 |
7 | # Login to the Service Principal Azure Account
8 | az login --service-principal -u ${SP_USER_NAME} -p ${SP_PASSWORD} --tenant ${SP_TENANT}
9 |
10 | # Update the ACR Docker Image with the dev branch of Docker image and python worker
11 | chmod a+x $(dirname "${BASH_SOURCE[0]}")/../dev_docker_setup/setup.sh
12 | $(dirname "${BASH_SOURCE[0]}")/../dev_docker_setup/setup.sh ${ACR_NAME} ${DEV_IMAGE_NAME} ${DOCKER_WORKING_DIR}
13 |
14 | # Setup the dev func executables
15 | chmod a+x $(dirname "${BASH_SOURCE[0]}")/../dev_func_setup/setup.sh
16 | $(dirname "${BASH_SOURCE[0]}")/../dev_func_setup/setup.sh ${FUNC_DEV_DIR} ${FUNC_DEV_URL}
--------------------------------------------------------------------------------
/tests/servicebus_functions/servicebus_trigger/__init__.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import azure.functions as azf
4 |
5 |
6 | def main(msg: azf.ServiceBusMessage) -> str:
7 | result = json.dumps({
8 | 'message_id': msg.message_id,
9 | 'body': msg.get_body().decode('utf-8'),
10 | 'content_type': msg.content_type,
11 | 'expiration_time': msg.expiration_time,
12 | 'label': msg.label,
13 | 'partition_key': msg.partition_key,
14 | 'reply_to': msg.reply_to,
15 | 'reply_to_session_id': msg.reply_to_session_id,
16 | 'scheduled_enqueue_time': msg.scheduled_enqueue_time,
17 | 'session_id': msg.session_id,
18 | 'time_to_live': msg.time_to_live,
19 | 'to': msg.to,
20 | 'user_properties': msg.user_properties,
21 | })
22 |
23 | return result
24 |
--------------------------------------------------------------------------------
/azure/functions_worker/bindings/__init__.py:
--------------------------------------------------------------------------------
1 | from .context import Context
2 | from .meta import check_input_type_annotation
3 | from .meta import check_output_type_annotation
4 | from .meta import is_binding, is_trigger_binding
5 | from .meta import from_incoming_proto, to_outgoing_proto
6 | from .out import Out
7 |
8 | # Import type implementations and converters
9 | # to get them registered and available:
10 | from . import blob # NoQA
11 | from . import cosmosdb # NoQA
12 | from . import eventgrid # NoQA
13 | from . import eventhub # NoQA
14 | from . import http # NoQA
15 | from . import queue # NoQA
16 | from . import servicebus # NoQA
17 | from . import timer # NoQA
18 |
19 |
20 | __all__ = (
21 | 'Out', 'Context',
22 | 'is_binding', 'is_trigger_binding',
23 | 'check_input_type_annotation', 'check_output_type_annotation',
24 | 'from_incoming_proto', 'to_outgoing_proto',
25 | )
26 |
--------------------------------------------------------------------------------
/pack/Microsoft.Azure.Functions.PythonWorkerRunEnvironments.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Microsoft.Azure.Functions.PythonWorkerRunEnvironments
5 | 1.0.0-beta0
6 | Microsoft
7 | Microsoft
8 | false
9 | Microsoft Azure Functions Python Worker Run Environments
10 | © .NET Foundation. All rights reserved.
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=.
11 | set BUILDDIR=_build
12 | set SPHINXPROJ=AzureFunctionsforPython
13 |
14 | if "%1" == "" goto help
15 |
16 | %SPHINXBUILD% >NUL 2>NUL
17 | if errorlevel 9009 (
18 | echo.
19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
20 | echo.installed, then set the SPHINXBUILD environment variable to point
21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
22 | echo.may add the Sphinx directory to PATH.
23 | echo.
24 | echo.If you don't have Sphinx installed, grab it from
25 | echo.http://sphinx-doc.org/
26 | exit /b 1
27 | )
28 |
29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
30 | goto end
31 |
32 | :help
33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
34 |
35 | :end
36 | popd
37 |
--------------------------------------------------------------------------------
/pack/templates/win_env_gen.yml:
--------------------------------------------------------------------------------
1 | parameters:
2 | jobName: 'WindowsEnvGen'
3 | dependency: 'Tests'
4 | vmImage: 'vs2017-win2016'
5 | pythonVersion: '3.6'
6 | artifactName: 'Windows'
7 |
8 | jobs:
9 | - job: ${{ parameters.jobName }}
10 | dependsOn: ${{ parameters.dependency }}
11 | pool:
12 | vmImage: ${{ parameters.vmImage }}
13 | steps:
14 | - task: UsePythonVersion@0
15 | inputs:
16 | versionSpec: ${{ parameters.pythonVersion }}
17 | addToPath: true
18 | - task: PowerShell@2
19 | inputs:
20 | filePath: 'pack\scripts\win_deps.ps1'
21 | - task: CopyFiles@2
22 | inputs:
23 | contents: |
24 | python\*
25 | targetFolder: '$(Build.ArtifactStagingDirectory)'
26 | flattenFolders: true
27 | - task: CopyFiles@2
28 | inputs:
29 | contents: deps\**\*
30 | targetFolder: '$(Build.ArtifactStagingDirectory)'
31 | - task: PublishBuildArtifacts@1
32 | inputs:
33 | pathtoPublish: '$(Build.ArtifactStagingDirectory)'
34 | artifactName: ${{ parameters.artifactName }}
--------------------------------------------------------------------------------
/tests/test_loader.py:
--------------------------------------------------------------------------------
1 | from azure.functions_worker import testutils
2 |
3 |
4 | class TestLoader(testutils.WebHostTestCase):
5 |
6 | @classmethod
7 | def get_script_dir(cls):
8 | return 'load_functions'
9 |
10 | def test_loader_simple(self):
11 | r = self.webhost.request('GET', 'simple')
12 | self.assertEqual(r.status_code, 200)
13 | self.assertEqual(r.text, '__app__.simple.main')
14 |
15 | def test_loader_custom_entrypoint(self):
16 | r = self.webhost.request('GET', 'entrypoint')
17 | self.assertEqual(r.status_code, 200)
18 | self.assertEqual(r.text, '__app__.entrypoint.main')
19 |
20 | def test_loader_subdir(self):
21 | r = self.webhost.request('GET', 'subdir')
22 | self.assertEqual(r.status_code, 200)
23 | self.assertEqual(r.text, '__app__.subdir.sub.main')
24 |
25 | def test_loader_relimport(self):
26 | r = self.webhost.request('GET', 'relimport')
27 | self.assertEqual(r.status_code, 200)
28 | self.assertEqual(r.text, '__app__.relimport.relative')
29 |
--------------------------------------------------------------------------------
/pack/templates/nix_env_gen.yml:
--------------------------------------------------------------------------------
1 | parameters:
2 | jobName: 'LinuxEnvGen'
3 | dependency: 'Tests'
4 | vmImage: 'ubuntu-16.04'
5 | pythonVersion: '3.6'
6 | artifactName: 'Linux'
7 |
8 | jobs:
9 | - job: ${{ parameters.jobName }}
10 | dependsOn: ${{ parameters.dependency }}
11 | pool:
12 | vmImage: ${{ parameters.vmImage }}
13 | steps:
14 | - task: UsePythonVersion@0
15 | inputs:
16 | versionSpec: ${{ parameters.pythonVersion }}
17 | addToPath: true
18 | - task: ShellScript@2
19 | inputs:
20 | disableAutoCwd: true
21 | scriptPath: 'pack/scripts/nix_deps.sh'
22 | - task: CopyFiles@2
23 | inputs:
24 | contents: |
25 | python/*
26 | targetFolder: '$(Build.ArtifactStagingDirectory)'
27 | flattenFolders: true
28 | - task: CopyFiles@2
29 | inputs:
30 | contents: deps/**/*
31 | targetFolder: '$(Build.ArtifactStagingDirectory)'
32 | - task: PublishBuildArtifacts@1
33 | inputs:
34 | pathtoPublish: '$(Build.ArtifactStagingDirectory)'
35 | artifactName: ${{ parameters.artifactName }}
--------------------------------------------------------------------------------
/tests/test_servicebus_functions.py:
--------------------------------------------------------------------------------
1 | import json
2 | import time
3 |
4 | from azure.functions_worker import testutils
5 |
6 |
7 | class TestServiceBusFunctions(testutils.WebHostTestCase):
8 |
9 | @classmethod
10 | def get_script_dir(cls):
11 | return 'servicebus_functions'
12 |
13 | def test_servicebus_basic(self):
14 | data = str(round(time.time()))
15 | r = self.webhost.request('POST', 'put_message',
16 | data=data)
17 | self.assertEqual(r.status_code, 200)
18 | self.assertEqual(r.text, 'OK')
19 |
20 | max_retries = 10
21 |
22 | for try_no in range(max_retries):
23 | # wait for trigger to process the queue item
24 | time.sleep(1)
25 |
26 | try:
27 | r = self.webhost.request('GET', 'get_servicebus_triggered')
28 | self.assertEqual(r.status_code, 200)
29 | msg = r.json()
30 | self.assertEqual(msg['body'], data)
31 | except (AssertionError, json.JSONDecodeError):
32 | if try_no == max_retries - 1:
33 | raise
34 | else:
35 | break
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation. All rights reserved.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE
22 |
--------------------------------------------------------------------------------
/azure/functions_worker/protos/_src/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation. All rights reserved.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE
22 |
--------------------------------------------------------------------------------
/azure/functions_worker/bindings/timer.py:
--------------------------------------------------------------------------------
1 | import json
2 | import typing
3 |
4 | from azure.functions import _abc as azf_abc
5 |
6 | from . import meta
7 | from .. import protos
8 |
9 |
10 | class TimerRequest(azf_abc.TimerRequest):
11 |
12 | def __init__(self, *, past_due: bool) -> None:
13 | self.__past_due = past_due
14 |
15 | @property
16 | def past_due(self):
17 | return self.__past_due
18 |
19 |
20 | class TimerRequestConverter(meta.InConverter,
21 | binding='timerTrigger', trigger=True):
22 |
23 | @classmethod
24 | def check_input_type_annotation(
25 | cls, pytype: type, datatype: protos.BindingInfo.DataType) -> bool:
26 | if datatype is protos.BindingInfo.undefined:
27 | return issubclass(pytype, azf_abc.TimerRequest)
28 | else:
29 | return False
30 |
31 | @classmethod
32 | def from_proto(cls, data: protos.TypedData, *,
33 | pytype: typing.Optional[type],
34 | trigger_metadata) -> typing.Any:
35 | if data.WhichOneof('data') != 'json':
36 | raise NotImplementedError
37 |
38 | info = json.loads(data.json)
39 | return TimerRequest(
40 | past_due=info.get('IsPastDue', False))
41 |
--------------------------------------------------------------------------------
/tests/test_eventhub_functions.py:
--------------------------------------------------------------------------------
1 | import json
2 | import time
3 |
4 | from azure.functions_worker import testutils
5 |
6 |
7 | class TestEventHubFunctions(testutils.WebHostTestCase):
8 |
9 | @classmethod
10 | def get_script_dir(cls):
11 | return 'eventhub_functions'
12 |
13 | def test_eventhub_trigger(self):
14 | data = str(round(time.time()))
15 | doc = {'id': data}
16 | r = self.webhost.request('POST', 'eventhub_output',
17 | data=json.dumps(doc))
18 | self.assertEqual(r.status_code, 200)
19 | self.assertEqual(r.text, 'OK')
20 |
21 | max_retries = 10
22 |
23 | for try_no in range(max_retries):
24 | # Allow trigger to fire.
25 | time.sleep(2)
26 |
27 | try:
28 | # Check that the trigger has fired.
29 | r = self.webhost.request('GET', 'get_eventhub_triggered')
30 | self.assertEqual(r.status_code, 200)
31 | response = r.json()
32 |
33 | self.assertEqual(
34 | response,
35 | doc
36 | )
37 | except AssertionError as e:
38 | if try_no == max_retries - 1:
39 | raise
40 | else:
41 | break
42 |
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/test_runners/helpers/helper.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | RESET='\033[0m' # No Color
4 | YELLOW='\033[1;33m'
5 | ORANGE='\033[0;33m'
6 | RED='\033[0;31m'
7 |
8 |
9 | yellow() {
10 | echo -e "${YELLOW}$1${RESET}"
11 | }
12 | get_result_of() {
13 | if [[ -z "$1" ]]
14 | then
15 | echo -e "${RED}FAILED${RESET}"
16 | elif [[ ! -f "$1" ]]
17 | then
18 | echo -e "${ORANGE}SKIPPED${RESET}"
19 | else
20 | cat "$1"
21 | fi
22 | }
23 |
24 | print_row_line() {
25 | # (32 (-10 for colors) + 4) * 6 args + 1 for beauty
26 | row=$(printf "%157s" "-")
27 | echo "${row// /-}"
28 | }
29 |
30 | print_row() {
31 | printf "%-4s" "|"
32 | for arg in "$@"
33 | do
34 | printf "%-32s %-4s" ${arg} "|"
35 | done
36 | printf "\n"
37 | }
38 |
39 | # Expects a test_script, test_log, timeout, test_result
40 | run_test() {
41 | exit_code=0
42 | set -o pipefail
43 | chmod a+x $1
44 | if [[ ${TESTS_VERBOSE} = "SILENT" ]]
45 | then
46 | timeout $3 $1 > $2
47 | exit_code=$?
48 | else
49 | timeout $3 $1 2>&1 | tee $2
50 | exit_code=$?
51 | fi
52 | # This means we had a timeout
53 | if [[ ${exit_code} = 124 ]]
54 | then
55 | echo "Test Timed Out!"
56 | echo -e "${ORANGE}TIMEOUT${RESET}" > $4
57 | fi
58 | }
59 |
60 |
--------------------------------------------------------------------------------
/.ci/e2e/running-environments/Docker/test_runner.Dockerfile:
--------------------------------------------------------------------------------
1 | # When running, make sure to mount Docker sock and use the python-worker directory as the base dir
2 | # Example-
3 | # docker build -f .ci\e2e\running-environments\Docker\test_runner.Dockerfile -t my-test-image
4 | # docker run -v /var/run/docker.sock:/var/run/docker.sock my-test-image
5 |
6 | FROM mcr.microsoft.com/azure-functions/python:2.0
7 |
8 | COPY . /azure-functions-python-worker
9 |
10 | RUN apt-get update && \
11 | apt-get install azure-functions-core-tools jq git unzip dos2unix -y
12 |
13 | # Install docker amd azure CLI
14 | RUN curl -sSL https://get.docker.com/ | sh && \
15 | apt-get update && \
16 | apt-get install apt-transport-https lsb-release software-properties-common dirmngr -y && \
17 | AZ_REPO=$(lsb_release -cs) && \
18 | echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ $AZ_REPO main" | tee /etc/apt/sources.list.d/azure-cli.list && \
19 | apt-key --keyring /etc/apt/trusted.gpg.d/Microsoft.gpg adv --keyserver packages.microsoft.com --recv-keys BC528686B50D79E339D3721CEB3E94ADBE1229CF && \
20 | apt-get update && \
21 | apt-get install azure-cli -y
22 |
23 | ENV ENVIRONMENT=DOCKER
24 | ENV EXIT_ON_FAIL=FALSE
25 |
26 | RUN find /azure-functions-python-worker/.ci/e2e -type f -print0 | xargs -0 dos2unix
27 |
28 | CMD [ "bash", "/azure-functions-python-worker/.ci/e2e/running-environments/Docker/start_tests_docker.sh" ]
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/func_tests_core/customer_churn_app/build_native_deps_test.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | source "$(dirname "${BASH_SOURCE[0]}")/../helpers/helper.sh"
4 | ensure_usage "$@"
5 |
6 | mkdir -p "$1"
7 | cd "$1"
8 | git clone https://github.com/asavaritayal/customer-churn-prediction
9 | cd customer-churn-prediction
10 | FUNCTIONS_PYTHON_DOCKER_IMAGE=$4 "$3" azure functionapp publish "$2" --build-native-deps
11 |
12 | if [[ $? -eq 0 ]]
13 | then
14 | sleep 5s
15 | # https://stackoverflow.com/questions/2220301/how-to-evaluate-http-response-codes-from-bash-shell-script
16 | STATUS_CODE=$(curl --write-out %{http_code} --silent --output /dev/null https://$2.azurewebsites.net/api/predict)
17 | verify_status_code $1.result
18 | else
19 | echo -e "${RED}Publishing failed (1/2)${RESET}"
20 | # Sometimes due to flakiness with the docker daemon or an azure resource may cause it to fail-
21 | # So, we retry, but just once
22 | echo "Retrying once....."
23 | FUNCTIONS_PYTHON_DOCKER_IMAGE=$4 "$3" azure functionapp publish "$2" --build-native-deps
24 | if [[ $? -eq 0 ]]
25 | then
26 | sleep 5s
27 | STATUS_CODE=$(curl --write-out %{http_code} --silent --output /dev/null https://$2.azurewebsites.net/api/predict)
28 | verify_status_code $1.result
29 | else
30 | echo -e "${RED}Publishing failed (2/2)${RESET}"
31 | echo -e "${RED}FAILED${RESET}" > $1.result
32 | fi
33 | fi
34 |
35 | cd ../..
36 | rm -rf "$1"
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/func_tests_core/customer_churn_app/no_bundler_test.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | source "$(dirname "${BASH_SOURCE[0]}")/../helpers/helper.sh"
4 | ensure_usage "$@"
5 |
6 | mkdir -p "$1"
7 | cd "$1"
8 | git clone https://github.com/asavaritayal/customer-churn-prediction
9 | cd customer-churn-prediction
10 | FUNCTIONS_PYTHON_DOCKER_IMAGE=$4 "$3" azure functionapp publish "$2" --build-native-deps --no-bundler
11 |
12 | if [[ $? -eq 0 ]]
13 | then
14 | sleep 5s
15 | # https://stackoverflow.com/questions/2220301/how-to-evaluate-http-response-codes-from-bash-shell-script
16 | STATUS_CODE=$(curl --write-out %{http_code} --silent --output /dev/null https://$2.azurewebsites.net/api/predict)
17 | verify_status_code $1.result
18 | else
19 | echo -e "${RED}Publishing failed (1/2)${RESET}"
20 | # Sometimes due to flakiness with the docker daemon or an azure resource may cause it to fail-
21 | # So, we retry, but just once
22 | echo "Retrying once....."
23 | FUNCTIONS_PYTHON_DOCKER_IMAGE=$4 "$3" azure functionapp publish "$2" --build-native-deps --no-bundler
24 | if [[ $? -eq 0 ]]
25 | then
26 | sleep 5s
27 | STATUS_CODE=$(curl --write-out %{http_code} --silent --output /dev/null https://$2.azurewebsites.net/api/predict)
28 | verify_status_code $1.result
29 | else
30 | echo -e "${RED}Publishing failed (2/2)${RESET}"
31 | echo -e "${RED}FAILED${RESET}" > $1.result
32 | fi
33 | fi
34 |
35 | cd ../..
36 | rm -rf "$1"
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/dev_docker_setup/setup.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 | USAGE="usage ./script "
5 |
6 | if [[ -z "$1" ]]
7 | then
8 | echo "Missing name of the ACR"
9 | echo ${USAGE}
10 | exit 1
11 | fi
12 |
13 | if [[ -z "$2" ]]
14 | then
15 | echo "Missing name of the dev image to use in ACR"
16 | echo ${USAGE}
17 | exit 1
18 | fi
19 |
20 | if [[ -z "$3" ]]
21 | then
22 | echo "Missing name of the working directory"
23 | echo ${USAGE}
24 | exit 1
25 | fi
26 |
27 | SCRIPT_DIR=`realpath $(dirname "${BASH_SOURCE[0]}")`
28 |
29 | mkdir -p $3
30 | cd $3
31 | az acr login --name $1
32 | git clone https://github.com/Azure/azure-functions-docker
33 | docker build -f ./azure-functions-docker/host/2.0/stretch/amd64/base.Dockerfile ./azure-functions-docker/host/2.0/stretch/amd64 -t azure-functions-base-dev
34 | # The directory to build from needs to be the amd64, because the Dockerfile copies resources
35 | docker build --build-arg BASE_IMAGE=azure-functions-base-dev -f ./azure-functions-docker/host/2.0/stretch/amd64/python.Dockerfile -t azure-functions-python-dev ./azure-functions-docker/host/2.0/stretch/amd64
36 | docker build --build-arg BASE_IMAGE=azure-functions-python-dev -f ${SCRIPT_DIR}/dev.Dockerfile ${SCRIPT_DIR}/../../../.. -t azure-functions-python-dev-updated
37 | docker tag azure-functions-python-dev-updated $1.azurecr.io/$2
38 | docker push $1.azurecr.io/$2
39 | echo "New image pushed to the ACR."
40 | echo "Starting cleanup"
41 | cd ..
42 | rm -r $3
43 | echo "Completed cleanup"
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/test_runners/helpers/get_config_variables.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Get the configuration file
4 | GLOBAL_CONFIG="$(dirname "${BASH_SOURCE[0]}")/../../publish_config.json"
5 | if [[ -n "$1" ]]
6 | then
7 | echo "Using the provided global config"
8 | GLOBAL_CONFIG=$1
9 | fi
10 |
11 | # Docker Dev Variables
12 | DOCKER_CONFIG="$(cat "${GLOBAL_CONFIG}" | jq '.docker_setup')"
13 | ACR_NAME="$(echo "${DOCKER_CONFIG}" | jq -r '.acr_name')"
14 | DEV_IMAGE_NAME="$(echo "${DOCKER_CONFIG}" | jq -r '.dev_image_name')"
15 | DEV_DOCKER_IMAGE=${ACR_NAME}.azurecr.io/${DEV_IMAGE_NAME}
16 | DOCKER_WORKING_DIR="$(echo "${DOCKER_CONFIG}" | jq -r '.working_dir')"
17 |
18 | # Docker Prod Variables
19 | PROD_DOCKER_IMAGE="$(echo "${DOCKER_CONFIG}" | jq -r '.prod_image')"
20 |
21 | # Func Dev Variables
22 | FUNC_CONFIG="$(cat "${GLOBAL_CONFIG}" | jq '.func_setup')"
23 | FUNC_DEV_DIR="$(echo "${FUNC_CONFIG}" | jq -r '.func_dev_dir')"
24 | FUNC_DEV_URL="$(echo "${FUNC_CONFIG}" | jq -r '.func_dev_url')"
25 |
26 | # Working dir variables
27 | TESTS_CONFIG="$(cat "${GLOBAL_CONFIG}" | jq '.tests')"
28 | WORKING_DIR="$(echo "${TESTS_CONFIG}" | jq -r '.working_dir')"
29 | TESTS_LOGS="$(echo "${TESTS_CONFIG}" | jq -r '.logs')"
30 | TESTS_TIMEOUT="$(echo "${TESTS_CONFIG}" | jq -r '.timeout')"
31 |
32 | # Service Principal variables
33 | SP_CONFIG="$(cat "${GLOBAL_CONFIG}" | jq '.azure_service_principal')"
34 | SP_USER_NAME="$(echo "${SP_CONFIG}" | jq -r '.user_id')"
35 | SP_PASSWORD="$(echo "${SP_CONFIG}" | jq -r '.password')"
36 | SP_TENANT="$(echo "${SP_CONFIG}" | jq -r '.tenant')"
37 |
--------------------------------------------------------------------------------
/azure/functions_worker/logging.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import logging.handlers
3 | import sys
4 |
5 |
6 | logger = logging.getLogger('azure.functions_worker')
7 | error_logger = logging.getLogger('azure.functions_worker_errors')
8 |
9 |
10 | def setup(log_level, log_destination):
11 | if log_level == 'TRACE':
12 | log_level = 'DEBUG'
13 |
14 | formatter = logging.Formatter(
15 | 'LanguageWorkerConsoleLog %(levelname)s: %(message)s')
16 |
17 | error_handler = None
18 | handler = None
19 |
20 | if log_destination is None:
21 | # With no explicit log destination we do split logging,
22 | # errors go into stderr, everything else -- to stdout.
23 | error_handler = logging.StreamHandler(sys.stderr)
24 | error_handler.setFormatter(formatter)
25 | error_handler.setLevel(getattr(logging, log_level))
26 |
27 | handler = logging.StreamHandler(sys.stdout)
28 |
29 | elif log_destination in ('stdout', 'stderr'):
30 | handler = logging.StreamHandler(getattr(sys, log_destination))
31 |
32 | elif log_destination == 'syslog':
33 | handler = logging.handlers.SysLogHandler()
34 |
35 | else:
36 | handler = logging.FileHandler(log_destination)
37 |
38 | if error_handler is None:
39 | error_handler = handler
40 |
41 | handler.setFormatter(formatter)
42 | handler.setLevel(getattr(logging, log_level))
43 |
44 | logger.addHandler(handler)
45 | logger.setLevel(getattr(logging, log_level))
46 |
47 | error_logger.addHandler(error_handler)
48 | error_logger.setLevel(getattr(logging, log_level))
49 |
--------------------------------------------------------------------------------
/tests/test_mock_timer_functions.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | from azure.functions_worker import protos
4 | from azure.functions_worker import testutils
5 |
6 |
7 | class TestTimerFunctions(testutils.AsyncTestCase):
8 |
9 | async def test_mock_timer__return_pastdue(self):
10 | async with testutils.start_mockhost(
11 | script_root='timer_functions') as host:
12 |
13 | func_id, r = await host.load_function('return_pastdue')
14 |
15 | self.assertEqual(r.response.function_id, func_id)
16 | self.assertEqual(r.response.result.status,
17 | protos.StatusResult.Success)
18 |
19 | async def call_and_check(due: bool):
20 | _, r = await host.invoke_function(
21 | 'return_pastdue', [
22 | protos.ParameterBinding(
23 | name='timer',
24 | data=protos.TypedData(
25 | json=json.dumps({
26 | 'IsPastDue': due
27 | })))
28 | ])
29 | self.assertEqual(r.response.result.status,
30 | protos.StatusResult.Success)
31 | self.assertEqual(
32 | list(r.response.output_data), [
33 | protos.ParameterBinding(
34 | name='pastdue',
35 | data=protos.TypedData(string=str(due)))
36 | ])
37 |
38 | await call_and_check(True)
39 | await call_and_check(False)
40 |
--------------------------------------------------------------------------------
/azure/functions_worker/bindings/eventgrid.py:
--------------------------------------------------------------------------------
1 | import json
2 | import typing
3 |
4 | from azure.functions import _eventgrid
5 |
6 | from . import meta
7 | from .. import protos
8 |
9 |
10 | class EventGridEventInConverter(meta.InConverter,
11 | binding='eventGridTrigger', trigger=True):
12 |
13 | @classmethod
14 | def check_input_type_annotation(
15 | cls, pytype: type, datatype: protos.BindingInfo.DataType) -> bool:
16 | if datatype is protos.BindingInfo.undefined:
17 | return issubclass(pytype, _eventgrid.EventGridEvent)
18 | else:
19 | return False
20 |
21 | @classmethod
22 | def from_proto(cls, data: protos.TypedData, *,
23 | pytype: typing.Optional[type],
24 | trigger_metadata) -> typing.Any:
25 | data_type = data.WhichOneof('data')
26 |
27 | if data_type == 'json':
28 | body = json.loads(data.json)
29 | else:
30 | raise NotImplementedError(
31 | f'unsupported event grid payload type: {data_type}')
32 |
33 | if trigger_metadata is None:
34 | raise NotImplementedError(
35 | f'missing trigger metadata for event grid input')
36 |
37 | return _eventgrid.EventGridEvent(
38 | id=body.get('id'),
39 | topic=body.get('topic'),
40 | subject=body.get('subject'),
41 | event_type=body.get('eventType'),
42 | event_time=cls._parse_datetime(body.get('eventTime')),
43 | data=body.get('data'),
44 | data_version=body.get('dataVersion'),
45 | )
46 |
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/func_tests_core/helpers/helper.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | USAGE="usage ./script "
4 | # https://stackoverflow.com/questions/5947742/how-to-change-the-output-color-of-echo-in-linux
5 | RED='\033[0;31m'
6 | GREEN='\033[0;32m'
7 | RESET='\033[0m' # No Color
8 |
9 | ensure_usage() {
10 |
11 | if [[ -z "$1" ]]
12 | then
13 | echo "Missing name of the directory"
14 | echo ${USAGE}
15 | exit 1
16 | fi
17 |
18 | if [[ -z "$2" ]]
19 | then
20 | echo "Missing name of the functions app to publish"
21 | echo ${USAGE}
22 | exit 1
23 | fi
24 |
25 | if [[ -z "$3" ]]
26 | then
27 | echo "Missing name of the func executable"
28 | echo ${USAGE}
29 | exit 1
30 | fi
31 |
32 | if [[ -z "$4" ]]
33 | then
34 | echo "Missing name of the docker image to use"
35 | echo ${USAGE}
36 | exit 1
37 | fi
38 |
39 | }
40 |
41 | # Assuming Status_code is set
42 | verify_status_code() {
43 | if [[ -z "$1" ]]
44 | then
45 | echo "Missing name of the log file"
46 | exit 1
47 | fi
48 | if [[ "${STATUS_CODE}" -ne 200 ]]
49 | then
50 | MESSAGE="${RED}TEST FAILED: Expected Status Code 200. Found Status Code ${STATUS_CODE}${RESET}"
51 | (>&2 echo -e ${MESSAGE})
52 | echo -e "${RED}FAILED${RESET}" > $1
53 | else
54 | MESSAGE="${GREEN}TEST PASSED: Status Code 200.${RESET}"
55 | echo -e ${MESSAGE}
56 | echo -e "${GREEN}PASSED${RESET}" > $1
57 | fi
58 | }
59 |
60 | fail_after_timeout() {
61 | timeout $1 ${@:2}
62 | }
--------------------------------------------------------------------------------
/docs/api.rst:
--------------------------------------------------------------------------------
1 | .. _azure-functions-reference:
2 |
3 | =============
4 | API Reference
5 | =============
6 |
7 | .. module:: azure.functions
8 | :synopsis: Azure Functions bindings.
9 |
10 | .. currentmodule:: azure.functions
11 |
12 |
13 | .. _azure-functions-bindings-blob:
14 |
15 | Blob Bindings
16 | =============
17 |
18 | .. autoclass:: azure.functions.InputStream
19 | :members:
20 |
21 |
22 | .. _azure-functions-bindings-http:
23 |
24 | HTTP Bindings
25 | =============
26 |
27 | .. autoclass:: azure.functions.HttpRequest
28 | :members:
29 |
30 | .. autoclass:: azure.functions.HttpResponse
31 | :members:
32 |
33 |
34 | .. _azure-functions-bindings-queue:
35 |
36 | Queue Bindings
37 | ==============
38 |
39 | .. autoclass:: azure.functions.QueueMessage
40 | :members:
41 |
42 |
43 | .. _azure-functions-bindings-timer:
44 |
45 | Timer Bindings
46 | ==============
47 |
48 | .. autoclass:: azure.functions.TimerRequest
49 | :members:
50 |
51 |
52 | .. _azure-functions-bindings-cosmosdb:
53 |
54 | CosmosDB Bindings
55 | =================
56 |
57 | .. autoclass:: azure.functions.Document
58 | :members:
59 |
60 | .. describe:: doc[field]
61 |
62 | Return the field of *doc* with field name *field*.
63 |
64 | .. describe:: doc[field] = value
65 |
66 | Set field of *doc* with field name *field* to *value*.
67 |
68 | .. autoclass:: azure.functions.DocumentList
69 | :members:
70 |
71 |
72 | .. _azure-functions-bindings-context:
73 |
74 | Function Context
75 | ================
76 |
77 | .. autoclass:: azure.functions.Context
78 | :members:
79 |
80 |
81 | Out Parameters
82 | ==============
83 |
84 | .. autoclass:: azure.functions.Out
85 | :members:
86 |
--------------------------------------------------------------------------------
/python/worker.py:
--------------------------------------------------------------------------------
1 | """Main entrypoint."""
2 |
3 |
4 | try:
5 | from azure.functions_worker.main import main
6 |
7 | except ImportError:
8 | # Compatibility with hard-bundled pre-beta worker versions in
9 | # deployed function apps.
10 | import argparse
11 | import traceback
12 |
13 | def parse_args():
14 | parser = argparse.ArgumentParser(
15 | description='Python Azure Functions Worker')
16 | parser.add_argument('--host')
17 | parser.add_argument('--port', type=int)
18 | parser.add_argument('--workerId', dest='worker_id')
19 | parser.add_argument('--requestId', dest='request_id')
20 | parser.add_argument('--log-level', type=str, default='INFO',
21 | choices=['TRACE', 'INFO', 'WARNING', 'ERROR'],)
22 | parser.add_argument('--log-to', type=str, default=None,
23 | help='log destination: stdout, stderr, '
24 | 'syslog, or a file path')
25 | parser.add_argument('--grpcMaxMessageLength', type=int,
26 | dest='grpc_max_msg_len')
27 | return parser.parse_args()
28 |
29 | def main():
30 | args = parse_args()
31 |
32 | import azure.functions # NoQA
33 | import azure.functions_worker
34 | from azure.functions_worker import aio_compat
35 |
36 | try:
37 | return aio_compat.run(azure.functions_worker.start_async(
38 | args.host, args.port, args.worker_id, args.request_id,
39 | args.grpc_max_msg_len))
40 | except Exception:
41 | print(traceback.format_exc(), flush=True)
42 | raise
43 |
44 |
45 | if __name__ == '__main__':
46 | main()
47 |
--------------------------------------------------------------------------------
/tests/test_code_quality.py:
--------------------------------------------------------------------------------
1 | import pathlib
2 | import subprocess
3 | import sys
4 | import unittest
5 |
6 |
7 | ROOT_PATH = pathlib.Path(__file__).parent.parent
8 |
9 |
10 | class TestCodeQuality(unittest.TestCase):
11 | def test_mypy(self):
12 | try:
13 | import mypy # NoQA
14 | except ImportError:
15 | raise unittest.SkipTest('mypy module is missing')
16 |
17 | try:
18 | subprocess.run(
19 | [sys.executable, '-m', 'mypy', '-m', 'azure.functions_worker'],
20 | check=True,
21 | stdout=subprocess.PIPE,
22 | stderr=subprocess.PIPE,
23 | cwd=str(ROOT_PATH))
24 | except subprocess.CalledProcessError as ex:
25 | output = ex.output.decode()
26 | raise AssertionError(
27 | f'mypy validation failed:\n{output}') from None
28 |
29 | def test_flake8(self):
30 | try:
31 | import flake8 # NoQA
32 | except ImportError:
33 | raise unittest.SkipTest('flake8 moudule is missing')
34 |
35 | config_path = ROOT_PATH / '.flake8'
36 | if not config_path.exists():
37 | raise unittest.SkipTest('could not locate the .flake8 file')
38 |
39 | try:
40 | subprocess.run(
41 | [sys.executable, '-m', 'flake8', '--config', str(config_path)],
42 | check=True,
43 | stdout=subprocess.PIPE,
44 | stderr=subprocess.PIPE,
45 | cwd=str(ROOT_PATH))
46 | except subprocess.CalledProcessError as ex:
47 | output = ex.output.decode()
48 | raise AssertionError(
49 | f'flake8 validation failed:\n{output}') from None
50 |
--------------------------------------------------------------------------------
/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | #### Investigative information
7 |
8 | Please provide the following:
9 |
10 | - Timestamp:
11 | - Function App name:
12 | - Function name(s) (as appropriate):
13 | - Core Tools version:
14 |
15 | #### Repro steps
16 |
17 | Provide the steps required to reproduce the problem:
18 |
19 |
26 |
27 | #### Expected behavior
28 |
29 | Provide a description of the expected behavior.
30 |
31 |
36 |
37 | #### Actual behavior
38 |
39 | Provide a description of the actual behavior observed.
40 |
41 |
46 |
47 | #### Known workarounds
48 |
49 | Provide a description of any known workarounds.
50 |
51 |
56 |
57 | #### Related information
58 |
59 | Provide any related information
60 |
61 | * Links to source
62 | * Contents of the requirements.txt file
63 | * Bindings used
64 |
65 |
89 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 |
49 | # Translations
50 | *.mo
51 | *.pot
52 |
53 | # Django stuff:
54 | *.log
55 | local_settings.py
56 |
57 | # Flask stuff:
58 | instance/
59 | .webassets-cache
60 |
61 | # Scrapy stuff:
62 | .scrapy
63 |
64 | # Sphinx documentation
65 | docs/_build/
66 |
67 | # PyBuilder
68 | target/
69 |
70 | # Jupyter Notebook
71 | .ipynb_checkpoints
72 |
73 | # pyenv
74 | .python-version
75 |
76 | # celery beat schedule file
77 | celerybeat-schedule
78 |
79 | # SageMath parsed files
80 | *.sage.py
81 |
82 | # dotenv
83 | .env
84 |
85 | # virtualenv
86 | .venv
87 | venv/
88 | ENV/
89 |
90 | # Spyder project settings
91 | .spyderproject
92 | .spyproject
93 |
94 | # Rope project settings
95 | .ropeproject
96 |
97 | # mkdocs documentation
98 | /site
99 |
100 | # mypy
101 | .mypy_cache/
102 |
103 | # nuget
104 | *.nupkg
105 |
106 | .testconfig
107 | .pytest_cache
108 |
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/func_tests_core/new_functionapp/packapp_test.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | source "$(dirname "${BASH_SOURCE[0]}")/../helpers/helper.sh"
4 | ensure_usage "$@"
5 |
6 | # Setup a new func app from prod func
7 | mkdir -p "$1"
8 | cd "$1"
9 | python -m venv env_new_build_native
10 | source env_new_build_native/bin/activate
11 | "$3" init . --worker-runtime python
12 | "$3" new --template httptrigger --name httptriggerTest
13 |
14 | # Change auth level to anonymous
15 | jq '.bindings[].authLevel="anonymous"' httptriggerTest/function.json > httptriggerTest/replaced.json
16 | mv httptriggerTest/replaced.json httptriggerTest/function.json
17 |
18 | # Publish and verify
19 | FUNCTIONS_PYTHON_DOCKER_IMAGE=$4 "$3" azure functionapp publish "$2"
20 |
21 | if [[ $? -eq 0 ]]
22 | then
23 | sleep 5s
24 | # https://stackoverflow.com/questions/2220301/how-to-evaluate-http-response-codes-from-bash-shell-script
25 | STATUS_CODE=$(curl --write-out %{http_code} --silent --output /dev/null https://$2.azurewebsites.net/api/httptriggerTest?name=test)
26 | verify_status_code $1.result
27 | else
28 | echo -e "${RED}Publishing failed (1/2)${RESET}"
29 | # Sometimes due to flakiness with the docker daemon or an azure resource may cause it to fail-
30 | # So, we retry, but just once
31 | echo "Retrying once....."
32 | FUNCTIONS_PYTHON_DOCKER_IMAGE=$4 "$3" azure functionapp publish "$2"
33 | if [[ $? -eq 0 ]]
34 | then
35 | sleep 5s
36 | STATUS_CODE=$(curl --write-out %{http_code} --silent --output /dev/null https://$2.azurewebsites.net/api/httptriggerTest?name=test)
37 | verify_status_code $1.result
38 | else
39 | echo -e "${RED}Publishing failed (2/2)${RESET}"
40 | echo -e "${RED}FAILED${RESET}" > $1.result
41 | fi
42 | fi
43 |
44 | deactivate
45 | cd ..
46 | rm -rf "$1"
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/func_tests_core/new_functionapp/build_native_deps_test.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | source "$(dirname "${BASH_SOURCE[0]}")/../helpers/helper.sh"
4 | ensure_usage "$@"
5 |
6 | # Setup a new func app from prod func
7 | mkdir -p "$1"
8 | cd "$1"
9 | python -m venv env_new_build_native
10 | source env_new_build_native/bin/activate
11 | "$3" init . --worker-runtime python
12 | "$3" new --template httptrigger --name httptriggerTest
13 |
14 | # Change auth level to anonymous
15 | jq '.bindings[].authLevel="anonymous"' httptriggerTest/function.json > httptriggerTest/replaced.json
16 | mv httptriggerTest/replaced.json httptriggerTest/function.json
17 |
18 | # Publish and verify
19 | FUNCTIONS_PYTHON_DOCKER_IMAGE=$4 "$3" azure functionapp publish "$2" --build-native-deps
20 |
21 | if [[ $? -eq 0 ]]
22 | then
23 | sleep 5s
24 | # https://stackoverflow.com/questions/2220301/how-to-evaluate-http-response-codes-from-bash-shell-script
25 | STATUS_CODE=$(curl --write-out %{http_code} --silent --output /dev/null https://$2.azurewebsites.net/api/httptriggerTest?name=test)
26 | verify_status_code $1.result
27 | else
28 | echo -e "${RED}Publishing failed (1/2)${RESET}"
29 | # Sometimes due to flakiness with the docker daemon or an azure resource may cause it to fail-
30 | # So, we retry, but just once
31 | echo "Retrying once....."
32 | FUNCTIONS_PYTHON_DOCKER_IMAGE=$4 "$3" azure functionapp publish "$2" --build-native-deps
33 | if [[ $? -eq 0 ]]
34 | then
35 | sleep 5s
36 | STATUS_CODE=$(curl --write-out %{http_code} --silent --output /dev/null https://$2.azurewebsites.net/api/httptriggerTest?name=test)
37 | verify_status_code $1.result
38 | else
39 | echo -e "${RED}Publishing failed (2/2)${RESET}"
40 | echo -e "${RED}FAILED${RESET}" > $1.result
41 | fi
42 | fi
43 |
44 | deactivate
45 | cd ..
46 | rm -rf "$1"
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/func_tests_core/new_functionapp/no_bundler_test.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | source "$(dirname "${BASH_SOURCE[0]}")/../helpers/helper.sh"
4 | ensure_usage "$@"
5 |
6 | # Setup a new func app from prod func
7 | mkdir -p "$1"
8 | cd "$1"
9 | python -m venv env_new_build_native
10 | source env_new_build_native/bin/activate
11 | "$3" init . --worker-runtime python
12 | "$3" new --template httptrigger --name httptriggerTest
13 |
14 | # Change auth level to anonymous
15 | jq '.bindings[].authLevel="anonymous"' httptriggerTest/function.json > httptriggerTest/replaced.json
16 | mv httptriggerTest/replaced.json httptriggerTest/function.json
17 |
18 | # Publish and verify
19 | FUNCTIONS_PYTHON_DOCKER_IMAGE=$4 "$3" azure functionapp publish "$2" --build-native-deps --no-bundler
20 |
21 | if [[ $? -eq 0 ]]
22 | then
23 | sleep 5s
24 | # https://stackoverflow.com/questions/2220301/how-to-evaluate-http-response-codes-from-bash-shell-script
25 | STATUS_CODE=$(curl --write-out %{http_code} --silent --output /dev/null https://$2.azurewebsites.net/api/httptriggerTest?name=test)
26 | verify_status_code $1.result
27 | else
28 | echo -e "${RED}Publishing failed (1/2)${RESET}"
29 | # Sometimes due to flakiness with the docker daemon or an azure resource may cause it to fail-
30 | # So, we retry, but just once
31 | echo "Retrying once....."
32 | FUNCTIONS_PYTHON_DOCKER_IMAGE=$4 "$3" azure functionapp publish "$2" --build-native-deps --no-bundler
33 | if [[ $? -eq 0 ]]
34 | then
35 | sleep 5s
36 | STATUS_CODE=$(curl --write-out %{http_code} --silent --output /dev/null https://$2.azurewebsites.net/api/httptriggerTest?name=test)
37 | verify_status_code $1.result
38 | else
39 | echo -e "${RED}Publishing failed (2/2)${RESET}"
40 | echo -e "${RED}FAILED${RESET}" > $1.result
41 | fi
42 | fi
43 |
44 | deactivate
45 | cd ..
46 | rm -rf "$1"
--------------------------------------------------------------------------------
/azure/functions_worker/main.py:
--------------------------------------------------------------------------------
1 | """Main entrypoint."""
2 |
3 |
4 | import argparse
5 |
6 | from . import aio_compat
7 | from . import dispatcher
8 | from . import logging
9 | from .logging import error_logger, logger
10 |
11 |
12 | def parse_args():
13 | parser = argparse.ArgumentParser(
14 | description='Python Azure Functions Worker')
15 | parser.add_argument('--host')
16 | parser.add_argument('--port', type=int)
17 | parser.add_argument('--workerId', dest='worker_id')
18 | parser.add_argument('--requestId', dest='request_id')
19 | parser.add_argument('--log-level', type=str, default='INFO',
20 | choices=['TRACE', 'INFO', 'WARNING', 'ERROR'],)
21 | parser.add_argument('--log-to', type=str, default=None,
22 | help='log destination: stdout, stderr, '
23 | 'syslog, or a file path')
24 | parser.add_argument('--grpcMaxMessageLength', type=int,
25 | dest='grpc_max_msg_len')
26 | return parser.parse_args()
27 |
28 |
29 | def main():
30 | args = parse_args()
31 | logging.setup(log_level=args.log_level, log_destination=args.log_to)
32 |
33 | logger.info('Starting Azure Functions Python Worker.')
34 | logger.info('Worker ID: %s, Request ID: %s, Host Address: %s:%s',
35 | args.worker_id, args.request_id, args.host, args.port)
36 |
37 | try:
38 | return aio_compat.run(start_async(
39 | args.host, args.port, args.worker_id, args.request_id,
40 | args.grpc_max_msg_len))
41 | except Exception:
42 | error_logger.exception('unhandled error in functions worker')
43 | raise
44 |
45 |
46 | async def start_async(host, port, worker_id, request_id, grpc_max_msg_len):
47 | disp = await dispatcher.Dispatcher.connect(
48 | host, port, worker_id, request_id,
49 | connect_timeout=5.0, max_msg_len=grpc_max_msg_len)
50 |
51 | await disp.dispatch_forever()
52 |
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/publish_config.json:
--------------------------------------------------------------------------------
1 | {
2 | "azure_service_principal": {
3 | "user_id": "${AZURE_SERVICE_PRINCIPAL_USER}",
4 | "password": "${AZURE_SERVICE_PRINCIPAL_PASSWORD}",
5 | "tenant": "${AZURE_SERVICE_PRINCIPAL_TENANT}"
6 | },
7 | "docker_setup": {
8 | "acr_name": "${ACR_NAME}",
9 | "working_dir": "/docker-dir",
10 | "dev_image_name": "${ACR_IMAGE_NAME}",
11 | "prod_image": "mcr.microsoft.com/azure-functions/python:2.0"
12 | },
13 | "func_setup": {
14 | "func_dev_dir": "/func-dev-dir",
15 | "func_dev_url": "${FUNC_DEV_URL}"
16 | },
17 | "tests": {
18 | "working_dir": "/tests-dir",
19 | "logs": "/tests-logs",
20 | "timeout": "3000s"
21 | },
22 | "publish_function_app": {
23 | "prod_func_prod_docker": {
24 | "new_function": {
25 | "build_native_deps": "${FUNCTION_APP}",
26 | "no_bundler": "${FUNCTION_APP}",
27 | "packapp": "${FUNCTION_APP}"
28 | },
29 | "customer_churn": {
30 | "build_native_deps": "${FUNCTION_APP}",
31 | "no_bundler": "${FUNCTION_APP}"
32 | }
33 | },
34 | "prod_func_dev_docker": {
35 | "new_function": {
36 | "build_native_deps": "${FUNCTION_APP}",
37 | "no_bundler": "${FUNCTION_APP}",
38 | "packapp": "${FUNCTION_APP}"
39 | },
40 | "customer_churn": {
41 | "build_native_deps": "${FUNCTION_APP}",
42 | "no_bundler": "${FUNCTION_APP}"
43 | }
44 | },
45 | "dev_func_prod_docker": {
46 | "new_function": {
47 | "build_native_deps": "${FUNCTION_APP}",
48 | "no_bundler": "${FUNCTION_APP}",
49 | "packapp": "${FUNCTION_APP}"
50 | },
51 | "customer_churn": {
52 | "build_native_deps": "${FUNCTION_APP}",
53 | "no_bundler": "${FUNCTION_APP}"
54 | }
55 | },
56 | "dev_func_dev_docker": {
57 | "new_function": {
58 | "build_native_deps": "${FUNCTION_APP}",
59 | "no_bundler": "${FUNCTION_APP}",
60 | "packapp": "${FUNCTION_APP}"
61 | },
62 | "customer_churn": {
63 | "build_native_deps": "${FUNCTION_APP}",
64 | "no_bundler": "${FUNCTION_APP}"
65 | }
66 | }
67 | }
68 | }
--------------------------------------------------------------------------------
/azure/functions_worker/loader.py:
--------------------------------------------------------------------------------
1 | """Python functions loader."""
2 |
3 |
4 | import importlib
5 | import importlib.machinery
6 | import importlib.util
7 | import os
8 | import os.path
9 | import pathlib
10 | import sys
11 | import typing
12 |
13 |
14 | _AZURE_NAMESPACE = '__app__'
15 |
16 | _submodule_dirs = []
17 |
18 |
19 | def register_function_dir(path: os.PathLike):
20 | _submodule_dirs.append(os.fspath(path))
21 |
22 |
23 | def install():
24 | if _AZURE_NAMESPACE not in sys.modules:
25 | # Create and register the __app__ namespace package.
26 | ns_spec = importlib.machinery.ModuleSpec(_AZURE_NAMESPACE, None)
27 | ns_spec.submodule_search_locations = _submodule_dirs
28 | ns_pkg = importlib.util.module_from_spec(ns_spec)
29 | sys.modules[_AZURE_NAMESPACE] = ns_pkg
30 |
31 |
32 | def uninstall():
33 | pass
34 |
35 |
36 | def load_function(name: str, directory: str, script_file: str,
37 | entry_point: typing.Optional[str]):
38 | dir_path = pathlib.Path(directory)
39 | script_path = pathlib.Path(script_file)
40 | if not entry_point:
41 | entry_point = 'main'
42 |
43 | register_function_dir(dir_path.parent)
44 |
45 | try:
46 | rel_script_path = script_path.relative_to(dir_path.parent)
47 | except ValueError:
48 | raise RuntimeError(
49 | f'script path {script_file} is not relative to the specified '
50 | f'directory {directory}'
51 | )
52 |
53 | last_part = rel_script_path.parts[-1]
54 | modname, ext = os.path.splitext(last_part)
55 | if ext != '.py':
56 | raise RuntimeError(
57 | f'cannot load function {name}: '
58 | f'invalid Python filename {script_file}')
59 |
60 | modname_parts = [_AZURE_NAMESPACE]
61 | modname_parts.extend(rel_script_path.parts[:-1])
62 | modname_parts.append(modname)
63 |
64 | fullmodname = '.'.join(modname_parts)
65 |
66 | mod = importlib.import_module(fullmodname)
67 |
68 | func = getattr(mod, entry_point, None)
69 | if func is None or not callable(func):
70 | raise RuntimeError(
71 | f'cannot load function {name}: function {entry_point}() is not '
72 | f'present in {rel_script_path}')
73 |
74 | return func
75 |
--------------------------------------------------------------------------------
/azure-pipelines-nightly.yml:
--------------------------------------------------------------------------------
1 | jobs:
2 | - job: LinuxNightly
3 | timeoutInMinutes: 0
4 | pool:
5 | vmImage: 'ubuntu-16.04'
6 | steps:
7 | - task: UsePythonVersion@0
8 | inputs:
9 | versionSpec: '3.6'
10 | addToPath: true
11 | name: pythonPath
12 | displayName: Update Python Path
13 | - bash: |
14 |
15 | echo "alias python=$(pythonPath.pythonLocation)/python" > ~/.bashrc
16 | which python
17 |
18 | sudo apt-get update
19 | sudo apt-get install azure-functions-core-tools jq git unzip gettext -y
20 |
21 | # Install azure CLI
22 | sudo apt-get install apt-transport-https lsb-release software-properties-common dirmngr -y
23 | AZ_REPO=$(lsb_release -cs)
24 | sudo echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ $AZ_REPO main" | tee /etc/apt/sources.list.d/azure-cli.list
25 | sudo apt-key --keyring /etc/apt/trusted.gpg.d/Microsoft.gpg adv --keyserver packages.microsoft.com --recv-keys BC528686B50D79E339D3721CEB3E94ADBE1229CF
26 | sudo apt-get update
27 | sudo apt-get install azure-cli -y
28 | displayName: Initial Setup
29 | continueOnError: false
30 | - bash: |
31 | envsubst < .ci/e2e/publish_tests/publish_config.json > .ci/e2e/publish_tests/publish_config_filled.json
32 | mv .ci/e2e/publish_tests/publish_config_filled.json .ci/e2e/publish_tests/publish_config.json
33 | displayName: Populate Settings
34 | continueOnError: false
35 | env:
36 | AZURE_SERVICE_PRINCIPAL_USER: $(AZURE_SERVICE_PRINCIPAL_USER)
37 | AZURE_SERVICE_PRINCIPAL_PASSWORD: ${AZURE_SERVICE_PRINCIPAL_PASSWORD}
38 | AZURE_SERVICE_PRINCIPAL_TENANT: $(AZURE_SERVICE_PRINCIPAL_TENANT)
39 | ACR_NAME: $(ACR_NAME)
40 | ACR_IMAGE_NAME: $(ACR_IMAGE_NAME)
41 | FUNCTION_APP: $(FUNCTION_APP)
42 | FUNC_DEV_URL: $(FUNC_DEV_URL)
43 | - bash: |
44 | chmod a+x .ci/e2e/publish_tests/test_runners/setup_test_environment.sh
45 | sudo .ci/e2e/publish_tests/test_runners/setup_test_environment.sh
46 | displayName: Setup Testing artifacts
47 | continueOnError: false
48 | - bash: |
49 | chmod a+x .ci/e2e/publish_tests/test_runners/run_all_serial.sh
50 | find .ci/e2e/publish_tests/ -type f -iname "*.sh" -exec chmod a+x {} \;
51 | sudo PATH=${PATH} .ci/e2e/publish_tests/test_runners/run_all_serial.sh
52 | displayName: E2E Tests
53 | continueOnError: false
--------------------------------------------------------------------------------
/tests/test_cosmosdb_functions.py:
--------------------------------------------------------------------------------
1 | import json
2 | import time
3 |
4 | from azure.functions_worker import testutils
5 |
6 |
7 | class TestCosmosDBFunctions(testutils.WebHostTestCase):
8 |
9 | @classmethod
10 | def get_script_dir(cls):
11 | return 'cosmosdb_functions'
12 |
13 | def test_cosmosdb_trigger(self):
14 | time.sleep(5)
15 | data = str(round(time.time()))
16 | doc = {'id': 'cosmosdb-trigger-test', 'data': data}
17 | r = self.webhost.request('POST', 'put_document',
18 | data=json.dumps(doc))
19 | self.assertEqual(r.status_code, 200)
20 | self.assertEqual(r.text, 'OK')
21 |
22 | max_retries = 10
23 |
24 | for try_no in range(max_retries):
25 | # Allow trigger to fire
26 | time.sleep(2)
27 |
28 | try:
29 | # Check that the trigger has fired
30 | r = self.webhost.request('GET', 'get_cosmosdb_triggered')
31 | self.assertEqual(r.status_code, 200)
32 | response = r.json()
33 | response.pop('_metadata', None)
34 |
35 | self.assertEqual(
36 | response,
37 | doc
38 | )
39 | except AssertionError as e:
40 | if try_no == max_retries - 1:
41 | raise
42 | else:
43 | break
44 |
45 | def test_cosmosdb_input(self):
46 | time.sleep(5)
47 | data = str(round(time.time()))
48 | doc = {'id': 'cosmosdb-input-test', 'data': data}
49 | r = self.webhost.request('POST', 'put_document',
50 | data=json.dumps(doc))
51 | self.assertEqual(r.status_code, 200)
52 | self.assertEqual(r.text, 'OK')
53 |
54 | max_retries = 10
55 |
56 | for try_no in range(max_retries):
57 | # Allow trigger to fire
58 | time.sleep(2)
59 |
60 | try:
61 | # Check that the trigger has fired
62 | r = self.webhost.request('GET', 'cosmosdb_input')
63 | self.assertEqual(r.status_code, 200)
64 | response = r.json()
65 |
66 | self.assertEqual(
67 | response,
68 | doc
69 | )
70 | except AssertionError as e:
71 | if try_no == max_retries - 1:
72 | raise
73 | else:
74 | break
75 |
--------------------------------------------------------------------------------
/azure/functions_worker/bindings/cosmosdb.py:
--------------------------------------------------------------------------------
1 | import collections.abc
2 | import json
3 | import typing
4 |
5 | from azure.functions import _cosmosdb as cdb
6 |
7 | from . import meta
8 | from .. import protos
9 |
10 |
11 | class CosmosDBConverter(meta.InConverter, meta.OutConverter,
12 | binding='cosmosDB'):
13 |
14 | @classmethod
15 | def check_input_type_annotation(
16 | cls, pytype: type, datatype: protos.BindingInfo.DataType) -> bool:
17 | if datatype is protos.BindingInfo.undefined:
18 | return issubclass(pytype, cdb.DocumentList)
19 | else:
20 | return False
21 |
22 | @classmethod
23 | def check_output_type_annotation(cls, pytype: type) -> bool:
24 | return issubclass(pytype, (cdb.DocumentList, cdb.Document))
25 |
26 | @classmethod
27 | def from_proto(cls, data: protos.TypedData, *,
28 | pytype: typing.Optional[type],
29 | trigger_metadata) -> cdb.DocumentList:
30 | data_type = data.WhichOneof('data')
31 |
32 | if data_type == 'string':
33 | body = data.string
34 |
35 | elif data_type == 'bytes':
36 | body = data.bytes.decode('utf-8')
37 |
38 | elif data_type == 'json':
39 | body = data.json
40 |
41 | else:
42 | raise NotImplementedError(
43 | f'unsupported queue payload type: {data_type}')
44 |
45 | documents = json.loads(body)
46 | if not isinstance(documents, list):
47 | documents = [documents]
48 |
49 | return cdb.DocumentList(
50 | cdb.Document.from_dict(doc) for doc in documents)
51 |
52 | @classmethod
53 | def to_proto(cls, obj: typing.Any, *,
54 | pytype: typing.Optional[type]) -> protos.TypedData:
55 | if isinstance(obj, cdb.Document):
56 | data = cdb.DocumentList([obj])
57 |
58 | elif isinstance(obj, cdb.DocumentList):
59 | data = obj
60 |
61 | elif isinstance(obj, collections.abc.Iterable):
62 | data = cdb.DocumentList()
63 |
64 | for doc in obj:
65 | if not isinstance(doc, cdb.Document):
66 | raise NotImplementedError
67 | else:
68 | data.append(doc)
69 |
70 | else:
71 | raise NotImplementedError
72 |
73 | return protos.TypedData(
74 | json=json.dumps([dict(d) for d in data])
75 | )
76 |
77 |
78 | class CosmosDBTriggerConverter(CosmosDBConverter,
79 | binding='cosmosDBTrigger', trigger=True):
80 | pass
81 |
--------------------------------------------------------------------------------
/tests/test_queue_functions.py:
--------------------------------------------------------------------------------
1 | import time
2 |
3 | from azure.functions_worker import testutils
4 |
5 |
6 | class TestQueueFunctions(testutils.WebHostTestCase):
7 |
8 | @classmethod
9 | def get_script_dir(cls):
10 | return 'queue_functions'
11 |
12 | def test_queue_basic(self):
13 | r = self.webhost.request('POST', 'put_queue',
14 | data='test-message')
15 | self.assertEqual(r.status_code, 200)
16 | self.assertEqual(r.text, 'OK')
17 |
18 | # wait for queue_trigger to process the queue item
19 | time.sleep(1)
20 |
21 | r = self.webhost.request('GET', 'get_queue_blob')
22 | self.assertEqual(r.status_code, 200)
23 | msg_info = r.json()
24 |
25 | self.assertIn('queue', msg_info)
26 | msg = msg_info['queue']
27 |
28 | self.assertEqual(msg['body'], 'test-message')
29 | for attr in {'id', 'expiration_time', 'insertion_time',
30 | 'time_next_visible', 'pop_receipt', 'dequeue_count'}:
31 | self.assertIsNotNone(msg.get(attr))
32 |
33 | def test_queue_return(self):
34 | r = self.webhost.request('POST', 'put_queue_return',
35 | data='test-message-return')
36 | self.assertEqual(r.status_code, 200)
37 |
38 | # wait for queue_trigger to process the queue item
39 | time.sleep(1)
40 |
41 | r = self.webhost.request('GET', 'get_queue_blob_return')
42 | self.assertEqual(r.status_code, 200)
43 | self.assertEqual(r.text, 'test-message-return')
44 |
45 | def test_queue_message_object_return(self):
46 | r = self.webhost.request('POST', 'put_queue_message_return',
47 | data='test-message-object-return')
48 | self.assertEqual(r.status_code, 200)
49 |
50 | # wait for queue_trigger to process the queue item
51 | time.sleep(1)
52 |
53 | r = self.webhost.request('GET', 'get_queue_blob_message_return')
54 | self.assertEqual(r.status_code, 200)
55 | self.assertEqual(r.text, 'test-message-object-return')
56 |
57 | def test_queue_return_multiple(self):
58 | r = self.webhost.request('POST', 'put_queue_return_multiple',
59 | data='foo')
60 | self.assertTrue(200 <= r.status_code < 300)
61 |
62 | # wait for queue_trigger to process the queue item
63 | time.sleep(1)
64 |
65 | def test_queue_return_multiple_outparam(self):
66 | r = self.webhost.request('POST', 'put_queue_multiple_out',
67 | data='foo')
68 | self.assertTrue(200 <= r.status_code < 300)
69 | self.assertEqual(r.text, 'HTTP response: foo')
70 |
--------------------------------------------------------------------------------
/azure/functions_worker/aio_compat.py:
--------------------------------------------------------------------------------
1 | """Backport of asyncio.run() function from Python 3.7.
2 |
3 | Source: https://github.com/python/cpython/blob/
4 | bd093355a6aaf2f4ca3ed153e195da57870a55eb/Lib/asyncio/runners.py
5 | """
6 |
7 |
8 | import asyncio
9 |
10 |
11 | def get_running_loop():
12 | """Return the running event loop. Raise a RuntimeError if there is none.
13 |
14 | This function is thread-specific.
15 | """
16 | loop = asyncio._get_running_loop()
17 | if loop is None:
18 | raise RuntimeError('no running event loop')
19 | return loop
20 |
21 |
22 | def run(main, *, debug=False):
23 | """Run a coroutine.
24 |
25 | This function runs the passed coroutine, taking care of
26 | managing the asyncio event loop and finalizing asynchronous
27 | generators.
28 |
29 | This function cannot be called when another asyncio event loop is
30 | running in the same thread.
31 |
32 | If debug is True, the event loop will be run in debug mode.
33 | This function always creates a new event loop and closes it at the end.
34 |
35 | It should be used as a main entry point for asyncio programs, and should
36 | ideally only be called once.
37 | """
38 | if asyncio._get_running_loop() is not None:
39 | raise RuntimeError(
40 | "asyncio.run() cannot be called from a running event loop")
41 |
42 | if not asyncio.iscoroutine(main):
43 | raise ValueError("a coroutine was expected, got {!r}".format(main))
44 |
45 | loop = asyncio.new_event_loop()
46 | try:
47 | asyncio.set_event_loop(loop)
48 | loop.set_debug(debug)
49 | return loop.run_until_complete(main)
50 | finally:
51 | try:
52 | _cancel_all_tasks(loop)
53 | loop.run_until_complete(loop.shutdown_asyncgens())
54 | finally:
55 | asyncio.set_event_loop(None)
56 | loop.close()
57 |
58 |
59 | def _cancel_all_tasks(loop):
60 | to_cancel = [task for task in asyncio.Task.all_tasks(loop)
61 | if not task.done()]
62 | if not to_cancel:
63 | return
64 |
65 | for task in to_cancel:
66 | task.cancel()
67 |
68 | loop.run_until_complete(
69 | asyncio.gather(*to_cancel, loop=loop, return_exceptions=True))
70 |
71 | for task in to_cancel:
72 | if task.cancelled():
73 | continue
74 | if task.exception() is not None:
75 | loop.call_exception_handler({
76 | 'message': 'unhandled exception during asyncio.run() shutdown',
77 | 'exception': task.exception(),
78 | 'task': task,
79 | })
80 |
81 |
82 | try:
83 | # Try to import the 'run' function from asyncio.
84 | from asyncio import run, get_running_loop # NoQA
85 | except ImportError:
86 | # Python <= 3.6
87 | pass
88 |
--------------------------------------------------------------------------------
/azure-pipelines.yml:
--------------------------------------------------------------------------------
1 | name: 1.0.0-beta$(Date:yyyyMMdd)$(Rev:.r)
2 |
3 | trigger:
4 | - dev
5 | - master
6 |
7 | variables:
8 | DOTNET_VERSION: '2.2.103'
9 |
10 | jobs:
11 | - job: Tests
12 | pool:
13 | vmImage: 'ubuntu-16.04'
14 | strategy:
15 | matrix:
16 | Python36:
17 | pythonVersion: '3.6'
18 | Python37:
19 | pythonVersion: '3.7'
20 | maxParallel: 1
21 | steps:
22 | - task: UsePythonVersion@0
23 | inputs:
24 | versionSpec: '$(pythonVersion)'
25 | addToPath: true
26 | - task: ShellScript@2
27 | inputs:
28 | disableAutoCwd: true # Execute in current directory
29 | scriptPath: .ci/linux_devops_tools.sh
30 | displayName: 'Install Core Tools'
31 | - task: DotNetCoreInstaller@0
32 | inputs:
33 | packageType: 'sdk'
34 | version: $(DOTNET_VERSION)
35 | displayName: 'Install dotnet'
36 | - task: ShellScript@2
37 | inputs:
38 | disableAutoCwd: true
39 | scriptPath: .ci/linux_devops_build.sh
40 | displayName: 'Build'
41 | - bash: |
42 | chmod +x .ci/linux_devops_tests.sh
43 | .ci/linux_devops_tests.sh
44 | env:
45 | LINUXSTORAGECONNECTIONSTRING: $(LinuxStorageConnectionString)
46 | LINUXCOSMOSDBCONNECTIONSTRING: $(LinuxCosmosDBConnectionString)
47 | LINUXEVENTHUBCONNECTIONSTRING: $(LinuxEventHubConnectionString)
48 | LINUXSERVICEBUSCONNECTIONSTRING: $(LinuxServiceBusConnectionString)
49 | displayName: 'Tests'
50 |
51 | - template: pack/templates/win_env_gen.yml
52 | parameters:
53 | jobName: 'WindowsEnvGen'
54 | dependency: 'Tests'
55 | vmImage: 'vs2017-win2016'
56 | pythonVersion: '3.6'
57 | artifactName: 'Windows'
58 |
59 | - template: pack/templates/nix_env_gen.yml
60 | parameters:
61 | jobName: 'LinuxEnvGen'
62 | dependency: 'Tests'
63 | vmImage: 'ubuntu-16.04'
64 | pythonVersion: '3.6'
65 | artifactName: 'Linux'
66 |
67 | - template: pack/templates/nix_env_gen.yml
68 | parameters:
69 | jobName: 'MacEnvGen'
70 | dependency: 'Tests'
71 | vmImage: 'macOS-10.13'
72 | pythonVersion: '3.6'
73 | artifactName: 'Mac'
74 |
75 | - job: PackageEnvironments
76 | dependsOn: ['WindowsEnvGen',
77 | 'LinuxEnvGen',
78 | 'MacEnvGen'
79 | ]
80 | pool:
81 | vmImage: 'vs2017-win2016'
82 | steps:
83 | - task: DownloadBuildArtifacts@0
84 | inputs:
85 | buildType: 'current'
86 | downloadType: 'specific'
87 | downloadPath: '$(Build.SourcesDirectory)'
88 | - task: NuGetCommand@2
89 | inputs:
90 | command: pack
91 | packagesToPack: 'pack\Microsoft.Azure.Functions.PythonWorkerRunEnvironments.nuspec'
92 | versioningScheme: 'byEnvVar'
93 | versionEnvVar: BUILD_BUILDNUMBER # Replaces version in nuspec
94 | - task: PublishBuildArtifacts@1
95 | inputs:
96 | pathtoPublish: '$(Build.ArtifactStagingDirectory)'
97 | artifactName: 'PythonWorkerRunEnvironments'
98 |
--------------------------------------------------------------------------------
/tests/test_eventgrid_functions.py:
--------------------------------------------------------------------------------
1 | import time
2 | import requests
3 | import unittest
4 | import uuid
5 |
6 | from azure.functions_worker import testutils
7 |
8 |
9 | class TestEventGridFunctions(testutils.WebHostTestCase):
10 |
11 | @classmethod
12 | def get_script_dir(cls):
13 | return 'eventgrid_functions'
14 |
15 | def request(self, meth, funcname, *args, **kwargs):
16 | request_method = getattr(requests, meth.lower())
17 | url = f'{self.webhost._addr}/runtime/webhooks/eventgrid'
18 | params = dict(kwargs.pop('params', {}))
19 | params['functionName'] = funcname
20 | if 'code' not in params:
21 | params['code'] = 'testSystemKey'
22 | headers = dict(kwargs.pop('headers', {}))
23 | headers['aeg-event-type'] = 'Notification'
24 | return request_method(url, *args, params=params, headers=headers,
25 | **kwargs)
26 |
27 | @unittest.skip("fails with 401 with recent host versions")
28 | def test_eventgrid_trigger(self):
29 | data = [{
30 | "topic": "test-topic",
31 | "subject": "test-subject",
32 | "eventType": "Microsoft.Storage.BlobCreated",
33 | "eventTime": "2018-01-01T00:00:00.000000123Z",
34 | "id": str(uuid.uuid4()),
35 | "data": {
36 | "api": "PutBlockList",
37 | "clientRequestId": "2c169f2f-7b3b-4d99-839b-c92a2d25801b",
38 | "requestId": "44d4f022-001e-003c-466b-940cba000000",
39 | "eTag": "0x8D562831044DDD0",
40 | "contentType": "application/octet-stream",
41 | "contentLength": 2248,
42 | "blobType": "BlockBlob",
43 | "ur1": "foo",
44 | "sequencer": "000000000000272D000000000003D60F",
45 | "storageDiagnostics": {
46 | "batchId": "b4229b3a-4d50-4ff4-a9f2-039ccf26efe9"
47 | }
48 | },
49 | "dataVersion": "",
50 | "metadataVersion": "1"
51 | }]
52 |
53 | r = self.request('POST', 'eventgrid_trigger', json=data)
54 | self.assertEqual(r.status_code, 202)
55 |
56 | max_retries = 10
57 |
58 | for try_no in range(max_retries):
59 | # Allow trigger to fire.
60 | time.sleep(2)
61 |
62 | try:
63 | # Check that the trigger has fired.
64 | r = self.webhost.request('GET', 'get_eventgrid_triggered')
65 | self.assertEqual(r.status_code, 200)
66 | response = r.json()
67 |
68 | self.assertEqual(
69 | response,
70 | {
71 | 'id': data[0]['id'],
72 | 'data': data[0]['data'],
73 | 'topic': data[0]['topic'],
74 | 'subject': data[0]['subject'],
75 | 'event_type': data[0]['eventType'],
76 | }
77 | )
78 | except AssertionError as e:
79 | if try_no == max_retries - 1:
80 | raise
81 | else:
82 | break
83 |
--------------------------------------------------------------------------------
/tests/test_mock_http_functions.py:
--------------------------------------------------------------------------------
1 | from azure.functions_worker import protos
2 | from azure.functions_worker import testutils
3 |
4 |
5 | class TestMockHost(testutils.AsyncTestCase):
6 |
7 | async def test_call_sync_function_check_logs(self):
8 | async with testutils.start_mockhost() as host:
9 | await host.load_function('sync_logging')
10 |
11 | invoke_id, r = await host.invoke_function(
12 | 'sync_logging', [
13 | protos.ParameterBinding(
14 | name='req',
15 | data=protos.TypedData(
16 | http=protos.RpcHttp(
17 | method='GET')))
18 | ])
19 |
20 | self.assertEqual(r.response.result.status,
21 | protos.StatusResult.Success)
22 |
23 | self.assertEqual(len(r.logs), 1)
24 |
25 | log = r.logs[0]
26 | self.assertEqual(log.invocation_id, invoke_id)
27 | self.assertTrue(log.message.startswith(
28 | 'a gracefully handled error'))
29 |
30 | self.assertEqual(r.response.return_value.string, 'OK-sync')
31 |
32 | async def test_call_async_function_check_logs(self):
33 | async with testutils.start_mockhost() as host:
34 | await host.load_function('async_logging')
35 |
36 | invoke_id, r = await host.invoke_function(
37 | 'async_logging', [
38 | protos.ParameterBinding(
39 | name='req',
40 | data=protos.TypedData(
41 | http=protos.RpcHttp(
42 | method='GET')))
43 | ])
44 |
45 | self.assertEqual(r.response.result.status,
46 | protos.StatusResult.Success)
47 |
48 | self.assertEqual(len(r.logs), 2)
49 |
50 | self.assertEqual(r.logs[0].invocation_id, invoke_id)
51 | self.assertEqual(r.logs[0].message, 'hello info')
52 | self.assertEqual(r.logs[0].level, protos.RpcLog.Information)
53 |
54 | self.assertEqual(r.logs[1].invocation_id, invoke_id)
55 | self.assertTrue(r.logs[1].message.startswith('and another error'))
56 | self.assertEqual(r.logs[1].level, protos.RpcLog.Error)
57 |
58 | self.assertEqual(r.response.return_value.string, 'OK-async')
59 |
60 | async def test_handles_unsupported_messages_gracefully(self):
61 | async with testutils.start_mockhost() as host:
62 | # Intentionally send a message to worker that isn't
63 | # going to be ever supported by it. The idea is that
64 | # workers should survive such messages and continue
65 | # their operation. If anything, the host can always
66 | # terminate the worker.
67 | await host.send(
68 | protos.StreamingMessage(
69 | worker_heartbeat=protos.WorkerHeartbeat()))
70 |
71 | _, r = await host.load_function('return_out')
72 | self.assertEqual(r.response.result.status,
73 | protos.StatusResult.Success)
74 |
--------------------------------------------------------------------------------
/tests/test_blob_functions.py:
--------------------------------------------------------------------------------
1 | import time
2 |
3 | from azure.functions_worker import testutils
4 |
5 |
6 | class TestBlobFunctions(testutils.WebHostTestCase):
7 |
8 | @classmethod
9 | def get_script_dir(cls):
10 | return 'blob_functions'
11 |
12 | def test_blob_io_str(self):
13 | r = self.webhost.request('POST', 'put_blob_str', data='test-data')
14 | self.assertEqual(r.status_code, 200)
15 | self.assertEqual(r.text, 'OK')
16 |
17 | r = self.webhost.request('GET', 'get_blob_str')
18 | self.assertEqual(r.status_code, 200)
19 | self.assertEqual(r.text, 'test-data')
20 |
21 | r = self.webhost.request('GET', 'get_blob_as_str')
22 | self.assertEqual(r.status_code, 200)
23 | self.assertEqual(r.text, 'test-data')
24 |
25 | def test_blob_io_bytes(self):
26 | r = self.webhost.request('POST', 'put_blob_bytes',
27 | data='test-dată'.encode('utf-8'))
28 | self.assertEqual(r.status_code, 200)
29 | self.assertEqual(r.text, 'OK')
30 |
31 | r = self.webhost.request('POST', 'get_blob_bytes')
32 | self.assertEqual(r.status_code, 200)
33 | self.assertEqual(r.text, 'test-dată')
34 |
35 | r = self.webhost.request('POST', 'get_blob_as_bytes')
36 | self.assertEqual(r.status_code, 200)
37 | self.assertEqual(r.text, 'test-dată')
38 |
39 | def test_blob_io_filelike(self):
40 | r = self.webhost.request('POST', 'put_blob_filelike')
41 | self.assertEqual(r.status_code, 200)
42 | self.assertEqual(r.text, 'OK')
43 |
44 | r = self.webhost.request('POST', 'get_blob_filelike')
45 | self.assertEqual(r.status_code, 200)
46 | self.assertEqual(r.text, 'filelike')
47 |
48 | def test_blob_io_return(self):
49 | r = self.webhost.request('POST', 'put_blob_return')
50 | self.assertEqual(r.status_code, 200)
51 |
52 | r = self.webhost.request('POST', 'get_blob_return')
53 | self.assertEqual(r.status_code, 200)
54 | self.assertEqual(r.text, 'FROM RETURN')
55 |
56 | def test_blob_trigger(self):
57 | data = str(round(time.time()))
58 |
59 | r = self.webhost.request('POST', 'put_blob_trigger',
60 | data=data.encode('utf-8'))
61 | self.assertEqual(r.status_code, 200)
62 | self.assertEqual(r.text, 'OK')
63 |
64 | max_retries = 10
65 |
66 | for try_no in range(max_retries):
67 | # Allow trigger to fire
68 | time.sleep(2)
69 |
70 | try:
71 | # Check that the trigger has fired
72 | r = self.webhost.request('GET', 'get_blob_triggered')
73 | self.assertEqual(r.status_code, 200)
74 | response = r.json()
75 |
76 | self.assertEqual(
77 | response,
78 | {
79 | 'name': 'python-worker-tests/test-blob-trigger.txt',
80 | 'length': 10,
81 | 'content': data
82 | }
83 | )
84 | except AssertionError:
85 | if try_no == max_retries - 1:
86 | raise
87 |
--------------------------------------------------------------------------------
/azure/functions_worker/bindings/eventhub.py:
--------------------------------------------------------------------------------
1 | import json
2 | import typing
3 |
4 | from azure.functions import _eventhub
5 |
6 | from . import meta
7 | from .. import protos
8 |
9 |
10 | class EventHubConverter(meta.InConverter, meta.OutConverter,
11 | binding='eventHub'):
12 |
13 | @classmethod
14 | def check_input_type_annotation(
15 | cls, pytype: type, datatype: protos.BindingInfo.DataType) -> bool:
16 | if datatype is protos.BindingInfo.undefined:
17 | return issubclass(pytype, _eventhub.EventHubEvent)
18 | else:
19 | return False
20 |
21 | @classmethod
22 | def check_output_type_annotation(cls, pytype) -> bool:
23 | return (
24 | issubclass(pytype, (str, bytes))
25 | or (issubclass(pytype, typing.List)
26 | and issubclass(pytype.__args__[0], str))
27 | )
28 |
29 | @classmethod
30 | def from_proto(cls, data: protos.TypedData, *,
31 | pytype: typing.Optional[type],
32 | trigger_metadata) -> _eventhub.EventHubEvent:
33 | data_type = data.WhichOneof('data')
34 |
35 | if data_type == 'string':
36 | body = data.string.encode('utf-8')
37 |
38 | elif data_type == 'bytes':
39 | body = data.bytes
40 |
41 | elif data_type == 'json':
42 | body = data.json.encode('utf-8')
43 |
44 | else:
45 | raise NotImplementedError(
46 | f'unsupported event data payload type: {data_type}')
47 |
48 | return _eventhub.EventHubEvent(body=body)
49 |
50 | @classmethod
51 | def to_proto(cls, obj: typing.Any, *,
52 | pytype: typing.Optional[type]) -> protos.TypedData:
53 | if isinstance(obj, str):
54 | data = protos.TypedData(string=obj)
55 |
56 | elif isinstance(obj, bytes):
57 | data = protos.TypedData(bytes=obj)
58 |
59 | elif isinstance(obj, list):
60 | data = protos.TypedData(json=json.dumps(obj))
61 |
62 | return data
63 |
64 |
65 | class EventHubTriggerConverter(EventHubConverter,
66 | binding='eventHubTrigger', trigger=True):
67 |
68 | @classmethod
69 | def from_proto(cls, data: protos.TypedData, *,
70 | pytype: typing.Optional[type],
71 | trigger_metadata) -> _eventhub.EventHubEvent:
72 | data_type = data.WhichOneof('data')
73 |
74 | if data_type == 'string':
75 | body = data.string.encode('utf-8')
76 |
77 | elif data_type == 'bytes':
78 | body = data.bytes
79 |
80 | elif data_type == 'json':
81 | body = data.json.encode('utf-8')
82 |
83 | else:
84 | raise NotImplementedError(
85 | f'unsupported event data payload type: {data_type}')
86 |
87 | return _eventhub.EventHubEvent(
88 | body=body,
89 | enqueued_time=cls._parse_datetime_metadata(
90 | trigger_metadata, 'EnqueuedTime'),
91 | partition_key=cls._decode_trigger_metadata_field(
92 | trigger_metadata, 'PartitionKey', python_type=str),
93 | sequence_number=cls._decode_trigger_metadata_field(
94 | trigger_metadata, 'SequenceNumber', python_type=int),
95 | offset=cls._decode_trigger_metadata_field(
96 | trigger_metadata, 'Offset', python_type=str)
97 | )
98 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | |Branch|Status|
2 | |---|---|
3 | |master|[](https://azfunc.visualstudio.com/Azure%20Functions%20Python/_build/latest?definitionId=3&branchName=master)|
4 | |dev|[](https://azfunc.visualstudio.com/Azure%20Functions%20Python/_build/latest?definitionId=3&branchName=dev)|
5 |
6 | This repository will host the Python language worker implementation for Azure Functions. We'll also be using it to track work items related to Python support. Please feel free to leave comments about any of the features and design patterns.
7 |
8 | > :construction: The project is currently **work in progress**. Please **do not use in production** as we expect developments over time. To receive important updates, including breaking changes announcements, watch the [Azure App Service announcements](https://github.com/Azure/app-service-announcements/issues) repository. :construction:
9 |
10 | # Overview
11 |
12 | Python support for Azure Functions is based on Python3.6, serverless hosting on Linux and the Functions 2.0 runtime.
13 |
14 | Here is the current status of Python in Azure Functions:
15 |
16 | What's available?
17 |
18 | - Build, test, debug and publish using Azure Functions Core Tools (CLI) or Visual Studio Code
19 | - Triggers / Bindings : HTTP, Blob, Queue, Timer, Cosmos DB, Event Grid, Event Hubs and Service Bus
20 | - Create a Python Function on Linux using a custom docker image
21 |
22 | What's coming?
23 |
24 | - Triggers / Bindings : Custom binding support
25 | - Python 3.7 support
26 |
27 | # Get Started
28 |
29 | - [Create your first Python function](https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-first-function-python)
30 | - [Developer guide](https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference-python)
31 | - [Binding API reference](https://docs.microsoft.com/en-us/python/api/azure-functions/azure.functions?view=azure-python)
32 | - [Develop using VS Code](https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-first-function-vs-code)
33 | - [Create a Python Function on Linux using a custom docker image](https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-function-linux-custom-image)
34 |
35 | # Give Feedback
36 |
37 | Issues and feature requests are tracked in a variety of places. To report this feedback, please file an issue to the relevant repository below:
38 |
39 | |Item|Description|Link|
40 | |----|-----|-----|
41 | | Python Worker | Programming Model, Triggers & Bindings |[File an Issue](https://github.com/Azure/azure-functions-python-worker/issues)|
42 | | Linux | Base Docker Images |[File an Issue](https://github.com/Azure/azure-functions-docker/issues)|
43 | | Runtime | Script Host & Language Extensibility |[File an Issue](https://github.com/Azure/azure-functions-host/issues)|
44 | | Core Tools | Command line interface for local development |[File an Issue](https://github.com/Azure/azure-functions-core-tools/issues)|
45 | | Portal | User Interface or Experience Issue |[File an Issue](https://github.com/azure/azure-functions-ux/issues)|
46 | | Templates | Code Issues with Creation Template |[File an Issue](https://github.com/Azure/azure-functions-templates/issues)|
47 |
48 | # Contribute
49 |
50 | This project welcomes contributions and suggestions. Most contributions require you to agree to a
51 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
52 | the rights to use your contribution. For details, visit https://cla.microsoft.com.
53 |
54 | Here are some pointers to get started:
55 |
56 | - [Language worker architecture](https://github.com/Azure/azure-functions-python-worker/wiki/Worker-Architecture)
57 | - [Setting up the development environment](https://github.com/Azure/azure-functions-python-worker/wiki/Contributor-Guide)
58 | - [Adding support for a new binding](https://github.com/Azure/azure-functions-python-worker/wiki/Adding-support-for-a-new-binding-type)
59 |
60 | When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
61 | a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
62 | provided by the bot. You will only need to do this once across all repos using our CLA.
63 |
64 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
65 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
66 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
67 |
--------------------------------------------------------------------------------
/azure/functions_worker/protos/_src/README.md:
--------------------------------------------------------------------------------
1 | # Azure Functions Languge Worker Protobuf
2 |
3 | This repository contains the protobuf definition file which defines the gRPC service which is used between the Azure WebJobs Script host and the Azure Functions language workers. This repo is shared across many repos in many languages (for each worker) by using git commands.
4 |
5 | To use this repo in Azure Functions language workers, follow steps below to add this repo as a subtree (*Adding This Repo*). If this repo is already embedded in a language worker repo, follow the steps to update the consumed file (*Pulling Updates*).
6 |
7 | Learn more about Azure Function's projects on the [meta](https://github.com/azure/azure-functions) repo.
8 |
9 | ## Adding This Repo
10 |
11 | From within the Azure Functions language worker repo:
12 | 1. Define remote branch for cleaner git commands
13 | - `git remote add proto-file https://github.com/azure/azure-functions-language-worker-protobuf.git`
14 | - `git fetch proto-file`
15 | 2. Index contents of azure-functions-worker-protobuf to language worker repo
16 | - `git read-tree --prefix= -u proto-file/`
17 | 3. Add new path in language worker repo to .gitignore file
18 | - In .gitignore, add path in language worker repo
19 | 4. Finalize with commit
20 | - `git commit -m "Added subtree from https://github.com/azure/azure-functions-language-worker-protobuf. Branch: . Commit: "`
21 | - `git push`
22 |
23 | ## Pulling Updates
24 |
25 | From within the Azure Functions language worker repo:
26 | 1. Define remote branch for cleaner git commands
27 | - `git remote add proto-file https://github.com/azure/azure-functions-language-worker-protobuf.git`
28 | - `git fetch proto-file`
29 | 2. Merge updates
30 | - `git merge -s subtree proto-file/ --squash --allow-unrelated-histories`
31 | - You can also merge with an explicit path to subtree: `git merge -X subtree= --squash proto-file/ --allow-unrelated-histories`
32 | 3. Finalize with commit
33 | - `git commit -m "Updated subtree from https://github.com/azure/azure-functions-language-worker-protobuf. Branch: . Commit: "`
34 | - `git push`
35 |
36 | ## Consuming FunctionRPC.proto
37 | *Note: Update versionNumber before running following commands*
38 |
39 | ## CSharp
40 | ```
41 | set NUGET_PATH=%UserProfile%\.nuget\packages
42 | set GRPC_TOOLS_PATH=%NUGET_PATH%\grpc.tools\\tools\windows_x86
43 | set PROTO_PATH=.\azure-functions-language-worker-protobuf\src\proto
44 | set PROTO=.\azure-functions-language-worker-protobuf\src\proto\FunctionRpc.proto
45 | set PROTOBUF_TOOLS=%NUGET_PATH%\google.protobuf.tools\\tools
46 | set MSGDIR=.\Messages
47 |
48 | if exist %MSGDIR% rmdir /s /q %MSGDIR%
49 | mkdir %MSGDIR%
50 |
51 | set OUTDIR=%MSGDIR%\DotNet
52 | mkdir %OUTDIR%
53 | %GRPC_TOOLS_PATH%\protoc.exe %PROTO% --csharp_out %OUTDIR% --grpc_out=%OUTDIR% --plugin=protoc-gen-grpc=%GRPC_TOOLS_PATH%\grpc_csharp_plugin.exe --proto_path=%PROTO_PATH% --proto_path=%PROTOBUF_TOOLS%
54 | ```
55 | ## JavaScript
56 | In package.json, add to the build script the following commands to build .js files and to build .ts files. Use and install npm package `protobufjs`.
57 |
58 | Generate JavaScript files:
59 | ```
60 | pbjs -t json-module -w commonjs -o azure-functions-language-worker-protobuf/src/rpc.js azure-functions-language-worker-protobuf/src/proto/FunctionRpc.proto
61 | ```
62 | Generate TypeScript files:
63 | ```
64 | pbjs -t static-module azure-functions-language-worker-protobuf/src/proto/FunctionRpc.proto -o azure-functions-language-worker-protobuf/src/rpc_static.js && pbts -o azure-functions-language-worker-protobuf/src/rpc.d.ts azure-functions-language-worker-protobuf/src/rpc_static.js
65 | ```
66 |
67 | ## Java
68 | Maven plugin : [protobuf-maven-plugin](https://www.xolstice.org/protobuf-maven-plugin/)
69 | In pom.xml add following under configuration for this plugin
70 | ${basedir}//azure-functions-language-worker-protobuf/src/proto
71 |
72 | ## Python
73 | --TODO
74 |
75 | ## Contributing
76 |
77 | This project welcomes contributions and suggestions. Most contributions require you to agree to a
78 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
79 | the rights to use your contribution. For details, visit https://cla.microsoft.com.
80 |
81 | When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
82 | a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
83 | provided by the bot. You will only need to do this once across all repos using our CLA.
84 |
85 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
86 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
87 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
88 |
--------------------------------------------------------------------------------
/azure/functions_worker/protos/_src/src/proto/google/protobuf/duration.proto:
--------------------------------------------------------------------------------
1 | // Protocol Buffers - Google's data interchange format
2 | // Copyright 2008 Google Inc. All rights reserved.
3 | // https://developers.google.com/protocol-buffers/
4 | //
5 | // Redistribution and use in source and binary forms, with or without
6 | // modification, are permitted provided that the following conditions are
7 | // met:
8 | //
9 | // * Redistributions of source code must retain the above copyright
10 | // notice, this list of conditions and the following disclaimer.
11 | // * Redistributions in binary form must reproduce the above
12 | // copyright notice, this list of conditions and the following disclaimer
13 | // in the documentation and/or other materials provided with the
14 | // distribution.
15 | // * Neither the name of Google Inc. nor the names of its
16 | // contributors may be used to endorse or promote products derived from
17 | // this software without specific prior written permission.
18 | //
19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
31 | syntax = "proto3";
32 |
33 | package google.protobuf;
34 |
35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes";
36 | option cc_enable_arenas = true;
37 | option go_package = "github.com/golang/protobuf/ptypes/duration";
38 | option java_package = "com.google.protobuf";
39 | option java_outer_classname = "DurationProto";
40 | option java_multiple_files = true;
41 | option objc_class_prefix = "GPB";
42 |
43 | // A Duration represents a signed, fixed-length span of time represented
44 | // as a count of seconds and fractions of seconds at nanosecond
45 | // resolution. It is independent of any calendar and concepts like "day"
46 | // or "month". It is related to Timestamp in that the difference between
47 | // two Timestamp values is a Duration and it can be added or subtracted
48 | // from a Timestamp. Range is approximately +-10,000 years.
49 | //
50 | // # Examples
51 | //
52 | // Example 1: Compute Duration from two Timestamps in pseudo code.
53 | //
54 | // Timestamp start = ...;
55 | // Timestamp end = ...;
56 | // Duration duration = ...;
57 | //
58 | // duration.seconds = end.seconds - start.seconds;
59 | // duration.nanos = end.nanos - start.nanos;
60 | //
61 | // if (duration.seconds < 0 && duration.nanos > 0) {
62 | // duration.seconds += 1;
63 | // duration.nanos -= 1000000000;
64 | // } else if (durations.seconds > 0 && duration.nanos < 0) {
65 | // duration.seconds -= 1;
66 | // duration.nanos += 1000000000;
67 | // }
68 | //
69 | // Example 2: Compute Timestamp from Timestamp + Duration in pseudo code.
70 | //
71 | // Timestamp start = ...;
72 | // Duration duration = ...;
73 | // Timestamp end = ...;
74 | //
75 | // end.seconds = start.seconds + duration.seconds;
76 | // end.nanos = start.nanos + duration.nanos;
77 | //
78 | // if (end.nanos < 0) {
79 | // end.seconds -= 1;
80 | // end.nanos += 1000000000;
81 | // } else if (end.nanos >= 1000000000) {
82 | // end.seconds += 1;
83 | // end.nanos -= 1000000000;
84 | // }
85 | //
86 | // Example 3: Compute Duration from datetime.timedelta in Python.
87 | //
88 | // td = datetime.timedelta(days=3, minutes=10)
89 | // duration = Duration()
90 | // duration.FromTimedelta(td)
91 | //
92 | // # JSON Mapping
93 | //
94 | // In JSON format, the Duration type is encoded as a string rather than an
95 | // object, where the string ends in the suffix "s" (indicating seconds) and
96 | // is preceded by the number of seconds, with nanoseconds expressed as
97 | // fractional seconds. For example, 3 seconds with 0 nanoseconds should be
98 | // encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should
99 | // be expressed in JSON format as "3.000000001s", and 3 seconds and 1
100 | // microsecond should be expressed in JSON format as "3.000001s".
101 | //
102 | //
103 | message Duration {
104 |
105 | // Signed seconds of the span of time. Must be from -315,576,000,000
106 | // to +315,576,000,000 inclusive. Note: these bounds are computed from:
107 | // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
108 | int64 seconds = 1;
109 |
110 | // Signed fractions of a second at nanosecond resolution of the span
111 | // of time. Durations less than one second are represented with a 0
112 | // `seconds` field and a positive or negative `nanos` field. For durations
113 | // of one second or more, a non-zero value for the `nanos` field must be
114 | // of the same sign as the `seconds` field. Must be from -999,999,999
115 | // to +999,999,999 inclusive.
116 | int32 nanos = 2;
117 | }
--------------------------------------------------------------------------------
/azure/functions_worker/bindings/blob.py:
--------------------------------------------------------------------------------
1 | import io
2 | import typing
3 |
4 | from azure.functions import _abc as azf_abc
5 |
6 | from . import meta
7 | from .. import protos
8 |
9 |
10 | class InputStream(azf_abc.InputStream):
11 | def __init__(self, *, data: bytes,
12 | name: typing.Optional[str]=None,
13 | uri: typing.Optional[str]=None,
14 | length: typing.Optional[int]=None) -> None:
15 | self._io = io.BytesIO(data)
16 | self._name = name
17 | self._length = length
18 | self._uri = uri
19 |
20 | @property
21 | def name(self) -> typing.Optional[str]:
22 | return self._name
23 |
24 | @property
25 | def length(self) -> typing.Optional[int]:
26 | return self._length
27 |
28 | @property
29 | def uri(self) -> typing.Optional[str]:
30 | return self._uri
31 |
32 | def read(self, size=-1) -> bytes:
33 | return self._io.read(size)
34 |
35 | def readable(self) -> bool:
36 | return True
37 |
38 | def seekable(self) -> bool:
39 | return False
40 |
41 | def writable(self) -> bool:
42 | return False
43 |
44 |
45 | class BlobConverter(meta.InConverter,
46 | meta.OutConverter,
47 | binding='blob'):
48 |
49 | @classmethod
50 | def check_input_type_annotation(
51 | cls, pytype: type, datatype: protos.BindingInfo.DataType) -> bool:
52 | if (datatype is protos.BindingInfo.undefined
53 | or datatype is protos.BindingInfo.stream):
54 | return issubclass(pytype, azf_abc.InputStream)
55 | elif (datatype is protos.BindingInfo.binary):
56 | return issubclass(pytype, bytes)
57 | elif (datatype is protos.BindingInfo.string):
58 | return issubclass(pytype, str)
59 | else: # Unknown datatype
60 | return False
61 |
62 | @classmethod
63 | def check_output_type_annotation(cls, pytype: type) -> bool:
64 | return (issubclass(pytype, (str, bytes, bytearray,
65 | azf_abc.InputStream) or
66 | callable(getattr(pytype, 'read', None))))
67 |
68 | @classmethod
69 | def to_proto(cls, obj: typing.Any, *,
70 | pytype: typing.Optional[type]) -> protos.TypedData:
71 | if callable(getattr(obj, 'read', None)):
72 | # file-like object
73 | obj = obj.read()
74 |
75 | if isinstance(obj, str):
76 | return protos.TypedData(string=obj)
77 |
78 | elif isinstance(obj, (bytes, bytearray)):
79 | return protos.TypedData(bytes=bytes(obj))
80 |
81 | else:
82 | raise NotImplementedError
83 |
84 | @classmethod
85 | def from_proto(cls, data: protos.TypedData, *,
86 | pytype: typing.Optional[type],
87 | trigger_metadata) -> typing.Any:
88 | data_type = data.WhichOneof('data')
89 |
90 | if pytype is str:
91 | # Bound as dataType: string
92 | if data_type == 'string':
93 | return data.string
94 | else:
95 | raise ValueError(
96 | f'unexpected type of data received for the "blob" binding '
97 | f'declared to receive a string: {data_type!r}'
98 | )
99 |
100 | return data.string
101 |
102 | elif pytype is bytes:
103 | if data_type == 'bytes':
104 | return data.bytes
105 | elif data_type == 'string':
106 | # This should not happen with the correct dataType spec,
107 | # but we can be forgiving in this case.
108 | return data.string.encode('utf-8')
109 | else:
110 | raise ValueError(
111 | f'unexpected type of data received for the "blob" binding '
112 | f'declared to receive bytes: {data_type!r}'
113 | )
114 |
115 | if data_type == 'string':
116 | data = data.string.encode('utf-8')
117 | elif data_type == 'bytes':
118 | data = data.bytes
119 | else:
120 | raise ValueError(
121 | f'unexpected type of data received for the "blob" binding '
122 | f': {data_type!r}'
123 | )
124 |
125 | if trigger_metadata is None:
126 | return InputStream(data=data)
127 | else:
128 | properties = cls._decode_trigger_metadata_field(
129 | trigger_metadata, 'Properties', python_type=dict)
130 | if properties:
131 | length = properties.get('Length')
132 | if length:
133 | length = int(length)
134 | else:
135 | length = None
136 | else:
137 | length = None
138 |
139 | return InputStream(
140 | data=data,
141 | name=cls._decode_trigger_metadata_field(
142 | trigger_metadata, 'BlobTrigger', python_type=str),
143 | length=length,
144 | uri=cls._decode_trigger_metadata_field(
145 | trigger_metadata, 'Uri', python_type=str),
146 | )
147 |
148 |
149 | class BlobTriggerConverter(BlobConverter,
150 | binding='blobTrigger', trigger=True):
151 | pass
152 |
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/test_runners/run_all_parallel.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Get the config file
4 | source "$(dirname "${BASH_SOURCE[0]}")/helpers/get_config_variables.sh"
5 | source "$(dirname "${BASH_SOURCE[0]}")/helpers/helper.sh"
6 |
7 | mkdir -p ${WORKING_DIR}
8 | mkdir -p ${TESTS_LOGS}
9 |
10 | BASE_DIR="$(dirname "${BASH_SOURCE[0]}")"
11 | RESET='\033[0m' # No Color
12 | BLUE='\033[1;34m'
13 | YELLOW='\033[1;33m'
14 |
15 | # This may be needed if docker image is restarted
16 | # Better to simply login again at every invoke
17 | echo "Ensuring login to Azure ACR"
18 | az acr login --name ${ACR_NAME} > /dev/null
19 |
20 | echo -e "Starting Parallel execution in background"
21 |
22 |
23 | run_test ${BASE_DIR}/dev_func_dev_docker/customer_build_native_deps.sh ${TESTS_LOGS}/dfddcbnd.log ${TESTS_TIMEOUT} ${WORKING_DIR}/dfddcbnd.result &
24 | run_test ${BASE_DIR}/dev_func_dev_docker/customer_no_bundler.sh ${TESTS_LOGS}/dfddcnb.log ${TESTS_TIMEOUT} ${WORKING_DIR}/dfddcnb.result &
25 | run_test ${BASE_DIR}/dev_func_dev_docker/new_build_native_deps.sh ${TESTS_LOGS}/dfddnbnd.log ${TESTS_TIMEOUT} ${WORKING_DIR}/dfddnbnd.result &
26 | run_test ${BASE_DIR}/dev_func_dev_docker/new_no_bundler.sh ${TESTS_LOGS}/dfddnnb.log ${TESTS_TIMEOUT} ${WORKING_DIR}/dfddnnb.result &
27 | run_test ${BASE_DIR}/dev_func_dev_docker/new_packapp.sh ${TESTS_LOGS}/dfddnpa.log ${TESTS_TIMEOUT} ${WORKING_DIR}/dfddnpa.result &
28 |
29 | run_test ${BASE_DIR}/dev_func_prod_docker/customer_build_native_deps.sh ${TESTS_LOGS}/dfpdcbnd.log ${TESTS_TIMEOUT} ${WORKING_DIR}/dfpdcbnd.result &
30 | run_test ${BASE_DIR}/dev_func_prod_docker/customer_no_bundler.sh ${TESTS_LOGS}/dfpdcnb.log ${TESTS_TIMEOUT} ${WORKING_DIR}/dfpdcnb.result &
31 | run_test ${BASE_DIR}/dev_func_prod_docker/new_build_native_deps.sh ${TESTS_LOGS}/dfpdnbnd.log ${TESTS_TIMEOUT} ${WORKING_DIR}/dfpdnbnd.result &
32 | run_test ${BASE_DIR}/dev_func_prod_docker/new_no_bundler.sh ${TESTS_LOGS}/dfpdnnb.log ${TESTS_TIMEOUT} ${WORKING_DIR}/dfpdnnb.result &
33 | run_test ${BASE_DIR}/dev_func_prod_docker/new_packapp.sh ${TESTS_LOGS}/dfpdnpa.log ${TESTS_TIMEOUT} ${WORKING_DIR}/dfpdnpa.result &
34 |
35 | run_test ${BASE_DIR}/prod_func_prod_docker/customer_build_native_deps.sh ${TESTS_LOGS}/pfpdcbnd.log ${TESTS_TIMEOUT} ${WORKING_DIR}/pfpdcbnd.result &
36 | run_test ${BASE_DIR}/prod_func_prod_docker/customer_no_bundler.sh ${TESTS_LOGS}/pfpdcnb.log ${TESTS_TIMEOUT} ${WORKING_DIR}/pfpdcnb.result &
37 | run_test ${BASE_DIR}/prod_func_prod_docker/new_build_native_deps.sh ${TESTS_LOGS}/pfpdnbnd.log ${TESTS_TIMEOUT} ${WORKING_DIR}/pfpdnbnd.result &
38 | run_test ${BASE_DIR}/prod_func_prod_docker/new_no_bundler.sh ${TESTS_LOGS}/pfpdnnb.log ${TESTS_TIMEOUT} ${WORKING_DIR}/pfpdnnb.result &
39 | run_test ${BASE_DIR}/prod_func_prod_docker/new_packapp.sh ${TESTS_LOGS}/pfpdnpa.log ${TESTS_TIMEOUT} ${WORKING_DIR}/pfpdnpa.result &
40 |
41 | run_test ${BASE_DIR}/prod_func_dev_docker/customer_build_native_deps.sh ${TESTS_LOGS}/pfddcbnd.log ${TESTS_TIMEOUT} ${WORKING_DIR}/pfddcbnd.result &
42 | run_test ${BASE_DIR}/prod_func_dev_docker/customer_no_bundler.sh ${TESTS_LOGS}/pfddcnb.log ${TESTS_TIMEOUT} ${WORKING_DIR}/pfddcnb.result &
43 | run_test ${BASE_DIR}/prod_func_dev_docker/new_build_native_deps.sh ${TESTS_LOGS}/pfddnbnd.log ${TESTS_TIMEOUT} ${WORKING_DIR}/pfddnbnd.result &
44 | run_test ${BASE_DIR}/prod_func_dev_docker/new_no_bundler.sh ${TESTS_LOGS}/pfddnnb.log ${TESTS_TIMEOUT} ${WORKING_DIR}/pfddnnb.result &
45 | run_test ${BASE_DIR}/prod_func_dev_docker/new_packapp.sh ${TESTS_LOGS}/pfddnpa.log ${TESTS_TIMEOUT} ${WORKING_DIR}/pfddnpa.result &
46 |
47 | echo "Waiting for tests to complete (this may take several minutes)..."
48 | wait
49 |
50 | echo "Test Completed!"
51 | yellow "Results-"
52 | printf "\n"
53 |
54 | print_row $(yellow "Version") $(yellow "Exist-Build-Native") \
55 | $(yellow "Exist-No-Bundler") $(yellow "New-Build-Native") $(yellow "New-No-Bundler") $(yellow "New-Packapp")
56 |
57 | print_row_line
58 |
59 | print_row $(yellow "dev-func-dev-docker") $(get_result_of ${WORKING_DIR}/dfddcbnd.result) \
60 | $(get_result_of ${WORKING_DIR}/dfddcnb.result) $(get_result_of ${WORKING_DIR}/dfddnbnd.result) \
61 | $(get_result_of ${WORKING_DIR}/dfddnnb.result) $(get_result_of ${WORKING_DIR}/dfddnpa.result)
62 |
63 | print_row $(yellow "dev-func-dev-docker") $(get_result_of ${WORKING_DIR}/dfpdcbnd.result) \
64 | $(get_result_of ${WORKING_DIR}/dfpdcnb.result) $(get_result_of ${WORKING_DIR}/dfpdnbnd.result) \
65 | $(get_result_of ${WORKING_DIR}/dfpdnnb.result) $(get_result_of ${WORKING_DIR}/dfpdnpa.result)
66 |
67 | print_row $(yellow "dev-func-dev-docker") $(get_result_of ${WORKING_DIR}/pfddcbnd.result) \
68 | $(get_result_of ${WORKING_DIR}/pfddcnb.result) $(get_result_of ${WORKING_DIR}/pfddnbnd.result) \
69 | $(get_result_of ${WORKING_DIR}/pfddnnb.result) $(get_result_of ${WORKING_DIR}/pfddnpa.result)
70 |
71 | print_row $(yellow "dev-func-dev-docker") $(get_result_of ${WORKING_DIR}/pfpdcbnd.result) \
72 | $(get_result_of ${WORKING_DIR}/pfpdcnb.result) $(get_result_of ${WORKING_DIR}/pfpdnbnd.result) \
73 | $(get_result_of ${WORKING_DIR}/pfpdnnb.result) $(get_result_of ${WORKING_DIR}/pfpdnpa.result)
74 |
75 |
76 | # This is useful when running in a container environment
77 | printf "\n"
78 | echo "All logs are available at ${TESTS_LOGS}"
79 | echo "Sleeping for infinity, in case you need to open bash in the container"
80 | if [[ ${ENVIRONMENT} = "DOCKER" ]]
81 | then
82 | sleep infinity
83 | fi
84 |
85 | if [[ ${EXIT_ON_FAIL} -ne "FALSE" ]]
86 | then
87 | NUMBER_PASSED=$(grep -l "PASSED" ${WORKING_DIR}/*.result | wc -l)
88 | if [[ ${NUMBER_PASSED} -ne 20 ]]
89 | then
90 | echo "Not all (20) tests passed! Dieing.."
91 | exit 1
92 | else
93 | echo "All tests passed (20/20)!"
94 | fi
95 | fi
--------------------------------------------------------------------------------
/.ci/e2e/publish_tests/test_runners/run_all_serial.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Get the config file
4 | source "$(dirname "${BASH_SOURCE[0]}")/helpers/get_config_variables.sh"
5 | source "$(dirname "${BASH_SOURCE[0]}")/helpers/helper.sh"
6 |
7 | mkdir -p ${WORKING_DIR}
8 | mkdir -p ${TESTS_LOGS}
9 |
10 | BASE_DIR="$(dirname "${BASH_SOURCE[0]}")"
11 | RESET='\033[0m' # No Color
12 | BLUE='\033[1;34m'
13 | YELLOW='\033[1;33m'
14 |
15 | # This may be needed if docker image is restarted
16 | # Better to simply login again at every invoke
17 | echo "Ensuring login to Azure ACR"
18 | az acr login --name ${ACR_NAME} > /dev/null
19 |
20 | echo -e "Starting Serial Execution..."
21 | echo "This will take a while"
22 |
23 |
24 | run_test ${BASE_DIR}/dev_func_dev_docker/customer_build_native_deps.sh ${TESTS_LOGS}/dfddcbnd.log ${TESTS_TIMEOUT} ${WORKING_DIR}/dfddcbnd.result
25 | run_test ${BASE_DIR}/dev_func_dev_docker/customer_no_bundler.sh ${TESTS_LOGS}/dfddcnb.log ${TESTS_TIMEOUT} ${WORKING_DIR}/dfddcnb.result
26 | run_test ${BASE_DIR}/dev_func_dev_docker/new_build_native_deps.sh ${TESTS_LOGS}/dfddnbnd.log ${TESTS_TIMEOUT} ${WORKING_DIR}/dfddnbnd.result
27 | run_test ${BASE_DIR}/dev_func_dev_docker/new_no_bundler.sh ${TESTS_LOGS}/dfddnnb.log ${TESTS_TIMEOUT} ${WORKING_DIR}/dfddnnb.result
28 | run_test ${BASE_DIR}/dev_func_dev_docker/new_packapp.sh ${TESTS_LOGS}/dfddnpa.log ${TESTS_TIMEOUT} ${WORKING_DIR}/dfddnpa.result
29 |
30 | run_test ${BASE_DIR}/dev_func_prod_docker/customer_build_native_deps.sh ${TESTS_LOGS}/dfpdcbnd.log ${TESTS_TIMEOUT} ${WORKING_DIR}/dfpdcbnd.result
31 | run_test ${BASE_DIR}/dev_func_prod_docker/customer_no_bundler.sh ${TESTS_LOGS}/dfpdcnb.log ${TESTS_TIMEOUT} ${WORKING_DIR}/dfpdcnb.result
32 | run_test ${BASE_DIR}/dev_func_prod_docker/new_build_native_deps.sh ${TESTS_LOGS}/dfpdnbnd.log ${TESTS_TIMEOUT} ${WORKING_DIR}/dfpdnbnd.result
33 | run_test ${BASE_DIR}/dev_func_prod_docker/new_no_bundler.sh ${TESTS_LOGS}/dfpdnnb.log ${TESTS_TIMEOUT} ${WORKING_DIR}/dfpdnnb.result
34 | run_test ${BASE_DIR}/dev_func_prod_docker/new_packapp.sh ${TESTS_LOGS}/dfpdnpa.log ${TESTS_TIMEOUT} ${WORKING_DIR}/dfpdnpa.result
35 |
36 | run_test ${BASE_DIR}/prod_func_prod_docker/customer_build_native_deps.sh ${TESTS_LOGS}/pfpdcbnd.log ${TESTS_TIMEOUT} ${WORKING_DIR}/pfpdcbnd.result
37 | run_test ${BASE_DIR}/prod_func_prod_docker/customer_no_bundler.sh ${TESTS_LOGS}/pfpdcnb.log ${TESTS_TIMEOUT} ${WORKING_DIR}/pfpdcnb.result
38 | run_test ${BASE_DIR}/prod_func_prod_docker/new_build_native_deps.sh ${TESTS_LOGS}/pfpdnbnd.log ${TESTS_TIMEOUT} ${WORKING_DIR}/pfpdnbnd.result
39 | run_test ${BASE_DIR}/prod_func_prod_docker/new_no_bundler.sh ${TESTS_LOGS}/pfpdnnb.log ${TESTS_TIMEOUT} ${WORKING_DIR}/pfpdnnb.result
40 | run_test ${BASE_DIR}/prod_func_prod_docker/new_packapp.sh ${TESTS_LOGS}/pfpdnpa.log ${TESTS_TIMEOUT} ${WORKING_DIR}/pfpdnpa.result
41 |
42 | echo "Ensuring login to Azure ACR (again because login has been flaky)"
43 | az acr login --name ${ACR_NAME} > /dev/null
44 | run_test ${BASE_DIR}/prod_func_dev_docker/customer_build_native_deps.sh ${TESTS_LOGS}/pfddcbnd.log ${TESTS_TIMEOUT} ${WORKING_DIR}/pfddcbnd.result
45 | run_test ${BASE_DIR}/prod_func_dev_docker/customer_no_bundler.sh ${TESTS_LOGS}/pfddcnb.log ${TESTS_TIMEOUT} ${WORKING_DIR}/pfddcnb.result
46 | run_test ${BASE_DIR}/prod_func_dev_docker/new_build_native_deps.sh ${TESTS_LOGS}/pfddnbnd.log ${TESTS_TIMEOUT} ${WORKING_DIR}/pfddnbnd.result
47 | run_test ${BASE_DIR}/prod_func_dev_docker/new_no_bundler.sh ${TESTS_LOGS}/pfddnnb.log ${TESTS_TIMEOUT} ${WORKING_DIR}/pfddnnb.result
48 | run_test ${BASE_DIR}/prod_func_dev_docker/new_packapp.sh ${TESTS_LOGS}/pfddnpa.log ${TESTS_TIMEOUT} ${WORKING_DIR}/pfddnpa.result
49 |
50 | echo "Test Completed!"
51 | yellow "Results-"
52 | printf "\n"
53 |
54 | print_row $(yellow "Version") $(yellow "Exist-Build-Native") \
55 | $(yellow "Exist-No-Bundler") $(yellow "New-Build-Native") $(yellow "New-No-Bundler") $(yellow "New-Packapp")
56 |
57 | print_row_line
58 |
59 | print_row $(yellow "dev-func-dev-docker") $(get_result_of ${WORKING_DIR}/dfddcbnd.result) \
60 | $(get_result_of ${WORKING_DIR}/dfddcnb.result) $(get_result_of ${WORKING_DIR}/dfddnbnd.result) \
61 | $(get_result_of ${WORKING_DIR}/dfddnnb.result) $(get_result_of ${WORKING_DIR}/dfddnpa.result)
62 |
63 | print_row $(yellow "dev-func-prod-docker") $(get_result_of ${WORKING_DIR}/dfpdcbnd.result) \
64 | $(get_result_of ${WORKING_DIR}/dfpdcnb.result) $(get_result_of ${WORKING_DIR}/dfpdnbnd.result) \
65 | $(get_result_of ${WORKING_DIR}/dfpdnnb.result) $(get_result_of ${WORKING_DIR}/dfpdnpa.result)
66 |
67 | print_row $(yellow "prod-func-dev-docker") $(get_result_of ${WORKING_DIR}/pfddcbnd.result) \
68 | $(get_result_of ${WORKING_DIR}/pfddcnb.result) $(get_result_of ${WORKING_DIR}/pfddnbnd.result) \
69 | $(get_result_of ${WORKING_DIR}/pfddnnb.result) $(get_result_of ${WORKING_DIR}/pfddnpa.result)
70 |
71 | print_row $(yellow "prod-func-prod-docker") $(get_result_of ${WORKING_DIR}/pfpdcbnd.result) \
72 | $(get_result_of ${WORKING_DIR}/pfpdcnb.result) $(get_result_of ${WORKING_DIR}/pfpdnbnd.result) \
73 | $(get_result_of ${WORKING_DIR}/pfpdnnb.result) $(get_result_of ${WORKING_DIR}/pfpdnpa.result)
74 |
75 | printf "\n"
76 | echo "All logs are available at ${TESTS_LOGS}"
77 |
78 | if [[ ${ENVIRONMENT} = "DOCKER" ]]
79 | then
80 | # This is useful when running in a container environment
81 | echo "Sleeping for infinity, in case you need to open bash in the container"
82 | sleep infinity
83 | fi
84 |
85 | if [[ -z ${EXIT_ON_FAIL} ]] || [[ ${EXIT_ON_FAIL} -ne "FALSE" ]]
86 | then
87 | NUMBER_PASSED=$(grep -l "PASSED" ${WORKING_DIR}/*.result | wc -l)
88 | if [[ ${NUMBER_PASSED} -ne 20 ]]
89 | then
90 | echo "Not all (20) tests passed! Dieing.."
91 | exit 1
92 | else
93 | echo "All tests passed (20/20)!"
94 | fi
95 | fi
--------------------------------------------------------------------------------
/azure/functions_worker/bindings/queue.py:
--------------------------------------------------------------------------------
1 | import collections.abc
2 | import datetime
3 | import json
4 | import typing
5 |
6 | from azure.functions import _abc as azf_abc
7 | from azure.functions import _queue as azf_queue
8 |
9 | from . import meta
10 | from .. import protos
11 |
12 |
13 | class QueueMessage(azf_queue.QueueMessage):
14 | """An HTTP response object."""
15 |
16 | def __init__(self, *,
17 | id=None, body=None,
18 | dequeue_count=None,
19 | expiration_time=None,
20 | insertion_time=None,
21 | time_next_visible=None,
22 | pop_receipt=None):
23 | super().__init__(id=id, body=body, pop_receipt=pop_receipt)
24 | self.__dequeue_count = dequeue_count
25 | self.__expiration_time = expiration_time
26 | self.__insertion_time = insertion_time
27 | self.__time_next_visible = time_next_visible
28 |
29 | @property
30 | def dequeue_count(self):
31 | return self.__dequeue_count
32 |
33 | @property
34 | def expiration_time(self):
35 | return self.__expiration_time
36 |
37 | @property
38 | def insertion_time(self):
39 | return self.__insertion_time
40 |
41 | @property
42 | def time_next_visible(self):
43 | return self.__time_next_visible
44 |
45 | def __repr__(self) -> str:
46 | return (
47 | f''
52 | )
53 |
54 |
55 | class QueueMessageInConverter(meta.InConverter,
56 | binding='queueTrigger', trigger=True):
57 |
58 | @classmethod
59 | def check_input_type_annotation(
60 | cls, pytype: type, datatype: protos.BindingInfo.DataType) -> bool:
61 | if datatype is protos.BindingInfo.undefined:
62 | return issubclass(pytype, azf_abc.QueueMessage)
63 | else:
64 | return False
65 |
66 | @classmethod
67 | def from_proto(cls, data: protos.TypedData, *,
68 | pytype: typing.Optional[type],
69 | trigger_metadata) -> typing.Any:
70 | data_type = data.WhichOneof('data')
71 |
72 | if data_type == 'string':
73 | body = data.string
74 |
75 | elif data_type == 'bytes':
76 | body = data.bytes
77 |
78 | else:
79 | raise NotImplementedError(
80 | f'unsupported queue payload type: {data_type}')
81 |
82 | if trigger_metadata is None:
83 | raise NotImplementedError(
84 | f'missing trigger metadata for queue input')
85 |
86 | return QueueMessage(
87 | id=cls._decode_trigger_metadata_field(
88 | trigger_metadata, 'Id', python_type=str),
89 | body=body,
90 | dequeue_count=cls._decode_trigger_metadata_field(
91 | trigger_metadata, 'DequeueCount', python_type=int),
92 | expiration_time=cls._parse_datetime_metadata(
93 | trigger_metadata, 'ExpirationTime'),
94 | insertion_time=cls._parse_datetime_metadata(
95 | trigger_metadata, 'InsertionTime'),
96 | time_next_visible=cls._parse_datetime_metadata(
97 | trigger_metadata, 'NextVisibleTime'),
98 | pop_receipt=cls._decode_trigger_metadata_field(
99 | trigger_metadata, 'PopReceipt', python_type=str)
100 | )
101 |
102 |
103 | class QueueMessageOutConverter(meta.OutConverter, binding='queue'):
104 |
105 | @classmethod
106 | def check_output_type_annotation(cls, pytype: type) -> bool:
107 | valid_types = (azf_abc.QueueMessage, str, bytes)
108 | return (
109 | meta.is_iterable_type_annotation(pytype, valid_types) or
110 | (isinstance(pytype, type) and issubclass(pytype, valid_types))
111 | )
112 |
113 | @classmethod
114 | def to_proto(cls, obj: typing.Any, *,
115 | pytype: typing.Optional[type]) -> protos.TypedData:
116 | if isinstance(obj, str):
117 | return protos.TypedData(string=obj)
118 |
119 | elif isinstance(obj, bytes):
120 | return protos.TypedData(bytes=obj)
121 |
122 | elif isinstance(obj, azf_queue.QueueMessage):
123 | return protos.TypedData(
124 | json=json.dumps({
125 | 'id': obj.id,
126 | 'body': obj.get_body().decode('utf-8'),
127 | })
128 | )
129 |
130 | elif isinstance(obj, collections.abc.Iterable):
131 | msgs = []
132 | for item in obj:
133 | if isinstance(item, str):
134 | msgs.append(item)
135 | elif isinstance(item, azf_queue.QueueMessage):
136 | msgs.append({
137 | 'id': item.id,
138 | 'body': item.get_body().decode('utf-8')
139 | })
140 | else:
141 | raise NotImplementedError(
142 | 'invalid data type in output '
143 | 'queue message list: {}'.format(type(item)))
144 |
145 | return protos.TypedData(
146 | json=json.dumps(msgs)
147 | )
148 |
149 | raise NotImplementedError
150 |
151 | @classmethod
152 | def _format_datetime(cls, dt: typing.Optional[datetime.datetime]):
153 | if dt is None:
154 | return None
155 | else:
156 | return dt.isoformat()
157 |
--------------------------------------------------------------------------------