diff --git a/.github/workflows/test_python.yml b/.github/workflows/test_python.yml index c0414c473..98da53e63 100644 --- a/.github/workflows/test_python.yml +++ b/.github/workflows/test_python.yml @@ -44,7 +44,9 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies working-directory: ./Python - run: pip install -r requirements.txt + run: | + pip install uv + uv pip install --system -r requirements.txt - name: Lint working-directory: ./Python run: python pyfmt.py --check_only --exclude "**/venv/**/*.py" **/*.py diff --git a/Python/alerts-to-discord/functions/main.py b/Python/alerts-to-discord/functions/main.py index d8eba4213..ab2592a53 100644 --- a/Python/alerts-to-discord/functions/main.py +++ b/Python/alerts-to-discord/functions/main.py @@ -24,8 +24,9 @@ DISCORD_WEBHOOK_URL = params.SecretParam("DISCORD_WEBHOOK_URL") -def post_message_to_discord(bot_name: str, message_body: str, - webhook_url: str) -> requests.Response: +def post_message_to_discord( + bot_name: str, message_body: str, webhook_url: str +) -> requests.Response: """Posts a message to Discord with Discord's Webhook API. Params: @@ -36,7 +37,8 @@ def post_message_to_discord(bot_name: str, message_body: str, raise EnvironmentError( "No webhook URL found. Set the Discord Webhook URL before deploying. " "Learn more about Discord webhooks here: " - "https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks") + "https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks" + ) return requests.post( url=webhook_url, @@ -44,8 +46,9 @@ def post_message_to_discord(bot_name: str, message_body: str, # Here's what the Discord API supports in the payload: # https://discord.com/developers/docs/resources/webhook#execute-webhook-jsonform-params "username": bot_name, - "content": message_body - }) + "content": message_body, + }, + ) # [START v2Alerts] @@ -110,7 +113,8 @@ def post_new_udid_to_discord(event: app_distribution_fn.NewTesterDeviceEvent) -> except (EnvironmentError, requests.HTTPError) as error: print( f"Unable to post iOS device registration alert for {app_dist.tester_email} to Discord.", - error) + error, + ) # [START v2PerformanceAlertTrigger] @@ -139,8 +143,9 @@ def post_performance_alert_to_discord(event: performance_fn.PerformanceThreshold try: # [START v2SendPerformanceAlertToDiscord] - response = post_message_to_discord("App Performance Bot", message, - DISCORD_WEBHOOK_URL.value) + response = post_message_to_discord( + "App Performance Bot", message, DISCORD_WEBHOOK_URL.value + ) if response.ok: print(f"Posted Firebase Performance alert {perf.event_name} to Discord.") pprint.pp(event.data.payload) diff --git a/Python/fcm-notifications/functions/main.py b/Python/fcm-notifications/functions/main.py index 362ad4791..0b7d41503 100644 --- a/Python/fcm-notifications/functions/main.py +++ b/Python/fcm-notifications/functions/main.py @@ -25,7 +25,7 @@ def send_follower_notification(event: db_fn.Event[db_fn.Change]) -> None: print(f"User {follower_uid} is now following user {followed_uid}") tokens_ref = db.reference(f"users/{followed_uid}/notificationTokens") notification_tokens = tokens_ref.get() - if (not isinstance(notification_tokens, dict) or len(notification_tokens) < 1): + if not isinstance(notification_tokens, dict) or len(notification_tokens) < 1: print("There are no tokens to send notifications to.") return print(f"There are {len(notification_tokens)} tokens to send notifications to.") @@ -52,6 +52,8 @@ def send_follower_notification(event: db_fn.Event[db_fn.Change]) -> None: if not isinstance(exception, exceptions.FirebaseError): continue message = exception.http_response.json()["error"]["message"] - if (isinstance(exception, messaging.UnregisteredError) or - message == "The registration token is not a valid FCM registration token"): + if ( + isinstance(exception, messaging.UnregisteredError) + or message == "The registration token is not a valid FCM registration token" + ): tokens_ref.child(msgs[i].token).delete() diff --git a/Python/post-signup-event/functions/main.py b/Python/post-signup-event/functions/main.py index 02f4e8cd5..d100ee1a6 100644 --- a/Python/post-signup-event/functions/main.py +++ b/Python/post-signup-event/functions/main.py @@ -31,7 +31,8 @@ # [START savegoogletoken] @identity_fn.before_user_created() def savegoogletoken( - event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeCreateResponse | None: + event: identity_fn.AuthBlockingEvent, +) -> identity_fn.BeforeCreateResponse | None: """During sign-up, save the Google OAuth2 access token and queue up a task to schedule an onboarding session on the user's Google Calendar. @@ -48,24 +49,19 @@ def savegoogletoken( doc_ref.set({"calendar_access_token": event.credential.access_token}, merge=True) tasks_client = google.cloud.tasks_v2.CloudTasksClient() - task_queue = tasks_client.queue_path(params.PROJECT_ID.value, - options.SupportedRegion.US_CENTRAL1, - "scheduleonboarding") + task_queue = tasks_client.queue_path( + params.PROJECT_ID.value, options.SupportedRegion.US_CENTRAL1, "scheduleonboarding" + ) target_uri = get_function_url("scheduleonboarding") - calendar_task = google.cloud.tasks_v2.Task(http_request={ - "http_method": google.cloud.tasks_v2.HttpMethod.POST, - "url": target_uri, - "headers": { - "Content-type": "application/json" + calendar_task = google.cloud.tasks_v2.Task( + http_request={ + "http_method": google.cloud.tasks_v2.HttpMethod.POST, + "url": target_uri, + "headers": {"Content-type": "application/json"}, + "body": json.dumps({"data": {"uid": event.data.uid}}).encode(), }, - "body": json.dumps({ - "data": { - "uid": event.data.uid - } - }).encode() - }, - schedule_time=datetime.now() + - timedelta(minutes=1)) + schedule_time=datetime.now() + timedelta(minutes=1), + ) tasks_client.create_task(parent=task_queue, task=calendar_task) # [END savegoogletoken] @@ -79,46 +75,48 @@ def scheduleonboarding(request: tasks_fn.CallableRequest) -> https_fn.Response: """ if "uid" not in request.data: - return https_fn.Response(status=https_fn.FunctionsErrorCode.INVALID_ARGUMENT, - response="No user specified.") + return https_fn.Response( + status=https_fn.FunctionsErrorCode.INVALID_ARGUMENT, response="No user specified." + ) uid = request.data["uid"] user_record: auth.UserRecord = auth.get_user(uid) if user_record.email is None: - return https_fn.Response(status=https_fn.FunctionsErrorCode.INVALID_ARGUMENT, - response="No email address on record.") + return https_fn.Response( + status=https_fn.FunctionsErrorCode.INVALID_ARGUMENT, + response="No email address on record.", + ) firestore_client: google.cloud.firestore.Client = firestore.client() user_info = firestore_client.collection("user_info").document(uid).get().to_dict() if not isinstance(user_info, dict) or "calendar_access_token" not in user_info: - return https_fn.Response(status=https_fn.FunctionsErrorCode.PERMISSION_DENIED, - response="No Google OAuth token found.") + return https_fn.Response( + status=https_fn.FunctionsErrorCode.PERMISSION_DENIED, + response="No Google OAuth token found.", + ) calendar_access_token = user_info["calendar_access_token"] firestore_client.collection("user_info").document(uid).update( - {"calendar_access_token": google.cloud.firestore.DELETE_FIELD}) + {"calendar_access_token": google.cloud.firestore.DELETE_FIELD} + ) google_credentials = google.oauth2.credentials.Credentials(token=calendar_access_token) - calendar_client = googleapiclient.discovery.build("calendar", - "v3", - credentials=google_credentials) + calendar_client = googleapiclient.discovery.build( + "calendar", "v3", credentials=google_credentials + ) calendar_event = { "summary": "Onboarding with ExampleCo", "location": "Video call", "description": "Walk through onboarding tasks with an ExampleCo engineer.", "start": { "dateTime": (datetime.now() + timedelta(days=3)).isoformat(), - "timeZone": "America/Los_Angeles" + "timeZone": "America/Los_Angeles", }, "end": { "dateTime": (datetime.now() + timedelta(days=3, hours=1)).isoformat(), - "timeZone": "America/Los_Angeles" + "timeZone": "America/Los_Angeles", }, - "attendees": [{ - "email": user_record.email - }, { - "email": "onboarding@example.com" - }] + "attendees": [{"email": user_record.email}, {"email": "onboarding@example.com"}], } calendar_client.events().insert(calendarId="primary", body=calendar_event).execute() @@ -137,10 +135,13 @@ def get_function_url(name: str, location: str = options.SupportedRegion.US_CENTR The URL of the function """ credentials, project_id = google.auth.default( - scopes=["https://www.googleapis.com/auth/cloud-platform"]) + scopes=["https://www.googleapis.com/auth/cloud-platform"] + ) authed_session = google.auth.transport.requests.AuthorizedSession(credentials) - url = ("https://cloudfunctions.googleapis.com/v2beta/" + - f"projects/{project_id}/locations/{location}/functions/{name}") + url = ( + "https://cloudfunctions.googleapis.com/v2beta/" + + f"projects/{project_id}/locations/{location}/functions/{name}" + ) response = authed_session.get(url) data = response.json() function_url = data["serviceConfig"]["uri"] diff --git a/Python/pyfmt.py b/Python/pyfmt.py index b9b5f414f..d1b420e60 100644 --- a/Python/pyfmt.py +++ b/Python/pyfmt.py @@ -17,8 +17,7 @@ import difflib import pathlib import re - -from yapf.yapflib import yapf_api +import subprocess start_tag_re = re.compile(r"^([ \t]*)#\s*\[START\s+(\w+).*\]\s*\n", flags=re.MULTILINE) end_tag_re = re.compile(r"^\s*#\s*\[END\s+(\w+).*\][ \t]*$", flags=re.MULTILINE) @@ -41,11 +40,14 @@ def check_and_diff(files: list[str]) -> int: orig = f.read() fmt = format(orig) diff = list( - difflib.unified_diff(orig.splitlines(), - fmt.splitlines(), - fromfile=file, - tofile=f"{file} (reformatted)", - lineterm="")) + difflib.unified_diff( + orig.splitlines(), + fmt.splitlines(), + fromfile=file, + tofile=f"{file} (reformatted)", + lineterm="", + ) + ) if len(diff) > 0: diff_count += 1 print("\n".join(diff), end="\n\n") @@ -53,7 +55,10 @@ def check_and_diff(files: list[str]) -> int: def format(src: str) -> str: - out, _ = yapf_api.FormatCode(src, style_config=pyproject_toml) + result = subprocess.run( + ["ruff", "format", "-"], input=src.encode("utf-8"), capture_output=True, check=True + ) + out = result.stdout.decode("utf-8") out = fix_region_tags(out) return out @@ -83,15 +88,19 @@ def fix_end_tag(m: re.Match) -> str: import argparse argparser = argparse.ArgumentParser() - argparser.add_argument("--check_only", - "-c", - action="store_true", - help="check files and print diffs, but don't modify files") - argparser.add_argument("--exclude", - "-e", - action="append", - default=[], - help="exclude file or glob (can specify multiple times)") + argparser.add_argument( + "--check_only", + "-c", + action="store_true", + help="check files and print diffs, but don't modify files", + ) + argparser.add_argument( + "--exclude", + "-e", + action="append", + default=[], + help="exclude file or glob (can specify multiple times)", + ) argparser.add_argument("file_or_glob", nargs="+") args = argparser.parse_args() diff --git a/Python/pyproject.toml b/Python/pyproject.toml index b2db2e343..dd09334c8 100644 --- a/Python/pyproject.toml +++ b/Python/pyproject.toml @@ -15,8 +15,7 @@ [project] name = "functions_samples" version = "1.0.0" -[tool.yapf] -based_on_style = "google" -column_limit = 100 +[tool.ruff] +line-length = 100 [tool.mypy] python_version = "3.10" \ No newline at end of file diff --git a/Python/quickstarts/auth-blocking-functions/functions/main.py b/Python/quickstarts/auth-blocking-functions/functions/main.py index 17abd3e25..3567d328d 100644 --- a/Python/quickstarts/auth-blocking-functions/functions/main.py +++ b/Python/quickstarts/auth-blocking-functions/functions/main.py @@ -22,14 +22,18 @@ # [START created_noop] @identity_fn.before_user_created() -def created_noop(event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeCreateResponse | None: +def created_noop( + event: identity_fn.AuthBlockingEvent, +) -> identity_fn.BeforeCreateResponse | None: return # [END created_noop] # [START signedin_noop] @identity_fn.before_user_signed_in() -def signedin_noop(event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeSignInResponse | None: +def signedin_noop( + event: identity_fn.AuthBlockingEvent, +) -> identity_fn.BeforeSignInResponse | None: return # [END signedin_noop] @@ -39,7 +43,8 @@ def signedin_noop(event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeSig # Block account creation with any non-acme email address. @identity_fn.before_user_created() def validatenewuser( - event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeCreateResponse | None: + event: identity_fn.AuthBlockingEvent, +) -> identity_fn.BeforeCreateResponse | None: # [END v2beforeCreateFunctionTrigger] # [START v2readUserData] # User data passed in from the CloudEvent. @@ -50,28 +55,36 @@ def validatenewuser( # Only users of a specific domain can sign up. if user.email is None or "@acme.com" not in user.email: # Return None so that Firebase Auth rejects the account creation. - raise https_fn.HttpsError(code=https_fn.FunctionsErrorCode.INVALID_ARGUMENT, - message="Unauthorized email") + raise https_fn.HttpsError( + code=https_fn.FunctionsErrorCode.INVALID_ARGUMENT, + message="Unauthorized email", + ) # [END v2domainHttpsError] # [END v2ValidateNewUser] # [START setdefaultname] @identity_fn.before_user_created() -def setdefaultname(event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeCreateResponse | None: +def setdefaultname( + event: identity_fn.AuthBlockingEvent, +) -> identity_fn.BeforeCreateResponse | None: return identity_fn.BeforeCreateResponse( # If no display name is provided, set it to "Guest". - display_name=event.data.display_name if event.data.display_name is not None else "Guest") + display_name=event.data.display_name if event.data.display_name is not None else "Guest" + ) # [END setdefaultname] # [START requireverified] @identity_fn.before_user_created() def requireverified( - event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeCreateResponse | None: + event: identity_fn.AuthBlockingEvent, +) -> identity_fn.BeforeCreateResponse | None: if event.data.email is not None and not event.data.email_verified: - raise https_fn.HttpsError(code=https_fn.FunctionsErrorCode.INVALID_ARGUMENT, - message="You must register using a trusted provider.") + raise https_fn.HttpsError( + code=https_fn.FunctionsErrorCode.INVALID_ARGUMENT, + message="You must register using a trusted provider.", + ) # [END requireverified] @@ -83,7 +96,8 @@ def send_verification_email_using_your_smtp_server(email, link): # [START sendverification] @identity_fn.before_user_created() def sendverification( - event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeCreateResponse | None: + event: identity_fn.AuthBlockingEvent, +) -> identity_fn.BeforeCreateResponse | None: if event.data.email is not None and not event.data.email_verified: link = auth.generate_email_verification_link(event.data.email) send_verification_email_using_your_smtp_server(event.data.email, link) @@ -93,16 +107,21 @@ def sendverification( # [START requireverifiedsignin] @identity_fn.before_user_signed_in() def requireverifiedsignin( - event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeSignInResponse | None: + event: identity_fn.AuthBlockingEvent, +) -> identity_fn.BeforeSignInResponse | None: if event.data.email is not None and not event.data.email_verified: - raise https_fn.HttpsError(code=https_fn.FunctionsErrorCode.INVALID_ARGUMENT, - message="You must verify your email address before signing in.") + raise https_fn.HttpsError( + code=https_fn.FunctionsErrorCode.INVALID_ARGUMENT, + message="You must verify your email address before signing in.", + ) # [END requireverifiedsignin] # [START trustfacebook] @identity_fn.before_user_created() -def markverified(event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeCreateResponse | None: +def markverified( + event: identity_fn.AuthBlockingEvent, +) -> identity_fn.BeforeCreateResponse | None: if event.data.email is not None and "@facebook.com" in event.data.email: return identity_fn.BeforeSignInResponse(email_verified=True) # [END trustfacebook] @@ -114,37 +133,54 @@ def is_suspicious(ip_address): # [START ipban] @identity_fn.before_user_signed_in() -def ipban(event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeSignInResponse | None: +def ipban( + event: identity_fn.AuthBlockingEvent, +) -> identity_fn.BeforeSignInResponse | None: if is_suspicious(event.ip_address): - raise https_fn.HttpsError(code=https_fn.FunctionsErrorCode.PERMISSION_DENIED, - message="IP banned.") + raise https_fn.HttpsError( + code=https_fn.FunctionsErrorCode.PERMISSION_DENIED, message="IP banned." + ) # [END ipban] # [START customclaims] @identity_fn.before_user_created() -def setemployeeid(event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeCreateResponse | None: - if (event.credential is not None and event.credential.claims is not None and - event.credential.provider_id == "saml.my-provider-id"): +def setemployeeid( + event: identity_fn.AuthBlockingEvent, +) -> identity_fn.BeforeCreateResponse | None: + if ( + event.credential is not None + and event.credential.claims is not None + and event.credential.provider_id == "saml.my-provider-id" + ): return identity_fn.BeforeCreateResponse( - custom_claims={"eid": event.credential.claims["employeeid"]}) + custom_claims={"eid": event.credential.claims["employeeid"]} + ) @identity_fn.before_user_signed_in() def copyclaimstosession( - event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeSignInResponse | None: - if (event.credential is not None and event.credential.claims is not None and - event.credential.provider_id == "saml.my-provider-id"): - return identity_fn.BeforeSignInResponse(session_claims={ - "role": event.credential.claims["role"], - "groups": event.credential.claims["groups"] - }) + event: identity_fn.AuthBlockingEvent, +) -> identity_fn.BeforeSignInResponse | None: + if ( + event.credential is not None + and event.credential.claims is not None + and event.credential.provider_id == "saml.my-provider-id" + ): + return identity_fn.BeforeSignInResponse( + session_claims={ + "role": event.credential.claims["role"], + "groups": event.credential.claims["groups"], + } + ) # [END customclaims] # [START logip] @identity_fn.before_user_signed_in() -def logip(event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeSignInResponse | None: +def logip( + event: identity_fn.AuthBlockingEvent, +) -> identity_fn.BeforeSignInResponse | None: return identity_fn.BeforeSignInResponse(session_claims={"signInIpAddress": event.ip_address}) # [END logip] @@ -160,7 +196,8 @@ def analyze_photo_with_ml(url): # [START sanitizeprofilephoto] @identity_fn.before_user_created() def sanitizeprofilephoto( - event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeCreateResponse | None: + event: identity_fn.AuthBlockingEvent, +) -> identity_fn.BeforeCreateResponse | None: if event.data.photo_url is not None: score = analyze_photo_with_ml(event.data.photo_url) if score > THRESHOLD: @@ -172,7 +209,9 @@ def sanitizeprofilephoto( # [START v2beforeSignInFunctionTrigger] # Block account sign in with any banned account. @identity_fn.before_user_signed_in() -def checkforban(event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeSignInResponse | None: +def checkforban( + event: identity_fn.AuthBlockingEvent, +) -> identity_fn.BeforeSignInResponse | None: # [END v2beforeSignInFunctionTrigger] # [START v2readEmailData] # Email passed from the CloudEvent. @@ -189,7 +228,9 @@ def checkforban(event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeSignI # Checking that the document exists for the email address. if doc.exists: # Throw an HttpsError so that Firebase Auth rejects the account sign in. - raise https_fn.HttpsError(code=https_fn.FunctionsErrorCode.INVALID_ARGUMENT, - message="Unauthorized email") + raise https_fn.HttpsError( + code=https_fn.FunctionsErrorCode.INVALID_ARGUMENT, + message="Unauthorized email", + ) # [END v2bannedHttpsError] # [END v2CheckForBan] diff --git a/Python/quickstarts/callable-functions/functions/main.py b/Python/quickstarts/callable-functions/functions/main.py index 5e2e1e43d..adcc7e4a2 100644 --- a/Python/quickstarts/callable-functions/functions/main.py +++ b/Python/quickstarts/callable-functions/functions/main.py @@ -46,8 +46,11 @@ def addnumbers(req: https_fn.CallableRequest) -> Any: # Throwing an HttpsError so that the client gets the error details. raise https_fn.HttpsError( code=https_fn.FunctionsErrorCode.INVALID_ARGUMENT, - message=('The function must be called with two arguments, "firstNumber"' - ' and "secondNumber", which must both be numbers.')) + message=( + 'The function must be called with two arguments, "firstNumber"' + ' and "secondNumber", which must both be numbers.' + ), + ) # [END v2addHttpsError] # [START v2returnAddData] @@ -55,7 +58,7 @@ def addnumbers(req: https_fn.CallableRequest) -> Any: "firstNumber": first_number, "secondNumber": second_number, "operator": "+", - "operationResult": first_number + second_number + "operationResult": first_number + second_number, } # [END v2returnAddData] # [END v2allAdd] @@ -74,23 +77,33 @@ def addmessage(req: https_fn.CallableRequest) -> Any: # [END v2readMessageData] except KeyError: # Throwing an HttpsError so that the client gets the error details. - raise https_fn.HttpsError(code=https_fn.FunctionsErrorCode.INVALID_ARGUMENT, - message=('The function must be called with one argument, "text",' - " containing the message text to add.")) + raise https_fn.HttpsError( + code=https_fn.FunctionsErrorCode.INVALID_ARGUMENT, + message=( + 'The function must be called with one argument, "text",' + " containing the message text to add." + ), + ) # [START v2messageHttpsErrors] # Checking attribute. if not isinstance(text, str) or len(text) < 1: # Throwing an HttpsError so that the client gets the error details. - raise https_fn.HttpsError(code=https_fn.FunctionsErrorCode.INVALID_ARGUMENT, - message=('The function must be called with one argument, "text",' - " containing the message text to add.")) + raise https_fn.HttpsError( + code=https_fn.FunctionsErrorCode.INVALID_ARGUMENT, + message=( + 'The function must be called with one argument, "text",' + " containing the message text to add." + ), + ) # Checking that the user is authenticated. if req.auth is None: # Throwing an HttpsError so that the client gets the error details. - raise https_fn.HttpsError(code=https_fn.FunctionsErrorCode.FAILED_PRECONDITION, - message="The function must be called while authenticated.") + raise https_fn.HttpsError( + code=https_fn.FunctionsErrorCode.FAILED_PRECONDITION, + message="The function must be called while authenticated.", + ) # [END v2messageHttpsErrors] # [START v2authIntegration] @@ -105,15 +118,12 @@ def addmessage(req: https_fn.CallableRequest) -> Any: # [START v2returnMessage] # Saving the new message to the Realtime Database. sanitized_message = sanitize_text(text) # Sanitize message. - db.reference("/messages").push({ # type: ignore - "text": sanitized_message, - "author": { - "uid": uid, - "name": name, - "picture": picture, - "email": email + db.reference("/messages").push( + { # type: ignore + "text": sanitized_message, + "author": {"uid": uid, "name": name, "picture": picture, "email": email}, } - }) + ) print("New message written") # Returning the sanitized message to the client. @@ -122,9 +132,9 @@ def addmessage(req: https_fn.CallableRequest) -> Any: except Exception as e: # Re-throwing the error as an HttpsError so that the client gets # the error details. - raise https_fn.HttpsError(code=https_fn.FunctionsErrorCode.UNKNOWN, - message="Error", - details=e) + raise https_fn.HttpsError( + code=https_fn.FunctionsErrorCode.UNKNOWN, message="Error", details=e + ) def sanitize_text(text: str) -> str: diff --git a/Python/quickstarts/custom-events/functions/main.py b/Python/quickstarts/custom-events/functions/main.py index 39490cb12..961ec698c 100644 --- a/Python/quickstarts/custom-events/functions/main.py +++ b/Python/quickstarts/custom-events/functions/main.py @@ -23,7 +23,8 @@ # [START imageresizedEvent] @eventarc_fn.on_custom_event_published( - event_type="firebase.extensions.storage-resize-images.v1.complete") + event_type="firebase.extensions.storage-resize-images.v1.complete" +) def onimageresized(event: eventarc_fn.CloudEvent) -> None: print("Received image resize completed event: ", event.type) @@ -43,7 +44,8 @@ def onimageresized(event: eventarc_fn.CloudEvent) -> None: @eventarc_fn.on_custom_event_published( event_type="firebase.extensions.storage-resize-images.v1.complete", channel="locations/us-west1/channels/firebase", - region="us-west1") + region="us-west1", +) def onimageresizedwest(event: eventarc_fn.CloudEvent) -> None: print("Received image resize completed event: ", event.type) # [START_EXCLUDE] diff --git a/Python/quickstarts/firestore-sync-auth/functions/main.py b/Python/quickstarts/firestore-sync-auth/functions/main.py index c897f63a3..61c47c924 100644 --- a/Python/quickstarts/firestore-sync-auth/functions/main.py +++ b/Python/quickstarts/firestore-sync-auth/functions/main.py @@ -19,7 +19,6 @@ # [START verifyComment] @firestore_fn.on_document_updated_with_auth_context(document="comments/{comment_id}") def verify_comment(event: Event[Change[DocumentSnapshot]]) -> None: - # Get the current and previous document values. new_value = event.data.after prev_value = event.data.before diff --git a/Python/quickstarts/monitor-cloud-logging/functions/main.py b/Python/quickstarts/monitor-cloud-logging/functions/main.py index dff4f87d3..88f760dbb 100644 --- a/Python/quickstarts/monitor-cloud-logging/functions/main.py +++ b/Python/quickstarts/monitor-cloud-logging/functions/main.py @@ -26,8 +26,12 @@ def hello_world(req: https_fn.Request) -> https_fn.Response: def get_inspirational_quote(req: https_fn.Request) -> https_fn.Response: firestore_client = firestore.client() today = datetime.date.today() - quote_of_the_month_ref = (firestore_client.collection("quotes").doc(str( - today.year)).collection("months").doc(str(today.month))) + quote_of_the_month_ref = ( + firestore_client.collection("quotes") + .doc(str(today.year)) + .collection("months") + .doc(str(today.month)) + ) default_quote = "Python has been an important part of Google since the beginning, and remains so as the system grows and evolves." diff --git a/Python/quickstarts/uppercase-firestore/functions/main.py b/Python/quickstarts/uppercase-firestore/functions/main.py index 765c8363c..24ad9902c 100644 --- a/Python/quickstarts/uppercase-firestore/functions/main.py +++ b/Python/quickstarts/uppercase-firestore/functions/main.py @@ -76,7 +76,7 @@ def makeuppercase(event: firestore_fn.Event[firestore_fn.DocumentSnapshot | None # [START makeUppercase2] @firestore_fn.on_document_written(document="messages/{pushId}") def makeuppercase2( - event: firestore_fn.Event[firestore_fn.Change[firestore_fn.DocumentSnapshot | None]] + event: firestore_fn.Event[firestore_fn.Change[firestore_fn.DocumentSnapshot | None]], ) -> None: """Listens for new documents to be added to /messages. If the document has an "original" field, creates an "uppercase" field containg the contents of diff --git a/Python/quickstarts/uppercase-rtdb/functions/main.py b/Python/quickstarts/uppercase-rtdb/functions/main.py index 538a5f4f9..052e71925 100644 --- a/Python/quickstarts/uppercase-rtdb/functions/main.py +++ b/Python/quickstarts/uppercase-rtdb/functions/main.py @@ -44,11 +44,13 @@ def addmessage(req: https_fn.Request) -> https_fn.Response: # Redirect with 303 SEE OTHER to the URL of the pushed object. scheme, location, path, query, fragment = ( - b.decode() for b in urllib_parse.urlsplit(app.options.get("databaseURL"))) + b.decode() for b in urllib_parse.urlsplit(app.options.get("databaseURL")) + ) path = f"{ref.path}.json" return https_fn.Response( status=303, - headers={"Location": urllib_parse.urlunsplit((scheme, location, path, query, fragment))}) + headers={"Location": urllib_parse.urlunsplit((scheme, location, path, query, fragment))}, + ) # [END adminSdkPush] # [END addMessage] diff --git a/Python/remote-config-diff/functions/main.py b/Python/remote-config-diff/functions/main.py index 907dc805b..067f9fc42 100644 --- a/Python/remote-config-diff/functions/main.py +++ b/Python/remote-config-diff/functions/main.py @@ -39,14 +39,19 @@ def showconfigdiff(event: remote_config_fn.CloudEvent[remote_config_fn.ConfigUpd current_version = int(event.data.version_number) # Figure out the differences between templates - remote_config_api = ("https://firebaseremoteconfig.googleapis.com/v1/" - f"projects/{app.project_id}/remoteConfig") - current_template = requests.get(remote_config_api, - params={"versionNumber": current_version}, - headers={"Authorization": f"Bearer {access_token}"}) - previous_template = requests.get(remote_config_api, - params={"versionNumber": current_version - 1}, - headers={"Authorization": f"Bearer {access_token}"}) + remote_config_api = ( + "https://firebaseremoteconfig.googleapis.com/v1/" f"projects/{app.project_id}/remoteConfig" + ) + current_template = requests.get( + remote_config_api, + params={"versionNumber": current_version}, + headers={"Authorization": f"Bearer {access_token}"}, + ) + previous_template = requests.get( + remote_config_api, + params={"versionNumber": current_version - 1}, + headers={"Authorization": f"Bearer {access_token}"}, + ) diff = deepdiff.DeepDiff(previous_template, current_template) # Log the difference diff --git a/Python/requirements.txt b/Python/requirements.txt index a26557a30..7549df46b 100644 --- a/Python/requirements.txt +++ b/Python/requirements.txt @@ -1 +1 @@ -yapf~=0.40.2 +ruff~=0.1.13 diff --git a/Python/taskqueues-backup-images/functions/main.py b/Python/taskqueues-backup-images/functions/main.py index dfd06b270..ea0bfb13c 100644 --- a/Python/taskqueues-backup-images/functions/main.py +++ b/Python/taskqueues-backup-images/functions/main.py @@ -35,28 +35,31 @@ BACKUP_COUNT = params.IntParam("BACKUP_COUNT", default=100).value HOURLY_BATCH_SIZE = params.IntParam("HOURLY_BATCH_SIZE", default=600).value BACKUP_BUCKET = params.StringParam( - "BACKUP_BUCKET", input=params.ResourceInput(type=params.ResourceType.STORAGE_BUCKET)).value + "BACKUP_BUCKET", input=params.ResourceInput(type=params.ResourceType.STORAGE_BUCKET) +).value NASA_API_KEY = params.StringParam("NASA_API_KEY").value # [START v2TaskFunctionSetup] -@tasks_fn.on_task_dispatched(retry_config=RetryConfig(max_attempts=5, min_backoff_seconds=60), - rate_limits=RateLimits(max_concurrent_dispatches=10)) +@tasks_fn.on_task_dispatched( + retry_config=RetryConfig(max_attempts=5, min_backoff_seconds=60), + rate_limits=RateLimits(max_concurrent_dispatches=10), +) def backupapod(req: tasks_fn.CallableRequest) -> str: """Grabs Astronomy Photo of the Day (APOD) using NASA's API.""" # [END v2TaskFunctionSetup] try: date = req.data["date"] except KeyError: - raise https_fn.HttpsError(code=https_fn.FunctionsErrorCode.INVALID_ARGUMENT, - message="Invalid payload. Must include date.") + raise https_fn.HttpsError( + code=https_fn.FunctionsErrorCode.INVALID_ARGUMENT, + message="Invalid payload. Must include date.", + ) print(f"Requesting data from APOD API for date {date}") - api_resp = requests.get(url="https://api.nasa.gov/planetary/apod", - params={ - "date": date, - "api_key": NASA_API_KEY - }) + api_resp = requests.get( + url="https://api.nasa.gov/planetary/apod", params={"date": date, "api_key": NASA_API_KEY} + ) if not api_resp.ok: print(f"Request to NASA APOD API failed with reponse {api_resp.status_code}") match api_resp.status_code: @@ -64,11 +67,14 @@ def backupapod(req: tasks_fn.CallableRequest) -> str: print("No APOD today.") return "No APOD today." case 500: - raise https_fn.HttpsError(code=https_fn.FunctionsErrorCode.UNAVAILABLE, - message="APOD API temporarily not available.") + raise https_fn.HttpsError( + code=https_fn.FunctionsErrorCode.UNAVAILABLE, + message="APOD API temporarily not available.", + ) case _: - raise https_fn.HttpsError(code=https_fn.FunctionsErrorCode.INTERNAL, - message="Uh-oh. Something broke.") + raise https_fn.HttpsError( + code=https_fn.FunctionsErrorCode.INTERNAL, message="Uh-oh. Something broke." + ) apod = api_resp.json() pic_url = apod["hdurl"] @@ -85,8 +91,9 @@ def backupapod(req: tasks_fn.CallableRequest) -> str: try: pic_blob.upload_from_string(pic_resp.content, content_type=pic_type) except: - raise https_fn.HttpsError(code=https_fn.FunctionsErrorCode.INTERNAL, - message="Uh-oh. Something broke.") + raise https_fn.HttpsError( + code=https_fn.FunctionsErrorCode.INTERNAL, message="Uh-oh. Something broke." + ) print(f"Saved {pic_url}") return f"Saved {pic_url}" @@ -110,9 +117,11 @@ def enqueuebackuptasks(_: https_fn.Request) -> https_fn.Response: backup_date = BACKUP_START_DATE + timedelta(days=i) body = {"data": {"date": backup_date.isoformat()[:10]}} - task_options = functions.TaskOptions(schedule_time=schedule_time, - dispatch_deadline_seconds=dispatch_deadline_seconds, - uri=target_uri) + task_options = functions.TaskOptions( + schedule_time=schedule_time, + dispatch_deadline_seconds=dispatch_deadline_seconds, + uri=target_uri, + ) task_queue.enqueue(body, task_options) return https_fn.Response(status=200, response=f"Enqueued {BACKUP_COUNT} tasks") # [END v2EnqueueTasks] @@ -129,10 +138,13 @@ def get_function_url(name: str, location: str = SupportedRegion.US_CENTRAL1) -> Returns: The URL of the function """ credentials, project_id = google.auth.default( - scopes=["https://www.googleapis.com/auth/cloud-platform"]) + scopes=["https://www.googleapis.com/auth/cloud-platform"] + ) authed_session = AuthorizedSession(credentials) - url = ("https://cloudfunctions.googleapis.com/v2beta/" + - f"projects/{project_id}/locations/{location}/functions/{name}") + url = ( + "https://cloudfunctions.googleapis.com/v2beta/" + + f"projects/{project_id}/locations/{location}/functions/{name}" + ) response = authed_session.get(url) data = response.json() function_url = data["serviceConfig"]["uri"] diff --git a/Python/testlab-to-slack/functions/main.py b/Python/testlab-to-slack/functions/main.py index 42b935d23..6938aa2b7 100644 --- a/Python/testlab-to-slack/functions/main.py +++ b/Python/testlab-to-slack/functions/main.py @@ -27,24 +27,16 @@ def post_to_slack(title: str, details: str) -> requests.Response: """Posts a message to Slack via a Webhook.""" - return requests.post(SLACK_WEBHOOK_URL.value, - json={ - "blocks": [{ - "type": "section", - "text": { - "type": "mrkdwn", - "text": title - } - }, { - "type": "divider" - }, { - "type": "section", - "text": { - "type": "mrkdwn", - "text": details - } - }] - }) + return requests.post( + SLACK_WEBHOOK_URL.value, + json={ + "blocks": [ + {"type": "section", "text": {"type": "mrkdwn", "text": title}}, + {"type": "divider"}, + {"type": "section", "text": {"type": "mrkdwn", "text": details}}, + ] + }, + ) # [END postToSlack] @@ -60,7 +52,7 @@ def slackmoji(status: test_lab_fn.TestState | test_lab_fn.OutcomeSummary) -> str test_lab_fn.TestState.PENDING: ":soon:", test_lab_fn.TestState.FINISHED: ":white_check_mark:", test_lab_fn.TestState.ERROR: ":red_circle:", - test_lab_fn.TestState.INVALID: ":large_orange_diamond:" + test_lab_fn.TestState.INVALID: ":large_orange_diamond:", } return status_slackmoji[status] if status in status_slackmoji else "" # [END getSlackmoji] @@ -69,7 +61,8 @@ def slackmoji(status: test_lab_fn.TestState | test_lab_fn.OutcomeSummary) -> str # [START posttestresultstoslack] @test_lab_fn.on_test_matrix_completed(secrets=["SLACK_WEBHOOK_URL"]) def posttestresultstoslack( - event: test_lab_fn.CloudEvent[test_lab_fn.TestMatrixCompletedData]) -> None: + event: test_lab_fn.CloudEvent[test_lab_fn.TestMatrixCompletedData], +) -> None: """Posts a test matrix result to Slack.""" # Obtain Test Matrix properties from the CloudEvent @@ -81,8 +74,10 @@ def posttestresultstoslack( title = f"{slackmoji(state)} {slackmoji(outcome_summary)} {test_matrix_id}" # Create the details of the message - details = (f"Status: *{state}* {slackmoji(state)}\n" - f"Outcome: *{outcome_summary}* {slackmoji(outcome_summary)}") + details = ( + f"Status: *{state}* {slackmoji(state)}\n" + f"Outcome: *{outcome_summary}* {slackmoji(outcome_summary)}" + ) # Post the message to Slack response = post_to_slack(title, details)