Skip to content

Commit 186e58c

Browse files
committed
V1 library worker
1 parent 4dc9617 commit 186e58c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+5055
-42
lines changed

azure_functions_worker_v1/README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# <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
2+
3+
| Branch | Build Status | CodeCov | Test Status |
4+
|--------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
5+
| 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) |
6+
7+
Python support for Azure Functions is based on Python 3.13 serverless hosting on Linux and the Functions 4.0 runtime.
8+
9+
Here is the current status of Python in Azure Functions:
10+
11+
What are the supported Python versions?
12+
13+
| Azure Functions Runtime | Python 3.13 |
14+
|----------------------------------|-------------|
15+
| Azure Functions 3.0 (deprecated) ||
16+
| Azure Functions 4.0 ||
17+
18+
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.
19+
20+
### What's available?
21+
22+
- Build, test, debug, and publish using Azure Functions Core Tools (CLI) or Visual Studio Code
23+
- Deploy Python Function project onto consumption, dedicated, elastic premium, or flex consumption plan.
24+
- Deploy Python Function project in a custom docker image onto dedicated or elastic premium plan.
25+
- Triggers / Bindings : Blob, Cosmos DB, Event Grid, Event Hub, HTTP, Kafka, MySQL, Queue, ServiceBus, SQL, Timer, and Warmup
26+
- Triggers / Bindings : Custom binding support
27+
28+
### What's new?
29+
30+
- [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)
31+
- [HTTP Streaming](https://techcommunity.microsoft.com/t5/azure-compute-blog/azure-functions-support-for-http-streams-in-python-is-now-in/ba-p/4146697)
32+
33+
### Get Started
34+
35+
- [Create your first Python function](https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-first-function-python)
36+
- [Developer guide](https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference-python)
37+
- [Binding API reference](https://docs.microsoft.com/en-us/python/api/azure-functions/azure.functions?view=azure-python)
38+
- [Develop using VS Code](https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-first-function-vs-code)
39+
- [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)
40+
41+
# Give Feedback
42+
43+
Issues and feature requests are tracked in a variety of places. To report this feedback, please file an issue to the relevant repository below:
44+
45+
| Item | Description | Link |
46+
|---------------|----------------------------------------------|--------------------------------------------------------------------------------|
47+
| Python Worker | Programming Model, Triggers & Bindings | [File an Issue](https://github.com/Azure/azure-functions-python-worker/issues) |
48+
| Linux | Base Docker Images | [File an Issue](https://github.com/Azure/azure-functions-docker/issues) |
49+
| Runtime | Script Host & Language Extensibility | [File an Issue](https://github.com/Azure/azure-functions-host/issues) |
50+
| VSCode | VSCode Extension for Azure Functions | [File an Issue](https://github.com/microsoft/vscode-azurefunctions/issues) |
51+
| Core Tools | Command Line Interface for Local Development | [File an Issue](https://github.com/Azure/azure-functions-core-tools/issues) |
52+
| Portal | User Interface or Experience Issue | [File an Issue](https://github.com/azure/azure-functions-ux/issues) |
53+
| Templates | Code Issues with Creation Template | [File an Issue](https://github.com/Azure/azure-functions-templates/issues) |
54+
55+
# Contribute
56+
57+
This project welcomes contributions and suggestions. Most contributions require you to agree to a
58+
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
59+
the rights to use your contribution. For details, visit https://cla.microsoft.com.
60+
61+
Here are some pointers to get started:
62+
63+
- [Language worker architecture](https://github.com/Azure/azure-functions-python-worker/wiki/Worker-Architecture)
64+
- [Setting up the development environment](https://github.com/Azure/azure-functions-python-worker/wiki/Contributor-Guide)
65+
- [Adding support for a new binding](https://github.com/Azure/azure-functions-python-worker/wiki/Adding-support-for-a-new-binding-type)
66+
- [Release instructions](https://github.com/Azure/azure-functions-python-worker/wiki/Release-Instructions)
67+
68+
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
69+
a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
70+
provided by the bot. You will only need to do this once across all repos using our CLA.
71+
72+
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
73+
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
74+
contact [[email protected]](mailto:[email protected]) with any additional questions or comments.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from .handle_event import (worker_init_request,
5+
functions_metadata_request,
6+
function_environment_reload_request,
7+
invocation_request,
8+
function_load_request)
9+
10+
__all__ = ('worker_init_request',
11+
'functions_metadata_request',
12+
'function_environment_reload_request',
13+
'invocation_request',
14+
'function_load_request')
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import threading
5+
6+
from typing import Type
7+
8+
from .retrycontext import RetryContext
9+
from .tracecontext import TraceContext
10+
11+
12+
class Context:
13+
def __init__(self,
14+
func_name: str,
15+
func_dir: str,
16+
invocation_id: str,
17+
thread_local_storage: Type[threading.local],
18+
trace_context: TraceContext,
19+
retry_context: RetryContext) -> None:
20+
self.__func_name = func_name
21+
self.__func_dir = func_dir
22+
self.__invocation_id = invocation_id
23+
self.__thread_local_storage = thread_local_storage
24+
self.__trace_context = trace_context
25+
self.__retry_context = retry_context
26+
27+
@property
28+
def invocation_id(self) -> str:
29+
return self.__invocation_id
30+
31+
@property
32+
def thread_local_storage(self) -> Type[threading.local]:
33+
return self.__thread_local_storage
34+
35+
@property
36+
def function_name(self) -> str:
37+
return self.__func_name
38+
39+
@property
40+
def function_directory(self) -> str:
41+
return self.__func_dir
42+
43+
@property
44+
def trace_context(self) -> TraceContext:
45+
return self.__trace_context
46+
47+
@property
48+
def retry_context(self) -> RetryContext:
49+
return self.__retry_context
50+
51+
52+
def get_context(invoc_request, name: str,
53+
directory: str) -> Context:
54+
""" For more information refer:
55+
https://aka.ms/azfunc-invocation-context
56+
"""
57+
trace_context = TraceContext(
58+
invoc_request.trace_context.trace_parent,
59+
invoc_request.trace_context.trace_state,
60+
invoc_request.trace_context.attributes)
61+
62+
retry_context = RetryContext(
63+
invoc_request.retry_context.retry_count,
64+
invoc_request.retry_context.max_retry_count,
65+
invoc_request.retry_context.exception)
66+
67+
return Context(
68+
name, directory, invoc_request.invocation_id,
69+
threading.local(), trace_context, retry_context)
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import json
5+
import logging
6+
7+
from datetime import datetime
8+
from typing import Any, List, Optional
9+
10+
from .nullable_converters import (
11+
to_nullable_bool,
12+
to_nullable_double,
13+
to_nullable_string,
14+
to_nullable_timestamp,
15+
)
16+
17+
try:
18+
from http.cookies import SimpleCookie
19+
except ImportError:
20+
from Cookie import SimpleCookie
21+
22+
23+
class Datum:
24+
def __init__(self, value, type):
25+
self.value = value
26+
self.type = type
27+
28+
@property
29+
def python_value(self) -> Any:
30+
if self.value is None or self.type is None:
31+
return None
32+
elif self.type in ('bytes', 'string', 'int', 'double'):
33+
return self.value
34+
elif self.type == 'json':
35+
return json.loads(self.value)
36+
elif self.type == 'collection_string':
37+
return [v for v in self.value.string]
38+
elif self.type == 'collection_bytes':
39+
return [v for v in self.value.bytes]
40+
elif self.type == 'collection_double':
41+
return [v for v in self.value.double]
42+
elif self.type == 'collection_sint64':
43+
return [v for v in self.value.sint64]
44+
else:
45+
return self.value
46+
47+
@property
48+
def python_type(self) -> type:
49+
return type(self.python_value)
50+
51+
def __eq__(self, other):
52+
if not isinstance(other, type(self)):
53+
return False
54+
55+
return self.value == other.value and self.type == other.type
56+
57+
def __hash__(self):
58+
return hash((type(self), (self.value, self.type)))
59+
60+
def __repr__(self):
61+
val_repr = repr(self.value)
62+
if len(val_repr) > 10:
63+
val_repr = val_repr[:10] + '...'
64+
return '<Datum ' + str(self.type) + val_repr + '>'
65+
66+
@classmethod
67+
def from_typed_data(cls, protos):
68+
try:
69+
td = protos.TypedData
70+
except Exception:
71+
# Todo: better catch for Datum.from_typed_data(http.body)
72+
# if the data being sent in is already protos.TypedData
73+
td = protos
74+
tt = td.WhichOneof('data')
75+
if tt == 'http':
76+
http = td.http
77+
val = dict(
78+
method=Datum(http.method, 'string'),
79+
url=Datum(http.url, 'string'),
80+
headers={
81+
k: Datum(v, 'string') for k, v in http.headers.items()
82+
},
83+
body=(
84+
Datum.from_typed_data(http.body)
85+
or Datum(type='bytes', value=b'')
86+
),
87+
params={
88+
k: Datum(v, 'string') for k, v in http.params.items()
89+
},
90+
query={
91+
k: Datum(v, 'string') for k, v in http.query.items()
92+
},
93+
)
94+
elif tt == 'string':
95+
val = td.string
96+
elif tt == 'bytes':
97+
val = td.bytes
98+
elif tt == 'json':
99+
val = td.json
100+
elif tt == 'collection_bytes':
101+
val = td.collection_bytes
102+
elif tt == 'collection_string':
103+
val = td.collection_string
104+
elif tt == 'collection_sint64':
105+
val = td.collection_sint64
106+
elif tt == 'model_binding_data':
107+
val = td.model_binding_data
108+
elif tt is None:
109+
return None
110+
else:
111+
raise NotImplementedError(
112+
'unsupported TypeData kind: {!r}'.format(tt)
113+
)
114+
115+
return cls(val, tt)
116+
117+
118+
def datum_as_proto(datum: Datum, protos):
119+
if datum.type == 'string':
120+
return protos.TypedData(string=datum.value)
121+
elif datum.type == 'bytes':
122+
return protos.TypedData(bytes=datum.value)
123+
elif datum.type == 'json':
124+
return protos.TypedData(json=datum.value)
125+
elif datum.type == 'http':
126+
return protos.TypedData(http=protos.RpcHttp(
127+
status_code=datum.value['status_code'].value,
128+
headers={
129+
k: v.value
130+
for k, v in datum.value['headers'].items()
131+
},
132+
cookies=parse_to_rpc_http_cookie_list(datum.value.get('cookies'), protos),
133+
enable_content_negotiation=False,
134+
body=datum_as_proto(datum.value['body'], protos),
135+
))
136+
elif datum.type is None:
137+
return None
138+
elif datum.type == 'dict':
139+
# TypedData doesn't support dict, so we return it as json
140+
return protos.TypedData(json=json.dumps(datum.value))
141+
elif datum.type == 'list':
142+
# TypedData doesn't support list, so we return it as json
143+
return protos.TypedData(json=json.dumps(datum.value))
144+
elif datum.type == 'int':
145+
return protos.TypedData(int=datum.value)
146+
elif datum.type == 'double':
147+
return protos.TypedData(double=datum.value)
148+
elif datum.type == 'bool':
149+
# TypedData doesn't support bool, so we return it as an int
150+
return protos.TypedData(int=int(datum.value))
151+
else:
152+
raise NotImplementedError(
153+
'unexpected Datum type: {!r}'.format(datum.type)
154+
)
155+
156+
157+
def parse_to_rpc_http_cookie_list(cookies: Optional[List[SimpleCookie]], protos):
158+
if cookies is None:
159+
return cookies
160+
161+
rpc_http_cookies = []
162+
163+
for cookie in cookies:
164+
for name, cookie_entity in cookie.items():
165+
rpc_http_cookies.append(
166+
protos.RpcHttpCookie(name=name,
167+
value=cookie_entity.value,
168+
domain=to_nullable_string(
169+
cookie_entity['domain'],
170+
'cookie.domain',
171+
protos),
172+
path=to_nullable_string(
173+
cookie_entity['path'],
174+
'cookie.path',
175+
protos),
176+
expires=to_nullable_timestamp(
177+
parse_cookie_attr_expires(
178+
cookie_entity), 'cookie.expires',
179+
protos),
180+
secure=to_nullable_bool(
181+
bool(cookie_entity['secure']),
182+
'cookie.secure',
183+
protos),
184+
http_only=to_nullable_bool(
185+
bool(cookie_entity['httponly']),
186+
'cookie.httpOnly',
187+
protos),
188+
same_site=parse_cookie_attr_same_site(
189+
cookie_entity, protos),
190+
max_age=to_nullable_double(
191+
cookie_entity['max-age'],
192+
'cookie.maxAge',
193+
protos)))
194+
195+
return rpc_http_cookies
196+
197+
198+
def parse_cookie_attr_expires(cookie_entity):
199+
expires = cookie_entity['expires']
200+
201+
if expires is not None and len(expires) != 0:
202+
try:
203+
return datetime.strptime(expires, "%a, %d %b %Y %H:%M:%S GMT")
204+
except ValueError:
205+
logging.error(
206+
"Can not parse value %s of expires in the cookie "
207+
"due to invalid format.", expires)
208+
raise
209+
except OverflowError:
210+
logging.error(
211+
"Can not parse value %s of expires in the cookie "
212+
"because the parsed date exceeds the largest valid C "
213+
"integer on your system.", expires)
214+
raise
215+
216+
return None
217+
218+
219+
def parse_cookie_attr_same_site(cookie_entity, protos):
220+
same_site = getattr(protos.RpcHttpCookie.SameSite, "None")
221+
try:
222+
raw_same_site_str = cookie_entity['samesite'].lower()
223+
224+
if raw_same_site_str == 'lax':
225+
same_site = protos.RpcHttpCookie.SameSite.Lax
226+
elif raw_same_site_str == 'strict':
227+
same_site = protos.RpcHttpCookie.SameSite.Strict
228+
elif raw_same_site_str == 'none':
229+
same_site = protos.RpcHttpCookie.SameSite.ExplicitNone
230+
except Exception:
231+
return same_site
232+
233+
return same_site

0 commit comments

Comments
 (0)