Skip to content

build: update version to 1.0.0a1 #1711

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions azure_functions_worker_v2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# <img src="https://raw.githubusercontent.com/Azure/azure-functions-python-worker/dev/docs/Azure.Functions.svg" width = "30" alt="Functions Header Image - Lightning Logo"> Azure Functions Python Worker

| Branch | Build Status | CodeCov | Test Status |
|--------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| dev | [![Build Status](https://img.shields.io/azure-devops/build/azfunc/public/658/dev)](https://azfunc.visualstudio.com/public/_build/latest?definitionId=658&branchName=dev) | [![codecov](https://codecov.io/gh/Azure/azure-functions-python-worker/branch/dev/graph/badge.svg)](https://codecov.io/gh/Azure/azure-functions-python-worker) | [![Test Status](https://img.shields.io/azure-devops/build/azfunc/public/658/dev)](https://azfunc.visualstudio.com/public/_build/latest?definitionId=658&branchName=dev) |

Python support for Azure Functions is based on Python 3.13 serverless hosting on Linux and the Functions 4.0 runtime.

Here is the current status of Python in Azure Functions:

What are the supported Python versions?

| Azure Functions Runtime | Python 3.13 |
|----------------------------------|-------------|
| Azure Functions 4.0 | ✔ |

For information about Azure Functions Runtime, please refer to [Azure Functions runtime versions overview](https://docs.microsoft.com/en-us/azure/azure-functions/functions-versions) page.

### What's available?

- Build, test, debug, and publish using Azure Functions Core Tools (CLI) or Visual Studio Code
- Deploy Python Function project onto consumption, dedicated, elastic premium, or flex consumption plan.
- Deploy Python Function project in a custom docker image onto dedicated or elastic premium plan.
- Triggers / Bindings : Blob, Cosmos DB, Event Grid, Event Hub, HTTP, Kafka, MySQL, Queue, ServiceBus, SQL, Timer, and Warmup
- Triggers / Bindings : Custom binding support

### What's new?

- [SDK Type Bindings for Blob](https://techcommunity.microsoft.com/t5/azure-compute-blog/azure-functions-sdk-type-bindings-for-azure-blob-storage-with/ba-p/4146744)
- [HTTP Streaming](https://techcommunity.microsoft.com/t5/azure-compute-blog/azure-functions-support-for-http-streams-in-python-is-now-in/ba-p/4146697)

### Get Started

- [Create your first Python function](https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-first-function-python)
- [Developer guide](https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference-python)
- [Binding API reference](https://docs.microsoft.com/en-us/python/api/azure-functions/azure.functions?view=azure-python)
- [Develop using VS Code](https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-first-function-vs-code)
- [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)

# Give Feedback

Issues and feature requests are tracked in a variety of places. To report this feedback, please file an issue to the relevant repository below:

| Item | Description | Link |
|---------------|----------------------------------------------|--------------------------------------------------------------------------------|
| Python Worker | Programming Model, Triggers & Bindings | [File an Issue](https://github.com/Azure/azure-functions-python-worker/issues) |
| Linux | Base Docker Images | [File an Issue](https://github.com/Azure/azure-functions-docker/issues) |
| Runtime | Script Host & Language Extensibility | [File an Issue](https://github.com/Azure/azure-functions-host/issues) |
| VSCode | VSCode Extension for Azure Functions | [File an Issue](https://github.com/microsoft/vscode-azurefunctions/issues) |
| Core Tools | Command Line Interface for Local Development | [File an Issue](https://github.com/Azure/azure-functions-core-tools/issues) |
| Portal | User Interface or Experience Issue | [File an Issue](https://github.com/azure/azure-functions-ux/issues) |
| Templates | Code Issues with Creation Template | [File an Issue](https://github.com/Azure/azure-functions-templates/issues) |

# Contribute

This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit https://cla.microsoft.com.

Here are some pointers to get started:

- [Language worker architecture](https://github.com/Azure/azure-functions-python-worker/wiki/Worker-Architecture)
- [Setting up the development environment](https://github.com/Azure/azure-functions-python-worker/wiki/Contributor-Guide)
- [Adding support for a new binding](https://github.com/Azure/azure-functions-python-worker/wiki/Adding-support-for-a-new-binding-type)
- [Release instructions](https://github.com/Azure/azure-functions-python-worker/wiki/Release-Instructions)

When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
provided by the bot. You will only need to do this once across all repos using our CLA.

This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [[email protected]](mailto:[email protected]) with any additional questions or comments.
13 changes: 13 additions & 0 deletions azure_functions_worker_v2/azure_functions_worker_v2/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
from .handle_event import (worker_init_request,
functions_metadata_request,
function_environment_reload_request,
invocation_request,
function_load_request)

__all__ = ('worker_init_request',
'functions_metadata_request',
'function_environment_reload_request',
'invocation_request',
'function_load_request')
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
import threading

from .retrycontext import RetryContext
from .tracecontext import TraceContext


class Context:
def __init__(self,
func_name: str,
func_dir: str,
invocation_id: str,
thread_local_storage: threading.local,
trace_context: TraceContext,
retry_context: RetryContext) -> None:
self.__func_name = func_name
self.__func_dir = func_dir
self.__invocation_id = invocation_id
self.__thread_local_storage = thread_local_storage
self.__trace_context = trace_context
self.__retry_context = retry_context

@property
def invocation_id(self) -> str:
return self.__invocation_id

@property
def thread_local_storage(self) -> threading.local:
return self.__thread_local_storage

@property
def function_name(self) -> str:
return self.__func_name

@property
def function_directory(self) -> str:
return self.__func_dir

@property
def trace_context(self) -> TraceContext:
return self.__trace_context

@property
def retry_context(self) -> RetryContext:
return self.__retry_context


def get_context(invoc_request, name: str,
directory: str) -> Context:
""" For more information refer:
https://aka.ms/azfunc-invocation-context
"""
trace_context = TraceContext(
invoc_request.trace_context.trace_parent,
invoc_request.trace_context.trace_state,
invoc_request.trace_context.attributes)

retry_context = RetryContext(
invoc_request.retry_context.retry_count,
invoc_request.retry_context.max_retry_count,
invoc_request.retry_context.exception)

return Context(
name, directory, invoc_request.invocation_id,
threading.local(), trace_context, retry_context)
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
import json
import logging

from datetime import datetime
from typing import Any, List, Optional

from .nullable_converters import (
to_nullable_bool,
to_nullable_double,
to_nullable_string,
to_nullable_timestamp,
)

try:
from http.cookies import SimpleCookie
except ImportError:
from Cookie import SimpleCookie # type: ignore


class Datum:
def __init__(self, value, type):
self.value = value
self.type = type

@property
def python_value(self) -> Any:
if self.value is None or self.type is None:
return None
elif self.type in ('bytes', 'string', 'int', 'double'):
return self.value
elif self.type == 'json':
return json.loads(self.value)
elif self.type == 'collection_string':
return [v for v in self.value.string]
elif self.type == 'collection_bytes':
return [v for v in self.value.bytes]
elif self.type == 'collection_double':
return [v for v in self.value.double]
elif self.type == 'collection_sint64':
return [v for v in self.value.sint64]
else:
return self.value

@property
def python_type(self) -> type:
return type(self.python_value)

def __eq__(self, other):
if not isinstance(other, type(self)):
return False

return self.value == other.value and self.type == other.type

def __hash__(self):
return hash((type(self), (self.value, self.type)))

def __repr__(self):
val_repr = repr(self.value)
if len(val_repr) > 10:
val_repr = val_repr[:10] + '...'
return '<Datum ' + str(self.type) + val_repr + '>'

@classmethod
def from_typed_data(cls, protos):
try:
td = protos.TypedData
except Exception:
td = protos
tt = td.WhichOneof('data')
if tt == 'http':
http = td.http
val = dict(
method=Datum(http.method, 'string'),
url=Datum(http.url, 'string'),
headers={
k: Datum(v, 'string') for k, v in http.headers.items()
},
body=(
Datum.from_typed_data(http.body)
or Datum(type='bytes', value=b'')
),
params={
k: Datum(v, 'string') for k, v in http.params.items()
},
query={
k: Datum(v, 'string') for k, v in http.query.items()
},
)
elif tt == 'string':
val = td.string
elif tt == 'bytes':
val = td.bytes
elif tt == 'json':
val = td.json
elif tt == 'collection_bytes':
val = td.collection_bytes
elif tt == 'collection_string':
val = td.collection_string
elif tt == 'collection_sint64':
val = td.collection_sint64
elif tt == 'model_binding_data':
val = td.model_binding_data
elif tt == 'collection_model_binding_data':
val = td.collection_model_binding_data
elif tt is None:
return None
else:
raise NotImplementedError(
'unsupported TypeData kind: %s' % tt
)

return cls(val, tt)


def datum_as_proto(datum: Datum, protos):
if datum.type == 'string':
return protos.TypedData(string=datum.value)
elif datum.type == 'bytes':
return protos.TypedData(bytes=datum.value)
elif datum.type == 'json':
return protos.TypedData(json=datum.value)
elif datum.type == 'http':
return protos.TypedData(http=protos.RpcHttp(
status_code=datum.value['status_code'].value,
headers={
k: v.value
for k, v in datum.value['headers'].items()
},
cookies=parse_to_rpc_http_cookie_list(datum.value.get('cookies'), protos),
enable_content_negotiation=False,
body=datum_as_proto(datum.value['body'], protos),
))
elif datum.type is None:
return None
elif datum.type == 'dict':
# TypedData doesn't support dict, so we return it as json
return protos.TypedData(json=json.dumps(datum.value))
elif datum.type == 'list':
# TypedData doesn't support list, so we return it as json
return protos.TypedData(json=json.dumps(datum.value))
elif datum.type == 'int':
return protos.TypedData(int=datum.value)
elif datum.type == 'double':
return protos.TypedData(double=datum.value)
elif datum.type == 'bool':
# TypedData doesn't support bool, so we return it as an int
return protos.TypedData(int=int(datum.value))
else:
raise NotImplementedError(
'unexpected Datum type: %s' % datum.type
)


def parse_to_rpc_http_cookie_list(cookies: Optional[List[SimpleCookie]], protos):
if cookies is None:
return cookies

rpc_http_cookies = []

for cookie in cookies:
for name, cookie_entity in cookie.items():
rpc_http_cookies.append(
protos.RpcHttpCookie(name=name,
value=cookie_entity.value,
domain=to_nullable_string(
cookie_entity['domain'],
'cookie.domain',
protos),
path=to_nullable_string(
cookie_entity['path'],
'cookie.path',
protos),
expires=to_nullable_timestamp(
parse_cookie_attr_expires(
cookie_entity), 'cookie.expires',
protos),
secure=to_nullable_bool(
bool(cookie_entity['secure']),
'cookie.secure',
protos),
http_only=to_nullable_bool(
bool(cookie_entity['httponly']),
'cookie.httpOnly',
protos),
same_site=parse_cookie_attr_same_site(
cookie_entity, protos),
max_age=to_nullable_double(
cookie_entity['max-age'],
'cookie.maxAge',
protos)))

return rpc_http_cookies


def parse_cookie_attr_expires(cookie_entity):
expires = cookie_entity['expires']

if expires is not None and len(expires) != 0:
try:
return datetime.strptime(expires, "%a, %d %b %Y %H:%M:%S GMT")
except ValueError:
logging.error(
"Can not parse value %s of expires in the cookie "
"due to invalid format.", expires)
raise
except OverflowError:
logging.error(
"Can not parse value %s of expires in the cookie "
"because the parsed date exceeds the largest valid C "
"integer on your system.", expires)
raise

return None


def parse_cookie_attr_same_site(cookie_entity, protos):
same_site = getattr(protos.RpcHttpCookie.SameSite, "None")
try:
raw_same_site_str = cookie_entity['samesite'].lower()

if raw_same_site_str == 'lax':
same_site = protos.RpcHttpCookie.SameSite.Lax
elif raw_same_site_str == 'strict':
same_site = protos.RpcHttpCookie.SameSite.Strict
elif raw_same_site_str == 'none':
same_site = protos.RpcHttpCookie.SameSite.ExplicitNone
except Exception:
return same_site

return same_site
Loading
Loading