From ce5bc639fec29c8561c1045bca7b6b1f34b65c13 Mon Sep 17 00:00:00 2001 From: "ry.yamafuji" Date: Sat, 6 Dec 2025 01:56:18 +0900 Subject: [PATCH] =?UTF-8?q?Cloud=20Function=E3=81=AE=E5=9F=BA=E6=9C=AC?= =?UTF-8?q?=E3=82=BD=E3=83=BC=E3=82=B9=E3=82=92=E8=BF=BD=E5=8A=A0=E3=81=97?= =?UTF-8?q?=E3=81=BE=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/example_event.py | 23 +++++++++ examples/example_http.py | 48 ++++++++++++++++++ readme/cloud_functions.md | 104 ++++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + ruff.toml | 2 +- src/main.py | 46 +++++++++++++++-- src/requirements.txt | 1 + 7 files changed, 219 insertions(+), 6 deletions(-) create mode 100644 examples/example_event.py create mode 100644 examples/example_http.py create mode 100644 readme/cloud_functions.md create mode 100644 src/requirements.txt diff --git a/examples/example_event.py b/examples/example_event.py new file mode 100644 index 0000000..5560808 --- /dev/null +++ b/examples/example_event.py @@ -0,0 +1,23 @@ +import functions_framework +from utils.custom_logger import get_logger + +logger = get_logger(__name__) + + +@functions_framework.cloud_event +def hello_pubsub(cloud_event): + logger.info("Cloud Function triggered") + data = cloud_event.data + logger.info(f"Received data: {data}") + + +if __name__ == "__main__": + # For local testing purposes + class DummyCloudEvent: + """デバッグ用のダミークラウドイベントクラス""" + def __init__(self, data): + self.data = data + + test_event = DummyCloudEvent(data={"message": "Hello from local test"}) + hello_pubsub(test_event) + diff --git a/examples/example_http.py b/examples/example_http.py new file mode 100644 index 0000000..53963ea --- /dev/null +++ b/examples/example_http.py @@ -0,0 +1,48 @@ +from flask import Request +import functions_framework +from utils.custom_logger import get_logger + +logger = get_logger(__name__) + +@functions_framework.http +def hello_request(request: Request): + """HTTPリクエストを処理するエンドポイント""" + + logger.info("Cloud Function triggered start") + + # Headerを取得する + ua = request.headers.get("User-Agent", "Unknown") + logger.info(f"User-Agent: {ua}") + + if request.method == "GET": + logger.info("Processing GET request") + return _get(request) + elif request.method == "POST": + logger.info("Processing POST request") + return _post(request) + else: + logger.warning(f"Unsupported HTTP method: {request.method}") + return {"error": "Unsupported HTTP method"}, 405 + + +def _get(request: Request): + """GETリクエストの処理""" + params = request.args + name = params.get("name", "World") + message = f"Hello, {name}!" + logger.info(f"Greeting generated: {message}") + return {"message": message} + + +def _post(request: Request): + """POSTリクエストの処理""" + data = request.get_json(silent=True) + if not data or "name" not in data: + logger.warning("Invalid POST request: 'name' not found in JSON body") + return {"error": "Please provide a 'name' in the JSON body."}, 400 + + name = data["name"] + message = f"Hello, {name}!" + logger.info(f"Greeting generated: {message}") + return {"message": message} + diff --git a/readme/cloud_functions.md b/readme/cloud_functions.md new file mode 100644 index 0000000..7fca9c3 --- /dev/null +++ b/readme/cloud_functions.md @@ -0,0 +1,104 @@ +# Cloud Functionsについて + +## Dev + +### リクエストをトリガーにする場合 + +**関数のエントリポインの定義** + +```py +@functions_framework.http +def hello_request(request: Request): + ... +``` + +**Cloud FUnctionsを起動する** + +```sh +functions-framework \ +--source=src/main.py \ +--target=hello_request \ +--signature-type=http \ +--port=8080 +``` + +ローカルでリクエストを送信する(ローカル) + +**GET** + +```sh +curl -X GET -H "Content-Type: application/json" \ + 'http://localhost:8080?name=python' +``` + +**POST** + +```sh +curl -X POST -H "Content-Type: application/json" \ + -d '{"name": "python"}' \ + http://localhost:8080 +``` + +**PUT** + +```sh +curl -X PUT -H "Content-Type: application/json" \ + -d '{"name": "python"}' \ + http://localhost:8080 +``` + + +### Eventをトリガーにする場合 + +ローカルで実行するにはイベントをHTTPにラップして Functions Frameworkで動かすのが基本です。 +本番ではPub/Sub / Storage / Firestore / Eventarc などのイベントトリガでCloud Functionsを起動します。 + +* **ユースケース**: + * Pub/Sub: Pub/Subイベントにより実行する + * Storage: GCSにファイルのアップロードなどがあった場合 + * Firestore: Firestoreのデータに変更等が生じた場合 + * Eventarc: イベントログなどをトリガーにする(BQにデータが登録された場合) + +[サンプルイベントソース](../examples/example_event.py) + +**関数のエントリポインの定義** + +```py +@functions_framework.cloud_event +def main(cloud_event): + ... +``` + +第1世代 (1st Gen)では以下のエントリポイントになる + +```py +def main(event, context) +``` + +**Cloud FUnctionsを起動する** + +```sh +functions-framework \ +--source=src/main.py \ +--target=hello_pubsub \ +--signature-type=cloudevent \ +--port=8080 +``` + +ローカルでイベントを送信する(ローカル) + +```sh +curl -X POST \ + -H "Content-Type: application/json" \ + -H "Ce-Specversion: 1.0" \ + -H "Ce-Type: google.cloud.pubsub.topic.v1.messagePublished" \ + -H "Ce-Source: //pubsub.googleapis.com/projects/test-project/topics/test-topic" \ + -H "Ce-Id: 1234567890" \ + -H "Ce-Time: 2025-12-06T00:00:00Z" \ + -d '{ + "message": { + "data": "'$(echo -n "hello-local" | base64)'" + } + }' \ + http://localhost:8080 +``` \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index e69de29..ead9a25 100644 --- a/requirements.txt +++ b/requirements.txt @@ -0,0 +1 @@ +functions-framework==3.* \ No newline at end of file diff --git a/ruff.toml b/ruff.toml index 5e76b6c..cba2bd5 100644 --- a/ruff.toml +++ b/ruff.toml @@ -8,5 +8,5 @@ line-length = 79 # BXX(バグの可能性) [lint] -select = ["F", "E", "W", "D101", "B"] +select = ["F", "E", "W", "D101", "D102", "D103", "B"] ignore = [] \ No newline at end of file diff --git a/src/main.py b/src/main.py index 790d938..f8db3cb 100644 --- a/src/main.py +++ b/src/main.py @@ -1,12 +1,48 @@ +from flask import Request +import functions_framework from utils.custom_logger import get_logger logger = get_logger(__name__) +@functions_framework.http +def main(request: Request): + """HTTPリクエストを処理するエンドポイント""" -def main(): - logger.info("Application started") - print("Hello, World!") + logger.info("Cloud Function triggered start") + + # Headerを取得する + ua = request.headers.get("User-Agent", "Unknown") + logger.info(f"User-Agent: {ua}") + + if request.method == "GET": + logger.info("Processing GET request") + return _get(request) + elif request.method == "POST": + logger.info("Processing POST request") + return _post(request) + else: + logger.warning(f"Unsupported HTTP method: {request.method}") + return {"error": "Unsupported HTTP method"}, 405 -if __name__ == "__main__": - main() +def _get(request: Request): + """GETリクエストの処理""" + params = request.args + name = params.get("name", "World") + message = f"Hello, {name}!" + logger.info(f"Greeting generated: {message}") + return {"message": message} + + +def _post(request: Request): + """POSTリクエストの処理""" + data = request.get_json(silent=True) + if not data or "name" not in data: + logger.warning("Invalid POST request: 'name' not found in JSON body") + return {"error": "Please provide a 'name' in the JSON body."}, 400 + + name = data["name"] + message = f"Hello, {name}!" + logger.info(f"Greeting generated: {message}") + return {"message": message} + diff --git a/src/requirements.txt b/src/requirements.txt new file mode 100644 index 0000000..ead9a25 --- /dev/null +++ b/src/requirements.txt @@ -0,0 +1 @@ +functions-framework==3.* \ No newline at end of file