Azure Functions – Příručka pro vývojáře v Pythonu
Tento průvodce představuje úvod do vývoje azure Functions pomocí Pythonu. Článek předpokládá, že jste už přečetli příručku pro vývojáře Azure Functions.
Důležité
Tento článek podporuje programovací model v1 i v2 pro Python ve službě Azure Functions. Model Pythonu v1 používá k definování funkcí soubor functions.json a nový model v2 umožňuje místo toho použít přístup založený na dekorátoru. Výsledkem tohoto nového přístupu je jednodušší struktura souborů, která je zaměřená na kód. V horní části článku zvolte selektor v2, abyste se dozvěděli o tomto novém programovacím modelu.
Jako vývojář Pythonu vás může také zajímat jeden z následujících článků:
Začínáme | Koncepty | Scénáře a ukázky |
---|---|---|
Začínáme | Koncepty | Ukázky |
---|---|---|
Možnosti vývoje
Oba programovací modely Python Functions podporují místní vývoj v jednom z následujících prostředí:
Programovací model Pythonu v2:
Programovací model Pythonu v1:
Funkce Pythonu v1 můžete vytvořit také na webu Azure Portal.
Tip
I když můžete vyvíjet funkce Azure založené na Pythonu místně ve Windows, Python se podporuje jenom v plánu hostování založeném na Linuxu, když běží v Azure. Další informace najdete v seznamu podporovaných kombinací operačního systému nebo modulu runtime.
Programovací model
Služba Azure Functions očekává, že ve skriptu Pythonu bude funkce bezstavová metoda, která zpracuje vstup a vytvoří výstup. Modul runtime ve výchozím nastavení očekává, že se metoda implementuje jako globální metoda volaná main()
v souboru __init__.py . Můžete také zadat alternativní vstupní bod.
Data se váže na funkci z triggerů a vazeb prostřednictvím atributů metody, které používají name
vlastnost definovanou v souboru function.json . Například následující soubor function.json popisuje jednoduchou funkci aktivovanou požadavkem HTTP s názvem req
:
{
"scriptFile": "__init__.py",
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get",
"post"
]
},
{
"type": "http",
"direction": "out",
"name": "$return"
}
]
}
Na základě této definice může soubor __init__.py, který obsahuje kód funkce, vypadat jako v následujícím příkladu:
def main(req):
user = req.params.get('user')
return f'Hello, {user}!'
Pomocí poznámek k typu Pythonu můžete také explicitně deklarovat typy atributů a návratový typ funkce. To vám pomůže používat IntelliSense a funkce automatického dokončování, které poskytuje mnoho editorů kódu Pythonu.
import azure.functions
def main(req: azure.functions.HttpRequest) -> str:
user = req.params.get('user')
return f'Hello, {user}!'
Pomocí poznámek Pythonu, které jsou součástí balíčku azure.functions.* k vytvoření vazby vstupu a výstupů k vašim metodám.
Služba Azure Functions očekává, že ve skriptu Pythonu bude funkce bezstavová metoda, která zpracuje vstup a vytvoří výstup. Modul runtime ve výchozím nastavení očekává, že se metoda implementuje jako globální metoda v souboru function_app.py .
Triggery a vazby lze deklarovat a používat ve funkci v přístupu založeném na dekorátoru. Jsou definované ve stejném souboru function_app.py jako funkce. Například následující soubor function_app.py představuje trigger funkce požadavkem HTTP.
@app.function_name(name="HttpTrigger1")
@app.route(route="req")
def main(req):
user = req.params.get("user")
return f"Hello, {user}!"
Pomocí poznámek k typu Pythonu můžete také explicitně deklarovat typy atributů a návratový typ funkce. To vám pomůže používat funkce IntelliSense a automatické dokončování, které poskytuje mnoho editorů kódu Pythonu.
import azure.functions as func
app = func.FunctionApp()
@app.function_name(name="HttpTrigger1")
@app.route(route="req")
def main(req: func.HttpRequest) -> str:
user = req.params.get("user")
return f"Hello, {user}!"
Informace o známých omezeních modelu v2 a jejich alternativních řešeních najdete v tématu Řešení chyb Pythonu ve službě Azure Functions.
Alternativní vstupní bod
Výchozí chování funkce můžete změnit tak, že volitelně zadáte scriptFile
vlastnosti v entryPoint
souboru function.json . Například následující function.json modulu runtime říká, aby jako vstupní bod vaší funkce Azure použil customentry()
metodu v souboru main.py .
{
"scriptFile": "main.py",
"entryPoint": "customentry",
"bindings": [
...
]
}
Vstupní bod je pouze v souboru function_app.py . Na funkce v projektu však můžete odkazovat v function_app.py pomocí podrobných plánů nebo importem.
Struktura složek
Doporučená struktura složek pro projekt funkcí Pythonu vypadá jako v následujícím příkladu:
<project_root>/
| - .venv/
| - .vscode/
| - my_first_function/
| | - __init__.py
| | - function.json
| | - example.py
| - my_second_function/
| | - __init__.py
| | - function.json
| - shared_code/
| | - __init__.py
| | - my_first_helper_function.py
| | - my_second_helper_function.py
| - tests/
| | - test_my_second_function.py
| - .funcignore
| - host.json
| - local.settings.json
| - requirements.txt
| - Dockerfile
Hlavní složka projektu, <project_root>, může obsahovat následující soubory:
- local.settings.json: Používá se k ukládání nastavení aplikací a připojovací řetězec při místním spuštění. Tento soubor se nepublikuje do Azure. Další informace najdete v souboru local.settings.file.
- requirements.txt: Obsahuje seznam balíčků Pythonu, které systém nainstaluje při publikování do Azure.
- host.json: Obsahuje možnosti konfigurace, které ovlivňují všechny funkce v instanci aplikace funkcí. Tento soubor se publikuje do Azure. Při místním spuštění nejsou podporované všechny možnosti. Další informace najdete v tématu host.json.
- .vscode/: (volitelné) Obsahuje uloženou konfiguraci editoru Visual Studio Code. Další informace najdete v nastavení editoru Visual Studio Code.
- .venv/: (volitelné) Obsahuje virtuální prostředí Pythonu používané místním vývojem.
- Dockerfile: (Volitelné) Používá se při publikování projektu ve vlastním kontejneru.
- tests/: (Volitelné) Obsahuje testovací případy vaší aplikace funkcí.
- .funcignore: (Volitelné) Deklaruje soubory, které by se neměly publikovat do Azure. Tento soubor obvykle obsahuje soubor .vscode/ pro ignorování nastavení editoru , .venv/ pro ignorování místního virtuálního prostředí Pythonu, testů a ignorování testovacích případů a local.settings.json , aby se zabránilo publikování místního nastavení aplikace.
Každá funkce má vlastní soubor kódu a konfigurační soubor vazby function.json.
Doporučená struktura složek pro projekt funkcí Pythonu vypadá jako v následujícím příkladu:
<project_root>/
| - .venv/
| - .vscode/
| - function_app.py
| - additional_functions.py
| - tests/
| | - test_my_function.py
| - .funcignore
| - host.json
| - local.settings.json
| - requirements.txt
| - Dockerfile
Hlavní složka projektu, <project_root>, může obsahovat následující soubory:
- .venv/: (Volitelné) Obsahuje virtuální prostředí Pythonu, které používá místní vývoj.
- .vscode/: (volitelné) Obsahuje uloženou konfiguraci editoru Visual Studio Code. Další informace najdete v nastavení editoru Visual Studio Code.
- function_app.py: Výchozí umístění pro všechny funkce a související triggery a vazby.
- additional_functions.py: (Volitelné) Všechny ostatní soubory Pythonu, které obsahují funkce (obvykle pro logické seskupení), které jsou odkazovány v function_app.py prostřednictvím podrobných plánů.
- tests/: (Volitelné) Obsahuje testovací případy vaší aplikace funkcí.
- .funcignore: (Volitelné) Deklaruje soubory, které by se neměly publikovat do Azure. Tento soubor obvykle obsahuje soubor .vscode/ pro ignorování nastavení editoru , .venv/ pro ignorování místního virtuálního prostředí Pythonu, testů/ ignorování testovacích případů a local.settings.json , aby se zabránilo publikování nastavení místní aplikace.
- host.json: Obsahuje možnosti konfigurace, které ovlivňují všechny funkce v instanci aplikace funkcí. Tento soubor se publikuje do Azure. Při místním spuštění nejsou podporované všechny možnosti. Další informace najdete v tématu host.json.
- local.settings.json: Slouží k ukládání nastavení aplikací a připojovací řetězec při místním spuštění. Tento soubor se nepublikuje do Azure. Další informace najdete v souboru local.settings.file.
- requirements.txt: Obsahuje seznam balíčků Pythonu, které systém nainstaluje při publikování do Azure.
- Dockerfile: (Volitelné) Používá se při publikování projektu ve vlastním kontejneru.
Když projekt nasadíte do aplikace funkcí v Azure, celý obsah hlavní složky projektu project_root <>by měl být součástí balíčku, ale ne do samotné složky, což znamená, že host.json by měl být v kořenovém adresáři balíčku. Doporučujeme udržovat testy ve složce spolu s dalšími funkcemi (v tomto příkladu testy/). Další informace najdete v tématu Testování částí.
Připojit k databázi
Azure Functions se dobře integruje se službou Azure Cosmos DB pro mnoho případů použití, včetně IoT, elektronického obchodování, hraní atd.
Například pro event sourcing jsou dvě služby integrované do architektur řízených událostmi pomocí funkcí kanálu změn ve službě Azure Cosmos DB. Kanál změn poskytuje podřízené mikroslužby schopnost spolehlivě a přírůstkově číst vkládání a aktualizace (například události objednávek). Tuto funkci lze použít k zajištění trvalého úložiště událostí jako zprostředkovatele zpráv pro události změny stavu a pracovní postup zpracování pořadí jednotek mezi mnoha mikroslužbami (které je možné implementovat jako bezserverové funkce Azure Functions).
Pokud se chcete připojit ke službě Azure Cosmos DB, nejprve vytvořte účet, databázi a kontejner. Pak můžete kód funkce připojit ke službě Azure Cosmos DB pomocí triggeru a vazeb, jako je tento příklad.
Pokud chcete implementovat složitější logiku aplikace, můžete také použít knihovnu Pythonu pro Cosmos DB. Asynchronní implementace vstupně-výstupních operací vypadá takto:
pip install azure-cosmos
pip install aiohttp
from azure.cosmos.aio import CosmosClient
from azure.cosmos import exceptions
from azure.cosmos.partition_key import PartitionKey
import asyncio
# Replace these values with your Cosmos DB connection information
endpoint = "https://azure-cosmos-nosql.documents.azure.com:443/"
key = "master_key"
database_id = "cosmicwerx"
container_id = "cosmicontainer"
partition_key = "/partition_key"
# Set the total throughput (RU/s) for the database and container
database_throughput = 1000
# Singleton CosmosClient instance
client = CosmosClient(endpoint, credential=key)
# Helper function to get or create database and container
async def get_or_create_container(client, database_id, container_id, partition_key):
database = await client.create_database_if_not_exists(id=database_id)
print(f'Database "{database_id}" created or retrieved successfully.')
container = await database.create_container_if_not_exists(id=container_id, partition_key=PartitionKey(path=partition_key))
print(f'Container with id "{container_id}" created')
return container
async def create_products():
container = await get_or_create_container(client, database_id, container_id, partition_key)
for i in range(10):
await container.upsert_item({
'id': f'item{i}',
'productName': 'Widget',
'productModel': f'Model {i}'
})
async def get_products():
items = []
container = await get_or_create_container(client, database_id, container_id, partition_key)
async for item in container.read_all_items():
items.append(item)
return items
async def query_products(product_name):
container = await get_or_create_container(client, database_id, container_id, partition_key)
query = f"SELECT * FROM c WHERE c.productName = '{product_name}'"
items = []
async for item in container.query_items(query=query, enable_cross_partition_query=True):
items.append(item)
return items
async def main():
await create_products()
all_products = await get_products()
print('All Products:', all_products)
queried_products = await query_products('Widget')
print('Queried Products:', queried_products)
if __name__ == "__main__":
asyncio.run(main())
Blueprints
Programovací model Pythonu v2 představuje koncept podrobných plánů. Podrobný plán je nová třída, která se vytvoří instance pro registraci funkcí mimo základní aplikaci funkcí. Funkce zaregistrované v instancích podrobného plánu nejsou indexovány přímo modulem runtime funkce. Aby se tyto funkce podrobného plánu indexovaly, musí aplikace funkcí zaregistrovat funkce z instancí podrobného plánu.
Použití podrobných plánů poskytuje následující výhody:
- Umožňuje rozdělit aplikaci funkcí na modulární komponenty, které umožňují definovat funkce ve více souborech Pythonu a rozdělit je na jednotlivé soubory.
- Poskytuje rozšiřitelná rozhraní veřejných aplikací funkcí pro sestavování a opakované použití vlastních rozhraní API.
Následující příklad ukazuje, jak používat podrobné plány:
Nejprve je v souboru http_blueprint.py definována funkce aktivovaná protokolem HTTP a přidána do objektu podrobného plánu.
import logging
import azure.functions as func
bp = func.Blueprint()
@bp.route(route="default_template")
def default_template(req: func.HttpRequest) -> func.HttpResponse:
logging.info('Python HTTP trigger function processed a request.')
name = req.params.get('name')
if not name:
try:
req_body = req.get_json()
except ValueError:
pass
else:
name = req_body.get('name')
if name:
return func.HttpResponse(
f"Hello, {name}. This HTTP-triggered function "
f"executed successfully.")
else:
return func.HttpResponse(
"This HTTP-triggered function executed successfully. "
"Pass a name in the query string or in the request body for a"
" personalized response.",
status_code=200
)
Dále se v souboru function_app.py naimportuje objekt podrobného plánu a jeho funkce se zaregistrují do aplikace funkcí.
import azure.functions as func
from http_blueprint import bp
app = func.FunctionApp()
app.register_functions(bp)
Poznámka:
Durable Functions také podporuje podrobné plány. Pokud chcete vytvářet podrobné plány pro aplikace Durable Functions, zaregistrujte orchestraci, aktivitu a triggery entit a vazby klientů pomocí azure-functions-durable
Blueprint
třídy, jak je znázorněno zde. Výsledný podrobný plán je pak možné zaregistrovat jako normální. Příklad najdete v naší ukázce .
Chování importu
Moduly v kódu funkce můžete importovat pomocí absolutních i relativních odkazů. Na základě dříve popsané struktury složek fungují následující importy z souboru <funkce project_root>\my_first_function\__init__.py:
from shared_code import my_first_helper_function #(absolute)
import shared_code.my_second_helper_function #(absolute)
from . import example #(relative)
Poznámka:
Pokud používáte absolutní syntaxi importu, musí shared_code/ složka obsahovat soubor __init__.py , který ho označí jako balíček Pythonu.
Následující __app__ import a nad rámec relativního importu nejvyšší úrovně jsou zastaralé, protože nejsou podporovány kontrolou statického typu a nepodporují se testovacími architekturami Pythonu:
from __app__.shared_code import my_first_helper_function #(deprecated __app__ import)
from ..shared_code import my_first_helper_function #(deprecated beyond top-level relative import)
Triggery a vstupy
Vstupy jsou rozdělené do dvou kategorií ve službě Azure Functions: vstup triggeru a další vstup. I když se v souboru function.json liší, jejich použití je v kódu Pythonu stejné. Připojovací řetězce nebo tajné kódy pro trigger a vstupní zdroje se mapují na hodnoty v souboru local.settings.json , když jsou spuštěné místně, a při spuštění v Azure se mapují na nastavení aplikace.
Například následující kód ukazuje rozdíl mezi těmito dvěma vstupy:
// function.json
{
"scriptFile": "__init__.py",
"bindings": [
{
"name": "req",
"direction": "in",
"type": "httpTrigger",
"authLevel": "anonymous",
"route": "items/{id}"
},
{
"name": "obj",
"direction": "in",
"type": "blob",
"path": "samples/{id}",
"connection": "STORAGE_CONNECTION_STRING"
}
]
}
// local.settings.json
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "python",
"STORAGE_CONNECTION_STRING": "<AZURE_STORAGE_CONNECTION_STRING>",
"AzureWebJobsStorage": "<azure-storage-connection-string>"
}
}
# __init__.py
import azure.functions as func
import logging
def main(req: func.HttpRequest, obj: func.InputStream):
logging.info(f'Python HTTP-triggered function processed: {obj.read()}')
Při vyvolání funkce se požadavek HTTP předá funkci jako req
. Položka se načte z účtu služby Azure Blob Storage na základě ID v adrese URL trasy a zpřístupní se jako obj
v těle funkce. Tady je zadaný účet úložiště připojovací řetězec, který se nachází v CONNECTION_STRING
nastavení aplikace.
Vstupy jsou rozdělené do dvou kategorií ve službě Azure Functions: vstup triggeru a další vstup. I když jsou definované pomocí různých dekorátorů, jejich použití je podobné v kódu Pythonu. Připojovací řetězce nebo tajné kódy pro trigger a vstupní zdroje se mapují na hodnoty v souboru local.settings.json , když jsou spuštěné místně, a při spuštění v Azure se mapují na nastavení aplikace.
Následující kód například ukazuje, jak definovat vstupní vazbu služby Blob Storage:
// local.settings.json
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "python",
"STORAGE_CONNECTION_STRING": "<AZURE_STORAGE_CONNECTION_STRING>",
"AzureWebJobsStorage": "<azure-storage-connection-string>",
"AzureWebJobsFeatureFlags": "EnableWorkerIndexing"
}
}
# function_app.py
import azure.functions as func
import logging
app = func.FunctionApp()
@app.route(route="req")
@app.read_blob(arg_name="obj", path="samples/{id}",
connection="STORAGE_CONNECTION_STRING")
def main(req: func.HttpRequest, obj: func.InputStream):
logging.info(f'Python HTTP-triggered function processed: {obj.read()}')
Při vyvolání funkce se požadavek HTTP předá funkci jako req
. Položka se načte z účtu služby Azure Blob Storage na základě ID v adrese URL trasy a zpřístupní se jako obj
v těle funkce. Tady je zadaný účet úložiště připojovací řetězec, který se nachází v STORAGE_CONNECTION_STRING
nastavení aplikace.
Pro operace s vazbami náročnými na data můžete chtít použít samostatný účet úložiště. Další informace najdete v doprovodných materiálech k účtu úložiště.
Vazby typu sady SDK (Preview)
U vybraných triggerů a vazeb můžete pracovat s datovými typy implementovanými podkladovými sadami Azure SDK a architekturami. Tyto vazby typu sady SDK umožňují interakci s daty , jako kdybyste používali podkladovou sadu SDK služby.
Důležité
Podpora vazeb typu sady SDK vyžaduje programovací model Pythonu v2.
Functions podporuje vazby typů sady Python SDK pro úložiště objektů blob v Azure, které umožňují pracovat s daty objektů blob pomocí základního BlobClient
typu.
Důležité
Podpora vazeb typu sady SDK pro Python je aktuálně ve verzi Preview:
- Musíte použít programovací model Pythonu v2.
- V současné době se podporují pouze synchronní typy sad SDK.
Požadavky
- Modul runtime Azure Functions verze 4.34 nebo novější.
- Python verze 3.9 nebo novější podporovaná verze
Povolení vazeb typu sady SDK pro rozšíření úložiště objektů blob
azurefunctions-extensions-bindings-blob
Přidejte balíček příponyrequirements.txt
do souboru v projektu, který by měl obsahovat alespoň tyto balíčky:azure-functions azurefunctions-extensions-bindings-blob
Přidejte tento kód do
function_app.py
souboru v projektu, který importuje vazby typu sady SDK:import azurefunctions.extensions.bindings.blob as blob
Příklady vazeb typu sady SDK
Tento příklad ukazuje, jak získat z triggeru BlobClient
úložiště objektů blob (blob_trigger
) i ze vstupní vazby triggeru HTTP (blob_input
):
import logging
import azure.functions as func
import azurefunctions.extensions.bindings.blob as blob
app = func.FunctionApp(http_auth_level=func.AuthLevel.ANONYMOUS)
@app.blob_trigger(
arg_name="client", path="PATH/TO/BLOB", connection="AzureWebJobsStorage"
)
def blob_trigger(client: blob.BlobClient):
logging.info(
f"Python blob trigger function processed blob \n"
f"Properties: {client.get_blob_properties()}\n"
f"Blob content head: {client.download_blob().read(size=1)}"
)
@app.route(route="file")
@app.blob_input(
arg_name="client", path="PATH/TO/BLOB", connection="AzureWebJobsStorage"
)
def blob_input(req: func.HttpRequest, client: blob.BlobClient):
logging.info(
f"Python blob input function processed blob \n"
f"Properties: {client.get_blob_properties()}\n"
f"Blob content head: {client.download_blob().read(size=1)}"
)
return "ok"
V úložišti rozšíření Pythonu můžete zobrazit další ukázky vazeb typů sady SDK pro úložiště objektů blob:
Streamy HTTP (Preview)
Streamy HTTP umožňují přijímat a vracet data z koncových bodů HTTP pomocí rozhraní API požadavků FastAPI a odpovědí povolených ve vašich funkcích. Tato rozhraní API umožňují hostiteli zpracovávat velká data ve zprávách HTTP jako bloky dat místo čtení celé zprávy do paměti.
Tato funkce umožňuje zpracovávat velké datové proudy, integrace OpenAI, dodávat dynamický obsah a podporovat další základní scénáře HTTP vyžadující interakce v reálném čase přes HTTP. U streamů HTTP můžete také použít typy odpovědí FastAPI. Bez datových proudů HTTP jsou velikost požadavků a odpovědí HTTP omezena omezeními paměti, ke kterým může dojít při zpracování celých datových částí zpráv v paměti.
Důležité
Podpora datových proudů HTTP vyžaduje programovací model Pythonu v2.
Důležité
Podpora datových proudů HTTP pro Python je aktuálně ve verzi Preview a vyžaduje použití programovacího modelu Pythonu v2.
Požadavky
- Modul runtime Azure Functions verze 4.34.1 nebo novější.
- Python verze 3.8 nebo novější podporovaná verze
Povolení streamů HTTP
Streamy HTTP jsou ve výchozím nastavení zakázané. Tuto funkci musíte povolit v nastavení aplikace a také aktualizovat kód tak, aby používal balíček FastAPI. Všimněte si, že když povolíte streamy HTTP, aplikace funkcí ve výchozím nastavení použije streamování HTTP a původní funkce HTTP nebudou fungovat.
azurefunctions-extensions-http-fastapi
Přidejte balíček příponyrequirements.txt
do souboru v projektu, který by měl obsahovat alespoň tyto balíčky:azure-functions azurefunctions-extensions-http-fastapi
Přidejte tento kód do
function_app.py
souboru v projektu, který importuje příponu FastAPI:from azurefunctions.extensions.http.fastapi import Request, StreamingResponse
Když nasadíte do Azure, přidejte do aplikace funkcí následující nastavení aplikace:
"PYTHON_ENABLE_INIT_INDEXING": "1"
Pokud nasazujete do linuxové spotřeby, přidejte také
"PYTHON_ISOLATE_WORKER_DEPENDENCIES": "1"
Při místním spuštění je také potřeba přidat tato stejná nastavení do
local.settings.json
souboru projektu.
Příklady streamů HTTP
Po povolení funkce streamování HTTP můžete vytvořit funkce, které streamuje data přes PROTOKOL HTTP.
Tento příklad je funkce aktivovaná protokolem HTTP, která streamuje data odpovědi HTTP. Tyto funkce můžete použít k podpoře scénářů, jako je odesílání dat událostí prostřednictvím kanálu pro vizualizaci v reálném čase nebo zjišťování anomálií ve velkých sadách dat a poskytování rychlých oznámení.
import time
import azure.functions as func
from azurefunctions.extensions.http.fastapi import Request, StreamingResponse
app = func.FunctionApp(http_auth_level=func.AuthLevel.ANONYMOUS)
def generate_sensor_data():
"""Generate real-time sensor data."""
for i in range(10):
# Simulate temperature and humidity readings
temperature = 20 + i
humidity = 50 + i
yield f"data: {{'temperature': {temperature}, 'humidity': {humidity}}}\n\n"
time.sleep(1)
@app.route(route="stream", methods=[func.HttpMethod.GET])
async def stream_sensor_data(req: Request) -> StreamingResponse:
"""Endpoint to stream real-time sensor data."""
return StreamingResponse(generate_sensor_data(), media_type="text/event-stream")
Tento příklad je funkce aktivovaná protokolem HTTP, která přijímá a zpracovává streamovaná data z klienta v reálném čase. Ukazuje možnosti nahrávání streamování, které můžou být užitečné pro scénáře, jako je zpracování nepřetržitých datových proudů a zpracování dat událostí ze zařízení IoT.
import azure.functions as func
from azurefunctions.extensions.http.fastapi import JSONResponse, Request
app = func.FunctionApp(http_auth_level=func.AuthLevel.ANONYMOUS)
@app.route(route="streaming_upload", methods=[func.HttpMethod.POST])
async def streaming_upload(req: Request) -> JSONResponse:
"""Handle streaming upload requests."""
# Process each chunk of data as it arrives
async for chunk in req.stream():
process_data_chunk(chunk)
# Once all data is received, return a JSON response indicating successful processing
return JSONResponse({"status": "Data uploaded and processed successfully"})
def process_data_chunk(chunk: bytes):
"""Process each data chunk."""
# Add custom processing logic here
pass
Volání datových proudů HTTP
Ke streamování volání koncových bodů FastAPI funkce musíte použít klientskou knihovnu HTTP. Klientský nástroj nebo prohlížeč, který používáte, nemusí nativně podporovat streamování nebo může vrátit pouze první blok dat.
K odesílání streamovaných dat do koncového bodu HTTP můžete použít podobný klientský skript:
import httpx # Be sure to add 'httpx' to 'requirements.txt'
import asyncio
async def stream_generator(file_path):
chunk_size = 2 * 1024 # Define your own chunk size
with open(file_path, 'rb') as file:
while chunk := file.read(chunk_size):
yield chunk
print(f"Sent chunk: {len(chunk)} bytes")
async def stream_to_server(url, file_path):
timeout = httpx.Timeout(60.0, connect=60.0)
async with httpx.AsyncClient(timeout=timeout) as client:
response = await client.post(url, content=stream_generator(file_path))
return response
async def stream_response(response):
if response.status_code == 200:
async for chunk in response.aiter_raw():
print(f"Received chunk: {len(chunk)} bytes")
else:
print(f"Error: {response}")
async def main():
print('helloworld')
# Customize your streaming endpoint served from core tool in variable 'url' if different.
url = 'http://localhost:7071/api/streaming_upload'
file_path = r'<file path>'
response = await stream_to_server(url, file_path)
print(response)
if __name__ == "__main__":
asyncio.run(main())
Výstupy
Výstup se dá vyjádřit jak ve návratové hodnotě, tak ve výstupních parametrech. Pokud existuje jenom jeden výstup, doporučujeme použít návratovou hodnotu. Pro více výstupů musíte použít výstupní parametry.
Chcete-li použít návratovou hodnotu funkce jako hodnotu výstupní vazby, name
vlastnost vazby by měla být nastavena na $return
v souboru function.json .
Chcete-li vytvořit více výstupů, použijte metodu set()
poskytovanou azure.functions.Out
rozhraním k přiřazení hodnoty k vazbě. Například následující funkce může odeslat zprávu do fronty a také vrátit odpověď HTTP.
{
"scriptFile": "__init__.py",
"bindings": [
{
"name": "req",
"direction": "in",
"type": "httpTrigger",
"authLevel": "anonymous"
},
{
"name": "msg",
"direction": "out",
"type": "queue",
"queueName": "outqueue",
"connection": "STORAGE_CONNECTION_STRING"
},
{
"name": "$return",
"direction": "out",
"type": "http"
}
]
}
import azure.functions as func
def main(req: func.HttpRequest,
msg: func.Out[func.QueueMessage]) -> str:
message = req.params.get('body')
msg.set(message)
return message
Výstup se dá vyjádřit jak ve návratové hodnotě, tak ve výstupních parametrech. Pokud existuje jenom jeden výstup, doporučujeme použít návratovou hodnotu. Pro více výstupů budete muset použít výstupní parametry.
Chcete-li vytvořit více výstupů, použijte metodu set()
poskytovanou azure.functions.Out
rozhraním k přiřazení hodnoty k vazbě. Například následující funkce může odeslat zprávu do fronty a také vrátit odpověď HTTP.
# function_app.py
import azure.functions as func
app = func.FunctionApp()
@app.write_blob(arg_name="msg", path="output-container/{name}",
connection="CONNECTION_STRING")
def test_function(req: func.HttpRequest,
msg: func.Out[str]) -> str:
message = req.params.get('body')
msg.set(message)
return message
Protokolování
Přístup k protokolovacímu modulu runtime Azure Functions je k dispozici prostřednictvím kořenové logging
obslužné rutiny ve vaší aplikaci funkcí. Tento protokolovací modul je svázaný s Application Insights a umožňuje označit upozornění a chyby, ke kterým dochází během provádění funkce.
Následující příklad zaznamená informační zprávu při vyvolání funkce prostřednictvím triggeru HTTP.
import logging
def main(req):
logging.info('Python HTTP trigger function processed a request.')
K dispozici jsou další metody protokolování, které umožňují zapisovat do konzoly na různých úrovních trasování:
metoda | Popis |
---|---|
critical(_message_) |
Zapíše zprávu s úrovní CRITICAL v kořenovém protokolovacím nástroje. |
error(_message_) |
Zapíše zprávu s úrovní ERROR v kořenovém protokolovacím nástroje. |
warning(_message_) |
Zapíše zprávu s upozorněním na úrovni v kořenovém protokolovacím nástroje. |
info(_message_) |
Zapíše zprávu s informací o úrovni v kořenovém protokolovacím systému. |
debug(_message_) |
Zapíše zprávu s úrovní DEBUG v kořenovém protokolovacím nástroji. |
Další informace o protokolování najdete v tématu Monitorování azure Functions.
Protokolování z vytvořených vláken
Pokud chcete zobrazit protokoly pocházející z vytvořených vláken, zahrňte context
do podpisu funkce argument. Tento argument obsahuje atribut thread_local_storage
, který ukládá místní invocation_id
. To lze nastavit na aktuální invocation_id
funkci, aby se zajistilo, že se kontext změní.
import azure.functions as func
import logging
import threading
def main(req, context):
logging.info('Python HTTP trigger function processed a request.')
t = threading.Thread(target=log_function, args=(context,))
t.start()
def log_function(context):
context.thread_local_storage.invocation_id = context.invocation_id
logging.info('Logging from thread.')
Protokolování vlastní telemetrie
Modul runtime služby Functions ve výchozím nastavení shromažďuje protokoly a další telemetrická data generovaná vašimi funkcemi. Tato telemetrie končí jako trasování ve službě Application Insights. Telemetrie požadavků a závislostí pro určité služby Azure se ve výchozím nastavení shromažďují také triggery a vazby.
Pokud chcete shromažďovat vlastní telemetrii požadavků a vlastních závislostí mimo vazby, můžete použít rozšíření OpenCensus Python. Toto rozšíření odesílá vlastní telemetrická data do vaší instance Application Insights. Seznam podporovaných rozšíření najdete v úložišti OpenCensus.
Poznámka:
Pokud chcete použít rozšíření OpenCensus Python, musíte ve své aplikaci funkcí povolit rozšíření pracovních procesů Pythonu nastavením PYTHON_ENABLE_WORKER_EXTENSIONS
na 1
. Musíte také přepnout na používání služby Application Insights připojovací řetězec tak, že do nastavení aplikace přidáte APPLICATIONINSIGHTS_CONNECTION_STRING
nastavení, pokud tam ještě není.
// requirements.txt
...
opencensus-extension-azure-functions
opencensus-ext-requests
import json
import logging
import requests
from opencensus.extension.azure.functions import OpenCensusExtension
from opencensus.trace import config_integration
config_integration.trace_integrations(['requests'])
OpenCensusExtension.configure()
def main(req, context):
logging.info('Executing HttpTrigger with OpenCensus extension')
# You must use context.tracer to create spans
with context.tracer.span("parent"):
response = requests.get(url='http://example.com')
return json.dumps({
'method': req.method,
'response': response.status_code,
'ctx_func_name': context.function_name,
'ctx_func_dir': context.function_directory,
'ctx_invocation_id': context.invocation_id,
'ctx_trace_context_Traceparent': context.trace_context.Traceparent,
'ctx_trace_context_Tracestate': context.trace_context.Tracestate,
'ctx_retry_context_RetryCount': context.retry_context.retry_count,
'ctx_retry_context_MaxRetryCount': context.retry_context.max_retry_count,
})
Trigger HTTP
Trigger HTTP je definován v souboru function.json . Vazba name
musí odpovídat pojmenovaného parametru ve funkci.
V předchozích příkladech se používá název req
vazby. Tento parametr je objekt HttpRequest a je vrácen objekt HttpResponse .
Z objektu HttpRequest můžete získat hlavičky požadavku, parametry dotazu, parametry trasy a text zprávy.
Následující příklad je ze šablony triggeru HTTP pro Python.
def main(req: func.HttpRequest) -> func.HttpResponse:
headers = {"my-http-header": "some-value"}
name = req.params.get('name')
if not name:
try:
req_body = req.get_json()
except ValueError:
pass
else:
name = req_body.get('name')
if name:
return func.HttpResponse(f"Hello {name}!", headers=headers)
else:
return func.HttpResponse(
"Please pass a name on the query string or in the request body",
headers=headers, status_code=400
)
V této funkci získáte hodnotu parametru name
dotazu z params
parametru HttpRequest objektu. Text zprávy kódovaný ve formátu JSON si můžete přečíst pomocí get_json
metody.
Podobně můžete nastavit status_code
a headers
pro zprávu odpovědi ve vrácený objekt HttpResponse .
Trigger HTTP je definován jako metoda, která přebírá pojmenovaný binding parametr, což je HttpRequest objekt a vrací HttpResponse objekt. Dekorátor použijete function_name
na metodu pro definování názvu funkce, zatímco koncový bod HTTP je nastaven použitím dekorátoru route
.
Tento příklad pochází ze šablony triggeru HTTP pro programovací model Pythonu v2, kde je req
název parametru vazby . Jedná se o ukázkový kód, který je k dispozici při vytváření funkce pomocí nástrojů Azure Functions Core Tools nebo editoru Visual Studio Code.
@app.function_name(name="HttpTrigger1")
@app.route(route="hello")
def test_function(req: func.HttpRequest) -> func.HttpResponse:
logging.info('Python HTTP trigger function processed a request.')
name = req.params.get('name')
if not name:
try:
req_body = req.get_json()
except ValueError:
pass
else:
name = req_body.get('name')
if name:
return func.HttpResponse(f"Hello, {name}. This HTTP-triggered function executed successfully.")
else:
return func.HttpResponse(
"This HTTP-triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.",
status_code=200
)
Z objektu HttpRequest můžete získat hlavičky požadavku, parametry dotazu, parametry trasy a text zprávy. V této funkci získáte hodnotu parametru name
dotazu z params
parametru HttpRequest objektu. Text zprávy kódovaný ve formátu JSON si můžete přečíst pomocí get_json
metody.
Podobně můžete nastavit status_code
a headers
pro zprávu odpovědi ve vrácený objekt HttpResponse .
Pokud chcete předat název v tomto příkladu, vložte adresu URL, která je zadaná při spuštění funkce, a pak ji připojte pomocí "?name={name}"
.
Webové architektury
S funkcemi Pythonu aktivovanými protokolem HTTP můžete použít rozhraní kompatibilní s rozhraním WSGI (Web Server Gateway Interface) kompatibilní s rozhraním WSGI (Web Server Gateway Interface). Tato část ukazuje, jak upravit funkce tak, aby podporovaly tyto architektury.
Nejprve musí být soubor function.json aktualizován tak, aby zahrnoval route
trigger HTTP, jak je znázorněno v následujícím příkladu:
{
"scriptFile": "__init__.py",
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get",
"post"
],
"route": "{*route}"
},
{
"type": "http",
"direction": "out",
"name": "$return"
}
]
}
Soubor host.json musí být také aktualizován tak, aby zahrnoval PROTOKOL HTTP routePrefix
, jak je znázorněno v následujícím příkladu:
{
"version": "2.0",
"logging":
{
"applicationInsights":
{
"samplingSettings":
{
"isEnabled": true,
"excludedTypes": "Request"
}
}
},
"extensionBundle":
{
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[3.*, 4.0.0)"
},
"extensions":
{
"http":
{
"routePrefix": ""
}
}
}
Aktualizujte soubor kódu Pythonu init.py v závislosti na rozhraní používaném vaší architekturou. Následující příklad ukazuje přístup obslužné rutiny ASGI nebo metodu obálky WSGI pro Flask:
S funkcemi Pythonu aktivovanými protokolem HTTP můžete použít rozhraní kompatibilní s asynchronním serverovým rozhraním (ASGI) a rozhraním WSGI (Web Server Gateway Interface). Nejprve je nutné aktualizovat soubor host.json tak, aby zahrnoval PROTOKOL HTTP routePrefix
, jak je znázorněno v následujícím příkladu:
{
"version": "2.0",
"logging":
{
"applicationInsights":
{
"samplingSettings":
{
"isEnabled": true,
"excludedTypes": "Request"
}
}
},
"extensionBundle":
{
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[2.*, 3.0.0)"
},
"extensions":
{
"http":
{
"routePrefix": ""
}
}
}
Kód architektury vypadá jako v následujícím příkladu:
AsgiFunctionApp
je třída aplikace funkcí nejvyšší úrovně pro vytváření funkcí ASGI HTTP.
# function_app.py
import azure.functions as func
from fastapi import FastAPI, Request, Response
fast_app = FastAPI()
@fast_app.get("/return_http_no_body")
async def return_http_no_body():
return Response(content="", media_type="text/plain")
app = func.AsgiFunctionApp(app=fast_app,
http_auth_level=func.AuthLevel.ANONYMOUS)
Škálování a výkon
Osvědčené postupy škálování a výkonu pro aplikace funkcí Pythonu najdete v článku o škálování a výkonu Pythonu.
Kontext
Pokud chcete získat kontext vyvolání funkce při jeho spuštění, zahrňte context
do podpisu argument.
Příklad:
import azure.functions
def main(req: azure.functions.HttpRequest,
context: azure.functions.Context) -> str:
return f'{context.invocation_id}'
Třída Context
má následující atributy řetězce:
Atribut | Popis |
---|---|
function_directory |
Adresář, ve kterém je funkce spuštěná. |
function_name |
Název funkce. |
invocation_id |
ID vyvolání aktuální funkce. |
thread_local_storage |
Místní úložiště podprocesu funkce. Obsahuje místní invocation_id prostředí pro protokolování z vytvořených vláken. |
trace_context |
Kontext distribuovaného trasování. Další informace najdete na webu Trace Context . |
retry_context |
Kontext pro opakování funkce. Další informace najdete na webu retry-policies . |
Globální proměnné
Není zaručeno, že se stav vaší aplikace zachová pro budoucí spuštění. Modul runtime Azure Functions ale často používá stejný proces pro více spuštění stejné aplikace. Pokud chcete výsledky nákladného výpočtu uložit do mezipaměti, deklarujte ho jako globální proměnnou.
CACHED_DATA = None
def main(req):
global CACHED_DATA
if CACHED_DATA is None:
CACHED_DATA = load_json()
# ... use CACHED_DATA in code
Proměnné prostředí
V Azure Functions se nastavení aplikace, jako jsou připojovací řetězec služby, zobrazují jako proměnné prostředí při jejich spuštění. Existují dva hlavní způsoby přístupu k těmto nastavením v kódu.
metoda | Popis |
---|---|
os.environ["myAppSetting"] |
Pokusí se získat nastavení aplikace podle názvu klíče a vyvolá chybu, když je neúspěšná. |
os.getenv("myAppSetting") |
Pokusí se získat nastavení aplikace podle názvu klíče a vrátí null , když je neúspěšná. |
Oba tyto způsoby vyžadují, abyste deklarovat import os
.
Následující příklad používá os.environ["myAppSetting"]
k získání nastavení aplikace s názvem myAppSetting
klíč :
import logging
import os
import azure.functions as func
def main(req: func.HttpRequest) -> func.HttpResponse:
# Get the setting named 'myAppSetting'
my_app_setting_value = os.environ["myAppSetting"]
logging.info(f'My app setting value:{my_app_setting_value}')
Pro místní vývoj se nastavení aplikace udržuje v souboru local.settings.json.
V Azure Functions se nastavení aplikace, jako jsou připojovací řetězec služby, zobrazují jako proměnné prostředí při jejich spuštění. Existují dva hlavní způsoby přístupu k těmto nastavením v kódu.
metoda | Popis |
---|---|
os.environ["myAppSetting"] |
Pokusí se získat nastavení aplikace podle názvu klíče a vyvolá chybu, když je neúspěšná. |
os.getenv("myAppSetting") |
Pokusí se získat nastavení aplikace podle názvu klíče a vrátí null , když je neúspěšná. |
Oba tyto způsoby vyžadují, abyste deklarovat import os
.
Následující příklad používá os.environ["myAppSetting"]
k získání nastavení aplikace s názvem myAppSetting
klíč :
import logging
import os
import azure.functions as func
app = func.FunctionApp()
@app.function_name(name="HttpTrigger1")
@app.route(route="req")
def main(req: func.HttpRequest) -> func.HttpResponse:
# Get the setting named 'myAppSetting'
my_app_setting_value = os.environ["myAppSetting"]
logging.info(f'My app setting value:{my_app_setting_value}')
Pro místní vývoj se nastavení aplikace udržuje v souboru local.settings.json.
Pokud používáte nový programovací model, povolte v souboru local.settings.json následující nastavení aplikace, jak je znázorněno tady:
"AzureWebJobsFeatureFlags": "EnableWorkerIndexing"
Při nasazování funkce se toto nastavení nevytvořilo automaticky. Toto nastavení musíte explicitně vytvořit ve své aplikaci funkcí v Azure, aby bylo možné ho spustit pomocí modelu v2.
Verze Pythonu
Azure Functions podporuje následující verze Pythonu:
Verze služby Functions | Verze Pythonu* |
---|---|
4.x | 3.11 3,10 3.9 3.8 3.7 |
3.x | 3.9 3.8 3.7 |
* Oficiální distribuce Pythonu
Pokud chcete při vytváření aplikace funkcí v Azure požádat o konkrétní verzi Pythonu --runtime-version
az functionapp create
, použijte možnost příkazu. Verze modulu runtime Služby Functions je nastavena možností --functions-version
. Verze Pythonu se nastavuje při vytváření aplikace funkcí a nedá se změnit pro aplikace spuštěné v plánu Consumption.
Modul runtime používá dostupnou verzi Pythonu při místním spuštění.
Změna verze Pythonu
Pokud chcete nastavit aplikaci funkcí Pythonu na konkrétní jazykovou verzi, musíte zadat jazyk a verzi jazyka v LinuxFxVersion
poli v konfiguraci webu. Pokud chcete například změnit aplikaci Python tak, aby používala Python 3.8, nastavte linuxFxVersion
na python|3.8
.
Další informace o podpoře jazyků v Azure Functions najdete v tématu Podporované jazyky v Azure Functions.
Správa balíčků
requests==2.19.1
pip install -r requirements.txt
Publikování do Azure
Vzdálené sestavení:
Výsledkem je menší balíček pro nasazení, který se má nahrát.
Při vzdáleném sestavení se závislosti získají vzdáleně v závislosti na obsahu souboru requirements.txt. Vzdálené sestavení je doporučenou metodou sestavení.
func azure functionapp publish <APP_NAME>
V nahraďte názvem vaší aplikace funkcí.
Místní sestavení:
Při vzdáleném sestavení se závislosti získají vzdáleně v závislosti na obsahu souboru requirements.txt.
func azure functionapp publish <APP_NAME> --build local
V nahraďte názvem vaší aplikace funkcí.
Další informace najdete v dokumentaci k portálu.
Instalace balíčků
pip install --target="<PROJECT_DIR>/.python_packages/lib/site-packages" -r requirements.txt
func azure functionapp publish <APP_NAME> --no-build
V nahraďte názvem vaší aplikace funkcí.
Testování částí
{
"scriptFile": "__init__.py",
"entryPoint": "main",
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get",
"post"
]
},
{
"type": "http",
"direction": "out",
"name": "$return"
}
]
}
# <project_root>/my_second_function/__init__.py
import azure.functions as func
import logging
# Use absolute import to resolve shared_code modules
from shared_code import my_second_helper_function
# Define an HTTP trigger that accepts the ?value=<int> query parameter
# Double the value and return the result in HttpResponse
def main(req: func.HttpRequest) -> func.HttpResponse:
logging.info('Executing my_second_function.')
initial_value: int = int(req.params.get('value'))
doubled_value: int = my_second_helper_function.double(initial_value)
return func.HttpResponse(
body=f"{initial_value} * 2 = {doubled_value}",
status_code=200
)
# <project_root>/shared_code/__init__.py
# Empty __init__.py file marks shared_code folder as a Python package
# <project_root>/shared_code/my_second_helper_function.py
def double(value: int) -> int:
return value * 2
# <project_root>/tests/test_my_second_function.py
import unittest
import azure.functions as func
from my_second_function import main
class TestFunction(unittest.TestCase):
def test_my_second_function(self):
# Construct a mock HTTP request.
req = func.HttpRequest(method='GET',
body=None,
url='/api/my_second_function',
params={'value': '21'})
# Call the function.
resp = main(req)
# Check the output.
self.assertEqual(resp.get_body(), b'21 * 2 = 42',)
# <project_root>/function_app.py
import azure.functions as func
import logging
# Use absolute import to resolve shared_code modules
from shared_code import my_second_helper_function
app = func.FunctionApp()
# Define the HTTP trigger that accepts the ?value=<int> query parameter
# Double the value and return the result in HttpResponse
@app.function_name(name="my_second_function")
@app.route(route="hello")
def main(req: func.HttpRequest) -> func.HttpResponse:
logging.info('Executing my_second_function.')
initial_value: int = int(req.params.get('value'))
doubled_value: int = my_second_helper_function.double(initial_value)
return func.HttpResponse(
body=f"{initial_value} * 2 = {doubled_value}",
status_code=200
)
# <project_root>/shared_code/__init__.py
# Empty __init__.py file marks shared_code folder as a Python package
# <project_root>/shared_code/my_second_helper_function.py
def double(value: int) -> int:
return value * 2
# <project_root>/tests/test_my_second_function.py
import unittest
import azure.functions as func
from function_app import main
class TestFunction(unittest.TestCase):
def test_my_second_function(self):
# Construct a mock HTTP request.
req = func.HttpRequest(method='GET',
body=None,
url='/api/my_second_function',
params={'value': '21'})
# Call the function.
func_call = main.build().get_user_function()
resp = func_call(req)
# Check the output.
self.assertEqual(
resp.get_body(),
b'21 * 2 = 42',
)
Dočasné soubory
Důležité
import logging
import azure.functions as func
import tempfile
from os import listdir
#---
tempFilePath = tempfile.gettempdir()
fp = tempfile.NamedTemporaryFile()
fp.write(b'Hello world!')
filesDirListInTemp = listdir(tempFilePath)
Co je standardní knihovna Pythonu?
Poznámka:
Poznámka:
getattr(azure.functions, '__version__', '< 1.2.1')
Modul runtime služby Functions | Verze Pythonu | |
---|---|---|
Verze 12.x | Python 3.7 Python 3.8 Python 3.7 |
Scope | Popis |
---|---|
- Definujte následující nastavení aplikace:
Důležité
# <project_root>/requirements.txt
application-level-extension==1.0.0
# <project_root>/Trigger/__init__.py
from application_level_extension import AppExtension
AppExtension.configure(key=value)
def main(req, context):
# Use context.app_ext_attributes here
Aktualizace rozšíření
Možnosti na úrovni aplikace.
metoda | Popis |
---|---|
init |
|
configure |
|
post_function_load_app_level |
|
pre_invocation_app_level |
|
post_invocation_app_level |
metoda | Popis |
---|---|
__init__ |
|
post_function_load |
|
pre_invocation |
|
post_invocation |
Sdílení prostředků různého původu
Azure Resource Manager podporuje technologii CORS (sdílení prostředků mezi zdroji). Další informace naleznete v tématu Sdílení prostředků různého původu.
Async
Známé problémy a nejčastější dotazy
Další kroky
Další informace naleznete v následujících zdrojích:
Váš názor
https://aka.ms/ContentUserFeedback.
Připravujeme: V průběhu roku 2024 budeme postupně vyřazovat problémy z GitHub coby mechanismus zpětné vazby pro obsah a nahrazovat ho novým systémem zpětné vazby. Další informace naleznete v tématu:Odeslat a zobrazit názory pro