Skip to content

Commit cae816b

Browse files
authored
Merge pull request #78522 from asavaritayal/func-python
Python preview updates
2 parents d61edef + ef1bcd7 commit cae816b

File tree

2 files changed

+139
-107
lines changed

2 files changed

+139
-107
lines changed

articles/azure-functions/functions-bindings-timer.md

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ See the language-specific example:
4343
* [C#](#c-example)
4444
* [C# script (.csx)](#c-script-example)
4545
* [F#](#f-example)
46-
* [JavaScript](#javascript-example)
4746
* [Java](#java-example)
47+
* [JavaScript](#javascript-example)
48+
* [Python](#python-example)
4849

4950
### C# example
5051

@@ -115,6 +116,21 @@ let Run(myTimer: TimerInfo, log: ILogger ) =
115116
log.LogInformation(sprintf "F# function executed at %s!" now)
116117
```
117118

119+
### Java example
120+
121+
The following example function triggers and executes every five minutes. The `@TimerTrigger` annotation on the function defines the schedule using the same string format as [CRON expressions](https://en.wikipedia.org/wiki/Cron#CRON_expression).
122+
123+
```java
124+
@FunctionName("keepAlive")
125+
public void keepAlive(
126+
@TimerTrigger(name = "keepAliveTrigger", schedule = "0 */5 * * * *") String timerInfo,
127+
ExecutionContext context
128+
) {
129+
// timeInfo is a JSON string, you can deserialize it to an object using your favorite JSON library
130+
context.getLogger().info("Timer is triggered: " + timerInfo);
131+
}
132+
```
133+
118134
### JavaScript example
119135

120136
The following example shows a timer trigger binding in a *function.json* file and a [JavaScript function](functions-reference-node.md) that uses the binding. The function writes a log indicating whether this function invocation is due to a missed schedule occurrence. A [timer object](#usage) is passed into the function.
@@ -146,21 +162,39 @@ module.exports = function (context, myTimer) {
146162
};
147163
```
148164

149-
### Java example
165+
### Python example
150166

151-
The following example function triggers and executes every five minutes. The `@TimerTrigger` annotation on the function defines the schedule using the same string format as [CRON expressions](https://en.wikipedia.org/wiki/Cron#CRON_expression).
167+
The following example uses a timer trigger binding whose configuration is described in the *function.json* file. The actual [Python function](functions-reference-python.md) that uses the binding is described in the *__init__.py* file. The object passed into the function is of type [azure.functions.TimerRequest object](/python/api/azure-functions/azure.functions.timerrequest). The function logic writes to the logs indicating whether the current invocation is due to a missed schedule occurrence.
152168

153-
```java
154-
@FunctionName("keepAlive")
155-
public void keepAlive(
156-
@TimerTrigger(name = "keepAliveTrigger", schedule = "0 */5 * * * *") String timerInfo,
157-
ExecutionContext context
158-
) {
159-
// timeInfo is a JSON string, you can deserialize it to an object using your favorite JSON library
160-
context.getLogger().info("Timer is triggered: " + timerInfo);
169+
Here's the binding data in the *function.json* file:
170+
171+
```json
172+
{
173+
"name": "mytimer",
174+
"type": "timerTrigger",
175+
"direction": "in",
176+
"schedule": "0 */5 * * * *"
161177
}
162178
```
163179

180+
Here's the Python code:
181+
182+
```python
183+
import datetime
184+
import logging
185+
186+
import azure.functions as func
187+
188+
def main(mytimer: func.TimerRequest) -> None:
189+
utc_timestamp = datetime.datetime.utcnow().replace(
190+
tzinfo=datetime.timezone.utc).isoformat()
191+
192+
if mytimer.past_due:
193+
logging.info('The timer is past due!')
194+
195+
logging.info('Python timer trigger function ran at %s', utc_timestamp)
196+
```
197+
164198
## Attributes
165199

166200
In [C# class libraries](functions-dotnet-class-library.md), use the [TimerTriggerAttribute](https://github.com/Azure/azure-webjobs-sdk-extensions/blob/master/src/WebJobs.Extensions/Extensions/Timers/TimerTriggerAttribute.cs).

articles/azure-functions/functions-reference-python.md

Lines changed: 94 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ This article is an introduction to developing Azure Functions using Python. The
2525

2626
An Azure Function should be a stateless method in your Python script that processes input and produces output. By default, the runtime expects the method to be implemented as a global method called `main()` in the `__init__.py` file.
2727

28-
You can change the default configuration by specifying the `scriptFile` and `entryPoint` properties in the `function.json` file. For example, the _function.json_ below tells the runtime to use the _customentry()_ method in the _main.py_ file, as the entry point for your Azure Function.
28+
You can change the default configuration by specifying the `scriptFile` and `entryPoint` properties in the *function.json* file. For example, the _function.json_ below tells the runtime to use the `customentry()` method in the _main.py_ file, as the entry point for your Azure Function.
2929

3030
```json
3131
{
@@ -35,7 +35,7 @@ You can change the default configuration by specifying the `scriptFile` and `en
3535
}
3636
```
3737

38-
Data from triggers and bindings is bound to the function via method attributes using the `name` property defined in the `function.json` configuration file. For example, the _function.json_ below describes a simple function triggered by an HTTP request named `req`:
38+
Data from triggers and bindings is bound to the function via method attributes using the `name` property defined in the *function.json* file. For example, the _function.json_ below describes a simple function triggered by an HTTP request named `req`:
3939

4040
```json
4141
{
@@ -63,7 +63,7 @@ def main(req):
6363
return f'Hello, {user}!'
6464
```
6565

66-
Optionally, you can also declare the parameter types and return type in the function using Python type annotations. For example, the same function can be written using annotations, as follows:
66+
Optionally, to leverage the intellisense and auto-complete features provided by your code editor, you can also declare the attribute types and return type in the function using Python type annotations.
6767

6868
```python
6969
import azure.functions
@@ -73,7 +73,7 @@ def main(req: azure.functions.HttpRequest) -> str:
7373
return f'Hello, {user}!'
7474
```
7575

76-
Use the Python annotations included in the [azure.functions.*](/python/api/azure-functions/azure.functions?view=azure-python) package to bind input and outputs to your methods.
76+
Use the Python annotations included in the [azure.functions.*](/python/api/azure-functions/azure.functions?view=azure-python) package to bind input and outputs to your methods.
7777

7878
## Folder structure
7979

@@ -92,25 +92,23 @@ The folder structure for a Python Functions project looks like the following:
9292
| | - mySecondHelperFunction.py
9393
| - host.json
9494
| - requirements.txt
95-
| - extensions.csproj
96-
| - bin
9795
```
9896

9997
There's a shared [host.json](functions-host-json.md) file that can be used to configure the function app. Each function has its own code file and binding configuration file (function.json).
10098

10199
Shared code should be kept in a separate folder. To reference modules in the SharedCode folder, you can use the following syntax:
102100

103101
```
104-
from ..SharedCode import myFirstHelperFunction
102+
from __app__.SharedCode import myFirstHelperFunction
105103
```
106104

107-
Binding extensions used by the Functions runtime are defined in the `extensions.csproj` file, with the actual library files in the `bin` folder. When developing locally, you must [register binding extensions](./functions-bindings-register.md#local-development-with-azure-functions-core-tools-and-extension-bundles) using Azure Functions Core Tools.
108-
109-
When deploying a Functions project to your function app in Azure, the entire content of the FunctionApp folder should be included in the package, but not the folder itself.
105+
When deploying a Function project to your function app in Azure, the entire content of the *FunctionApp* folder should be included in the package, but not the folder itself.
110106

111107
## Triggers and Inputs
112108

113-
Inputs are divided into two categories in Azure Functions: trigger input and additional input. Although they are different in `function.json`, the usage is identical in Python code. Connection strings for trigger and input sources should map to values in the `local.settings.json` file locally, and the application settings when running in Azure. Let's take the following code snippet as an example:
109+
Inputs are divided into two categories in Azure Functions: trigger input and additional input. Although they are different in the `function.json` file, usage is identical in Python code. Connection strings or secrets for trigger and input sources map to values in the `local.settings.json` file when running locally, and the application settings when running in Azure.
110+
111+
For example, the following code demonstrates the difference between the two:
114112

115113
```json
116114
// function.json
@@ -228,30 +226,17 @@ Additional logging methods are available that let you write to the console at di
228226
| logging.**info(_message_)** | Writes a message with level INFO on the root logger. |
229227
| logging.**debug(_message_)** | Writes a message with level DEBUG on the root logger. |
230228

231-
## Importing shared code into a function module
232-
233-
Python modules published alongside function modules must be imported using the relative import syntax:
234-
235-
```python
236-
from . import helpers # Use more dots to navigate up the folder structure.
237-
def main(req: func.HttpRequest):
238-
helpers.process_http_request(req)
239-
```
240-
241-
Alternatively, put shared code into a standalone package, publish it to a public or a private
242-
PyPI instance, and specify it as a regular dependency.
243-
244229
## Async
245230

246-
Since only a single Python process can exist per function app, it is recommended to implement your Azure Function as an asynchronous coroutine using the `async def` statement.
231+
We recommend that you write your Azure Function as an asynchronous coroutine using the `async def` statement.
247232

248233
```python
249234
# Will be run with asyncio directly
250235
async def main():
251236
await some_nonblocking_socket_io_op()
252237
```
253238

254-
If the main() function is synchronous (no `async` qualifier) we automatically run it in an `asyncio` thread-pool.
239+
If the main() function is synchronous (no `async` qualifier) we automatically run the function in an `asyncio` thread-pool.
255240

256241
```python
257242
# Would be run in an asyncio thread-pool
@@ -284,6 +269,21 @@ Name of the function.
284269
`invocation_id`
285270
ID of the current function invocation.
286271

272+
## Global variables
273+
274+
It is not guaranteed that the state of your app will be preserved for future executions. However, the Azure Functions runtime often reuses the same process for multiple executions of the same app. In order to cache the results of an expensive computation, declare it as a global variable.
275+
276+
```python
277+
CACHED_DATA = None
278+
279+
def main(req):
280+
global CACHED_DATA
281+
if CACHED_DATA is None:
282+
CACHED_DATA = load_json()
283+
284+
# ... use CACHED_DATA in code
285+
```
286+
287287
## Python version and package management
288288

289289
Currently, Azure Functions only supports Python 3.6.x (official CPython distribution).
@@ -292,10 +292,6 @@ When developing locally using the Azure Functions Core Tools or Visual Studio Co
292292

293293
For example, the following requirements file and pip command can be used to install the `requests` package from PyPI.
294294

295-
```bash
296-
pip install requests
297-
```
298-
299295
```txt
300296
requests==2.19.1
301297
```
@@ -304,20 +300,9 @@ requests==2.19.1
304300
pip install -r requirements.txt
305301
```
306302

307-
When you're ready for publishing, make sure that all your dependencies are listed in the `requirements.txt` file, located at the root of your project directory. To successfully execute your Azure Functions, the requirements file should contain a minimum of the following packages:
308-
309-
```txt
310-
azure-functions
311-
azure-functions-worker
312-
grpcio==1.14.1
313-
grpcio-tools==1.14.1
314-
protobuf==3.6.1
315-
six==1.11.0
316-
```
317-
318303
## Publishing to Azure
319304

320-
If you're using a package that requires a compiler and does not support the installation of manylinux-compatible wheels from PyPI, publishing to Azure will fail with the following error:
305+
When you're ready to publish, make sure that all your dependencies are listed in the *requirements.txt* file, which is located at the root of your project directory. If you're using a package that requires a compiler and does not support the installation of manylinux-compatible wheels from PyPI, publishing to Azure will fail with the following error:
321306

322307
```
323308
There was an error restoring dependencies.ERROR: cannot install <package name - version> dependency: binary dependencies without wheels are not supported.
@@ -332,71 +317,84 @@ func azure functionapp publish <app name> --build-native-deps
332317

333318
Underneath the covers, Core Tools will use docker to run the [mcr.microsoft.com/azure-functions/python](https://hub.docker.com/r/microsoft/azure-functions/) image as a container on your local machine. Using this environment, it'll then build and install the required modules from source distribution, before packaging them up for final deployment to Azure.
334319

335-
> [!NOTE]
336-
> Core Tools (func) uses the PyInstaller program to freeze the user's code and dependencies into a single stand-alone executable to run in Azure. This functionality is currently in preview and may not extend to all types of Python packages. If you're unable to import your modules, try publishing again using the `--no-bundler` option.
337-
> ```
338-
> func azure functionapp publish <app_name> --build-native-deps --no-bundler
339-
> ```
340-
> If you continue to experience issues, please let us know by [opening an issue](https://github.com/Azure/azure-functions-core-tools/issues/new) and including a description of the problem.
341-
342-
343-
To build your dependencies and publish using a continuous integration (CI) and continuous delivery (CD) system, you can use an [Azure Pipeline](https://docs.microsoft.com/azure/devops/pipelines/get-started-yaml?view=vsts) or [Travis CI custom script](https://docs.travis-ci.com/user/deployment/script/).
344-
345-
Following is an example `azure-pipelines.yml` script for the build and publishing process.
346-
```yml
347-
pool:
348-
vmImage: 'Ubuntu 16.04'
349-
350-
steps:
351-
- task: NodeTool@0
352-
inputs:
353-
versionSpec: '8.x'
354-
355-
- script: |
356-
set -e
357-
echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ wheezy main" | sudo tee /etc/apt/sources.list.d/azure-cli.list
358-
curl -L https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
359-
sudo apt-get install -y apt-transport-https
360-
echo "install Azure CLI..."
361-
sudo apt-get update && sudo apt-get install -y azure-cli
362-
npm i -g azure-functions-core-tools --unsafe-perm true
363-
echo "installing dotnet core"
364-
curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --channel 2.0
365-
- script: |
366-
set -e
367-
az login --service-principal --username "$(APP_ID)" --password "$(PASSWORD)" --tenant "$(TENANT_ID)"
368-
func settings add FUNCTIONS_WORKER_RUNTIME python
369-
func extensions install
370-
func azure functionapp publish $(APP_NAME) --build-native-deps
371-
```
320+
To build your dependencies and publish using a continuous delivery (CD) system, [use Azure DevOps Pipelines](https://docs.microsoft.com/azure/azure-functions/functions-how-to-azure-devops).
372321

373-
Following is an example `.travis.yaml` script for the build and publishing process.
322+
## Unit Testing
374323

375-
```yml
376-
sudo: required
324+
Functions written in Python can be tested like other Python code using standard testing frameworks. For most bindings, it's possible to create a mock input object by creating an instance of an appropriate class from the `azure.functions` package.
377325

378-
language: node_js
326+
For example, following is a mock test of an HTTP triggered function:
379327

380-
node_js:
381-
- "8"
328+
```python
329+
# myapp/__init__.py
330+
import azure.functions as func
331+
import logging
382332

383-
services:
384-
- docker
333+
def main(req: func.HttpRequest,
334+
obj: func.InputStream):
385335

386-
before_install:
387-
- echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ wheezy main" | sudo tee /etc/apt/sources.list.d/azure-cli.list
388-
- curl -L https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
389-
- sudo apt-get install -y apt-transport-https
390-
- sudo apt-get update && sudo apt-get install -y azure-cli
391-
- npm i -g azure-functions-core-tools --unsafe-perm true
336+
logging.info(f'Python HTTP triggered function processed: {obj.read()}')
337+
```
392338

339+
```python
340+
# myapp/test_func.py
341+
import unittest
393342

394-
script:
395-
- az login --service-principal --username "$APP_ID" --password "$PASSWORD" --tenant "$TENANT_ID"
396-
- az account get-access-token --query "accessToken" | func azure functionapp publish $APP_NAME --build-native-deps
343+
import azure.functions as func
344+
from . import my_function
345+
346+
class TestFunction(unittest.TestCase):
347+
def test_my_function(self):
348+
# Construct a mock HTTP request.
349+
req = func.HttpRequest(
350+
method='GET',
351+
body=None,
352+
url='/my_function',
353+
params={'name': 'Test'})
354+
355+
# Call the function.
356+
resp = my_function(req)
357+
358+
# Check the output.
359+
self.assertEqual(
360+
resp.get_body(),
361+
'Hello, Test!',
362+
)
363+
```
364+
365+
Here is another example, with a queue triggered function:
366+
367+
```python
368+
# myapp/__init__.py
369+
import azure.functions as func
397370

371+
def my_function(msg: func.QueueMessage) -> str:
372+
return f'msg body: {msg.get_body().decode()}'
398373
```
399374

375+
```python
376+
# myapp/test_func.py
377+
import unittest
378+
379+
import azure.functions as func
380+
from . import my_function
381+
382+
class TestFunction(unittest.TestCase):
383+
def test_my_function(self):
384+
# Construct a mock Queue message.
385+
req = func.QueueMessage(
386+
body=b'test')
387+
388+
# Call the function.
389+
resp = my_function(req)
390+
391+
# Check the output.
392+
self.assertEqual(
393+
resp,
394+
'msg body: test',
395+
)
396+
```
397+
400398
## Known issues and FAQ
401399

402400
All known issues and feature requests are tracked using [GitHub issues](https://github.com/Azure/azure-functions-python-worker/issues) list. If you run into a problem and can't find the issue in GitHub, open a new issue and include a detailed description of the problem.

0 commit comments

Comments
 (0)