From eff0040dee2bab9c4684f8425aa4d8ef28a9a244 Mon Sep 17 00:00:00 2001 From: Katie Nguyen <21978337+katiemn@users.noreply.github.com> Date: Thu, 20 Nov 2025 07:53:28 -0800 Subject: [PATCH 01/67] feat: nano banana pro model (#13641) --- .../imggen_mmflash_edit_img_with_txt_img.py | 9 ++------- .../imggen_mmflash_txt_and_img_with_txt.py | 6 ++---- genai/image_generation/imggen_mmflash_with_txt.py | 14 ++------------ 3 files changed, 6 insertions(+), 23 deletions(-) diff --git a/genai/image_generation/imggen_mmflash_edit_img_with_txt_img.py b/genai/image_generation/imggen_mmflash_edit_img_with_txt_img.py index b0a7bb2a94c..e2d9888a027 100644 --- a/genai/image_generation/imggen_mmflash_edit_img_with_txt_img.py +++ b/genai/image_generation/imggen_mmflash_edit_img_with_txt_img.py @@ -26,7 +26,7 @@ def generate_content() -> str: image = Image.open("test_resources/example-image-eiffel-tower.png") response = client.models.generate_content( - model="gemini-2.5-flash-image", + model="gemini-3-pro-image-preview", contents=[image, "Edit this image to make it look like a cartoon."], config=GenerateContentConfig(response_modalities=[Modality.TEXT, Modality.IMAGE]), ) @@ -36,12 +36,7 @@ def generate_content() -> str: elif part.inline_data: image = Image.open(BytesIO((part.inline_data.data))) image.save("output_folder/bw-example-image.png") - # Example response: - # Here's the cartoon-style edit of the image: - # Cartoon-style edit: - # - Simplified the Eiffel Tower with bolder lines and slightly exaggerated proportions. - # - Brightened and saturated the colors of the sky, fireworks, and foliage for a more vibrant, cartoonish look. - # .... + # [END googlegenaisdk_imggen_mmflash_edit_img_with_txt_img] return "output_folder/bw-example-image.png" diff --git a/genai/image_generation/imggen_mmflash_txt_and_img_with_txt.py b/genai/image_generation/imggen_mmflash_txt_and_img_with_txt.py index 9e54d7b895e..7a9d11103a7 100644 --- a/genai/image_generation/imggen_mmflash_txt_and_img_with_txt.py +++ b/genai/image_generation/imggen_mmflash_txt_and_img_with_txt.py @@ -23,7 +23,7 @@ def generate_content() -> int: client = genai.Client() response = client.models.generate_content( - model="gemini-2.5-flash-image", + model="gemini-3-pro-image-preview", contents=( "Generate an illustrated recipe for a paella." "Create images to go alongside the text as you generate the recipe" @@ -38,9 +38,7 @@ def generate_content() -> int: image = Image.open(BytesIO((part.inline_data.data))) image.save(f"output_folder/example-image-{i+1}.png") fp.write(f"![image](example-image-{i+1}.png)") - # Example response: - # A markdown page for a Paella recipe(`paella-recipe.md`) has been generated. - # It includes detailed steps and several images illustrating the cooking process. + # [END googlegenaisdk_imggen_mmflash_txt_and_img_with_txt] return True diff --git a/genai/image_generation/imggen_mmflash_with_txt.py b/genai/image_generation/imggen_mmflash_with_txt.py index ed0b6d416bf..0ee371b7e84 100644 --- a/genai/image_generation/imggen_mmflash_with_txt.py +++ b/genai/image_generation/imggen_mmflash_with_txt.py @@ -23,16 +23,10 @@ def generate_content() -> str: client = genai.Client() response = client.models.generate_content( - model="gemini-2.5-flash-image", + model="gemini-3-pro-image-preview", contents=("Generate an image of the Eiffel tower with fireworks in the background."), config=GenerateContentConfig( response_modalities=[Modality.TEXT, Modality.IMAGE], - candidate_count=1, - safety_settings=[ - {"method": "PROBABILITY"}, - {"category": "HARM_CATEGORY_DANGEROUS_CONTENT"}, - {"threshold": "BLOCK_MEDIUM_AND_ABOVE"}, - ], ), ) for part in response.candidates[0].content.parts: @@ -41,11 +35,7 @@ def generate_content() -> str: elif part.inline_data: image = Image.open(BytesIO((part.inline_data.data))) image.save("output_folder/example-image-eiffel-tower.png") - # Example response: - # I will generate an image of the Eiffel Tower at night, with a vibrant display of - # colorful fireworks exploding in the dark sky behind it. The tower will be - # illuminated, standing tall as the focal point of the scene, with the bursts of - # light from the fireworks creating a festive atmosphere. + # [END googlegenaisdk_imggen_mmflash_with_txt] return True From 800a114ed8f76b4056d4b9cfa320a0ee55e1e3e7 Mon Sep 17 00:00:00 2001 From: kuirensu-crypto Date: Wed, 3 Dec 2025 11:04:24 -0800 Subject: [PATCH 02/67] feat: add code samples for preference tuning (#13644) * feat: add code samples for preference tuning * update region tag * update requirement and add test * update test * add new line --------- Co-authored-by: James Su --- genai/tuning/preference_tuning_job_create.py | 74 ++++++++++++++++++++ genai/tuning/requirements.txt | 2 +- genai/tuning/test_tuning_examples.py | 21 ++++++ 3 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 genai/tuning/preference_tuning_job_create.py diff --git a/genai/tuning/preference_tuning_job_create.py b/genai/tuning/preference_tuning_job_create.py new file mode 100644 index 00000000000..13fa05d61d0 --- /dev/null +++ b/genai/tuning/preference_tuning_job_create.py @@ -0,0 +1,74 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def create_tuning_job() -> str: + # [START googlegenaisdk_preference_tuning_job_create] + import time + + from google import genai + from google.genai.types import HttpOptions, CreateTuningJobConfig, TuningDataset + + client = genai.Client(http_options=HttpOptions(api_version="v1")) + + training_dataset = TuningDataset( + gcs_uri="gs://mybucket/preference_tuning/data/train_data.jsonl", + ) + validation_dataset = TuningDataset( + gcs_uri="gs://mybucket/preference_tuning/data/validation_data.jsonl", + ) + + # Refer to https://docs.cloud.google.com/vertex-ai/generative-ai/docs/models/gemini-use-continuous-tuning#google-gen-ai-sdk + # for example to continuous tune from SFT tuned model. + tuning_job = client.tunings.tune( + base_model="gemini-2.5-flash", + training_dataset=training_dataset, + config=CreateTuningJobConfig( + tuned_model_display_name="Example tuning job", + method="PREFERENCE_TUNING", + validation_dataset=validation_dataset, + ), + ) + + running_states = set([ + "JOB_STATE_PENDING", + "JOB_STATE_RUNNING", + ]) + + while tuning_job.state in running_states: + print(tuning_job.state) + tuning_job = client.tunings.get(name=tuning_job.name) + time.sleep(60) + + print(tuning_job.tuned_model.model) + print(tuning_job.tuned_model.endpoint) + print(tuning_job.experiment) + # Example response: + # projects/123456789012/locations/us-central1/models/1234567890@1 + # projects/123456789012/locations/us-central1/endpoints/123456789012345 + # projects/123456789012/locations/us-central1/metadataStores/default/contexts/tuning-experiment-2025010112345678 + + if tuning_job.tuned_model.checkpoints: + for i, checkpoint in enumerate(tuning_job.tuned_model.checkpoints): + print(f"Checkpoint {i + 1}: ", checkpoint) + # Example response: + # Checkpoint 1: checkpoint_id='1' epoch=1 step=10 endpoint='projects/123456789012/locations/us-central1/endpoints/123456789000000' + # Checkpoint 2: checkpoint_id='2' epoch=2 step=20 endpoint='projects/123456789012/locations/us-central1/endpoints/123456789012345' + + # [END googlegenaisdk_preference_tuning_job_create] + return tuning_job.name + + +if __name__ == "__main__": + create_tuning_job() diff --git a/genai/tuning/requirements.txt b/genai/tuning/requirements.txt index 1efe7b29dbc..e5fdb322ca4 100644 --- a/genai/tuning/requirements.txt +++ b/genai/tuning/requirements.txt @@ -1 +1 @@ -google-genai==1.42.0 +google-genai==1.47.0 diff --git a/genai/tuning/test_tuning_examples.py b/genai/tuning/test_tuning_examples.py index c0e6ec2864d..25b46402622 100644 --- a/genai/tuning/test_tuning_examples.py +++ b/genai/tuning/test_tuning_examples.py @@ -20,6 +20,7 @@ from google.genai import types import pytest +import preference_tuning_job_create import tuning_job_create import tuning_job_get import tuning_job_list @@ -327,3 +328,23 @@ def test_tuning_with_pretuned_model(mock_genai_client: MagicMock) -> None: mock_genai_client.assert_called_once_with(http_options=types.HttpOptions(api_version="v1beta1")) mock_genai_client.return_value.tunings.tune.assert_called_once() assert response == "test-tuning-job" + + +@patch("google.genai.Client") +def test_preference_tuning_job_create(mock_genai_client: MagicMock) -> None: + # Mock the API response + mock_tuning_job = types.TuningJob( + name="test-tuning-job", + experiment="test-experiment", + tuned_model=types.TunedModel( + model="test-model", + endpoint="test-endpoint" + ) + ) + mock_genai_client.return_value.tunings.tune.return_value = mock_tuning_job + + response = preference_tuning_job_create.create_tuning_job() + + mock_genai_client.assert_called_once_with(http_options=types.HttpOptions(api_version="v1")) + mock_genai_client.return_value.tunings.tune.assert_called_once() + assert response == "test-tuning-job" From 6cbd32817d27e936839ff523851c614acda9b278 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 18:51:35 -0800 Subject: [PATCH 03/67] chore(deps): bump urllib3 (#13666) Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.5.0 to 2.6.0. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.5.0...2.6.0) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.6.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../flex-templates/pipeline_with_dependencies/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dataflow/flex-templates/pipeline_with_dependencies/requirements.txt b/dataflow/flex-templates/pipeline_with_dependencies/requirements.txt index eeed8f6f3ce..bef166bb943 100644 --- a/dataflow/flex-templates/pipeline_with_dependencies/requirements.txt +++ b/dataflow/flex-templates/pipeline_with_dependencies/requirements.txt @@ -305,7 +305,7 @@ typing-extensions==4.10.0 # via apache-beam tzlocal==5.2 # via js2py -urllib3==2.5.0 +urllib3==2.6.0 # via requests wrapt==1.16.0 # via deprecated From e1fe831fab975627419e64373ecee7eb11f47568 Mon Sep 17 00:00:00 2001 From: tbeyer-google Date: Fri, 12 Dec 2025 04:57:12 +0100 Subject: [PATCH 04/67] =?UTF-8?q?chore(genai):=20Update=20tools=5Fgoogle?= =?UTF-8?q?=5Fsearch=5Fwith=5Ftxt.py=20to=20include=20example=20for=20excl?= =?UTF-8?q?ude=E2=80=A6=20(#13600)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update tools_google_search_with_txt.py to include example for exclude_domains * Update genai/tools/tools_google_search_with_txt.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Update genai/tools/tools_google_search_with_txt.py * Update requirements * Update genai/tools/requirements.txt Co-authored-by: Katie McLaughlin --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Holt Skinner <13262395+holtskinner@users.noreply.github.com> Co-authored-by: Katie McLaughlin --- genai/tools/requirements.txt | 2 +- genai/tools/tools_google_search_with_txt.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/genai/tools/requirements.txt b/genai/tools/requirements.txt index 95d3e9bc0f0..9f6fafbe8ec 100644 --- a/genai/tools/requirements.txt +++ b/genai/tools/requirements.txt @@ -1,3 +1,3 @@ -google-genai==1.42.0 +google-genai==1.45.0 # PIl is required for tools_code_execution_with_txt_img.py pillow==11.1.0 diff --git a/genai/tools/tools_google_search_with_txt.py b/genai/tools/tools_google_search_with_txt.py index 2f650b01df9..4069071d0c3 100644 --- a/genai/tools/tools_google_search_with_txt.py +++ b/genai/tools/tools_google_search_with_txt.py @@ -31,7 +31,12 @@ def generate_content() -> str: config=GenerateContentConfig( tools=[ # Use Google Search Tool - Tool(google_search=GoogleSearch()) + Tool( + google_search=GoogleSearch( + # Optional: Domains to exclude from results + exclude_domains=["domain.com", "domain2.com"] + ) + ) ], ), ) From a1cc7a44610d526c7912a65f3449f4b5a9ec451e Mon Sep 17 00:00:00 2001 From: werman Date: Fri, 12 Dec 2025 17:05:00 -0800 Subject: [PATCH 05/67] feat: Custom Credential Supplier Documentation (#13634) * Included changes for python script for custom credential suppliers. * Made some test and format changes. * Scripts now read from a file instead of env variables. Changed readmes as well. * Added license header to pod.yaml. * fix: Update Dockerfile this updates to a more modern version of python and leverages the benefits of Docker's Layer caching. requirements.txt will rarely change so this way the depedency layer is cached and only rebuilt if you explictly change requirements.txt. * fix: clarify comments * fix: refactor main to seprate concerns simplify testing need to refactor testing based on this separation. * fix: update testing to match refactored main. * fix: update version to test * fix: use latest python * fix: last line * fix: address issues introduced in gitignore file * fix: cleanup README documentation. * fix: refine the README instructions. * fix: Apply suggestion from @gemini-code-assist[bot] Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * fix: starting region tag * fix: address whitespace linting issue * fix: address linting * Now using the storage library instead of calling the storage endpoint. * Removed unnecessary comments. * Formatting changes. * Changed default scopes. * Fixed PR Build run fixes. --------- Co-authored-by: Jennifer Davis Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .gitignore | 6 +- auth/custom-credentials/aws/Dockerfile | 15 ++ auth/custom-credentials/aws/README.md | 127 +++++++++++++++ ...ustom-credentials-aws-secrets.json.example | 8 + auth/custom-credentials/aws/noxfile_config.py | 17 ++ auth/custom-credentials/aws/pod.yaml | 40 +++++ .../aws/requirements-test.txt | 2 + auth/custom-credentials/aws/requirements.txt | 5 + auth/custom-credentials/aws/snippets.py | 153 ++++++++++++++++++ auth/custom-credentials/aws/snippets_test.py | 130 +++++++++++++++ auth/custom-credentials/okta/README.md | 83 ++++++++++ ...stom-credentials-okta-secrets.json.example | 8 + .../custom-credentials/okta/noxfile_config.py | 17 ++ .../okta/requirements-test.txt | 2 + auth/custom-credentials/okta/requirements.txt | 4 + auth/custom-credentials/okta/snippets.py | 138 ++++++++++++++++ auth/custom-credentials/okta/snippets_test.py | 134 +++++++++++++++ 17 files changed, 888 insertions(+), 1 deletion(-) create mode 100644 auth/custom-credentials/aws/Dockerfile create mode 100644 auth/custom-credentials/aws/README.md create mode 100644 auth/custom-credentials/aws/custom-credentials-aws-secrets.json.example create mode 100644 auth/custom-credentials/aws/noxfile_config.py create mode 100644 auth/custom-credentials/aws/pod.yaml create mode 100644 auth/custom-credentials/aws/requirements-test.txt create mode 100644 auth/custom-credentials/aws/requirements.txt create mode 100644 auth/custom-credentials/aws/snippets.py create mode 100644 auth/custom-credentials/aws/snippets_test.py create mode 100644 auth/custom-credentials/okta/README.md create mode 100644 auth/custom-credentials/okta/custom-credentials-okta-secrets.json.example create mode 100644 auth/custom-credentials/okta/noxfile_config.py create mode 100644 auth/custom-credentials/okta/requirements-test.txt create mode 100644 auth/custom-credentials/okta/requirements.txt create mode 100644 auth/custom-credentials/okta/snippets.py create mode 100644 auth/custom-credentials/okta/snippets_test.py diff --git a/.gitignore b/.gitignore index bcb6b89f6ff..80cf8846a58 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,8 @@ env/ .idea .env* **/venv -**/noxfile.py \ No newline at end of file +**/noxfile.py + +# Auth Local secrets file +auth/custom-credentials/okta/custom-credentials-okta-secrets.json +auth/custom-credentials/aws/custom-credentials-aws-secrets.json diff --git a/auth/custom-credentials/aws/Dockerfile b/auth/custom-credentials/aws/Dockerfile new file mode 100644 index 00000000000..d90d88aa0a8 --- /dev/null +++ b/auth/custom-credentials/aws/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.13-slim + +RUN useradd -m appuser + +WORKDIR /app + +COPY --chown=appuser:appuser requirements.txt . + +USER appuser +RUN pip install --no-cache-dir -r requirements.txt + +COPY --chown=appuser:appuser snippets.py . + + +CMD ["python3", "snippets.py"] diff --git a/auth/custom-credentials/aws/README.md b/auth/custom-credentials/aws/README.md new file mode 100644 index 00000000000..551c95ef691 --- /dev/null +++ b/auth/custom-credentials/aws/README.md @@ -0,0 +1,127 @@ +# Running the Custom AWS Credential Supplier Sample + +This sample demonstrates how to use a custom AWS security credential supplier to authenticate with Google Cloud using AWS as an external identity provider. It uses Boto3 (the AWS SDK for Python) to fetch credentials from sources like Amazon Elastic Kubernetes Service (EKS) with IAM Roles for Service Accounts(IRSA), Elastic Container Service (ECS), or Fargate. + +## Prerequisites + +* An AWS account. +* A Google Cloud project with the IAM API enabled. +* A GCS bucket. +* Python 3.10 or later installed. + +If you want to use AWS security credentials that cannot be retrieved using methods supported natively by the [google-auth](https://github.com/googleapis/google-auth-library-python) library, a custom `AwsSecurityCredentialsSupplier` implementation may be specified. The supplier must return valid, unexpired AWS security credentials when called by the Google Cloud Auth library. + + +## Running Locally + +For local development, you can provide credentials and configuration in a JSON file. + +### Install Dependencies + +Ensure you have Python installed, then install the required libraries: + +```bash +pip install -r requirements.txt +``` + +### Configure Credentials for Local Development + +1. Copy the example secrets file to a new file named `custom-credentials-aws-secrets.json`: + ```bash + cp custom-credentials-aws-secrets.json.example custom-credentials-aws-secrets.json + ``` +2. Open `custom-credentials-aws-secrets.json` and fill in the required values for your AWS and Google Cloud configuration. Do not check your `custom-credentials-aws-secrets.json` file into version control. + +**Note:** This file is only used for local development and is not needed when running in a containerized environment like EKS with IRSA. + + +### Run the Script + +```bash +python3 snippets.py +``` + +When run locally, the script will detect the `custom-credentials-aws-secrets.json` file and use it to configure the necessary environment variables for the Boto3 client. + +## Running in a Containerized Environment (EKS) + +This section provides a brief overview of how to run the sample in an Amazon EKS cluster. + +### EKS Cluster Setup + +First, you need an EKS cluster. You can create one using `eksctl` or the AWS Management Console. For detailed instructions, refer to the [Amazon EKS documentation](https://docs.aws.amazon.com/eks/latest/userguide/create-cluster.html). + +### Configure IAM Roles for Service Accounts (IRSA) + +IRSA enables you to associate an IAM role with a Kubernetes service account. This provides a secure way for your pods to access AWS services without hardcoding long-lived credentials. + +Run the following command to create the IAM role and bind it to a Kubernetes Service Account: + +```bash +eksctl create iamserviceaccount \ + --name your-k8s-service-account \ + --namespace default \ + --cluster your-cluster-name \ + --region your-aws-region \ + --role-name your-role-name \ + --attach-policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \ + --approve +``` + +> **Note**: The `--attach-policy-arn` flag is used here to demonstrate attaching permissions. Update this with the specific AWS policy ARN your application requires. + +For a deep dive into how this works without using `eksctl`, refer to the [IAM Roles for Service Accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) documentation. + +### Configure Google Cloud to Trust the AWS Role + +To allow your AWS role to authenticate as a Google Cloud service account, you need to configure Workload Identity Federation. This process involves these key steps: + +1. **Create a Workload Identity Pool and an AWS Provider:** The pool holds the configuration, and the provider is set up to trust your AWS account. + +2. **Create or select a Google Cloud Service Account:** This service account will be impersonated by your AWS role. + +3. **Bind the AWS Role to the Google Cloud Service Account:** Create an IAM policy binding that gives your AWS role the `Workload Identity User` (`roles/iam.workloadIdentityUser`) role on the Google Cloud service account. + +For more detailed information, see the documentation on [Configuring Workload Identity Federation](https://cloud.google.com/iam/docs/workload-identity-federation-with-other-clouds). + +**Alternative: Direct Access** + +> For supported resources, you can grant roles directly to the AWS identity, bypassing service account impersonation. To do this, grant a role (like `roles/storage.objectViewer`) to the workload identity principal (`principalSet://...`) directly on the resource's IAM policy. + +For more detailed information, see the documentation on [Configuring Workload Identity Federation](https://cloud.google.com/iam/docs/workload-identity-federation-with-other-clouds). + +### Containerize and Package the Application + +Create a `Dockerfile` for the Python application and push the image to a container registry (for example Amazon ECR) that your EKS cluster can access. + +**Note:** The provided [`Dockerfile`](Dockerfile) is an example and may need to be modified for your specific needs. + +Build and push the image: +```bash +docker build -t your-container-image:latest . +docker push your-container-image:latest +``` + +### Deploy to EKS + +Create a Kubernetes deployment manifest to deploy your application to the EKS cluster. See the [`pod.yaml`](pod.yaml) file for an example. + +**Note:** The provided [`pod.yaml`](pod.yaml) is an example and may need to be modified for your specific needs. + +Deploy the pod: + +```bash +kubectl apply -f pod.yaml +``` + +### Clean Up + +To clean up the resources, delete the EKS cluster and any other AWS and Google Cloud resources you created. + +```bash +eksctl delete cluster --name your-cluster-name +``` + +## Testing + +This sample is not continuously tested. It is provided for instructional purposes and may require modifications to work in your environment. diff --git a/auth/custom-credentials/aws/custom-credentials-aws-secrets.json.example b/auth/custom-credentials/aws/custom-credentials-aws-secrets.json.example new file mode 100644 index 00000000000..300dc70c138 --- /dev/null +++ b/auth/custom-credentials/aws/custom-credentials-aws-secrets.json.example @@ -0,0 +1,8 @@ +{ + "aws_access_key_id": "YOUR_AWS_ACCESS_KEY_ID", + "aws_secret_access_key": "YOUR_AWS_SECRET_ACCESS_KEY", + "aws_region": "YOUR_AWS_REGION", + "gcp_workload_audience": "YOUR_GCP_WORKLOAD_AUDIENCE", + "gcs_bucket_name": "YOUR_GCS_BUCKET_NAME", + "gcp_service_account_impersonation_url": "YOUR_GCP_SERVICE_ACCOUNT_IMPERSONATION_URL" +} diff --git a/auth/custom-credentials/aws/noxfile_config.py b/auth/custom-credentials/aws/noxfile_config.py new file mode 100644 index 00000000000..0ed973689f7 --- /dev/null +++ b/auth/custom-credentials/aws/noxfile_config.py @@ -0,0 +1,17 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +TEST_CONFIG_OVERRIDE = { + "ignored_versions": ["2.7", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"], +} diff --git a/auth/custom-credentials/aws/pod.yaml b/auth/custom-credentials/aws/pod.yaml new file mode 100644 index 00000000000..70b94bf25e2 --- /dev/null +++ b/auth/custom-credentials/aws/pod.yaml @@ -0,0 +1,40 @@ +# Copyright 2025 Google LLC +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: Pod +metadata: + name: custom-credential-pod +spec: + # The Kubernetes Service Account that is annotated with the corresponding + # AWS IAM role ARN. See the README for instructions on setting up IAM + # Roles for Service Accounts (IRSA). + serviceAccountName: your-k8s-service-account + containers: + - name: gcp-auth-sample + # The container image pushed to the container registry + # For example, Amazon Elastic Container Registry + image: your-container-image:latest + env: + # REQUIRED: The AWS region. Boto3 requires this to be set explicitly + # in containers. + - name: AWS_REGION + value: "your-aws-region" + # REQUIRED: The full identifier of the Workload Identity Pool provider + - name: GCP_WORKLOAD_AUDIENCE + value: "your-gcp-workload-audience" + # OPTIONAL: Enable Google Cloud service account impersonation + # - name: GCP_SERVICE_ACCOUNT_IMPERSONATION_URL + # value: "your-gcp-service-account-impersonation-url" + - name: GCS_BUCKET_NAME + value: "your-gcs-bucket-name" diff --git a/auth/custom-credentials/aws/requirements-test.txt b/auth/custom-credentials/aws/requirements-test.txt new file mode 100644 index 00000000000..43b24059d3e --- /dev/null +++ b/auth/custom-credentials/aws/requirements-test.txt @@ -0,0 +1,2 @@ +-r requirements.txt +pytest==8.2.0 diff --git a/auth/custom-credentials/aws/requirements.txt b/auth/custom-credentials/aws/requirements.txt new file mode 100644 index 00000000000..2c302888ed7 --- /dev/null +++ b/auth/custom-credentials/aws/requirements.txt @@ -0,0 +1,5 @@ +boto3==1.40.53 +google-auth==2.43.0 +google-cloud-storage==2.19.0 +python-dotenv==1.1.1 +requests==2.32.3 diff --git a/auth/custom-credentials/aws/snippets.py b/auth/custom-credentials/aws/snippets.py new file mode 100644 index 00000000000..2d77a123015 --- /dev/null +++ b/auth/custom-credentials/aws/snippets.py @@ -0,0 +1,153 @@ +# Copyright 2025 Google LLC +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START auth_custom_credential_supplier_aws] +import json +import os +import sys + +import boto3 +from google.auth import aws +from google.auth import exceptions +from google.cloud import storage + + +class CustomAwsSupplier(aws.AwsSecurityCredentialsSupplier): + """Custom AWS Security Credentials Supplier using Boto3.""" + + def __init__(self): + """Initializes the Boto3 session, prioritizing environment variables for region.""" + # Explicitly read the region from the environment first. + region = os.getenv("AWS_REGION") or os.getenv("AWS_DEFAULT_REGION") + + # If region is None, Boto3's discovery chain will be used when needed. + self.session = boto3.Session(region_name=region) + self._cached_region = None + + def get_aws_region(self, context, request) -> str: + """Returns the AWS region using Boto3's default provider chain.""" + if self._cached_region: + return self._cached_region + + self._cached_region = self.session.region_name + + if not self._cached_region: + raise exceptions.GoogleAuthError( + "Boto3 was unable to resolve an AWS region." + ) + + return self._cached_region + + def get_aws_security_credentials( + self, context, request=None + ) -> aws.AwsSecurityCredentials: + """Retrieves AWS security credentials using Boto3's default provider chain.""" + creds = self.session.get_credentials() + if not creds: + raise exceptions.GoogleAuthError( + "Unable to resolve AWS credentials from Boto3." + ) + + return aws.AwsSecurityCredentials( + access_key_id=creds.access_key, + secret_access_key=creds.secret_key, + session_token=creds.token, + ) + + +def authenticate_with_aws_credentials(bucket_name, audience, impersonation_url=None): + """Authenticates using the custom AWS supplier and gets bucket metadata. + + Returns: + dict: The bucket metadata response from the Google Cloud Storage API. + """ + + custom_supplier = CustomAwsSupplier() + + credentials = aws.Credentials( + audience=audience, + subject_token_type="urn:ietf:params:aws:token-type:aws4_request", + service_account_impersonation_url=impersonation_url, + aws_security_credentials_supplier=custom_supplier, + scopes=["https://www.googleapis.com/auth/devstorage.read_only"], + ) + + storage_client = storage.Client(credentials=credentials) + + bucket = storage_client.get_bucket(bucket_name) + + return bucket._properties + + +# [END auth_custom_credential_supplier_aws] + + +def _load_config_from_file(): + """ + If a local secrets file is present, load it into the environment. + This is a "just-in-time" configuration for local development. These + variables are only set for the current process and are not exposed to the + shell. + """ + secrets_file = "custom-credentials-aws-secrets.json" + if os.path.exists(secrets_file): + with open(secrets_file, "r") as f: + try: + secrets = json.load(f) + except json.JSONDecodeError: + print(f"Error: '{secrets_file}' is not valid JSON.", file=sys.stderr) + return + + os.environ["AWS_ACCESS_KEY_ID"] = secrets.get("aws_access_key_id", "") + os.environ["AWS_SECRET_ACCESS_KEY"] = secrets.get("aws_secret_access_key", "") + os.environ["AWS_REGION"] = secrets.get("aws_region", "") + os.environ["GCP_WORKLOAD_AUDIENCE"] = secrets.get("gcp_workload_audience", "") + os.environ["GCS_BUCKET_NAME"] = secrets.get("gcs_bucket_name", "") + os.environ["GCP_SERVICE_ACCOUNT_IMPERSONATION_URL"] = secrets.get( + "gcp_service_account_impersonation_url", "" + ) + + +def main(): + + # Reads the custom-credentials-aws-secrets.json if running locally. + _load_config_from_file() + + # Now, read the configuration from the environment. In a local run, these + # will be the values we just set. In a containerized run, they will be + # the values provided by the environment. + gcp_audience = os.getenv("GCP_WORKLOAD_AUDIENCE") + sa_impersonation_url = os.getenv("GCP_SERVICE_ACCOUNT_IMPERSONATION_URL") + gcs_bucket_name = os.getenv("GCS_BUCKET_NAME") + + if not all([gcp_audience, gcs_bucket_name]): + print( + "Required configuration missing. Please provide it in a " + "custom-credentials-aws-secrets.json file or as environment variables: " + "GCP_WORKLOAD_AUDIENCE, GCS_BUCKET_NAME" + ) + return + + try: + print(f"Retrieving metadata for bucket: {gcs_bucket_name}...") + metadata = authenticate_with_aws_credentials( + gcs_bucket_name, gcp_audience, sa_impersonation_url + ) + print("--- SUCCESS! ---") + print(json.dumps(metadata, indent=2)) + except Exception as e: + print(f"Authentication or Request failed: {e}") + + +if __name__ == "__main__": + main() diff --git a/auth/custom-credentials/aws/snippets_test.py b/auth/custom-credentials/aws/snippets_test.py new file mode 100644 index 00000000000..e0382cfc6f5 --- /dev/null +++ b/auth/custom-credentials/aws/snippets_test.py @@ -0,0 +1,130 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os +from unittest import mock + +import pytest + +import snippets + +# --- Unit Tests --- + + +@mock.patch.dict(os.environ, {"AWS_REGION": "us-west-2"}) +@mock.patch("boto3.Session") +def test_init_priority_env_var(mock_boto_session): + """Test that AWS_REGION env var takes priority during init.""" + snippets.CustomAwsSupplier() + mock_boto_session.assert_called_with(region_name="us-west-2") + + +@mock.patch.dict(os.environ, {}, clear=True) +@mock.patch("boto3.Session") +def test_get_aws_region_caching(mock_boto_session): + """Test that get_aws_region caches the result from Boto3.""" + mock_session_instance = mock_boto_session.return_value + mock_session_instance.region_name = "us-east-1" + + supplier = snippets.CustomAwsSupplier() + + # First call should hit the session + region = supplier.get_aws_region(None, None) + assert region == "us-east-1" + + # Change the mock to ensure we aren't calling it again + mock_session_instance.region_name = "us-west-2" + + # Second call should return the cached value + region2 = supplier.get_aws_region(None, None) + assert region2 == "us-east-1" + + +@mock.patch("boto3.Session") +def test_get_aws_security_credentials_success(mock_boto_session): + """Test successful retrieval of AWS credentials.""" + mock_session_instance = mock_boto_session.return_value + + mock_creds = mock.MagicMock() + mock_creds.access_key = "test-key" + mock_creds.secret_key = "test-secret" + mock_creds.token = "test-token" + mock_session_instance.get_credentials.return_value = mock_creds + + supplier = snippets.CustomAwsSupplier() + creds = supplier.get_aws_security_credentials(None) + + assert creds.access_key_id == "test-key" + assert creds.secret_access_key == "test-secret" + assert creds.session_token == "test-token" + + +@mock.patch("snippets.auth_requests.AuthorizedSession") +@mock.patch("snippets.aws.Credentials") +@mock.patch("snippets.CustomAwsSupplier") +def test_authenticate_unit_success(MockSupplier, MockAwsCreds, MockSession): + """Unit test for the main flow using mocks.""" + mock_response = mock.MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = {"name": "my-bucket"} + + mock_session_instance = MockSession.return_value + mock_session_instance.get.return_value = mock_response + + result = snippets.authenticate_with_aws_credentials( + bucket_name="my-bucket", + audience="//iam.googleapis.com/...", + impersonation_url=None, + ) + + assert result == {"name": "my-bucket"} + MockSupplier.assert_called_once() + MockAwsCreds.assert_called_once() + + +# --- System Test (Integration) --- + + +def test_authenticate_system(): + """ + System test that runs against the real API. + Skips automatically if custom-credentials-aws-secrets.json is missing or incomplete. + """ + if not os.path.exists("custom-credentials-aws-secrets.json"): + pytest.skip( + "Skipping system test: custom-credentials-aws-secrets.json not found." + ) + + with open("custom-credentials-aws-secrets.json", "r") as f: + secrets = json.load(f) + + required_keys = [ + "gcp_workload_audience", + "gcs_bucket_name", + "aws_access_key_id", + "aws_secret_access_key", + "aws_region", + ] + if not all(key in secrets and secrets[key] for key in required_keys): + pytest.skip( + "Skipping system test: custom-credentials-aws-secrets.json is missing or has empty required keys." + ) + + metadata = snippets.main() + + # Verify that the returned metadata is a dictionary with expected keys. + assert isinstance(metadata, dict) + assert "name" in metadata + assert metadata["name"] == secrets["gcs_bucket_name"] diff --git a/auth/custom-credentials/okta/README.md b/auth/custom-credentials/okta/README.md new file mode 100644 index 00000000000..96d444e85a4 --- /dev/null +++ b/auth/custom-credentials/okta/README.md @@ -0,0 +1,83 @@ +# Running the Custom Okta Credential Supplier Sample + +This sample demonstrates how to use a custom subject token supplier to authenticate with Google Cloud using Okta as an external identity provider. It uses the Client Credentials flow for machine-to-machine (M2M) authentication. + +## Prerequisites + +* An Okta developer account. +* A Google Cloud project with the IAM API enabled. +* A Google Cloud Storage bucket. Ensure that the authenticated user has access to this bucket. +* Python 3.10 or later installed. +* +## Okta Configuration + +Before running the sample, you need to configure an Okta application for Machine-to-Machine (M2M) communication. + +### Create an M2M Application in Okta + +1. Log in to your Okta developer console. +2. Navigate to **Applications** > **Applications** and click **Create App Integration**. +3. Select **API Services** as the sign-on method and click **Next**. +4. Give your application a name and click **Save**. + +### Obtain Okta Credentials + +Once the application is created, you will find the following information in the **General** tab: + +* **Okta Domain**: Your Okta developer domain (e.g., `https://dev-123456.okta.com`). +* **Client ID**: The client ID for your application. +* **Client Secret**: The client secret for your application. + +You will need these values to configure the sample. + +## Google Cloud Configuration + +You need to configure a Workload Identity Pool in Google Cloud to trust the Okta application. + +### Set up Workload Identity Federation + +1. In the Google Cloud Console, navigate to **IAM & Admin** > **Workload Identity Federation**. +2. Click **Create Pool** to create a new Workload Identity Pool. +3. Add a new **OIDC provider** to the pool. +4. Configure the provider with your Okta domain as the issuer URL. +5. Map the Okta `sub` (subject) assertion to a GCP principal. + +For detailed instructions, refer to the [Workload Identity Federation documentation](https://cloud.google.com/iam/docs/workload-identity-federation). + +## 3. Running the Script + +To run the sample on your local system, you need to install the dependencies and configure your credentials. + +### Install Dependencies + +```bash +pip install -r requirements.txt +``` + +### Configure Credentials + +1. Copy the example secrets file to a new file named `custom-credentials-okta-secrets.json`: + ```bash + cp custom-credentials-okta-secrets.json.example custom-credentials-okta-secrets.json + ``` +2. Open `custom-credentials-okta-secrets.json` and fill in the following values: + + * `okta_domain`: Your Okta developer domain (for example `https://dev-123456.okta.com`). + * `okta_client_id`: The client ID for your application. + * `okta_client_secret`: The client secret for your application. + * `gcp_workload_audience`: The audience for the Google Cloud Workload Identity Pool. This is the full identifier of the Workload Identity Pool provider. + * `gcs_bucket_name`: The name of the Google Cloud Storage bucket to access. + * `gcp_service_account_impersonation_url`: (Optional) The URL for service account impersonation. + + +### Run the Application + +```bash +python3 snippets.py +``` + +The script authenticates with Okta to get an OIDC token, exchanges that token for a Google Cloud federated token, and uses it to list metadata for the specified Google Cloud Storage bucket. + +## Testing + +This sample is not continuously tested. It is provided for instructional purposes and may require modifications to work in your environment. diff --git a/auth/custom-credentials/okta/custom-credentials-okta-secrets.json.example b/auth/custom-credentials/okta/custom-credentials-okta-secrets.json.example new file mode 100644 index 00000000000..fa04fda7cb2 --- /dev/null +++ b/auth/custom-credentials/okta/custom-credentials-okta-secrets.json.example @@ -0,0 +1,8 @@ +{ + "okta_domain": "https://your-okta-domain.okta.com", + "okta_client_id": "your-okta-client-id", + "okta_client_secret": "your-okta-client-secret", + "gcp_workload_audience": "//iam.googleapis.com/projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider", + "gcs_bucket_name": "your-gcs-bucket-name", + "gcp_service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/my-service-account@my-project.iam.gserviceaccount.com:generateAccessToken" +} diff --git a/auth/custom-credentials/okta/noxfile_config.py b/auth/custom-credentials/okta/noxfile_config.py new file mode 100644 index 00000000000..0ed973689f7 --- /dev/null +++ b/auth/custom-credentials/okta/noxfile_config.py @@ -0,0 +1,17 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +TEST_CONFIG_OVERRIDE = { + "ignored_versions": ["2.7", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"], +} diff --git a/auth/custom-credentials/okta/requirements-test.txt b/auth/custom-credentials/okta/requirements-test.txt new file mode 100644 index 00000000000..f47609d2651 --- /dev/null +++ b/auth/custom-credentials/okta/requirements-test.txt @@ -0,0 +1,2 @@ +-r requirements.txt +pytest==7.1.2 diff --git a/auth/custom-credentials/okta/requirements.txt b/auth/custom-credentials/okta/requirements.txt new file mode 100644 index 00000000000..d9669ebee9f --- /dev/null +++ b/auth/custom-credentials/okta/requirements.txt @@ -0,0 +1,4 @@ +requests==2.32.3 +google-cloud-storage==2.19.0 +google-auth==2.43.0 +python-dotenv==1.1.1 diff --git a/auth/custom-credentials/okta/snippets.py b/auth/custom-credentials/okta/snippets.py new file mode 100644 index 00000000000..02af2dadc93 --- /dev/null +++ b/auth/custom-credentials/okta/snippets.py @@ -0,0 +1,138 @@ +# Copyright 2025 Google LLC +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START auth_custom_credential_supplier_okta] +import json +import time +import urllib.parse + +from google.auth import identity_pool +from google.cloud import storage +import requests + + +class OktaClientCredentialsSupplier: + """A custom SubjectTokenSupplier that authenticates with Okta. + + This supplier uses the Client Credentials grant flow for machine-to-machine + (M2M) authentication with Okta. + """ + + def __init__(self, domain, client_id, client_secret): + self.okta_token_url = f"{domain.rstrip('/')}/oauth2/default/v1/token" + self.client_id = client_id + self.client_secret = client_secret + self.access_token = None + self.expiry_time = 0 + + def get_subject_token(self, context, request=None) -> str: + """Fetches a new token if the current one is expired or missing.""" + if self.access_token and time.time() < self.expiry_time - 60: + return self.access_token + self._fetch_okta_access_token() + return self.access_token + + def _fetch_okta_access_token(self): + """Performs the Client Credentials grant flow with Okta.""" + headers = { + "Content-Type": "application/x-www-form-urlencoded", + "Accept": "application/json", + } + data = { + "grant_type": "client_credentials", + "scope": "gcp.test.read", # Set scope as per Okta app config. + } + + response = requests.post( + self.okta_token_url, + headers=headers, + data=urllib.parse.urlencode(data), + auth=(self.client_id, self.client_secret), + ) + response.raise_for_status() + + token_data = response.json() + self.access_token = token_data["access_token"] + self.expiry_time = time.time() + token_data["expires_in"] + + +def authenticate_with_okta_credentials( + bucket_name, audience, domain, client_id, client_secret, impersonation_url=None +): + """Authenticates using the custom Okta supplier and gets bucket metadata. + + Returns: + dict: The bucket metadata response from the Google Cloud Storage API. + """ + + okta_supplier = OktaClientCredentialsSupplier(domain, client_id, client_secret) + + credentials = identity_pool.Credentials( + audience=audience, + subject_token_type="urn:ietf:params:oauth:token-type:jwt", + token_url="https://sts.googleapis.com/v1/token", + subject_token_supplier=okta_supplier, + default_scopes=["https://www.googleapis.com/auth/devstorage.read_only"], + service_account_impersonation_url=impersonation_url, + ) + + storage_client = storage.Client(credentials=credentials) + + bucket = storage_client.get_bucket(bucket_name) + + return bucket._properties + + +# [END auth_custom_credential_supplier_okta] + + +def main(): + try: + with open("custom-credentials-okta-secrets.json") as f: + secrets = json.load(f) + except FileNotFoundError: + print("Could not find custom-credentials-okta-secrets.json.") + return + + gcp_audience = secrets.get("gcp_workload_audience") + gcs_bucket_name = secrets.get("gcs_bucket_name") + sa_impersonation_url = secrets.get("gcp_service_account_impersonation_url") + + okta_domain = secrets.get("okta_domain") + okta_client_id = secrets.get("okta_client_id") + okta_client_secret = secrets.get("okta_client_secret") + + if not all( + [gcp_audience, gcs_bucket_name, okta_domain, okta_client_id, okta_client_secret] + ): + print("Missing required values in secrets.json.") + return + + try: + print(f"Retrieving metadata for bucket: {gcs_bucket_name}...") + metadata = authenticate_with_okta_credentials( + bucket_name=gcs_bucket_name, + audience=gcp_audience, + domain=okta_domain, + client_id=okta_client_id, + client_secret=okta_client_secret, + impersonation_url=sa_impersonation_url, + ) + print("--- SUCCESS! ---") + print(json.dumps(metadata, indent=2)) + except Exception as e: + print(f"Authentication or Request failed: {e}") + + +if __name__ == "__main__": + main() diff --git a/auth/custom-credentials/okta/snippets_test.py b/auth/custom-credentials/okta/snippets_test.py new file mode 100644 index 00000000000..1f05c4ad7bf --- /dev/null +++ b/auth/custom-credentials/okta/snippets_test.py @@ -0,0 +1,134 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os +import time +from unittest import mock +import urllib.parse + +import pytest + +import snippets + +# --- Unit Tests --- + + +def test_init_url_cleaning(): + """Test that the token URL strips trailing slashes.""" + s1 = snippets.OktaClientCredentialsSupplier("https://okta.com/", "id", "sec") + assert s1.okta_token_url == "https://okta.com/oauth2/default/v1/token" + + s2 = snippets.OktaClientCredentialsSupplier("https://okta.com", "id", "sec") + assert s2.okta_token_url == "https://okta.com/oauth2/default/v1/token" + + +@mock.patch("requests.post") +def test_get_subject_token_fetch(mock_post): + """Test fetching a new token from Okta.""" + supplier = snippets.OktaClientCredentialsSupplier("https://okta.com", "id", "sec") + + mock_response = mock.MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = {"access_token": "new-token", "expires_in": 3600} + mock_post.return_value = mock_response + + token = supplier.get_subject_token(None, None) + + assert token == "new-token" + mock_post.assert_called_once() + + # Verify args + _, kwargs = mock_post.call_args + assert kwargs["auth"] == ("id", "sec") + + sent_data = urllib.parse.parse_qs(kwargs["data"]) + assert sent_data["grant_type"][0] == "client_credentials" + + +@mock.patch("requests.post") +def test_get_subject_token_cached(mock_post): + """Test that cached token is returned if valid.""" + supplier = snippets.OktaClientCredentialsSupplier("https://okta.com", "id", "sec") + supplier.access_token = "cached-token" + supplier.expiry_time = time.time() + 3600 + + token = supplier.get_subject_token(None, None) + + assert token == "cached-token" + mock_post.assert_not_called() + + +@mock.patch("snippets.auth_requests.AuthorizedSession") +@mock.patch("snippets.identity_pool.Credentials") +@mock.patch("snippets.OktaClientCredentialsSupplier") +def test_authenticate_unit_success(MockSupplier, MockCreds, MockSession): + """Unit test for the main Okta auth flow.""" + mock_response = mock.MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = {"name": "test-bucket"} + + mock_session_instance = MockSession.return_value + mock_session_instance.get.return_value = mock_response + + metadata = snippets.authenticate_with_okta_credentials( + bucket_name="test-bucket", + audience="test-aud", + domain="https://okta.com", + client_id="id", + client_secret="sec", + impersonation_url=None, + ) + + assert metadata == {"name": "test-bucket"} + MockSupplier.assert_called_once() + MockCreds.assert_called_once() + + +# --- System Test --- + + +def test_authenticate_system(): + """ + System test that runs against the real API. + Skips automatically if custom-credentials-okta-secrets.json is missing or incomplete. + """ + if not os.path.exists("custom-credentials-okta-secrets.json"): + pytest.skip( + "Skipping system test: custom-credentials-okta-secrets.json not found." + ) + + with open("custom-credentials-okta-secrets.json", "r") as f: + secrets = json.load(f) + + required_keys = [ + "gcp_workload_audience", + "gcs_bucket_name", + "okta_domain", + "okta_client_id", + "okta_client_secret", + ] + if not all(key in secrets for key in required_keys): + pytest.skip( + "Skipping system test: custom-credentials-okta-secrets.json is missing required keys." + ) + + # The main() function handles the auth flow and printing. + # We mock the print function to verify the output. + with mock.patch("builtins.print") as mock_print: + snippets.main() + + # Check for the success message in the print output. + output = "\n".join([call.args[0] for call in mock_print.call_args_list]) + assert "--- SUCCESS! ---" in output From aaa3218a27e7a2a0f356f9063fff0ebae6aef49b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 Dec 2025 20:04:13 -0800 Subject: [PATCH 06/67] chore(deps): bump django in /appengine/standard_python3/django (#13660) Bumps [django](https://github.com/django/django) from 5.1.8 to 5.1.15. - [Commits](https://github.com/django/django/compare/5.1.8...5.1.15) --- updated-dependencies: - dependency-name: django dependency-version: 5.1.15 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- appengine/standard_python3/django/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appengine/standard_python3/django/requirements.txt b/appengine/standard_python3/django/requirements.txt index cdd4b54cf3e..60b4408e6b4 100644 --- a/appengine/standard_python3/django/requirements.txt +++ b/appengine/standard_python3/django/requirements.txt @@ -1,4 +1,4 @@ -Django==5.1.8; python_version >= "3.10" +Django==5.1.15; python_version >= "3.10" Django==4.2.17; python_version >= "3.8" and python_version < "3.10" Django==3.2.25; python_version < "3.8" django-environ==0.10.0 From 04df0496f8732139d37493adb9334b68a640e11b Mon Sep 17 00:00:00 2001 From: Jennifer Davis Date: Mon, 15 Dec 2025 17:56:18 +0000 Subject: [PATCH 07/67] fix: enable dialog_cx dependency updates to be tested/merged (#13670) * chore(deps): bump werkzeug from 3.0.6 to 3.1.4 in /dialogflow-cx Bumps [werkzeug](https://github.com/pallets/werkzeug) from 3.0.6 to 3.1.4. - [Release notes](https://github.com/pallets/werkzeug/releases) - [Changelog](https://github.com/pallets/werkzeug/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/werkzeug/compare/3.0.6...3.1.4) --- updated-dependencies: - dependency-name: werkzeug dependency-version: 3.1.4 dependency-type: direct:production ... Signed-off-by: dependabot[bot] * fix: validated in 3.10 --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dialogflow-cx/noxfile_config.py | 2 +- dialogflow-cx/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dialogflow-cx/noxfile_config.py b/dialogflow-cx/noxfile_config.py index 462f6d428f7..cc8143940ee 100644 --- a/dialogflow-cx/noxfile_config.py +++ b/dialogflow-cx/noxfile_config.py @@ -22,7 +22,7 @@ TEST_CONFIG_OVERRIDE = { # You can opt out from the test for specific Python versions. - "ignored_versions": ["2.7", "3.7", "3.9", "3.10", "3.11", "3.12", "3.13"], + "ignored_versions": ["2.7", "3.7", "3.8", "3.9", "3.11", "3.12", "3.13"], # An envvar key for determining the project id to use. Change it # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a # build specific Cloud project. You can also use your own string diff --git a/dialogflow-cx/requirements.txt b/dialogflow-cx/requirements.txt index c162d7a7e98..fe7011b74ee 100644 --- a/dialogflow-cx/requirements.txt +++ b/dialogflow-cx/requirements.txt @@ -2,7 +2,7 @@ google-cloud-dialogflow-cx==2.0.0 Flask==3.0.3 python-dateutil==2.9.0.post0 functions-framework==3.9.2 -Werkzeug==3.0.6 +Werkzeug==3.1.4 termcolor==3.0.0; python_version >= "3.9" termcolor==2.4.0; python_version == "3.8" pyaudio==0.2.14 \ No newline at end of file From 181247a3cabe09fa2a769cd66834256a99c2a435 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 17 Dec 2025 15:30:20 -0500 Subject: [PATCH 08/67] feat: add Python 3.14 to samples testing image (#13596) * feat: add Python 3.14 to samples testing image * address review feedback * formatting * wip * remove gpg;add sigstore;remove python 2.7 which doesn't support sigstore * fix typo * chore: add Py3.14 to noxfile-template --------- Co-authored-by: Victor Chudnovsky --- .kokoro/docker/Dockerfile | 89 ++++++++++++++++++-------- .kokoro/docker/fetch_gpg_keys.sh | 57 ----------------- .kokoro/python3.14/common.cfg | 59 +++++++++++++++++ .kokoro/python3.14/continuous.cfg | 21 ++++++ .kokoro/python3.14/periodic.cfg | 27 ++++++++ .kokoro/python3.14/presubmit.cfg | 21 ++++++ .kokoro/tests/run_tests.sh | 2 +- appengine/standard/noxfile-template.py | 2 +- appengine/standard/noxfile_config.py | 2 +- noxfile-template.py | 2 +- 10 files changed, 195 insertions(+), 87 deletions(-) delete mode 100755 .kokoro/docker/fetch_gpg_keys.sh create mode 100644 .kokoro/python3.14/common.cfg create mode 100644 .kokoro/python3.14/continuous.cfg create mode 100644 .kokoro/python3.14/periodic.cfg create mode 100644 .kokoro/python3.14/presubmit.cfg diff --git a/.kokoro/docker/Dockerfile b/.kokoro/docker/Dockerfile index ba9af12a933..c37e7f091e2 100644 --- a/.kokoro/docker/Dockerfile +++ b/.kokoro/docker/Dockerfile @@ -110,33 +110,68 @@ RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - \ && rm -rf /var/lib/apt/lists/* \ && rm -f /var/cache/apt/archives/*.deb -COPY fetch_gpg_keys.sh /tmp -# Install the desired versions of Python. -RUN set -ex \ - && export GNUPGHOME="$(mktemp -d)" \ - && echo "disable-ipv6" >> "${GNUPGHOME}/dirmngr.conf" \ - && /tmp/fetch_gpg_keys.sh \ - && for PYTHON_VERSION in 2.7.18 3.7.17 3.8.20 3.9.20 3.10.15 3.11.10 3.12.7 3.13.0; do \ - wget --no-check-certificate -O python-${PYTHON_VERSION}.tar.xz "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz" \ - && wget --no-check-certificate -O python-${PYTHON_VERSION}.tar.xz.asc "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz.asc" \ - && gpg --batch --verify python-${PYTHON_VERSION}.tar.xz.asc python-${PYTHON_VERSION}.tar.xz \ - && rm -r python-${PYTHON_VERSION}.tar.xz.asc \ - && mkdir -p /usr/src/python-${PYTHON_VERSION} \ - && tar -xJC /usr/src/python-${PYTHON_VERSION} --strip-components=1 -f python-${PYTHON_VERSION}.tar.xz \ - && rm python-${PYTHON_VERSION}.tar.xz \ - && cd /usr/src/python-${PYTHON_VERSION} \ - && ./configure \ - --enable-shared \ - # This works only on Python 2.7 and throws a warning on every other - # version, but seems otherwise harmless. - --enable-unicode=ucs4 \ - --with-system-ffi \ - --without-ensurepip \ - && make -j$(nproc) \ - && make install \ - && ldconfig \ +# From https://www.python.org/downloads/metadata/sigstore/ +# Starting with Python 3.14, Sigstore is the only method of signing and verification of release artifacts. +RUN LATEST_VERSION="2.6.1" && \ + wget "https://github.com/sigstore/cosign/releases/download/v${LATEST_VERSION}/cosign_${LATEST_VERSION}_amd64.deb" && \ + dpkg -i cosign_${LATEST_VERSION}_amd64.deb && \ + rm cosign_${LATEST_VERSION}_amd64.deb + +ARG PYTHON_VERSIONS="3.7.17 3.8.20 3.9.23 3.10.18 3.11.13 3.12.11 3.13.8 3.14.0" + +SHELL ["/bin/bash", "-c"] + +RUN set -eux; \ + # Define the required associative arrays completely. + declare -A PYTHON_IDENTITIES; \ + PYTHON_IDENTITIES=(\ + [3.7]="nad@python.org" \ + [3.8]="lukasz@langa.pl" \ + [3.9]="lukasz@langa.pl" \ + [3.10]="pablogsal@python.org" \ + [3.11]="pablogsal@python.org" \ + [3.12]="thomas@python.org" \ + [3.13]="thomas@python.org" \ + [3.14]="hugo@python.org" \ + ); \ + declare -A PYTHON_ISSUERS; \ + PYTHON_ISSUERS=(\ + [3.7]="https://github.com/login/oauth" \ + [3.8]="https://github.com/login/oauth" \ + [3.9]="https://github.com/login/oauth" \ + [3.10]="https://accounts.google.com" \ + [3.11]="https://accounts.google.com" \ + [3.12]="https://accounts.google.com" \ + [3.13]="https://accounts.google.com" \ + [3.14]="https://github.com/login/oauth" \ + ); \ + \ + for VERSION in $PYTHON_VERSIONS; do \ + # 1. Define VERSION_GROUP (e.g., 3.14 from 3.14.0) + VERSION_GROUP="$(echo "${VERSION}" | cut -d . -f 1,2)"; \ + \ + # 2. Look up IDENTITY and ISSUER using the defined VERSION_GROUP + IDENTITY="${PYTHON_IDENTITIES[$VERSION_GROUP]}"; \ + ISSUER="${PYTHON_ISSUERS[$VERSION_GROUP]}"; \ + \ + wget --quiet -O python-${VERSION}.tar.xz "https://www.python.org/ftp/python/${VERSION}/Python-$VERSION.tar.xz" \ + && wget --quiet -O python-${VERSION}.tar.xz.sigstore "https://www.python.org/ftp/python/${VERSION}/Python-$VERSION.tar.xz.sigstore" \ + # Verify the Python tarball signature with cosign. + && cosign verify-blob python-${VERSION}.tar.xz \ + --certificate-oidc-issuer "${ISSUER}" \ + --certificate-identity "${IDENTITY}" \ + --bundle python-${VERSION}.tar.xz.sigstore \ + && mkdir -p /usr/src/python-${VERSION} \ + && tar -xJC /usr/src/python-${VERSION} --strip-components=1 -f python-${VERSION}.tar.xz \ + && rm python-${VERSION}.tar.xz \ + && cd /usr/src/python-${VERSION} \ + && ./configure \ + --enable-shared \ + --with-system-ffi \ + && make -j$(nproc) \ + && make install \ + && ldconfig \ ; done \ - && rm -rf "${GNUPGHOME}" \ && rm -rf /usr/src/python* \ && rm -rf ~/.cache/ @@ -158,6 +193,7 @@ RUN wget --no-check-certificate -O /tmp/get-pip-3-7.py 'https://bootstrap.pypa.i && [ "$(pip list |tac|tac| awk -F '[ ()]+' '$1 == "pip" { print $2; exit }')" = "$PYTHON_PIP_VERSION" ] # Ensure Pip for all python3 versions +RUN python3.14 /tmp/get-pip.py RUN python3.13 /tmp/get-pip.py RUN python3.12 /tmp/get-pip.py RUN python3.11 /tmp/get-pip.py @@ -175,6 +211,7 @@ RUN python3.10 -m pip RUN python3.11 -m pip RUN python3.12 -m pip RUN python3.13 -m pip +RUN python3.14 -m pip # Install "setuptools" for Python 3.12+ (see https://docs.python.org/3/whatsnew/3.12.html#distutils) RUN python3.12 -m pip install --no-cache-dir setuptools diff --git a/.kokoro/docker/fetch_gpg_keys.sh b/.kokoro/docker/fetch_gpg_keys.sh deleted file mode 100755 index 5b8dbbab1ed..00000000000 --- a/.kokoro/docker/fetch_gpg_keys.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/bash -# Copyright 2020 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# A script to fetch gpg keys with retry. - -function retry { - if [[ "${#}" -le 1 ]]; then - echo "Usage: ${0} retry_count commands.." - exit 1 - fi - local retries=${1} - local command="${@:2}" - until [[ "${retries}" -le 0 ]]; do - $command && return 0 - if [[ $? -ne 0 ]]; then - echo "command failed, retrying" - ((retries--)) - fi - done - return 1 -} - -# 2.7.17 (Benjamin Peterson) -retry 3 gpg --keyserver keyserver.ubuntu.com --recv-keys \ - C01E1CAD5EA2C4F0B8E3571504C367C218ADD4FF - -# 3.4.10, 3.5.9 (Larry Hastings) -retry 3 gpg --keyserver keyserver.ubuntu.com --recv-keys \ - 97FC712E4C024BBEA48A61ED3A5CA953F73C700D - -# 3.6.9, 3.7.5 (Ned Deily) -retry 3 gpg --keyserver keyserver.ubuntu.com --recv-keys \ - 0D96DF4D4110E5C43FBFB17F2D347EA6AA65421D - -# 3.8.0, 3.9.0 (Łukasz Langa) -retry 3 gpg --keyserver keyserver.ubuntu.com --recv-keys \ - E3FF2839C048B25C084DEBE9B26995E310250568 - -# 3.10.x and 3.11.x (Pablo Galindo Salgado) -retry 3 gpg --keyserver keyserver.ubuntu.com --recv-keys \ - A035C8C19219BA821ECEA86B64E628F8D684696D - -# 3.12.x and 3.13.x source files and tags (Thomas Wouters) -retry 3 gpg --keyserver keyserver.ubuntu.com --recv-keys \ - A821E680E5FA6305 \ No newline at end of file diff --git a/.kokoro/python3.14/common.cfg b/.kokoro/python3.14/common.cfg new file mode 100644 index 00000000000..8d12e9ed952 --- /dev/null +++ b/.kokoro/python3.14/common.cfg @@ -0,0 +1,59 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Format: //devtools/kokoro/config/proto/build.proto + +timeout_mins: 300 + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" +} + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "python-docs-samples/.kokoro/trampoline_v2.sh" + +# Download secrets from Cloud Storage. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" + +# Access btlr binaries used in the tests +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/btlr" + +# Copy results for Resultstore +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Specify which tests to run +env_vars: { + key: "RUN_TESTS_SESSION" + value: "py-3.14" +} + +env_vars: { + key: "BUILD_SPECIFIC_GCLOUD_PROJECT" + value: "python-docs-samples-tests-314" +} + +# Number of test workers. +env_vars: { + key: "NUM_TEST_WORKERS" + value: "10" +} diff --git a/.kokoro/python3.14/continuous.cfg b/.kokoro/python3.14/continuous.cfg new file mode 100644 index 00000000000..5753c38482a --- /dev/null +++ b/.kokoro/python3.14/continuous.cfg @@ -0,0 +1,21 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Format: //devtools/kokoro/config/proto/build.proto + +# Tell the trampoline which build file to use. +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: ".kokoro/tests/run_tests_diff_head.sh" +} diff --git a/.kokoro/python3.14/periodic.cfg b/.kokoro/python3.14/periodic.cfg new file mode 100644 index 00000000000..8a14abb05ef --- /dev/null +++ b/.kokoro/python3.14/periodic.cfg @@ -0,0 +1,27 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Format: //devtools/kokoro/config/proto/build.proto + +# Tell the trampoline which build file to use. +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: ".kokoro/tests/run_tests.sh" +} + +# Tell Trampoline to upload the Docker image after successfull build. +env_vars: { + key: "TRAMPOLINE_IMAGE_UPLOAD" + value: "true" +} diff --git a/.kokoro/python3.14/presubmit.cfg b/.kokoro/python3.14/presubmit.cfg new file mode 100644 index 00000000000..b8ecd3b0d15 --- /dev/null +++ b/.kokoro/python3.14/presubmit.cfg @@ -0,0 +1,21 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Format: //devtools/kokoro/config/proto/build.proto + +# Tell the trampoline which build file to use. +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: ".kokoro/tests/run_tests_diff_main.sh" +} diff --git a/.kokoro/tests/run_tests.sh b/.kokoro/tests/run_tests.sh index 1715decdce7..191b40b09e0 100755 --- a/.kokoro/tests/run_tests.sh +++ b/.kokoro/tests/run_tests.sh @@ -58,7 +58,7 @@ if [[ $* == *--only-diff-head* ]]; then fi fi -# Because Kokoro runs presubmit builds simalteneously, we often see +# Because Kokoro runs presubmit builds simultaneously, we often see # quota related errors. I think we can avoid this by changing the # order of tests to execute (e.g. reverse order for py-3.8 # build). Currently there's no easy way to do that with btlr, so we diff --git a/appengine/standard/noxfile-template.py b/appengine/standard/noxfile-template.py index f96f3288d70..ae73deaa7f1 100644 --- a/appengine/standard/noxfile-template.py +++ b/appengine/standard/noxfile-template.py @@ -79,7 +79,7 @@ def get_pytest_env_vars(): # DO NOT EDIT - automatically generated. # All versions used to tested samples. -ALL_VERSIONS = ["2.7", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] +ALL_VERSIONS = ["2.7", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] # Any default versions that should be ignored. IGNORED_VERSIONS = ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] diff --git a/appengine/standard/noxfile_config.py b/appengine/standard/noxfile_config.py index 9d81eb86207..f39811085fa 100644 --- a/appengine/standard/noxfile_config.py +++ b/appengine/standard/noxfile_config.py @@ -24,7 +24,7 @@ TEST_CONFIG_OVERRIDE = { # You can opt out from the test for specific Python versions. - "ignored_versions": ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"], + "ignored_versions": ["3.6", "3.7", "3.8", "3.10", "3.11", "3.12", "3.13"], # Old samples are opted out of enforcing Python type hints # All new samples should feature them "enforce_type_hints": False, diff --git a/noxfile-template.py b/noxfile-template.py index 93b0186aedd..d2c4e7608ce 100644 --- a/noxfile-template.py +++ b/noxfile-template.py @@ -88,7 +88,7 @@ def get_pytest_env_vars() -> dict[str, str]: # All versions used to tested samples. -ALL_VERSIONS = ["2.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] +ALL_VERSIONS = ["2.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] # Any default versions that should be ignored. IGNORED_VERSIONS = TEST_CONFIG["ignored_versions"] From 71b333ded6474db802874b49469be59de3a5fcef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:25:14 -0800 Subject: [PATCH 09/67] chore(deps): bump django in /appengine/flexible/django_cloudsql (#13662) Bumps [django](https://github.com/django/django) from 5.2.8 to 5.2.9. - [Commits](https://github.com/django/django/compare/5.2.8...5.2.9) --- updated-dependencies: - dependency-name: django dependency-version: 5.2.9 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- appengine/flexible/django_cloudsql/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appengine/flexible/django_cloudsql/requirements.txt b/appengine/flexible/django_cloudsql/requirements.txt index 5d64cd3b97f..e309f97d5bc 100644 --- a/appengine/flexible/django_cloudsql/requirements.txt +++ b/appengine/flexible/django_cloudsql/requirements.txt @@ -1,4 +1,4 @@ -Django==5.2.8 +Django==5.2.9 gunicorn==23.0.0 psycopg2-binary==2.9.10 django-environ==0.12.0 From 87794e7f175ec186fef374ef02343597fe888b14 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:25:42 -0800 Subject: [PATCH 10/67] chore(deps): bump werkzeug from 3.0.3 to 3.1.4 in /iap (#13664) Bumps [werkzeug](https://github.com/pallets/werkzeug) from 3.0.3 to 3.1.4. - [Release notes](https://github.com/pallets/werkzeug/releases) - [Changelog](https://github.com/pallets/werkzeug/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/werkzeug/compare/3.0.3...3.1.4) --- updated-dependencies: - dependency-name: werkzeug dependency-version: 3.1.4 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- iap/app_engine_app/requirements.txt | 2 +- iap/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/iap/app_engine_app/requirements.txt b/iap/app_engine_app/requirements.txt index f306f93a9ca..6857fdf3175 100644 --- a/iap/app_engine_app/requirements.txt +++ b/iap/app_engine_app/requirements.txt @@ -1,2 +1,2 @@ Flask==3.0.3 -Werkzeug==3.0.3 +Werkzeug==3.1.4 diff --git a/iap/requirements.txt b/iap/requirements.txt index 3c2961ba6a2..850043f7bd1 100644 --- a/iap/requirements.txt +++ b/iap/requirements.txt @@ -4,6 +4,6 @@ google-auth==2.38.0 gunicorn==23.0.0 requests==2.32.4 requests-toolbelt==1.0.0 -Werkzeug==3.0.6 +Werkzeug==3.1.4 google-cloud-iam~=2.17.0 PyJWT~=2.10.1 \ No newline at end of file From c1dbaaf343d92ca55339f1fe4b7119b20ab1fda5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:26:16 -0800 Subject: [PATCH 11/67] chore(deps): bump django in /appengine/flexible/hello_world_django (#13661) Bumps [django](https://github.com/django/django) from 5.2.5 to 5.2.9. - [Commits](https://github.com/django/django/compare/5.2.5...5.2.9) --- updated-dependencies: - dependency-name: django dependency-version: 5.2.9 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- appengine/flexible/hello_world_django/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appengine/flexible/hello_world_django/requirements.txt b/appengine/flexible/hello_world_django/requirements.txt index 564852cb740..435ef2cb8ee 100644 --- a/appengine/flexible/hello_world_django/requirements.txt +++ b/appengine/flexible/hello_world_django/requirements.txt @@ -1,2 +1,2 @@ -Django==5.2.5 +Django==5.2.9 gunicorn==23.0.0 From 420b4003c11686f07de626c03f710d9b10b1160b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:27:00 -0800 Subject: [PATCH 12/67] chore(deps): bump django (#13657) Bumps [django](https://github.com/django/django) from 5.2.5 to 5.2.9. - [Commits](https://github.com/django/django/compare/5.2.5...5.2.9) --- updated-dependencies: - dependency-name: django dependency-version: 5.2.9 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../hello_world_django/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appengine/flexible_python37_and_earlier/hello_world_django/requirements.txt b/appengine/flexible_python37_and_earlier/hello_world_django/requirements.txt index 564852cb740..435ef2cb8ee 100644 --- a/appengine/flexible_python37_and_earlier/hello_world_django/requirements.txt +++ b/appengine/flexible_python37_and_earlier/hello_world_django/requirements.txt @@ -1,2 +1,2 @@ -Django==5.2.5 +Django==5.2.9 gunicorn==23.0.0 From ba4707d78aa619c4395ae3238e2f3a05ea5cf809 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:43:40 -0800 Subject: [PATCH 13/67] chore(deps): bump django in /kubernetes_engine/django_tutorial (#13656) Bumps [django](https://github.com/django/django) from 5.2.5 to 5.2.9. - [Commits](https://github.com/django/django/compare/5.2.5...5.2.9) --- updated-dependencies: - dependency-name: django dependency-version: 5.2.9 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- kubernetes_engine/django_tutorial/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kubernetes_engine/django_tutorial/requirements.txt b/kubernetes_engine/django_tutorial/requirements.txt index 0c01249d943..1ef339da5ba 100644 --- a/kubernetes_engine/django_tutorial/requirements.txt +++ b/kubernetes_engine/django_tutorial/requirements.txt @@ -1,4 +1,4 @@ -Django==5.2.5; python_version >= "3.10" +Django==5.2.9; python_version >= "3.10" Django==4.2.24; python_version >= "3.8" and python_version < "3.10" # Uncomment the mysqlclient requirement if you are using MySQL rather than # PostgreSQL. You must also have a MySQL client installed in that case. From 1af32d126393c2c1b2500ad4c1a91ad6614d92d4 Mon Sep 17 00:00:00 2001 From: ShellyAgg <106765931+ShellyAgg@users.noreply.github.com> Date: Thu, 18 Dec 2025 07:42:24 +0530 Subject: [PATCH 14/67] Update quickstart samples for python 3.14 (#13672) * Update quickstart samples for python 3.14 * Update pytest * Update pytest to be a working version for both 3.9, 3.14 --------- Co-authored-by: Katie McLaughlin Co-authored-by: Katie McLaughlin --- appengine/flexible/hello_world/app.yaml | 2 +- .../building-an-app/building-an-app-1/app.yaml | 2 +- .../building-an-app/building-an-app-1/requirements-test.txt | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/appengine/flexible/hello_world/app.yaml b/appengine/flexible/hello_world/app.yaml index ac38af83425..78198c8821a 100644 --- a/appengine/flexible/hello_world/app.yaml +++ b/appengine/flexible/hello_world/app.yaml @@ -17,7 +17,7 @@ env: flex entrypoint: gunicorn -b :$PORT main:app runtime_config: - operating_system: ubuntu22 + operating_system: ubuntu24 # This sample incurs costs to run on the App Engine flexible environment. # The settings below are to reduce costs during testing and are not appropriate diff --git a/appengine/standard_python3/building-an-app/building-an-app-1/app.yaml b/appengine/standard_python3/building-an-app/building-an-app-1/app.yaml index 100d540982b..2ecf42a0f4f 100644 --- a/appengine/standard_python3/building-an-app/building-an-app-1/app.yaml +++ b/appengine/standard_python3/building-an-app/building-an-app-1/app.yaml @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -runtime: python313 +runtime: python314 handlers: # This configures Google App Engine to serve the files in the app's static diff --git a/appengine/standard_python3/building-an-app/building-an-app-1/requirements-test.txt b/appengine/standard_python3/building-an-app/building-an-app-1/requirements-test.txt index c2845bffbe8..c987bcfee7e 100644 --- a/appengine/standard_python3/building-an-app/building-an-app-1/requirements-test.txt +++ b/appengine/standard_python3/building-an-app/building-an-app-1/requirements-test.txt @@ -1 +1,2 @@ -pytest==7.0.1 +pytest==7.0.1; python_version == '3.9' +pytest==9.0.2; python_version >= '3.10' From 282ba1f828117cfbe5b7ddf78014fe9aa748f488 Mon Sep 17 00:00:00 2001 From: Lkhagvadorj Sukhtsoodol Date: Mon, 5 Jan 2026 18:46:43 +0000 Subject: [PATCH 15/67] docs: correcting some docs on python script (#13697) --- composer/tools/composer_migrate.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer/tools/composer_migrate.py b/composer/tools/composer_migrate.py index ecbbb97dae8..c4ef2fbb5f9 100644 --- a/composer/tools/composer_migrate.py +++ b/composer/tools/composer_migrate.py @@ -108,7 +108,7 @@ def unpause_dag( dag_id: str, environment_name: str, ) -> Any: - """Unpauses all DAGs in a Composer environment.""" + """Unpauses a DAG in a Composer environment.""" command = ( f"CLOUDSDK_API_ENDPOINT_OVERRIDES_COMPOSER={self.sdk_endpoint} gcloud" " composer environments run" @@ -363,7 +363,7 @@ def main( pprint.pformat(target_environment), ) logger.warning( - "Composer 3 environnment workloads config may be different from the" + "Composer 3 environment workloads config may be different from the" " source environment." ) logger.warning( @@ -413,7 +413,7 @@ def main( client.load_snapshot(target_environment_name, snapshot_path) logger.info("Snapshot loaded.") - # 6. Unpase DAGs in the new environment + # 6. Unpause DAGs in the new environment logger.info("STEP 6: Unpausing DAGs in the new environment...") all_dags_present = False # Wait until all DAGs from source environment are visible. From 749fdc3a44a6bd959a42573af84061e12b6d49b9 Mon Sep 17 00:00:00 2001 From: Katie McLaughlin Date: Tue, 6 Jan 2026 08:31:04 +1100 Subject: [PATCH 16/67] feat!: remove Python 2.7 from tested versions (#13675) * feat!: remove Python 2.7 from ALL_VERSIONS * remove app engine forked noxfile-template This file should be no longer required since the only difference was 2.7 * remove 2.7 configurations --- .kokoro/python2.7/common.cfg | 54 ------ .kokoro/python2.7/continuous.cfg | 21 --- .kokoro/python2.7/periodic.cfg | 22 --- .kokoro/python2.7/presubmit.cfg | 21 --- appengine/standard/noxfile-template.py | 246 ------------------------- noxfile-template.py | 2 +- 6 files changed, 1 insertion(+), 365 deletions(-) delete mode 100644 .kokoro/python2.7/common.cfg delete mode 100644 .kokoro/python2.7/continuous.cfg delete mode 100644 .kokoro/python2.7/periodic.cfg delete mode 100644 .kokoro/python2.7/presubmit.cfg delete mode 100644 appengine/standard/noxfile-template.py diff --git a/.kokoro/python2.7/common.cfg b/.kokoro/python2.7/common.cfg deleted file mode 100644 index ad2c8f64523..00000000000 --- a/.kokoro/python2.7/common.cfg +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright 2019 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Format: //devtools/kokoro/config/proto/build.proto - -timeout_mins: 300 - -# Configure the docker image for kokoro-trampoline. -env_vars: { - key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" -} - -# Download trampoline resources. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Use the trampoline script to run in docker. -build_file: "python-docs-samples/.kokoro/trampoline_v2.sh" - -# Download secrets from Cloud Storage. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" - -# Access btlr binaries used in the tests -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/btlr" - -# Copy results for Resultstore -action { - define_artifacts { - regex: "**/*sponge_log.xml" - } -} - -# Specify which tests to run -env_vars: { - key: "RUN_TESTS_SESSION" - value: "py-2.7" -} - -# Declare build specific Cloud project. -env_vars: { - key: "BUILD_SPECIFIC_GCLOUD_PROJECT" - value: "python-docs-samples-tests" -} diff --git a/.kokoro/python2.7/continuous.cfg b/.kokoro/python2.7/continuous.cfg deleted file mode 100644 index cfbe29058c8..00000000000 --- a/.kokoro/python2.7/continuous.cfg +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2020 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Format: //devtools/kokoro/config/proto/build.proto - -# Tell the trampoline which build file to use. -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: ".kokoro/tests/run_tests_diff_head.sh" -} diff --git a/.kokoro/python2.7/periodic.cfg b/.kokoro/python2.7/periodic.cfg deleted file mode 100644 index 1921dd0a999..00000000000 --- a/.kokoro/python2.7/periodic.cfg +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright 2020 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Format: //devtools/kokoro/config/proto/build.proto - -# Tell the trampoline which build file to use. -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: ".kokoro/tests/run_tests.sh" -} - diff --git a/.kokoro/python2.7/presubmit.cfg b/.kokoro/python2.7/presubmit.cfg deleted file mode 100644 index d74d307bbed..00000000000 --- a/.kokoro/python2.7/presubmit.cfg +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2019 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Format: //devtools/kokoro/config/proto/build.proto - -# Tell the trampoline which build file to use. -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: ".kokoro/tests/run_tests_diff_main.sh" -} diff --git a/appengine/standard/noxfile-template.py b/appengine/standard/noxfile-template.py deleted file mode 100644 index ae73deaa7f1..00000000000 --- a/appengine/standard/noxfile-template.py +++ /dev/null @@ -1,246 +0,0 @@ -# Copyright 2019 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import print_function - -import os -from pathlib import Path -import sys - -import nox -import tempfile - - -# WARNING - WARNING - WARNING - WARNING - WARNING -# WARNING - WARNING - WARNING - WARNING - WARNING -# DO NOT EDIT THIS FILE EVER! -# WARNING - WARNING - WARNING - WARNING - WARNING -# WARNING - WARNING - WARNING - WARNING - WARNING - -# Copy `noxfile_config.py` to your directory and modify it instead. - -# `TEST_CONFIG` dict is a configuration hook that allows users to -# modify the test configurations. The values here should be in sync -# with `noxfile_config.py`. Users will copy `noxfile_config.py` into -# their directory and modify it. - -TEST_CONFIG = { - # You can opt out from the test for specific Python versions. - "ignored_versions": ["2.7", "3.7", "3.8", "3.10", "3.12", "3.13"], - # An envvar key for determining the project id to use. Change it - # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a - # build specific Cloud project. You can also use your own string - # to use your own Cloud project. - "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", - # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', - # A dictionary you want to inject into your test. Don't put any - # secrets here. These values will override predefined values. - "envs": {}, -} - - -try: - # Ensure we can import noxfile_config in the project's directory. - sys.path.append(".") - from noxfile_config import TEST_CONFIG_OVERRIDE -except ImportError as e: - print("No user noxfile_config found: detail: {}".format(e)) - TEST_CONFIG_OVERRIDE = {} - -# Update the TEST_CONFIG with the user supplied values. -TEST_CONFIG.update(TEST_CONFIG_OVERRIDE) - - -def get_pytest_env_vars(): - """Returns a dict for pytest invocation.""" - ret = {} - - # Override the GCLOUD_PROJECT and the alias. - env_key = TEST_CONFIG["gcloud_project_env"] - # This should error out if not set. - ret["GOOGLE_CLOUD_PROJECT"] = os.environ[env_key] - ret["GCLOUD_PROJECT"] = os.environ[env_key] # deprecated - - # Apply user supplied envs. - ret.update(TEST_CONFIG["envs"]) - return ret - - -# DO NOT EDIT - automatically generated. -# All versions used to tested samples. -ALL_VERSIONS = ["2.7", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] - -# Any default versions that should be ignored. -IGNORED_VERSIONS = ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] - -TESTED_VERSIONS = sorted([v for v in ALL_VERSIONS if v not in IGNORED_VERSIONS]) - -INSTALL_LIBRARY_FROM_SOURCE = bool(os.environ.get("INSTALL_LIBRARY_FROM_SOURCE", False)) -# -# Style Checks -# - - -# Ignore I202 "Additional newline in a section of imports." to accommodate -# region tags in import blocks. Since we specify an explicit ignore, we also -# have to explicitly ignore the list of default ignores: -# `E121,E123,E126,E226,E24,E704,W503,W504` as shown by `flake8 --help`. -def _determine_local_import_names(start_dir): - """Determines all import names that should be considered "local". - - This is used when running the linter to insure that import order is - properly checked. - """ - file_ext_pairs = [os.path.splitext(path) for path in os.listdir(start_dir)] - return [ - basename - for basename, extension in file_ext_pairs - if extension == ".py" - or os.path.isdir(os.path.join(start_dir, basename)) - and basename not in ("__pycache__") - ] - - -FLAKE8_COMMON_ARGS = [ - "--show-source", - "--builtin=gettext", - "--max-complexity=20", - "--import-order-style=google", - "--exclude=.nox,.cache,env,lib,generated_pb2,*_pb2.py,*_pb2_grpc.py", - "--ignore=E121,E123,E126,E203,E226,E24,E266,E501,E704,W503,W504,I100,I201,I202", - "--max-line-length=88", -] - - -@nox.session -def lint(session): - session.install("flake8", "flake8-import-order") - - local_names = _determine_local_import_names(".") - args = FLAKE8_COMMON_ARGS + [ - "--application-import-names", - ",".join(local_names), - ".", - ] - session.run("flake8", *args) - - -# -# Sample Tests -# - - -PYTEST_COMMON_ARGS = ["--junitxml=sponge_log.xml"] - - -def _session_tests(session, post_install=None): - """Runs py.test for a particular project.""" - if os.path.exists("requirements.txt"): - session.install("-r", "requirements.txt") - - if os.path.exists("requirements-test.txt"): - session.install("-r", "requirements-test.txt") - - if post_install: - post_install(session) - - session.run( - "pytest", - *(PYTEST_COMMON_ARGS + session.posargs), - # Pytest will return 5 when no tests are collected. This can happen - # on travis where slow and flaky tests are excluded. - # See http://doc.pytest.org/en/latest/_modules/_pytest/main.html - success_codes=[0, 5], - env=get_pytest_env_vars() - ) - - -_GAE_ROOT = os.environ.get("GAE_ROOT") -if _GAE_ROOT is None: - _GAE_ROOT = tempfile.mkdtemp() - - -def find_download_appengine_sdk_py(filename): - """Find a file with the given name upwards.""" - d = os.getcwd() - while d != "/": - fullpath = os.path.join(d, filename) - if os.path.isfile(fullpath): - return fullpath - d = os.path.abspath(d + "/../") - - -def _setup_appengine_sdk(session): - """Installs the App Engine SDK, if needed.""" - session.env["GAE_SDK_PATH"] = os.path.join(_GAE_ROOT, "google_appengine") - download_appengine_sdk_py = find_download_appengine_sdk_py( - "download-appengine-sdk.py" - ) - session.install("requests") - session.run("python", download_appengine_sdk_py, _GAE_ROOT) - - -@nox.session(python=ALL_VERSIONS) -def py(session): - """Runs py.test for a sample using the specified version of Python.""" - if session.python in TESTED_VERSIONS: - # Create a lib directory if needed, - # otherwise the App Engine vendor library will complain. - if not os.path.isdir("lib"): - os.mkdir("lib") - - # mailjet_rest has an issue with requests being required pre install - # https://github.com/mailjet/mailjet-apiv3-python/issues/38 - if "appengine/standard/mailjet" in os.getcwd(): - session.install("requests") - - _session_tests(session, post_install=_setup_appengine_sdk) - else: - print("SKIPPED: {} tests are disabled for this sample.".format(session.python)) - - -# -# Readmegen -# - - -def _get_repo_root(): - """Returns the root folder of the project.""" - # Get root of this repository. Assume we don't have directories nested deeper than 10 items. - p = Path(os.getcwd()) - for i in range(10): - if p is None: - break - if Path(p / ".git").exists(): - return str(p) - p = p.parent - raise Exception("Unable to detect repository root.") - - -GENERATED_READMES = sorted([x for x in Path(".").rglob("*.rst.in")]) - - -@nox.session -@nox.parametrize("path", GENERATED_READMES) -def readmegen(session, path): - """(Re-)generates the readme for a sample.""" - session.install("jinja2", "pyyaml") - - if os.path.exists(os.path.join(path, "requirements.txt")): - session.install("-r", os.path.join(path, "requirements.txt")) - - in_file = os.path.join(path, "README.rst.in") - session.run( - "python", _get_repo_root() + "/scripts/readme-gen/readme_gen.py", in_file - ) diff --git a/noxfile-template.py b/noxfile-template.py index d2c4e7608ce..09bd81c1b77 100644 --- a/noxfile-template.py +++ b/noxfile-template.py @@ -88,7 +88,7 @@ def get_pytest_env_vars() -> dict[str, str]: # All versions used to tested samples. -ALL_VERSIONS = ["2.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] +ALL_VERSIONS = ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] # Any default versions that should be ignored. IGNORED_VERSIONS = TEST_CONFIG["ignored_versions"] From d0de01cd59cbd92a365c49cf535c68bd03297901 Mon Sep 17 00:00:00 2001 From: Tomo Suzuki Date: Tue, 6 Jan 2026 22:38:58 -0500 Subject: [PATCH 17/67] chore: remove build badges (#13676) b/468377909 --- README.md | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/README.md b/README.md index e699be6032e..398102e8902 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ Python samples for [Google Cloud Platform products][cloud]. -[![Build Status][py-2.7-shield]][py-2.7-link] [![Build Status][py-3.9-shield]][py-3.9-link] [![Build Status][py-3.10-shield]][py-3.10-link] [![Build Status][py-3.11-shield]][py-3.11-link] [![Build Status][py-3.12-shield]][py-3.12-link] [![Build Status][py-3.13-shield]][py-3.13-link] - ## Google Cloud Samples Check out some of the samples found on this repository on the [Google Cloud Samples](https://cloud.google.com/docs/samples?l=python) page. @@ -66,16 +64,3 @@ Contributions welcome! See the [Contributing Guide](CONTRIBUTING.md). [cloud_python_setup]: https://cloud.google.com/python/setup [auth_command]: https://cloud.google.com/sdk/gcloud/reference/beta/auth/application-default/login [gcp_auth]: https://cloud.google.com/docs/authentication#projects_and_resources - -[py-2.7-shield]: https://storage.googleapis.com/cloud-devrel-public/python-docs-samples/badges/py-2.7.svg -[py-2.7-link]: https://storage.googleapis.com/cloud-devrel-public/python-docs-samples/badges/py-2.7.html -[py-3.9-shield]: https://storage.googleapis.com/cloud-devrel-public/python-docs-samples/badges/py-3.9.svg -[py-3.9-link]: https://storage.googleapis.com/cloud-devrel-public/python-docs-samples/badges/py-3.9.html -[py-3.10-shield]: https://storage.googleapis.com/cloud-devrel-public/python-docs-samples/badges/py-310.svg -[py-3.10-link]: https://storage.googleapis.com/cloud-devrel-public/python-docs-samples/badges/py-3.10.html -[py-3.11-shield]: https://storage.googleapis.com/cloud-devrel-public/python-docs-samples/badges/py-311.svg -[py-3.11-link]: https://storage.googleapis.com/cloud-devrel-public/python-docs-samples/badges/py-3.11.html -[py-3.12-shield]: https://storage.googleapis.com/cloud-devrel-public/python-docs-samples/badges/py-3.12.svg -[py-3.12-link]: https://storage.googleapis.com/cloud-devrel-public/python-docs-samples/badges/py-3.12.html -[py-3.13-shield]: https://storage.googleapis.com/cloud-devrel-public/python-docs-samples/badges/py-3.13.svg -[py-3.13-link]: https://storage.googleapis.com/cloud-devrel-public/python-docs-samples/badges/py-3.13.html From e55d3a7c004b9b3ca01a7395add473a97cfe58f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alan=20Ram=C3=ADrez=20Herrera?= Date: Fri, 9 Jan 2026 18:03:57 -0600 Subject: [PATCH 18/67] chore: fix detect logos snippets (#13733) --- vision/snippets/detect/detect.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/vision/snippets/detect/detect.py b/vision/snippets/detect/detect.py index 1cfa698d747..271ad1b9a53 100644 --- a/vision/snippets/detect/detect.py +++ b/vision/snippets/detect/detect.py @@ -266,7 +266,12 @@ def detect_logos(path): image = vision.Image(content=content) - response = client.logo_detection(image=image) + request = { + "image": image, + "features": [{"type_": vision.Feature.Type.LOGO_DETECTION}], + } + + response = client.annotate_image(request=request) logos = response.logo_annotations print("Logos:") @@ -293,7 +298,13 @@ def detect_logos_uri(uri): image = vision.Image() image.source.image_uri = uri - response = client.logo_detection(image=image) + request = { + "image": image, + "features": [{"type_": vision.Feature.Type.LOGO_DETECTION}], + } + + response = client.annotate_image(request=request) + logos = response.logo_annotations print("Logos:") From 1aabd31e5aa36fa1d343b89524a6d25de6ed0ac1 Mon Sep 17 00:00:00 2001 From: bhandarivijay-png Date: Mon, 12 Jan 2026 22:49:56 +0000 Subject: [PATCH 19/67] chore: migrate cleanup commands from gsutil to gcloud storage (#13732) * chore: migrate cleanup commands from gsutil to gcloud storage * chore: migrate cleanup commands from gsutil to gcloud storage --------- Co-authored-by: Jennifer Davis --- people-and-planet-ai/conftest.py | 2 +- pubsublite/spark-connector/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/people-and-planet-ai/conftest.py b/people-and-planet-ai/conftest.py index fed54feb9b9..1bf49d26a00 100644 --- a/people-and-planet-ai/conftest.py +++ b/people-and-planet-ai/conftest.py @@ -84,7 +84,7 @@ def bucket_name(test_name: str, location: str, unique_id: str) -> Iterable[str]: # Try to remove all files before deleting the bucket. # Deleting a bucket with too many files results in an error. try: - run_cmd("gsutil", "-m", "rm", "-rf", f"gs://{bucket_name}/*") + run_cmd("gcloud", "storage", "rm", "--recursive", f"gs://{bucket_name}/**") except RuntimeError: # If no files were found and it fails, ignore the error. pass diff --git a/pubsublite/spark-connector/README.md b/pubsublite/spark-connector/README.md index c133fd66f64..cdef86589f7 100644 --- a/pubsublite/spark-connector/README.md +++ b/pubsublite/spark-connector/README.md @@ -54,7 +54,7 @@ Get the connector's uber jar from this [public Cloud Storage location]. Alternat ```bash export BUCKET_ID=your-gcs-bucket-id - gsutil mb gs://$BUCKET_ID + gcloud storage buckets create gs://$BUCKET_ID ``` ## Python setup From a76ad1d7267881f18ef0c022c859c0267113f428 Mon Sep 17 00:00:00 2001 From: bhandarivijay-png Date: Mon, 12 Jan 2026 22:50:56 +0000 Subject: [PATCH 20/67] Migrate gsutil usage to gcloud storage (#13731) Co-authored-by: Jennifer Davis --- videointelligence/samples/analyze/resources/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/videointelligence/samples/analyze/resources/README.md b/videointelligence/samples/analyze/resources/README.md index 1acbef1484a..74f763d8f94 100644 --- a/videointelligence/samples/analyze/resources/README.md +++ b/videointelligence/samples/analyze/resources/README.md @@ -9,7 +9,7 @@ Copy from Google Cloud Storage to this folder for testing video analysis of local files. For `cat.mp4` used in the usage example, run the following `gcloud` command. - gsutil cp gs://cloud-samples-data/video/cat.mp4 . + gcloud storage cp gs://cloud-samples-data/video/cat.mp4 . Now, when you run the following command, the video used for label detection will be passed from here: From 21ea70b99cb1c623bc0c8751768936a2aec358f2 Mon Sep 17 00:00:00 2001 From: Katie Nguyen <21978337+katiemn@users.noreply.github.com> Date: Mon, 12 Jan 2026 21:32:08 -0800 Subject: [PATCH 21/67] feat: update Veo model to 3.1 for video extension (#13740) --- genai/video_generation/videogen_with_vid.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/genai/video_generation/videogen_with_vid.py b/genai/video_generation/videogen_with_vid.py index b28fa3b73aa..efcd63bcb4b 100644 --- a/genai/video_generation/videogen_with_vid.py +++ b/genai/video_generation/videogen_with_vid.py @@ -25,14 +25,13 @@ def generate_videos_from_video(output_gcs_uri: str) -> str: # output_gcs_uri = "gs://your-bucket/your-prefix" operation = client.models.generate_videos( - model="veo-2.0-generate-001", + model="veo-3.1-generate-preview", prompt="a butterfly flies in and lands on the flower", video=Video( uri="gs://cloud-samples-data/generative-ai/video/flower.mp4", mime_type="video/mp4", ), config=GenerateVideosConfig( - aspect_ratio="16:9", output_gcs_uri=output_gcs_uri, ), ) From c27330dc196a060e91bb838c41f63b6353df8633 Mon Sep 17 00:00:00 2001 From: Katie Nguyen <21978337+katiemn@users.noreply.github.com> Date: Wed, 14 Jan 2026 13:35:39 -0800 Subject: [PATCH 22/67] feat: change aspect ratio for veo r2v sample (#13741) --- genai/video_generation/videogen_with_reference.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/genai/video_generation/videogen_with_reference.py b/genai/video_generation/videogen_with_reference.py index 74f03afa68b..6543530ff9d 100644 --- a/genai/video_generation/videogen_with_reference.py +++ b/genai/video_generation/videogen_with_reference.py @@ -26,18 +26,18 @@ def generate_videos_from_reference(output_gcs_uri: str) -> str: operation = client.models.generate_videos( model="veo-3.1-generate-preview", - prompt="slowly rotate this coffee mug in a 360 degree circle", + prompt="A person walks in carrying a vase full of flowers and places the vase on a kitchen table.", config=GenerateVideosConfig( reference_images=[ VideoGenerationReferenceImage( image=Image( - gcs_uri="gs://cloud-samples-data/generative-ai/image/mug.png", + gcs_uri="gs://cloud-samples-data/generative-ai/image/vase.png", mime_type="image/png", ), reference_type="asset", ), ], - aspect_ratio="16:9", + aspect_ratio="9:16", output_gcs_uri=output_gcs_uri, ), ) From 86dff30f6fd1e44b78964c97f6c9adffcd3494c7 Mon Sep 17 00:00:00 2001 From: Katie Nguyen <21978337+katiemn@users.noreply.github.com> Date: Fri, 23 Jan 2026 10:35:31 -0800 Subject: [PATCH 23/67] feat: update vto model (#13760) --- genai/image_generation/imggen_virtual_try_on_with_txt_img.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/genai/image_generation/imggen_virtual_try_on_with_txt_img.py b/genai/image_generation/imggen_virtual_try_on_with_txt_img.py index 98d0c17c76e..f1e6b6cc5cd 100644 --- a/genai/image_generation/imggen_virtual_try_on_with_txt_img.py +++ b/genai/image_generation/imggen_virtual_try_on_with_txt_img.py @@ -26,7 +26,7 @@ def virtual_try_on(output_file: str) -> Image: # output_file = "output-image.png" image = client.models.recontext_image( - model="virtual-try-on-preview-08-04", + model="virtual-try-on-001", source=RecontextImageSource( person_image=Image.from_file(location="test_resources/man.png"), product_images=[ From 1571b8d972c437c32ff3449b0fc9c8ca8f10cc1c Mon Sep 17 00:00:00 2001 From: Jennifer Davis Date: Mon, 26 Jan 2026 15:04:31 -0800 Subject: [PATCH 24/67] fix: remove all individual assignments and empty comments (#13762) folks have moved on and this should stick to teams, some of the teams are no longer in operation either, but we should audit that separately. this removes assigning individuals to issues at the minimum. this should ultimately be migrated over to GitHub functionality but this resolves individuals getting assigned at the minimum. --- .github/blunderbuss.yml | 62 ----------------------------------------- 1 file changed, 62 deletions(-) diff --git a/.github/blunderbuss.yml b/.github/blunderbuss.yml index bf218bbc3f9..f298284000e 100644 --- a/.github/blunderbuss.yml +++ b/.github/blunderbuss.yml @@ -16,12 +16,6 @@ # Updates should be made to both assign_issues_by & assign_prs_by sections ### assign_issues_by: - # DEE teams - - labels: - - "api: people-and-planet-ai" - to: - - davidcavazos - # AppEco teams - labels: - "api: cloudsql" @@ -57,21 +51,6 @@ assign_issues_by: to: - GoogleCloudPlatform/api-bigquery - # AppEco individuals - - labels: - - "api: aml-ai" - to: - - nickcook - - labels: - - "api: bigquery" - to: - - shollyman - - labels: - - "api: datascienceonramp" - to: - - leahecole - - bradmiro - # Self-service teams - labels: - "api: asset" @@ -120,27 +99,10 @@ assign_issues_by: to: - GoogleCloudPlatform/googleapi-dataplex - # Self-service individuals - - labels: - - "api: auth" - to: - - arithmetic1728 - - labels: - - "api: appengine" - to: - - jinglundong - - ### # Updates should be made to both assign_issues_by & assign_prs_by sections ### assign_prs_by: - # DEE teams - - labels: - - "api: people-and-planet-ai" - to: - - davidcavazos - # AppEco teams - labels: - "api: cloudsql" @@ -170,17 +132,6 @@ assign_prs_by: to: - GoogleCloudPlatform/cloud-dpes-composer - # AppEco individuals - - labels: - - "api: bigquery" - to: - - shollyman - - labels: - - "api: datascienceonramp" - to: - - leahecole - - bradmiro - # Self-service teams - labels: - "api: asset" @@ -235,16 +186,3 @@ assign_prs_by: - "api: connectgateway" to: - GoogleCloudPlatform/connectgateway - # Self-service individuals - - labels: - - "api: auth" - to: - - arithmetic1728 - - labels: - - "api: appengine" - to: - - jinglundong - -### -# Updates should be made to both assign_issues_by & assign_prs_by sections -### From 688be802ca4abd316e08e54ee577f9e49f974c4a Mon Sep 17 00:00:00 2001 From: Anayeli Date: Thu, 29 Jan 2026 15:03:56 -0600 Subject: [PATCH 25/67] chore(bigquery): Remove unused legacy region tag for delete transfer (#13764) --- bigquery-datatransfer/snippets/manage_transfer_configs.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/bigquery-datatransfer/snippets/manage_transfer_configs.py b/bigquery-datatransfer/snippets/manage_transfer_configs.py index cd865455c10..3b47bb37e1f 100644 --- a/bigquery-datatransfer/snippets/manage_transfer_configs.py +++ b/bigquery-datatransfer/snippets/manage_transfer_configs.py @@ -162,24 +162,20 @@ def schedule_backfill_manual_transfer(override_values={}): def delete_config(override_values={}): - # [START bigquerydatatransfer_delete_transfer] import google.api_core.exceptions from google.cloud import bigquery_datatransfer transfer_client = bigquery_datatransfer.DataTransferServiceClient() transfer_config_name = "projects/1234/locations/us/transferConfigs/abcd" - # [END bigquerydatatransfer_delete_transfer] # To facilitate testing, we replace values with alternatives # provided by the testing harness. transfer_config_name = override_values.get( "transfer_config_name", transfer_config_name ) - # [START bigquerydatatransfer_delete_transfer] try: transfer_client.delete_transfer_config(name=transfer_config_name) except google.api_core.exceptions.NotFound: print("Transfer config not found.") else: print(f"Deleted transfer config: {transfer_config_name}") - # [END bigquerydatatransfer_delete_transfer] From 100b3974d4ba03ef0d72859c9c1f0716ef6e516b Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Sat, 31 Jan 2026 00:33:36 +0000 Subject: [PATCH 26/67] chore(deps): update dependency transformers to v5 (#13761) --- .../weather-forecasting/serving/weather-model/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/people-and-planet-ai/weather-forecasting/serving/weather-model/pyproject.toml b/people-and-planet-ai/weather-forecasting/serving/weather-model/pyproject.toml index 6f6c66d33a9..43c03683ccd 100644 --- a/people-and-planet-ai/weather-forecasting/serving/weather-model/pyproject.toml +++ b/people-and-planet-ai/weather-forecasting/serving/weather-model/pyproject.toml @@ -19,7 +19,7 @@ version = "1.0.0" dependencies = [ "datasets==4.0.0", "torch==2.8.0", # make sure this matches the `container_uri` in `notebooks/3-training.ipynb` - "transformers==4.48.0", + "transformers==5.0.0", ] [project.scripts] From ea541dcd190c37cc705fd4297d572e7538426cf9 Mon Sep 17 00:00:00 2001 From: Pavel Salnikov <90701144+pavel-salnikov@users.noreply.github.com> Date: Mon, 2 Feb 2026 19:18:21 +0100 Subject: [PATCH 27/67] Update images for KPO and GKEO, make samples compatible with Airflow 3 (#13750) * Make Composer 2/3 examples compatible with Airflow 3 - Change schedule_interval to schedule - Import secrets from airflow.providers.cncf.kubernetes * Update images used by KPO and GKEO examples - Use an image from marketplace.gcr.io/google/ * Fix lint * Ignore Python 3.14 when testing Airflow DAGs * Ignore Python 3.14 when testing Airflow DAGs (Airflow 1 samples) --- composer/airflow_1_samples/gke_operator.py | 2 +- composer/airflow_1_samples/kubernetes_pod_operator.py | 2 +- composer/airflow_1_samples/noxfile_config.py | 2 +- composer/workflows/gke_operator.py | 4 ++-- composer/workflows/kubernetes_pod_operator.py | 2 +- composer/workflows/kubernetes_pod_operator_c2.py | 9 +++++---- composer/workflows/noxfile_config.py | 1 + 7 files changed, 12 insertions(+), 10 deletions(-) diff --git a/composer/airflow_1_samples/gke_operator.py b/composer/airflow_1_samples/gke_operator.py index b3638655b20..082d3333f9a 100644 --- a/composer/airflow_1_samples/gke_operator.py +++ b/composer/airflow_1_samples/gke_operator.py @@ -92,7 +92,7 @@ # project-id as the gcr.io images and the service account that Composer # uses has permission to access the Google Container Registry # (the default service account has permission) - image="gcr.io/gcp-runtimes/ubuntu_18_0_4", + image="marketplace.gcr.io/google/ubuntu2204", ) # [END composer_gkeoperator_minconfig_airflow_1] diff --git a/composer/airflow_1_samples/kubernetes_pod_operator.py b/composer/airflow_1_samples/kubernetes_pod_operator.py index 11abdb6b1ec..2799f467ec9 100644 --- a/composer/airflow_1_samples/kubernetes_pod_operator.py +++ b/composer/airflow_1_samples/kubernetes_pod_operator.py @@ -93,7 +93,7 @@ # project-id as the gcr.io images and the service account that Composer # uses has permission to access the Google Container Registry # (the default service account has permission) - image="gcr.io/gcp-runtimes/ubuntu_18_0_4", + image="marketplace.gcr.io/google/ubuntu2204", ) # [END composer_kubernetespodoperator_minconfig_airflow_1] # [START composer_kubernetespodoperator_templateconfig_airflow_1] diff --git a/composer/airflow_1_samples/noxfile_config.py b/composer/airflow_1_samples/noxfile_config.py index 7185f415100..21ea6aca21a 100644 --- a/composer/airflow_1_samples/noxfile_config.py +++ b/composer/airflow_1_samples/noxfile_config.py @@ -32,7 +32,7 @@ # You can opt out from the test for specific Python versions. # Skipping for Python 3.9 due to numpy compilation failure. # Skipping 3.6 and 3.7, they are more out of date - "ignored_versions": ["2.7", "3.6", "3.7", "3.9", "3.10", "3.11", "3.12", "3.13"], + "ignored_versions": ["2.7", "3.6", "3.7", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"], # Old samples are opted out of enforcing Python type hints # All new samples should feature them "enforce_type_hints": False, diff --git a/composer/workflows/gke_operator.py b/composer/workflows/gke_operator.py index 2f1eaa62c8a..31536ba55e7 100644 --- a/composer/workflows/gke_operator.py +++ b/composer/workflows/gke_operator.py @@ -29,7 +29,7 @@ with models.DAG( "example_gcp_gke", - schedule_interval=None, # Override to match your needs + schedule=None, # Override to match your needs start_date=days_ago(1), tags=["example"], ) as dag: @@ -86,7 +86,7 @@ # project-id as the gcr.io images and the service account that Composer # uses has permission to access the Google Container Registry # (the default service account has permission) - image="gcr.io/gcp-runtimes/ubuntu_18_0_4", + image="marketplace.gcr.io/google/ubuntu2204", ) # [END composer_gkeoperator_minconfig] diff --git a/composer/workflows/kubernetes_pod_operator.py b/composer/workflows/kubernetes_pod_operator.py index f679ead81d7..26dcb9d5173 100644 --- a/composer/workflows/kubernetes_pod_operator.py +++ b/composer/workflows/kubernetes_pod_operator.py @@ -96,7 +96,7 @@ # project-id as the gcr.io images and the service account that Composer # uses has permission to access the Google Container Registry # (the default service account has permission) - image="gcr.io/gcp-runtimes/ubuntu_18_0_4", + image="marketplace.gcr.io/google/ubuntu2204", ) # [END composer_kubernetespodoperator_minconfig] # [START composer_kubernetespodoperator_templateconfig] diff --git a/composer/workflows/kubernetes_pod_operator_c2.py b/composer/workflows/kubernetes_pod_operator_c2.py index 65e43289695..0a227058d77 100644 --- a/composer/workflows/kubernetes_pod_operator_c2.py +++ b/composer/workflows/kubernetes_pod_operator_c2.py @@ -17,10 +17,11 @@ import datetime from airflow import models -from airflow.kubernetes.secret import Secret + from airflow.providers.cncf.kubernetes.operators.pod import ( KubernetesPodOperator, ) +from airflow.providers.cncf.kubernetes.secret import Secret from kubernetes.client import models as k8s_models # A Secret is an object that contains a small amount of sensitive data such as @@ -60,7 +61,7 @@ # required to debug. with models.DAG( dag_id="composer_sample_kubernetes_pod", - schedule_interval=datetime.timedelta(days=1), + schedule=datetime.timedelta(days=1), start_date=YESTERDAY, ) as dag: # Only name, image, and task_id are required to create a @@ -88,7 +89,7 @@ # project-id as the gcr.io images and the service account that Composer # uses has permission to access the Google Container Registry # (the default service account has permission) - image="gcr.io/gcp-runtimes/ubuntu_20_0_4", + image="marketplace.gcr.io/google/ubuntu2204", # Specifies path to kubernetes config. The config_file is templated. config_file="/home/airflow/composer_kube_config", # Identifier of connection that should be used @@ -130,7 +131,7 @@ task_id="ex-kube-secrets", name="ex-kube-secrets", namespace="composer-user-workloads", - image="gcr.io/gcp-runtimes/ubuntu_20_0_4", + image="marketplace.gcr.io/google/ubuntu2204", startup_timeout_seconds=300, # The secrets to pass to Pod, the Pod will fail to create if the # secrets you specify in a Secret object do not exist in Kubernetes. diff --git a/composer/workflows/noxfile_config.py b/composer/workflows/noxfile_config.py index 7eeb5bb5817..1dbb9beffd2 100644 --- a/composer/workflows/noxfile_config.py +++ b/composer/workflows/noxfile_config.py @@ -39,6 +39,7 @@ "3.10", "3.12", "3.13", + "3.14", ], # Old samples are opted out of enforcing Python type hints # All new samples should feature them From 442527178630b6b6c6e7dbe5e3b7b6fe6ca26c45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alan=20Ram=C3=ADrez=20Herrera?= Date: Mon, 2 Feb 2026 14:31:38 -0600 Subject: [PATCH 28/67] chore(bigquery-datatransfer): remove bigquerydatatransfer_list_configs region tag from manage_transfer_configs.py snippet (#13759) * chore(bigquery-datatransfer): remove bigquerydatatransfer_list_configs region tag from manage_transfer_configs.py snippet * chore(bigquery-datatransfer): remove bigquery list config snippet and tests --------- Co-authored-by: Jennifer Davis --- .../snippets/manage_transfer_configs.py | 21 ------------------- .../snippets/manage_transfer_configs_test.py | 7 ------- 2 files changed, 28 deletions(-) diff --git a/bigquery-datatransfer/snippets/manage_transfer_configs.py b/bigquery-datatransfer/snippets/manage_transfer_configs.py index 3b47bb37e1f..cfc344705a1 100644 --- a/bigquery-datatransfer/snippets/manage_transfer_configs.py +++ b/bigquery-datatransfer/snippets/manage_transfer_configs.py @@ -13,27 +13,6 @@ # limitations under the License. -def list_configs(override_values={}): - # [START bigquerydatatransfer_list_configs] - from google.cloud import bigquery_datatransfer - - transfer_client = bigquery_datatransfer.DataTransferServiceClient() - - project_id = "my-project" - # [END bigquerydatatransfer_list_configs] - # To facilitate testing, we replace values with alternatives - # provided by the testing harness. - project_id = override_values.get("project_id", project_id) - # [START bigquerydatatransfer_list_configs] - parent = transfer_client.common_project_path(project_id) - - configs = transfer_client.list_transfer_configs(parent=parent) - print("Got the following configs:") - for config in configs: - print(f"\tID: {config.name}, Schedule: {config.schedule}") - # [END bigquerydatatransfer_list_configs] - - def update_config(override_values={}): # [START bigquerydatatransfer_update_config] from google.cloud import bigquery_datatransfer diff --git a/bigquery-datatransfer/snippets/manage_transfer_configs_test.py b/bigquery-datatransfer/snippets/manage_transfer_configs_test.py index 5504f19cbf9..5aa68cc10a9 100644 --- a/bigquery-datatransfer/snippets/manage_transfer_configs_test.py +++ b/bigquery-datatransfer/snippets/manage_transfer_configs_test.py @@ -15,13 +15,6 @@ from . import manage_transfer_configs -def test_list_configs(capsys, project_id, transfer_config_name): - manage_transfer_configs.list_configs({"project_id": project_id}) - out, _ = capsys.readouterr() - assert "Got the following configs:" in out - assert transfer_config_name in out - - def test_update_config(capsys, transfer_config_name): manage_transfer_configs.update_config( { From f71b420e2b32ce69a88f90b3b53ce8bb22ac581e Mon Sep 17 00:00:00 2001 From: Jennifer Davis Date: Mon, 2 Feb 2026 12:38:37 -0800 Subject: [PATCH 29/67] fix: removing snippet-gen samples from handwritten directory (#13767) --- .../v1/language_sentiment_text.py | 55 ------------------- .../v1/language_sentiment_text_test.py | 27 --------- .../v1/requirements-test.txt | 1 - .../generated-samples/v1/requirements.txt | 1 - 4 files changed, 84 deletions(-) delete mode 100644 language/snippets/generated-samples/v1/language_sentiment_text.py delete mode 100644 language/snippets/generated-samples/v1/language_sentiment_text_test.py delete mode 100644 language/snippets/generated-samples/v1/requirements-test.txt delete mode 100644 language/snippets/generated-samples/v1/requirements.txt diff --git a/language/snippets/generated-samples/v1/language_sentiment_text.py b/language/snippets/generated-samples/v1/language_sentiment_text.py deleted file mode 100644 index 81b738f1395..00000000000 --- a/language/snippets/generated-samples/v1/language_sentiment_text.py +++ /dev/null @@ -1,55 +0,0 @@ -# -# Copyright 2018 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# DO NOT EDIT! This is a generated sample ("Request", "analyze_sentiment") - -# To install the latest published package dependency, execute the following: -# pip install google-cloud-language - -import sys - -# isort: split -# [START language_sentiment_text] - -from google.cloud import language_v1 - - -def sample_analyze_sentiment(content): - client = language_v1.LanguageServiceClient() - - # content = 'Your text to analyze, e.g. Hello, world!' - - if isinstance(content, bytes): - content = content.decode("utf-8") - - type_ = language_v1.Document.Type.PLAIN_TEXT - document = {"type_": type_, "content": content} - - response = client.analyze_sentiment(request={"document": document}) - sentiment = response.document_sentiment - print(f"Score: {sentiment.score}") - print(f"Magnitude: {sentiment.magnitude}") - - -# [END language_sentiment_text] - - -def main(): - # FIXME: Convert argv from strings to the correct types. - sample_analyze_sentiment(*sys.argv[1:]) - - -if __name__ == "__main__": - main() diff --git a/language/snippets/generated-samples/v1/language_sentiment_text_test.py b/language/snippets/generated-samples/v1/language_sentiment_text_test.py deleted file mode 100644 index cfa7199a1df..00000000000 --- a/language/snippets/generated-samples/v1/language_sentiment_text_test.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2018 Google, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import language_sentiment_text - - -def test_analyze_sentiment_text_positive(capsys): - language_sentiment_text.sample_analyze_sentiment("Happy Happy Joy Joy") - out, _ = capsys.readouterr() - assert "Score: 0." in out - - -def test_analyze_sentiment_text_negative(capsys): - language_sentiment_text.sample_analyze_sentiment("Angry Angry Sad Sad") - out, _ = capsys.readouterr() - assert "Score: -0." in out diff --git a/language/snippets/generated-samples/v1/requirements-test.txt b/language/snippets/generated-samples/v1/requirements-test.txt deleted file mode 100644 index 15d066af319..00000000000 --- a/language/snippets/generated-samples/v1/requirements-test.txt +++ /dev/null @@ -1 +0,0 @@ -pytest==8.2.0 diff --git a/language/snippets/generated-samples/v1/requirements.txt b/language/snippets/generated-samples/v1/requirements.txt deleted file mode 100644 index b432a6e4238..00000000000 --- a/language/snippets/generated-samples/v1/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -google-cloud-language==2.15.1 From 92d6169f0aa8ded47942c91381877b34b174149f Mon Sep 17 00:00:00 2001 From: Lingqing Gan Date: Mon, 2 Feb 2026 22:01:47 -0800 Subject: [PATCH 30/67] chore: migrate code from googleapis/python-db-dtypes-pandas (#13776) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add `time` and `date` dtypes Migrated from BigQuery https://github.com/googleapis/python-bigquery/pull/972 * chore: add license headers to __init__.py files * chore: fail samples nox session if python version is missing (#17) * chore(python): Add kokoro configs for python 3.10 samples testing (#26) * feat: rename dbtime and dbdate dtypes to avoid future conflicts with pandas (#32) * docs: add how-to guide and include API reference (#33) * docs: add how-to guide and include API reference * fix indentation * fix types in sample * fix types in sample * chore(samples): Add check for tests in directory (#54) Source-Link: https://github.com/googleapis/synthtool/commit/52aef91f8d25223d9dbdb4aebd94ba8eea2101f3 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:36a95b8f494e4674dc9eee9af98961293b51b86b3649942aac800ae6c1f796d4 Co-authored-by: Owl Bot * chore(python): Noxfile recognizes that tests can live in a folder (#58) Source-Link: https://github.com/googleapis/synthtool/commit/4760d8dce1351d93658cb11d02a1b7ceb23ae5d7 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:f0e4b51deef56bed74d3e2359c583fc104a8d6367da3984fc5c66938db738828 Co-authored-by: Owl Bot * chore(deps): update all dependencies (#68) * chore(deps): update all dependencies * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Update setup.py Co-authored-by: Owl Bot Co-authored-by: Tim Swast * chore: Adding support for pytest-xdist and pytest-parallel (#76) Source-Link: https://github.com/googleapis/synthtool/commit/82f5cb283efffe96e1b6cd634738e0e7de2cd90a Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:5d8da01438ece4021d135433f2cf3227aa39ef0eaccc941d62aa35e6902832ae Co-authored-by: Owl Bot * chore(deps): update dependency pytest to v7.1.0 (#80) * chore(deps): update dependency pytest to v7.1.1 (#83) * chore(python): use black==22.3.0 (#96) Source-Link: https://github.com/googleapis/synthtool/commit/6fab84af09f2cf89a031fd8671d1def6b2931b11 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:7cffbc10910c3ab1b852c05114a08d374c195a81cdec1d4a67a1d129331d0bfe * chore(python): add nox session to sort python imports (#102) Source-Link: https://github.com/googleapis/synthtool/commit/1b71c10e20de7ed3f97f692f99a0e3399b67049f Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:00c9d764fd1cd56265f12a5ef4b99a0c9e87cf261018099141e2ca5158890416 Co-authored-by: Owl Bot * chore(deps): update dependency pytest to v7.1.2 (#105) * fix: require python 3.7+ (#125) * chore(python): drop python 3.6 Source-Link: https://github.com/googleapis/synthtool/commit/4f89b13af10d086458f9b379e56a614f9d6dab7b Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:e7bb19d47c13839fe8c147e50e02e8b6cf5da8edd1af8b82208cd6f66cc2829c * require python 3.7+ in setup.py * remove python 3.6 sample configs * update product documentation in .repo-metadata.json * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * exclude templated readme * require python 3.7+ in setup.py * remove python 3.6 from noxfile * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * update README Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou * chore(deps): update dependency pytest to v7.1.3 (#144) * chore(deps): update all dependencies * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou * chore: detect samples tests in nested directories (#146) Source-Link: https://github.com/googleapis/synthtool/commit/50db768f450a50d7c1fd62513c113c9bb96fd434 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:e09366bdf0fd9c8976592988390b24d53583dd9f002d476934da43725adbb978 Co-authored-by: Owl Bot * chore(deps): update dependency pytest to v7.2.0 (#151) * chore(python): drop flake8-import-order in samples noxfile (#156) Source-Link: https://github.com/googleapis/synthtool/commit/6ed3a831cb9ff69ef8a504c353e098ec0192ad93 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:3abfa0f1886adaf0b83f07cb117b24a639ea1cb9cffe56d43280b977033563eb Co-authored-by: Owl Bot * chore(python): add support for python 3.11 (#168) Source-Link: https://github.com/googleapis/synthtool/commit/7197a001ffb6d8ce7b0b9b11c280f0c536c1033a Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:c43f1d918bcf817d337aa29ff833439494a158a0831508fda4ec75dc4c0d0320 Co-authored-by: Owl Bot * chore(deps): update dependency pytest to v7.2.1 (#170) * chore(deps): update dependency pytest to v7.2.1 * use python 3.11 in prerelease github action workflow * use python 3.11 in compliance github action workflow --------- Co-authored-by: Anthonios Partheniou * chore(deps): update dependency pytest to v7.2.2 (#174) * chore(deps): update dependency pytest to v7.3.1 (#184) * chore(deps): update dependency pytest to v7.3.2 (#188) Co-authored-by: Anthonios Partheniou * chore(deps): update dependency pytest to v7.4.0 (#191) Co-authored-by: Anthonios Partheniou * chore(deps): update all dependencies (#209) * chore(deps): update all dependencies * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot * chore(deps): update all dependencies (#221) * chore(deps): update all dependencies * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot * feat: Add support for Python 3.12 (#223) * chore(python): Add Python 3.12 Source-Link: https://github.com/googleapis/synthtool/commit/af16e6d4672cc7b400f144de2fc3068b54ff47d2 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:bacc3af03bff793a03add584537b36b5644342931ad989e3ba1171d3bd5399f5 * add constraints file for python 3.12 * remove constraints file for python 3.6 * Add trove classifiers for python 3.11 / 3.12 * update prerelease and compliance workflows to use python 3.12 --------- Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou * chore(deps): update dependency pytest to v7.4.4 (#235) Co-authored-by: Lingqing Gan * chore(deps): update dependency pytest to v8 (#239) * chore(deps): update dependency pytest to v8 * pin pytest == 7.4.4 for python 3.7 --------- Co-authored-by: Lingqing Gan * chore(deps): update dependency pytest to v8.0.1 (#243) * chore(deps): update dependency pytest to v8.0.1 * pin pytest version for python==3.7 --------- Co-authored-by: Lingqing Gan * chore(deps): update dependency pytest to v8.0.2 (#247) * chore(deps): update dependency pytest to v8.0.2 * pin pytest==7.4.4 for python 3.7 --------- Co-authored-by: Lingqing Gan * chore(deps): update dependency pytest to v8.1.1 (#251) * chore(deps): update dependency pytest to v8.1.1 * pin pytest==7.4.4 for python 3.7 --------- Co-authored-by: Lingqing Gan * testing: use arbitrary equality for pinned pytest version (#262) * chore: format code files with nox (#281) * chore(deps): update all dependencies (#290) * chore(deps): update all dependencies * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot * build: use multiScm for Kokoro release builds (#294) Source-Link: https://github.com/googleapis/synthtool/commit/0da16589204e7f61911f64fcb30ac2d3b6e59b31 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:5cddfe2fb5019bbf78335bc55f15bc13e18354a56b3ff46e1834f8e540807f05 Co-authored-by: Owl Bot * deps!: Drop support for Python 3.7 and 3.8 (AI Experiment) (#337) * feat: Drop support for Python 3.7 and 3.8 Removes support for Python 3.7 and 3.8, establishing Python 3.9 as the new minimum supported version. This change involves: - Updating `python_requires` and classifiers in `setup.py`. - Modifying Python versions in `noxfile.py` (default, unit tests, system tests) and ensuring constraint file logic remains correct. - Updating the GitHub Actions workflow (`unittest.yml`) matrix, runner, and coverage job version. - Deleting constraint files for Python 3.7 and 3.8 (`testing/constraints-3.7.txt`, `testing/constraints-3.8.txt`). - Removing Kokoro sample configuration directories (`.kokoro/samples/python3.7/`, `.kokoro/samples/python3.8/`). - Updating supported version mentions in `README.rst`. - Removing 3.7 and 3.8 from the `ALL_VERSIONS` list in `samples/snippets/noxfile.py`. * Updates python version in lint.yml * Updates owlbot, removing reference to 3.8 * Updates CONTRIBUTING.rst * updates pytest warnings * Removes test_samples-impl ref to older virtualenv package * Removes references to pandas older than 1.5.0 * Removes pandas older than 1.5 and misc changes * updates pandas in setup.py * more updates related to pandas * still broken * Updates FutureWarning tests to account for unittest coverage * Updates json array type tests to account for unittest coverage * updates python version checks to ensure coverage * update json test for unittest coverage * Update pandas_backports unittests to ensure coverage * Updates per review comments * moves class from version specific compliance file to generic file * Removes weird cut and paste error * fix linting errors * updates import statement to ensure import of JSONArrowType * Revise required github status checks * update linting * temporarily marking a class as no cover * more updates * marked several snippets as pragma no cover * updates linting * Updates constraints and setup.py * migrates class from one time compliance file to another * updating pyarrow version * Updates linting * removes determine all and module reload tests * updates re: ndarrybackedextensionarray * testing blacken as part of owlbot processing using 3.8 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * updates blacken to 3.10 * update python version in lint.yml * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * updates owlbot.py * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * updates owlbot.py * testing lint.yml * testing linting issue * testing linting issue * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * updates lint_setup session * Update noxfile.py --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: Owl Bot * test: updates python versions and sets owlbot excludes (#367) * chore(deps): update all dependencies * updates to prevent changes to docs and lint.ymls * updates lint python version to 3.10 * remove comments, update lint python version * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Mend Renovate Co-authored-by: Owl Bot * chore(deps): update dependency pytest to v8.4.1 (#368) * chore(deps): update all dependencies * Update docs.yml * Update lint.yml --------- Co-authored-by: Lingqing Gan * chore(deps): update all dependencies (#373) * feat: Add support for Python 3.14 (#380) This PR adds Python 3.14 as a supported version, and fixed tests for the new version This PR also adds a dependency bound to pandas, as the [upcoming 3.0.0 version is incompatible](https://github.com/googleapis/python-db-dtypes-pandas/issues/381) * correct header * add noxconfig.py * delete noxfile.py * add * lint --------- Co-authored-by: Tim Swast Co-authored-by: gcf-owl-bot[bot] <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Co-authored-by: Owl Bot Co-authored-by: WhiteSource Renovate Co-authored-by: Anthonios Partheniou Co-authored-by: Chelsea Lin <124939984+chelsea-lin@users.noreply.github.com> Co-authored-by: Chalmer Lowe Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: Daniel Sanche --- bigquery/python-db-dtypes-pandas/__init__.py | 13 +++ bigquery/python-db-dtypes-pandas/pytest.ini | 0 .../snippets/__init__.py | 13 +++ .../snippets/noxconfig.py | 42 ++++++++++ .../snippets/pandas_date_and_time.py | 79 +++++++++++++++++++ .../snippets/pandas_date_and_time_test.py | 60 ++++++++++++++ .../snippets/requirements-test.txt | 1 + .../snippets/requirements.txt | 4 + 8 files changed, 212 insertions(+) create mode 100644 bigquery/python-db-dtypes-pandas/__init__.py create mode 100644 bigquery/python-db-dtypes-pandas/pytest.ini create mode 100644 bigquery/python-db-dtypes-pandas/snippets/__init__.py create mode 100644 bigquery/python-db-dtypes-pandas/snippets/noxconfig.py create mode 100644 bigquery/python-db-dtypes-pandas/snippets/pandas_date_and_time.py create mode 100644 bigquery/python-db-dtypes-pandas/snippets/pandas_date_and_time_test.py create mode 100644 bigquery/python-db-dtypes-pandas/snippets/requirements-test.txt create mode 100644 bigquery/python-db-dtypes-pandas/snippets/requirements.txt diff --git a/bigquery/python-db-dtypes-pandas/__init__.py b/bigquery/python-db-dtypes-pandas/__init__.py new file mode 100644 index 00000000000..7e1ec16ec8c --- /dev/null +++ b/bigquery/python-db-dtypes-pandas/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/bigquery/python-db-dtypes-pandas/pytest.ini b/bigquery/python-db-dtypes-pandas/pytest.ini new file mode 100644 index 00000000000..e69de29bb2d diff --git a/bigquery/python-db-dtypes-pandas/snippets/__init__.py b/bigquery/python-db-dtypes-pandas/snippets/__init__.py new file mode 100644 index 00000000000..7e1ec16ec8c --- /dev/null +++ b/bigquery/python-db-dtypes-pandas/snippets/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/bigquery/python-db-dtypes-pandas/snippets/noxconfig.py b/bigquery/python-db-dtypes-pandas/snippets/noxconfig.py new file mode 100644 index 00000000000..b9d835eefee --- /dev/null +++ b/bigquery/python-db-dtypes-pandas/snippets/noxconfig.py @@ -0,0 +1,42 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Default TEST_CONFIG_OVERRIDE for python repos. + +# You can copy this file into your directory, then it will be imported from +# the noxfile.py. + +# The source of truth: +# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/noxfile_config.py + +TEST_CONFIG_OVERRIDE = { + # You can opt out from the test for specific Python versions. + "ignored_versions": ["2.7", "3.7", "3.8"], + # Old samples are opted out of enforcing Python type hints + # All new samples should feature them + "enforce_type_hints": True, + # An envvar key for determining the project id to use. Change it + # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a + # build specific Cloud project. You can also use your own string + # to use your own Cloud project. + "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", + # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', + # If you need to use a specific version of pip, + # change pip_version_override to the string representation + # of the version number, for example, "20.2.4" + "pip_version_override": None, + # A dictionary you want to inject into your test. Don't put any + # secrets here. These values will override predefined values. + "envs": {}, +} diff --git a/bigquery/python-db-dtypes-pandas/snippets/pandas_date_and_time.py b/bigquery/python-db-dtypes-pandas/snippets/pandas_date_and_time.py new file mode 100644 index 00000000000..b6e55813064 --- /dev/null +++ b/bigquery/python-db-dtypes-pandas/snippets/pandas_date_and_time.py @@ -0,0 +1,79 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def pandas_date_and_time(): + # [START bigquery_pandas_date_create] + + import datetime + + import pandas as pd + + import db_dtypes # noqa import to register dtypes + + dates = pd.Series([datetime.date(2021, 9, 17), "2021-9-18"], dtype="dbdate") + + # [END bigquery_pandas_date_create] + # [START bigquery_pandas_date_as_datetime] + + datetimes = dates.astype("datetime64") + + # [END bigquery_pandas_date_as_datetime] + # [START bigquery_pandas_date_sub] + + dates2 = pd.Series(["2021-1-1", "2021-1-2"], dtype="dbdate") + diffs = dates - dates2 + + # [END bigquery_pandas_date_sub] + # [START bigquery_pandas_date_add_offset] + + do = pd.DateOffset(days=1) + after = dates + do + before = dates - do + + # [END bigquery_pandas_date_add_offset] + # [START bigquery_pandas_time_create] + + times = pd.Series([datetime.time(1, 2, 3, 456789), "12:00:00.6"], dtype="dbtime") + + # [END bigquery_pandas_time_create] + # [START bigquery_pandas_time_as_timedelta] + + timedeltas = times.astype("timedelta64") + + # [END bigquery_pandas_time_as_timedelta] + + # Combine datetime64 and timedelta64 to confirm adding dates and times are + # equivalent. + combined0 = datetimes + timedeltas + + # [START bigquery_pandas_combine_date_time] + + combined = dates + times + + # [END bigquery_pandas_combine_date_time] + + return ( + dates, + datetimes, + dates2, + diffs, + do, + after, + before, + times, + timedeltas, + combined, + combined0, + ) diff --git a/bigquery/python-db-dtypes-pandas/snippets/pandas_date_and_time_test.py b/bigquery/python-db-dtypes-pandas/snippets/pandas_date_and_time_test.py new file mode 100644 index 00000000000..56641765c30 --- /dev/null +++ b/bigquery/python-db-dtypes-pandas/snippets/pandas_date_and_time_test.py @@ -0,0 +1,60 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime + +import numpy as np +from pandas import Timestamp + + +def test_pandas_date_and_time(): + from .pandas_date_and_time import pandas_date_and_time + + ( + dates, + _, + dates2, + diffs, + do, + after, + before, + times, + _, + combined, + combined0, + ) = pandas_date_and_time() + + assert str(dates.dtype) == "dbdate" + assert list(dates) == [datetime.date(2021, 9, 17), datetime.date(2021, 9, 18)] + + assert np.array_equal( + diffs, + dates.astype("datetime64") - dates2.astype("datetime64"), + ) + + assert np.array_equal(after, dates.astype("object") + do) + assert np.array_equal(before, dates.astype("object") - do) + + assert str(times.dtype) == "dbtime" + assert list(times) == [ + datetime.time(1, 2, 3, 456789), + datetime.time(12, 0, 0, 600000), + ] + + for c in combined0, combined: + assert str(c.dtype) == "datetime64[ns]" + assert list(c) == [ + Timestamp("2021-09-17 01:02:03.456789"), + Timestamp("2021-09-18 12:00:00.600000"), + ] diff --git a/bigquery/python-db-dtypes-pandas/snippets/requirements-test.txt b/bigquery/python-db-dtypes-pandas/snippets/requirements-test.txt new file mode 100644 index 00000000000..9471b3d92fb --- /dev/null +++ b/bigquery/python-db-dtypes-pandas/snippets/requirements-test.txt @@ -0,0 +1 @@ +pytest==8.4.2 diff --git a/bigquery/python-db-dtypes-pandas/snippets/requirements.txt b/bigquery/python-db-dtypes-pandas/snippets/requirements.txt new file mode 100644 index 00000000000..5a18bf31224 --- /dev/null +++ b/bigquery/python-db-dtypes-pandas/snippets/requirements.txt @@ -0,0 +1,4 @@ +db-dtypes +numpy +pandas +pyarrow From 2bd9864da46efdfebad0d0a4896807d5f90ef064 Mon Sep 17 00:00:00 2001 From: Jennifer Davis Date: Tue, 3 Feb 2026 13:25:45 -0800 Subject: [PATCH 31/67] chore: delete deprecated storage directory (#13784) as per b/475331567 this repo no longer referenced --- .../storage/README.md | 37 -------- .../storage/app.yaml | 25 ------ .../storage/main.py | 88 ------------------- .../storage/main_test.py | 61 ------------- .../storage/noxfile_config.py | 43 --------- .../storage/requirements-test.txt | 2 - .../storage/requirements.txt | 6 -- 7 files changed, 262 deletions(-) delete mode 100644 appengine/flexible_python37_and_earlier/storage/README.md delete mode 100644 appengine/flexible_python37_and_earlier/storage/app.yaml delete mode 100644 appengine/flexible_python37_and_earlier/storage/main.py delete mode 100644 appengine/flexible_python37_and_earlier/storage/main_test.py delete mode 100644 appengine/flexible_python37_and_earlier/storage/noxfile_config.py delete mode 100644 appengine/flexible_python37_and_earlier/storage/requirements-test.txt delete mode 100644 appengine/flexible_python37_and_earlier/storage/requirements.txt diff --git a/appengine/flexible_python37_and_earlier/storage/README.md b/appengine/flexible_python37_and_earlier/storage/README.md deleted file mode 100644 index a2af4d60741..00000000000 --- a/appengine/flexible_python37_and_earlier/storage/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# Python Google Cloud Storage sample for Google App Engine Flexible Environment - -[![Open in Cloud Shell][shell_img]][shell_link] - -[shell_img]: http://gstatic.com/cloudssh/images/open-btn.png -[shell_link]: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=appengine/flexible_python37_and_earlier/storage/README.md - -This sample demonstrates how to use [Google Cloud Storage](https://cloud.google.com/storage/) on [Google App Engine Flexible Environment](https://cloud.google.com/appengine). - -## Setup - -Before you can run or deploy the sample, you will need to do the following: - -1. Enable the Cloud Storage API in the [Google Developers Console](https://console.developers.google.com/project/_/apiui/apiview/storage/overview). - -2. Create a Cloud Storage Bucket. You can do this with the [Google Cloud SDK](https://cloud.google.com/sdk) with the following command: - - $ gsutil mb gs://[your-bucket-name] - -3. Set the default ACL on your bucket to public read in order to serve files directly from Cloud Storage. You can do this with the [Google Cloud SDK](https://cloud.google.com/sdk) with the following command: - - $ gsutil defacl set public-read gs://[your-bucket-name] - -4. Update the environment variables in ``app.yaml``. - -## Running locally - -Refer to the [top-level README](../README.md) for instructions on running and deploying. - -When running locally, you can use the [Google Cloud SDK](https://cloud.google.com/sdk) to provide authentication to use Google Cloud APIs: - - $ gcloud init - -Then set environment variables before starting your application: - - $ export CLOUD_STORAGE_BUCKET=[your-bucket-name] - $ python main.py diff --git a/appengine/flexible_python37_and_earlier/storage/app.yaml b/appengine/flexible_python37_and_earlier/storage/app.yaml deleted file mode 100644 index e21a4c0ae91..00000000000 --- a/appengine/flexible_python37_and_earlier/storage/app.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2021 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -runtime: python -env: flex -entrypoint: gunicorn -b :$PORT main:app - -runtime_config: - python_version: 3 - -#[START gae_flex_storage_yaml] -env_variables: - CLOUD_STORAGE_BUCKET: your-bucket-name -#[END gae_flex_storage_yaml] diff --git a/appengine/flexible_python37_and_earlier/storage/main.py b/appengine/flexible_python37_and_earlier/storage/main.py deleted file mode 100644 index dc06fd2ae8e..00000000000 --- a/appengine/flexible_python37_and_earlier/storage/main.py +++ /dev/null @@ -1,88 +0,0 @@ -# Copyright 2015 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# [START gae_flex_storage_app] -from __future__ import annotations - -import logging -import os - -from flask import Flask, request -from google.cloud import storage - -app = Flask(__name__) - -# Configure this environment variable via app.yaml -CLOUD_STORAGE_BUCKET = os.environ["CLOUD_STORAGE_BUCKET"] - - -@app.route("/") -def index() -> str: - return """ -
- - -
-""" - - -@app.route("/upload", methods=["POST"]) -def upload() -> str: - """Process the uploaded file and upload it to Google Cloud Storage.""" - uploaded_file = request.files.get("file") - - if not uploaded_file: - return "No file uploaded.", 400 - - # Create a Cloud Storage client. - gcs = storage.Client() - - # Get the bucket that the file will be uploaded to. - bucket = gcs.get_bucket(CLOUD_STORAGE_BUCKET) - - # Create a new blob and upload the file's content. - blob = bucket.blob(uploaded_file.filename) - - blob.upload_from_string( - uploaded_file.read(), content_type=uploaded_file.content_type - ) - - # Make the blob public. This is not necessary if the - # entire bucket is public. - # See https://cloud.google.com/storage/docs/access-control/making-data-public. - blob.make_public() - - # The public URL can be used to directly access the uploaded file via HTTP. - return blob.public_url - - -@app.errorhandler(500) -def server_error(e: Exception | int) -> str: - logging.exception("An error occurred during a request.") - return ( - """ - An internal error occurred:
{}
- See logs for full stacktrace. - """.format( - e - ), - 500, - ) - - -if __name__ == "__main__": - # This is used when running locally. Gunicorn is used to run the - # application on Google App Engine. See entrypoint in app.yaml. - app.run(host="127.0.0.1", port=8080, debug=True) -# [END gae_flex_storage_app] diff --git a/appengine/flexible_python37_and_earlier/storage/main_test.py b/appengine/flexible_python37_and_earlier/storage/main_test.py deleted file mode 100644 index ceb979d1ba6..00000000000 --- a/appengine/flexible_python37_and_earlier/storage/main_test.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright 2015 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from io import BytesIO -import os -import uuid - -import flask -import flask.testing -from google.cloud import storage -import pytest -import requests - -import main - - -@pytest.fixture -def client() -> flask.testing.FlaskClient: - main.app.testing = True - return main.app.test_client() - - -def test_index(client: flask.testing.FlaskClient) -> None: - r = client.get("/") - assert r.status_code == 200 - - -@pytest.fixture(scope="module") -def blob_name() -> str: - name = f"gae-flex-storage-{uuid.uuid4()}" - yield name - - bucket = storage.Client().bucket(os.environ["CLOUD_STORAGE_BUCKET"]) - blob = bucket.blob(name) - blob.delete() - - -def test_upload(client: flask.testing.FlaskClient, blob_name: str) -> None: - # Upload a simple file - file_content = b"This is some test content." - - r = client.post("/upload", data={"file": (BytesIO(file_content), blob_name)}) - - assert r.status_code == 200 - - # The app should return the public cloud storage URL for the uploaded - # file. Download and verify it. - cloud_storage_url = r.data.decode("utf-8") - r = requests.get(cloud_storage_url) - assert r.text.encode("utf-8") == file_content diff --git a/appengine/flexible_python37_and_earlier/storage/noxfile_config.py b/appengine/flexible_python37_and_earlier/storage/noxfile_config.py deleted file mode 100644 index 6c2c81fa22b..00000000000 --- a/appengine/flexible_python37_and_earlier/storage/noxfile_config.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright 2021 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Default TEST_CONFIG_OVERRIDE for python repos. - -# You can copy this file into your directory, then it will be imported from -# the noxfile.py. - -# The source of truth: -# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/noxfile_config.py - -TEST_CONFIG_OVERRIDE = { - # You can opt out from the test for specific Python versions. - # Skipping for Python 3.9 due to pyarrow compilation failure. - "ignored_versions": ["2.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"], - # Old samples are opted out of enforcing Python type hints - # All new samples should feature them - "enforce_type_hints": True, - # An envvar key for determining the project id to use. Change it - # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a - # build specific Cloud project. You can also use your own string - # to use your own Cloud project. - "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", - # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', - # If you need to use a specific version of pip, - # change pip_version_override to the string representation - # of the version number, for example, "20.2.4" - "pip_version_override": None, - # A dictionary you want to inject into your test. Don't put any - # secrets here. These values will override predefined values. - "envs": {"CLOUD_STORAGE_BUCKET": "python-docs-samples-tests-public"}, -} diff --git a/appengine/flexible_python37_and_earlier/storage/requirements-test.txt b/appengine/flexible_python37_and_earlier/storage/requirements-test.txt deleted file mode 100644 index f27726d7455..00000000000 --- a/appengine/flexible_python37_and_earlier/storage/requirements-test.txt +++ /dev/null @@ -1,2 +0,0 @@ -pytest==8.2.0 -google-cloud-storage==2.9.0 diff --git a/appengine/flexible_python37_and_earlier/storage/requirements.txt b/appengine/flexible_python37_and_earlier/storage/requirements.txt deleted file mode 100644 index 994d3201309..00000000000 --- a/appengine/flexible_python37_and_earlier/storage/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -Flask==3.0.3; python_version > '3.6' -Flask==2.0.3; python_version < '3.7' -werkzeug==3.0.3; python_version > '3.7' -werkzeug==2.3.8; python_version <= '3.7' -google-cloud-storage==2.9.0 -gunicorn==23.0.0 From 7b849d1a6e6bca04100e9e77edbc261a9e3009b3 Mon Sep 17 00:00:00 2001 From: Jennifer Davis Date: Tue, 3 Feb 2026 15:39:27 -0800 Subject: [PATCH 32/67] fix: remove deprecated instance django_cloudsql (#13788) resolves b/475324491 --- .../django_cloudsql/README.md | 25 --- .../django_cloudsql/app.yaml | 25 --- .../django_cloudsql/manage.py | 24 --- .../django_cloudsql/mysite/__init__.py | 0 .../django_cloudsql/mysite/settings.py | 176 ------------------ .../django_cloudsql/mysite/urls.py | 25 --- .../django_cloudsql/mysite/wsgi.py | 22 --- .../django_cloudsql/noxfile_config.py | 39 ---- .../django_cloudsql/polls/__init__.py | 0 .../django_cloudsql/polls/admin.py | 19 -- .../django_cloudsql/polls/apps.py | 19 -- .../django_cloudsql/polls/models.py | 29 --- .../django_cloudsql/polls/test_polls.py | 22 --- .../django_cloudsql/polls/urls.py | 21 --- .../django_cloudsql/polls/views.py | 19 -- .../django_cloudsql/requirements-test.txt | 2 - .../django_cloudsql/requirements.txt | 6 - 17 files changed, 473 deletions(-) delete mode 100644 appengine/flexible_python37_and_earlier/django_cloudsql/README.md delete mode 100644 appengine/flexible_python37_and_earlier/django_cloudsql/app.yaml delete mode 100755 appengine/flexible_python37_and_earlier/django_cloudsql/manage.py delete mode 100644 appengine/flexible_python37_and_earlier/django_cloudsql/mysite/__init__.py delete mode 100644 appengine/flexible_python37_and_earlier/django_cloudsql/mysite/settings.py delete mode 100644 appengine/flexible_python37_and_earlier/django_cloudsql/mysite/urls.py delete mode 100644 appengine/flexible_python37_and_earlier/django_cloudsql/mysite/wsgi.py delete mode 100644 appengine/flexible_python37_and_earlier/django_cloudsql/noxfile_config.py delete mode 100644 appengine/flexible_python37_and_earlier/django_cloudsql/polls/__init__.py delete mode 100644 appengine/flexible_python37_and_earlier/django_cloudsql/polls/admin.py delete mode 100644 appengine/flexible_python37_and_earlier/django_cloudsql/polls/apps.py delete mode 100644 appengine/flexible_python37_and_earlier/django_cloudsql/polls/models.py delete mode 100644 appengine/flexible_python37_and_earlier/django_cloudsql/polls/test_polls.py delete mode 100644 appengine/flexible_python37_and_earlier/django_cloudsql/polls/urls.py delete mode 100644 appengine/flexible_python37_and_earlier/django_cloudsql/polls/views.py delete mode 100644 appengine/flexible_python37_and_earlier/django_cloudsql/requirements-test.txt delete mode 100644 appengine/flexible_python37_and_earlier/django_cloudsql/requirements.txt diff --git a/appengine/flexible_python37_and_earlier/django_cloudsql/README.md b/appengine/flexible_python37_and_earlier/django_cloudsql/README.md deleted file mode 100644 index 60e3ff2f5e7..00000000000 --- a/appengine/flexible_python37_and_earlier/django_cloudsql/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# Getting started with Django on Google Cloud Platform on App Engine Flexible - -[![Open in Cloud Shell][shell_img]][shell_link] - -[shell_img]: http://gstatic.com/cloudssh/images/open-btn.png -[shell_link]: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=appengine/flexible_python37_and_earlier/django_cloudsql/README.md - -This repository is an example of how to run a [Django](https://www.djangoproject.com/) -app on Google App Engine Flexible Environment. It uses the -[Writing your first Django app](https://docs.djangoproject.com/en/stable/intro/tutorial01/) as the -example app to deploy. - - -# Tutorial -See our [Running Django in the App Engine Flexible Environment](https://cloud.google.com/python/django/flexible-environment) tutorial for instructions for setting up and deploying this sample application. - - -## Contributing changes - -* See [CONTRIBUTING.md](CONTRIBUTING.md) - - -## Licensing - -* See [LICENSE](LICENSE) diff --git a/appengine/flexible_python37_and_earlier/django_cloudsql/app.yaml b/appengine/flexible_python37_and_earlier/django_cloudsql/app.yaml deleted file mode 100644 index 7fcf498d62e..00000000000 --- a/appengine/flexible_python37_and_earlier/django_cloudsql/app.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2021 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# [START gaeflex_py_django_app_yaml] -runtime: python -env: flex -entrypoint: gunicorn -b :$PORT mysite.wsgi - -beta_settings: - cloud_sql_instances: PROJECT_ID:REGION:INSTANCE_NAME - -runtime_config: - python_version: 3.7 -# [END gaeflex_py_django_app_yaml] diff --git a/appengine/flexible_python37_and_earlier/django_cloudsql/manage.py b/appengine/flexible_python37_and_earlier/django_cloudsql/manage.py deleted file mode 100755 index 89fb5ae5607..00000000000 --- a/appengine/flexible_python37_and_earlier/django_cloudsql/manage.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python -# Copyright 2015 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") - - from django.core.management import execute_from_command_line - - execute_from_command_line(sys.argv) diff --git a/appengine/flexible_python37_and_earlier/django_cloudsql/mysite/__init__.py b/appengine/flexible_python37_and_earlier/django_cloudsql/mysite/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/appengine/flexible_python37_and_earlier/django_cloudsql/mysite/settings.py b/appengine/flexible_python37_and_earlier/django_cloudsql/mysite/settings.py deleted file mode 100644 index ab4d8e7d5e1..00000000000 --- a/appengine/flexible_python37_and_earlier/django_cloudsql/mysite/settings.py +++ /dev/null @@ -1,176 +0,0 @@ -# Copyright 2015 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import io -import os - -import environ -from google.cloud import secretmanager - -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - -# [START gaeflex_py_django_secret_config] -env = environ.Env(DEBUG=(bool, False)) -env_file = os.path.join(BASE_DIR, ".env") - -if os.path.isfile(env_file): - # Use a local secret file, if provided - - env.read_env(env_file) -# [START_EXCLUDE] -elif os.getenv("TRAMPOLINE_CI", None): - # Create local settings if running with CI, for unit testing - - placeholder = ( - f"SECRET_KEY=a\n" - "GS_BUCKET_NAME=None\n" - f"DATABASE_URL=sqlite://{os.path.join(BASE_DIR, 'db.sqlite3')}" - ) - env.read_env(io.StringIO(placeholder)) -# [END_EXCLUDE] -elif os.environ.get("GOOGLE_CLOUD_PROJECT", None): - # Pull secrets from Secret Manager - project_id = os.environ.get("GOOGLE_CLOUD_PROJECT") - - client = secretmanager.SecretManagerServiceClient() - settings_name = os.environ.get("SETTINGS_NAME", "django_settings") - name = f"projects/{project_id}/secrets/{settings_name}/versions/latest" - payload = client.access_secret_version(name=name).payload.data.decode("UTF-8") - - env.read_env(io.StringIO(payload)) -else: - raise Exception("No local .env or GOOGLE_CLOUD_PROJECT detected. No secrets found.") -# [END gaeflex_py_django_secret_config] - -SECRET_KEY = env("SECRET_KEY") - -# SECURITY WARNING: don't run with debug turned on in production! -# Change this to "False" when you are ready for production -DEBUG = env("DEBUG") - -# SECURITY WARNING: App Engine's security features ensure that it is safe to -# have ALLOWED_HOSTS = ['*'] when the app is deployed. If you deploy a Django -# app not on App Engine, make sure to set an appropriate host here. -ALLOWED_HOSTS = ["*"] - -# Application definition - -INSTALLED_APPS = ( - "django.contrib.admin", - "django.contrib.auth", - "django.contrib.contenttypes", - "django.contrib.sessions", - "django.contrib.messages", - "django.contrib.staticfiles", - "polls", -) - -MIDDLEWARE = ( - "django.middleware.security.SecurityMiddleware", - "django.contrib.sessions.middleware.SessionMiddleware", - "django.middleware.common.CommonMiddleware", - "django.middleware.csrf.CsrfViewMiddleware", - "django.contrib.auth.middleware.AuthenticationMiddleware", - "django.contrib.messages.middleware.MessageMiddleware", - "django.middleware.clickjacking.XFrameOptionsMiddleware", -) - -ROOT_URLCONF = "mysite.urls" - -TEMPLATES = [ - { - "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [], - "APP_DIRS": True, - "OPTIONS": { - "context_processors": [ - "django.template.context_processors.debug", - "django.template.context_processors.request", - "django.contrib.auth.context_processors.auth", - "django.contrib.messages.context_processors.messages", - ], - }, - }, -] - -WSGI_APPLICATION = "mysite.wsgi.application" - -# Database - -# [START gaeflex_py_django_database_config] -# Use django-environ to parse the connection string -DATABASES = {"default": env.db()} - -# If the flag as been set, configure to use proxy -if os.getenv("USE_CLOUD_SQL_AUTH_PROXY", None): - DATABASES["default"]["HOST"] = "127.0.0.1" - DATABASES["default"]["PORT"] = 5432 - -# [END gaeflex_py_django_database_config] - -# Use a in-memory sqlite3 database when testing in CI systems -if os.getenv("TRAMPOLINE_CI", None): - DATABASES = { - "default": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": os.path.join(BASE_DIR, "db.sqlite3"), - } - } - - -# Password validation - -AUTH_PASSWORD_VALIDATORS = [ - { - "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", # noqa: 501 - }, - { - "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", # noqa: 501 - }, - { - "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", # noqa: 501 - }, - { - "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", # noqa: 501 - }, -] - -# Internationalization -# https://docs.djangoproject.com/en/stable/topics/i18n/ - -LANGUAGE_CODE = "en-us" - -TIME_ZONE = "UTC" - -USE_I18N = True - -USE_L10N = True - -USE_TZ = True - -# Static files (CSS, JavaScript, Images) -# [START gaeflex_py_django_static_config] -# Define static storage via django-storages[google] -GS_BUCKET_NAME = env("GS_BUCKET_NAME") -STATIC_URL = "/static/" -DEFAULT_FILE_STORAGE = "storages.backends.gcloud.GoogleCloudStorage" -STATICFILES_STORAGE = "storages.backends.gcloud.GoogleCloudStorage" -GS_DEFAULT_ACL = "publicRead" -# [END gaeflex_py_django_static_config] - -# Default primary key field type -# https://docs.djangoproject.com/en/stable/ref/settings/#default-auto-field - -DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" diff --git a/appengine/flexible_python37_and_earlier/django_cloudsql/mysite/urls.py b/appengine/flexible_python37_and_earlier/django_cloudsql/mysite/urls.py deleted file mode 100644 index 62e72564fc2..00000000000 --- a/appengine/flexible_python37_and_earlier/django_cloudsql/mysite/urls.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2015 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# [START gaeflex_py_django_local_static] -from django.conf import settings -from django.conf.urls.static import static -from django.contrib import admin -from django.urls import include, path - -urlpatterns = [ - path("", include("polls.urls")), - path("admin/", admin.site.urls), -] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) -# [END gaeflex_py_django_local_static] diff --git a/appengine/flexible_python37_and_earlier/django_cloudsql/mysite/wsgi.py b/appengine/flexible_python37_and_earlier/django_cloudsql/mysite/wsgi.py deleted file mode 100644 index 968cf994b60..00000000000 --- a/appengine/flexible_python37_and_earlier/django_cloudsql/mysite/wsgi.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright 2015 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import os - -from django.core.wsgi import get_wsgi_application - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") - -application = get_wsgi_application() diff --git a/appengine/flexible_python37_and_earlier/django_cloudsql/noxfile_config.py b/appengine/flexible_python37_and_earlier/django_cloudsql/noxfile_config.py deleted file mode 100644 index a51f3680ad6..00000000000 --- a/appengine/flexible_python37_and_earlier/django_cloudsql/noxfile_config.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright 2020 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Default TEST_CONFIG_OVERRIDE for python repos. - -# You can copy this file into your directory, then it will be imported from -# the noxfile.py. - -# The source of truth: -# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/noxfile_config.py - -TEST_CONFIG_OVERRIDE = { - # You can opt out from the test for specific Python versions. - # Skipping for Python 3.9 due to pyarrow compilation failure. - "ignored_versions": ["2.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"], - # Old samples are opted out of enforcing Python type hints - # All new samples should feature them - "enforce_type_hints": False, - # An envvar key for determining the project id to use. Change it - # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a - # build specific Cloud project. You can also use your own string - # to use your own Cloud project. - "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", - # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', - # A dictionary you want to inject into your test. Don't put any - # secrets here. These values will override predefined values. - "envs": {"DJANGO_SETTINGS_MODULE": "mysite.settings"}, -} diff --git a/appengine/flexible_python37_and_earlier/django_cloudsql/polls/__init__.py b/appengine/flexible_python37_and_earlier/django_cloudsql/polls/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/appengine/flexible_python37_and_earlier/django_cloudsql/polls/admin.py b/appengine/flexible_python37_and_earlier/django_cloudsql/polls/admin.py deleted file mode 100644 index 5fc6d71455b..00000000000 --- a/appengine/flexible_python37_and_earlier/django_cloudsql/polls/admin.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2015 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from django.contrib import admin - -from .models import Question - -admin.site.register(Question) diff --git a/appengine/flexible_python37_and_earlier/django_cloudsql/polls/apps.py b/appengine/flexible_python37_and_earlier/django_cloudsql/polls/apps.py deleted file mode 100644 index 88bdacda7c7..00000000000 --- a/appengine/flexible_python37_and_earlier/django_cloudsql/polls/apps.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2015 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from django.apps import AppConfig - - -class PollsConfig(AppConfig): - name = "polls" diff --git a/appengine/flexible_python37_and_earlier/django_cloudsql/polls/models.py b/appengine/flexible_python37_and_earlier/django_cloudsql/polls/models.py deleted file mode 100644 index 5d2bf302721..00000000000 --- a/appengine/flexible_python37_and_earlier/django_cloudsql/polls/models.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright 2015 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from django.db import models - - -class Question(models.Model): - question_text = models.CharField(max_length=200) - pub_date = models.DateTimeField("date published") - - -class Choice(models.Model): - question = models.ForeignKey(Question, on_delete=models.CASCADE) - choice_text = models.CharField(max_length=200) - votes = models.IntegerField(default=0) - - -# Create your models here. diff --git a/appengine/flexible_python37_and_earlier/django_cloudsql/polls/test_polls.py b/appengine/flexible_python37_and_earlier/django_cloudsql/polls/test_polls.py deleted file mode 100644 index 3ce4c624bbb..00000000000 --- a/appengine/flexible_python37_and_earlier/django_cloudsql/polls/test_polls.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright 2020 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from django.test import Client, TestCase # noqa: 401 - - -class PollViewTests(TestCase): - def test_index_view(self): - response = self.client.get("/") - assert response.status_code == 200 - assert "Hello, world" in str(response.content) diff --git a/appengine/flexible_python37_and_earlier/django_cloudsql/polls/urls.py b/appengine/flexible_python37_and_earlier/django_cloudsql/polls/urls.py deleted file mode 100644 index ca52d749043..00000000000 --- a/appengine/flexible_python37_and_earlier/django_cloudsql/polls/urls.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2015 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from django.urls import path - -from . import views - -urlpatterns = [ - path("", views.index, name="index"), -] diff --git a/appengine/flexible_python37_and_earlier/django_cloudsql/polls/views.py b/appengine/flexible_python37_and_earlier/django_cloudsql/polls/views.py deleted file mode 100644 index 262f571d568..00000000000 --- a/appengine/flexible_python37_and_earlier/django_cloudsql/polls/views.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2015 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from django.http import HttpResponse - - -def index(request): - return HttpResponse("Hello, world. You're at the polls index.") diff --git a/appengine/flexible_python37_and_earlier/django_cloudsql/requirements-test.txt b/appengine/flexible_python37_and_earlier/django_cloudsql/requirements-test.txt deleted file mode 100644 index 5e5d2c73a81..00000000000 --- a/appengine/flexible_python37_and_earlier/django_cloudsql/requirements-test.txt +++ /dev/null @@ -1,2 +0,0 @@ -pytest==8.2.0 -pytest-django==4.9.0 diff --git a/appengine/flexible_python37_and_earlier/django_cloudsql/requirements.txt b/appengine/flexible_python37_and_earlier/django_cloudsql/requirements.txt deleted file mode 100644 index 284290f2532..00000000000 --- a/appengine/flexible_python37_and_earlier/django_cloudsql/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -Django==5.2.5 -gunicorn==23.0.0 -psycopg2-binary==2.9.10 -django-environ==0.12.0 -google-cloud-secret-manager==2.21.1 -django-storages[google]==1.14.6 From 7b085933e6e91f7692c673d0748201c2f72d40e6 Mon Sep 17 00:00:00 2001 From: Jennifer Davis Date: Tue, 3 Feb 2026 18:58:45 -0800 Subject: [PATCH 33/67] fix: remove deprecated sample (#13785) this resolves b/475331956 --- .../datastore/README.md | 24 ----- .../datastore/app.yaml | 20 ---- .../datastore/main.py | 91 ------------------- .../datastore/main_test.py | 24 ----- .../datastore/noxfile_config.py | 39 -------- .../datastore/requirements-test.txt | 1 - .../datastore/requirements.txt | 5 - 7 files changed, 204 deletions(-) delete mode 100644 appengine/flexible_python37_and_earlier/datastore/README.md delete mode 100644 appengine/flexible_python37_and_earlier/datastore/app.yaml delete mode 100644 appengine/flexible_python37_and_earlier/datastore/main.py delete mode 100644 appengine/flexible_python37_and_earlier/datastore/main_test.py delete mode 100644 appengine/flexible_python37_and_earlier/datastore/noxfile_config.py delete mode 100644 appengine/flexible_python37_and_earlier/datastore/requirements-test.txt delete mode 100644 appengine/flexible_python37_and_earlier/datastore/requirements.txt diff --git a/appengine/flexible_python37_and_earlier/datastore/README.md b/appengine/flexible_python37_and_earlier/datastore/README.md deleted file mode 100644 index 5676c53aab9..00000000000 --- a/appengine/flexible_python37_and_earlier/datastore/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# Python Google Cloud Datastore sample for Google App Engine Flexible Environment - -[![Open in Cloud Shell][shell_img]][shell_link] - -[shell_img]: http://gstatic.com/cloudssh/images/open-btn.png -[shell_link]: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=appengine/flexible_python37_and_earlier/datastore/README.md - -This sample demonstrates how to use [Google Cloud Datastore](https://cloud.google.com/datastore/) on [Google App Engine Flexible Environment](https://cloud.google.com/appengine). - -## Setup - -Before you can run or deploy the sample, you will need to enable the Cloud Datastore API in the [Google Developers Console](https://console.developers.google.com/project/_/apiui/apiview/datastore/overview). - -## Running locally - -Refer to the [top-level README](../README.md) for instructions on running and deploying. - -When running locally, you can use the [Google Cloud SDK](https://cloud.google.com/sdk) to provide authentication to use Google Cloud APIs: - - $ gcloud init - -Starting your application: - - $ python main.py diff --git a/appengine/flexible_python37_and_earlier/datastore/app.yaml b/appengine/flexible_python37_and_earlier/datastore/app.yaml deleted file mode 100644 index ca76f83fc3b..00000000000 --- a/appengine/flexible_python37_and_earlier/datastore/app.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2021 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -runtime: python -env: flex -entrypoint: gunicorn -b :$PORT main:app - -runtime_config: - python_version: 3 diff --git a/appengine/flexible_python37_and_earlier/datastore/main.py b/appengine/flexible_python37_and_earlier/datastore/main.py deleted file mode 100644 index ac1cec4ee5b..00000000000 --- a/appengine/flexible_python37_and_earlier/datastore/main.py +++ /dev/null @@ -1,91 +0,0 @@ -# Copyright 2015 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import datetime -import logging -import socket - -from flask import Flask, request -from google.cloud import datastore - - -app = Flask(__name__) - - -def is_ipv6(addr): - """Checks if a given address is an IPv6 address.""" - try: - socket.inet_pton(socket.AF_INET6, addr) - return True - except OSError: - return False - - -# [START gae_flex_datastore_app] -@app.route("/") -def index(): - ds = datastore.Client() - - user_ip = request.remote_addr - - # Keep only the first two octets of the IP address. - if is_ipv6(user_ip): - user_ip = ":".join(user_ip.split(":")[:2]) - else: - user_ip = ".".join(user_ip.split(".")[:2]) - - entity = datastore.Entity(key=ds.key("visit")) - entity.update( - { - "user_ip": user_ip, - "timestamp": datetime.datetime.now(tz=datetime.timezone.utc), - } - ) - - ds.put(entity) - query = ds.query(kind="visit", order=("-timestamp",)) - - results = [] - for x in query.fetch(limit=10): - try: - results.append("Time: {timestamp} Addr: {user_ip}".format(**x)) - except KeyError: - print("Error with result format, skipping entry.") - - output = "Last 10 visits:\n{}".format("\n".join(results)) - - return output, 200, {"Content-Type": "text/plain; charset=utf-8"} - - -# [END gae_flex_datastore_app] - - -@app.errorhandler(500) -def server_error(e): - logging.exception("An error occurred during a request.") - return ( - """ - An internal error occurred:
{}
- See logs for full stacktrace. - """.format( - e - ), - 500, - ) - - -if __name__ == "__main__": - # This is used when running locally. Gunicorn is used to run the - # application on Google App Engine. See entrypoint in app.yaml. - app.run(host="127.0.0.1", port=8080, debug=True) diff --git a/appengine/flexible_python37_and_earlier/datastore/main_test.py b/appengine/flexible_python37_and_earlier/datastore/main_test.py deleted file mode 100644 index 6b17c44ca79..00000000000 --- a/appengine/flexible_python37_and_earlier/datastore/main_test.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright 2015 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import main - - -def test_index(): - main.app.testing = True - client = main.app.test_client() - - r = client.get("/", environ_base={"REMOTE_ADDR": "127.0.0.1"}) - assert r.status_code == 200 - assert "Last 10 visits" in r.data.decode("utf-8") diff --git a/appengine/flexible_python37_and_earlier/datastore/noxfile_config.py b/appengine/flexible_python37_and_earlier/datastore/noxfile_config.py deleted file mode 100644 index 1665dd736f8..00000000000 --- a/appengine/flexible_python37_and_earlier/datastore/noxfile_config.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Default TEST_CONFIG_OVERRIDE for python repos. - -# You can copy this file into your directory, then it will be imported from -# the noxfile.py. - -# The source of truth: -# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/noxfile_config.py - -TEST_CONFIG_OVERRIDE = { - # You can opt out from the test for specific Python versions. - # Skipping for Python 3.9 due to pyarrow compilation failure. - "ignored_versions": ["2.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"], - # Old samples are opted out of enforcing Python type hints - # All new samples should feature them - "enforce_type_hints": False, - # An envvar key for determining the project id to use. Change it - # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a - # build specific Cloud project. You can also use your own string - # to use your own Cloud project. - "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", - # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', - # A dictionary you want to inject into your test. Don't put any - # secrets here. These values will override predefined values. - "envs": {}, -} diff --git a/appengine/flexible_python37_and_earlier/datastore/requirements-test.txt b/appengine/flexible_python37_and_earlier/datastore/requirements-test.txt deleted file mode 100644 index 15d066af319..00000000000 --- a/appengine/flexible_python37_and_earlier/datastore/requirements-test.txt +++ /dev/null @@ -1 +0,0 @@ -pytest==8.2.0 diff --git a/appengine/flexible_python37_and_earlier/datastore/requirements.txt b/appengine/flexible_python37_and_earlier/datastore/requirements.txt deleted file mode 100644 index ff3c9dcce0c..00000000000 --- a/appengine/flexible_python37_and_earlier/datastore/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -Flask==3.0.3; python_version > '3.6' -Flask==2.3.3; python_version < '3.7' -google-cloud-datastore==2.20.2 -gunicorn==23.0.0 -Werkzeug==3.0.3 From 60e1e6efd4973b054652868302cbb6abe987bfed Mon Sep 17 00:00:00 2001 From: Jennifer Davis Date: Tue, 3 Feb 2026 19:00:27 -0800 Subject: [PATCH 34/67] fix: remove deprecated instance of sample (#13786) fixes b/475331294 --- .../tasks/Dockerfile | 31 ----- .../tasks/README.md | 122 ---------------- .../tasks/app.flexible.yaml | 20 --- .../tasks/app.yaml | 15 -- .../tasks/create_app_engine_queue_task.py | 121 ---------------- .../create_app_engine_queue_task_test.py | 28 ---- .../tasks/main.py | 43 ------ .../tasks/main_test.py | 46 ------- .../tasks/noxfile_config.py | 39 ------ .../tasks/requirements-test.txt | 1 - .../tasks/requirements.txt | 5 - .../tasks/snippets_test.py | 130 ------------------ .../tasks/templates/index.html | 21 --- 13 files changed, 622 deletions(-) delete mode 100644 appengine/flexible_python37_and_earlier/tasks/Dockerfile delete mode 100644 appengine/flexible_python37_and_earlier/tasks/README.md delete mode 100644 appengine/flexible_python37_and_earlier/tasks/app.flexible.yaml delete mode 100644 appengine/flexible_python37_and_earlier/tasks/app.yaml delete mode 100644 appengine/flexible_python37_and_earlier/tasks/create_app_engine_queue_task.py delete mode 100644 appengine/flexible_python37_and_earlier/tasks/create_app_engine_queue_task_test.py delete mode 100644 appengine/flexible_python37_and_earlier/tasks/main.py delete mode 100644 appengine/flexible_python37_and_earlier/tasks/main_test.py delete mode 100644 appengine/flexible_python37_and_earlier/tasks/noxfile_config.py delete mode 100644 appengine/flexible_python37_and_earlier/tasks/requirements-test.txt delete mode 100644 appengine/flexible_python37_and_earlier/tasks/requirements.txt delete mode 100644 appengine/flexible_python37_and_earlier/tasks/snippets_test.py delete mode 100644 appengine/flexible_python37_and_earlier/tasks/templates/index.html diff --git a/appengine/flexible_python37_and_earlier/tasks/Dockerfile b/appengine/flexible_python37_and_earlier/tasks/Dockerfile deleted file mode 100644 index 5aaeb51144d..00000000000 --- a/appengine/flexible_python37_and_earlier/tasks/Dockerfile +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright 2021 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Use the official Python image. -# https://hub.docker.com/_/python -FROM python:3.11 - -# Copy local code to the container image. -ENV APP_HOME /app -WORKDIR $APP_HOME -COPY . . - -# Install production dependencies. -RUN pip install Flask gunicorn - -# Run the web service on container startup. Here we use the gunicorn -# webserver, with one worker process and 8 threads. -# For environments with multiple CPU cores, increase the number of workers -# to be equal to the cores available. -CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 main:app diff --git a/appengine/flexible_python37_and_earlier/tasks/README.md b/appengine/flexible_python37_and_earlier/tasks/README.md deleted file mode 100644 index 5eb60d5fa45..00000000000 --- a/appengine/flexible_python37_and_earlier/tasks/README.md +++ /dev/null @@ -1,122 +0,0 @@ -# Google Cloud Tasks Samples - -[![Open in Cloud Shell][shell_img]][shell_link] - -[shell_img]: http://gstatic.com/cloudssh/images/open-btn.png -[shell_link]: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=appengine/flexible_python37_and_earlier/tasks/README.md - -Sample command-line programs for interacting with the Cloud Tasks API -. - -App Engine queues push tasks to an App Engine HTTP target. This directory -contains both the App Engine app to deploy, as well as the snippets to run -locally to push tasks to it, which could also be called on App Engine. - -`create_app_engine_queue_task.py` is a simple command-line program to create -tasks to be pushed to the App Engine app. - -`main.py` is the main App Engine app. This app serves as an endpoint to receive -App Engine task attempts. - -`app.yaml` configures the App Engine app. - - -## Prerequisites to run locally: - -Please refer to [Setting Up a Python Development Environment](https://cloud.google.com/python/setup). - -### Authentication - -To set up authentication, please refer to our -[authentication getting started guide](https://cloud.google.com/docs/authentication/getting-started). - -### Install Dependencies - -To install the dependencies for this sample, use the following command: - -```sh -pip install -r requirements.txt -``` - -This sample uses the common protos in the [googleapis](https://github.com/googleapis/googleapis) -repository. For more info, see -[Protocol Buffer Basics](https://developers.google.com/protocol-buffers/docs/pythontutorial). - -## Deploying the App Engine App - -Deploy the App Engine app with gcloud: - -* To deploy to the Standard environment: - ```sh - gcloud app deploy app.yaml - ``` -* To deploy to the Flexible environment: - ```sh - gcloud app deploy app.flexible.yaml - ``` - -Verify the index page is serving: - -```sh -gcloud app browse -``` - -The App Engine app serves as a target for the push requests. It has an -endpoint `/example_task_handler` that reads the payload (i.e., the request body) -of the HTTP POST request and logs it. The log output can be viewed with: - -```sh -gcloud app logs read -``` - -## Creating a queue - -To create a queue using the Cloud SDK, use the following gcloud command: - -```sh -gcloud tasks queues create my-appengine-queue -``` - -Note: A newly created queue will route to the default App Engine service and -version unless configured to do otherwise. - -## Run the Sample Using the Command Line - -Set environment variables: - -First, your project ID: - -```sh -export PROJECT_ID=my-project-id -``` - -Then the queue ID, as specified at queue creation time. Queue IDs already -created can be listed with `gcloud tasks queues list`. - -```sh -export QUEUE_ID=my-appengine-queue -``` - -And finally the location ID, which can be discovered with -`gcloud tasks queues describe $QUEUE_ID`, with the location embedded in -the "name" value (for instance, if the name is -"projects/my-project/locations/us-central1/queues/my-appengine-queue", then the -location is "us-central1"). - -```sh -export LOCATION_ID=us-central1 -``` - -### Using App Engine Queues - -Running the sample will create a task, targeted at the `/example_task_handler` -endpoint, with a payload specified: - -> **Note** -> Please update -> [create_app_engine_queue_task.py](./create_app_engine_queue_task.py) before running the following -> command. - -```sh -python create_app_engine_queue_task.py --project=$PROJECT_ID --queue=$QUEUE_ID --location=$LOCATION_ID --payload=hello -``` diff --git a/appengine/flexible_python37_and_earlier/tasks/app.flexible.yaml b/appengine/flexible_python37_and_earlier/tasks/app.flexible.yaml deleted file mode 100644 index 5b3b333fda6..00000000000 --- a/appengine/flexible_python37_and_earlier/tasks/app.flexible.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2019 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -runtime: python -env: flex -entrypoint: gunicorn -b :$PORT --threads=4 main:app - -runtime_config: - python_version: 3 diff --git a/appengine/flexible_python37_and_earlier/tasks/app.yaml b/appengine/flexible_python37_and_earlier/tasks/app.yaml deleted file mode 100644 index 15ac0d97205..00000000000 --- a/appengine/flexible_python37_and_earlier/tasks/app.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2019 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -runtime: python37 diff --git a/appengine/flexible_python37_and_earlier/tasks/create_app_engine_queue_task.py b/appengine/flexible_python37_and_earlier/tasks/create_app_engine_queue_task.py deleted file mode 100644 index 7ddb6fb5a69..00000000000 --- a/appengine/flexible_python37_and_earlier/tasks/create_app_engine_queue_task.py +++ /dev/null @@ -1,121 +0,0 @@ -# Copyright 2019 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import argparse - - -def create_task(project, queue, location, payload=None, in_seconds=None): - # [START cloud_tasks_appengine_create_task] - """Create a task for a given queue with an arbitrary payload.""" - - from google.cloud import tasks_v2 - from google.protobuf import timestamp_pb2 - import datetime - import json - - # Create a client. - client = tasks_v2.CloudTasksClient() - - # TODO(developer): Uncomment these lines and replace with your values. - # project = 'my-project-id' - # queue = 'my-appengine-queue' - # location = 'us-central1' - # payload = 'hello' or {'param': 'value'} for application/json - # in_seconds = None - - # Construct the fully qualified queue name. - parent = client.queue_path(project, location, queue) - - # Construct the request body. - task = { - "app_engine_http_request": { # Specify the type of request. - "http_method": tasks_v2.HttpMethod.POST, - "relative_uri": "/example_task_handler", - } - } - if payload is not None: - if isinstance(payload, dict): - # Convert dict to JSON string - payload = json.dumps(payload) - # specify http content-type to application/json - task["app_engine_http_request"]["headers"] = { - "Content-type": "application/json" - } - # The API expects a payload of type bytes. - converted_payload = payload.encode() - - # Add the payload to the request. - task["app_engine_http_request"]["body"] = converted_payload - - if in_seconds is not None: - # Convert "seconds from now" into an rfc3339 datetime string. - d = datetime.datetime.now(tz=datetime.timezone.utc) + datetime.timedelta( - seconds=in_seconds - ) - - # Create Timestamp protobuf. - timestamp = timestamp_pb2.Timestamp() - timestamp.FromDatetime(d) - - # Add the timestamp to the tasks. - task["schedule_time"] = timestamp - - # Use the client to build and send the task. - response = client.create_task(parent=parent, task=task) - - print(f"Created task {response.name}") - return response - - -# [END cloud_tasks_appengine_create_task] - - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - description=create_task.__doc__, - formatter_class=argparse.RawDescriptionHelpFormatter, - ) - - parser.add_argument( - "--project", - help="Project of the queue to add the task to.", - required=True, - ) - - parser.add_argument( - "--queue", - help="ID (short name) of the queue to add the task to.", - required=True, - ) - - parser.add_argument( - "--location", - help="Location of the queue to add the task to.", - required=True, - ) - - parser.add_argument( - "--payload", help="Optional payload to attach to the push queue." - ) - - parser.add_argument( - "--in_seconds", - type=int, - help="The number of seconds from now to schedule task attempt.", - ) - - args = parser.parse_args() - - create_task(args.project, args.queue, args.location, args.payload, args.in_seconds) diff --git a/appengine/flexible_python37_and_earlier/tasks/create_app_engine_queue_task_test.py b/appengine/flexible_python37_and_earlier/tasks/create_app_engine_queue_task_test.py deleted file mode 100644 index 3bacaed03ac..00000000000 --- a/appengine/flexible_python37_and_earlier/tasks/create_app_engine_queue_task_test.py +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright 2019 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os - -import create_app_engine_queue_task - -TEST_PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT") -TEST_LOCATION = os.getenv("TEST_QUEUE_LOCATION", "us-central1") -TEST_QUEUE_NAME = os.getenv("TEST_QUEUE_NAME", "my-appengine-queue") - - -def test_create_task(): - result = create_app_engine_queue_task.create_task( - TEST_PROJECT_ID, TEST_QUEUE_NAME, TEST_LOCATION - ) - assert TEST_QUEUE_NAME in result.name diff --git a/appengine/flexible_python37_and_earlier/tasks/main.py b/appengine/flexible_python37_and_earlier/tasks/main.py deleted file mode 100644 index 4cb9b84a0b6..00000000000 --- a/appengine/flexible_python37_and_earlier/tasks/main.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright 2019 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""App Engine app to serve as an endpoint for App Engine queue samples.""" - -# [START cloud_tasks_appengine_quickstart] -from flask import Flask, render_template, request - -app = Flask(__name__) - - -@app.route("/example_task_handler", methods=["POST"]) -def example_task_handler(): - """Log the request payload.""" - payload = request.get_data(as_text=True) or "(empty payload)" - print(f"Received task with payload: {payload}") - return render_template("index.html", payload=payload) - - -# [END cloud_tasks_appengine_quickstart] - - -@app.route("/") -def hello(): - """Basic index to verify app is serving.""" - return "Hello World!" - - -if __name__ == "__main__": - # This is used when running locally. Gunicorn is used to run the - # application on Google App Engine. See entrypoint in app.yaml. - app.run(host="127.0.0.1", port=8080, debug=True) diff --git a/appengine/flexible_python37_and_earlier/tasks/main_test.py b/appengine/flexible_python37_and_earlier/tasks/main_test.py deleted file mode 100644 index 42b96402dd0..00000000000 --- a/appengine/flexible_python37_and_earlier/tasks/main_test.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright 2019 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - - -@pytest.fixture -def app(): - import main - - main.app.testing = True - return main.app.test_client() - - -def test_index(app): - r = app.get("/") - assert r.status_code == 200 - - -def test_log_payload(capsys, app): - payload = "test_payload" - - r = app.post("/example_task_handler", data=payload) - assert r.status_code == 200 - - out, _ = capsys.readouterr() - assert payload in out - - -def test_empty_payload(capsys, app): - r = app.post("/example_task_handler") - assert r.status_code == 200 - - out, _ = capsys.readouterr() - assert "empty payload" in out diff --git a/appengine/flexible_python37_and_earlier/tasks/noxfile_config.py b/appengine/flexible_python37_and_earlier/tasks/noxfile_config.py deleted file mode 100644 index 1665dd736f8..00000000000 --- a/appengine/flexible_python37_and_earlier/tasks/noxfile_config.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Default TEST_CONFIG_OVERRIDE for python repos. - -# You can copy this file into your directory, then it will be imported from -# the noxfile.py. - -# The source of truth: -# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/noxfile_config.py - -TEST_CONFIG_OVERRIDE = { - # You can opt out from the test for specific Python versions. - # Skipping for Python 3.9 due to pyarrow compilation failure. - "ignored_versions": ["2.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"], - # Old samples are opted out of enforcing Python type hints - # All new samples should feature them - "enforce_type_hints": False, - # An envvar key for determining the project id to use. Change it - # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a - # build specific Cloud project. You can also use your own string - # to use your own Cloud project. - "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", - # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', - # A dictionary you want to inject into your test. Don't put any - # secrets here. These values will override predefined values. - "envs": {}, -} diff --git a/appengine/flexible_python37_and_earlier/tasks/requirements-test.txt b/appengine/flexible_python37_and_earlier/tasks/requirements-test.txt deleted file mode 100644 index 15d066af319..00000000000 --- a/appengine/flexible_python37_and_earlier/tasks/requirements-test.txt +++ /dev/null @@ -1 +0,0 @@ -pytest==8.2.0 diff --git a/appengine/flexible_python37_and_earlier/tasks/requirements.txt b/appengine/flexible_python37_and_earlier/tasks/requirements.txt deleted file mode 100644 index 93643e9fb2a..00000000000 --- a/appengine/flexible_python37_and_earlier/tasks/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -Flask==3.0.3; python_version > '3.6' -Flask==2.0.3; python_version < '3.7' -gunicorn==23.0.0 -google-cloud-tasks==2.18.0 -Werkzeug==3.0.3 diff --git a/appengine/flexible_python37_and_earlier/tasks/snippets_test.py b/appengine/flexible_python37_and_earlier/tasks/snippets_test.py deleted file mode 100644 index d0483389fc8..00000000000 --- a/appengine/flexible_python37_and_earlier/tasks/snippets_test.py +++ /dev/null @@ -1,130 +0,0 @@ -# Copyright 2019 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import uuid - -import pytest - -import snippets - -TEST_PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT") -TEST_LOCATION = os.getenv("TEST_QUEUE_LOCATION", "us-central1") -QUEUE_NAME_1 = f"queue-{uuid.uuid4()}" -QUEUE_NAME_2 = f"queue-{uuid.uuid4()}" - - -@pytest.mark.order1 -def test_create_queue(): - name = "projects/{}/locations/{}/queues/{}".format( - TEST_PROJECT_ID, TEST_LOCATION, QUEUE_NAME_2 - ) - result = snippets.create_queue( - TEST_PROJECT_ID, TEST_LOCATION, QUEUE_NAME_1, QUEUE_NAME_2 - ) - assert name in result.name - - -@pytest.mark.order2 -def test_update_queue(): - name = "projects/{}/locations/{}/queues/{}".format( - TEST_PROJECT_ID, TEST_LOCATION, QUEUE_NAME_1 - ) - result = snippets.update_queue(TEST_PROJECT_ID, TEST_LOCATION, QUEUE_NAME_1) - assert name in result.name - - -@pytest.mark.order3 -def test_create_task(): - name = "projects/{}/locations/{}/queues/{}".format( - TEST_PROJECT_ID, TEST_LOCATION, QUEUE_NAME_1 - ) - result = snippets.create_task(TEST_PROJECT_ID, TEST_LOCATION, QUEUE_NAME_1) - assert name in result.name - - -@pytest.mark.order4 -def test_create_task_with_data(): - name = "projects/{}/locations/{}/queues/{}".format( - TEST_PROJECT_ID, TEST_LOCATION, QUEUE_NAME_1 - ) - result = snippets.create_tasks_with_data( - TEST_PROJECT_ID, TEST_LOCATION, QUEUE_NAME_1 - ) - assert name in result.name - - -@pytest.mark.order5 -def test_create_task_with_name(): - name = "projects/{}/locations/{}/queues/{}".format( - TEST_PROJECT_ID, TEST_LOCATION, QUEUE_NAME_1 - ) - result = snippets.create_task_with_name( - TEST_PROJECT_ID, TEST_LOCATION, QUEUE_NAME_1, "foo" - ) - assert name in result.name - - -@pytest.mark.order6 -def test_delete_task(): - result = snippets.delete_task(TEST_PROJECT_ID, TEST_LOCATION, QUEUE_NAME_1) - assert result is None - - -@pytest.mark.order7 -def test_purge_queue(): - name = "projects/{}/locations/{}/queues/{}".format( - TEST_PROJECT_ID, TEST_LOCATION, QUEUE_NAME_1 - ) - result = snippets.purge_queue(TEST_PROJECT_ID, TEST_LOCATION, QUEUE_NAME_1) - assert name in result.name - - -@pytest.mark.order8 -def test_pause_queue(): - name = "projects/{}/locations/{}/queues/{}".format( - TEST_PROJECT_ID, TEST_LOCATION, QUEUE_NAME_1 - ) - result = snippets.pause_queue(TEST_PROJECT_ID, TEST_LOCATION, QUEUE_NAME_1) - assert name in result.name - - -@pytest.mark.order9 -def test_delete_queue(): - result = snippets.delete_queue(TEST_PROJECT_ID, TEST_LOCATION, QUEUE_NAME_1) - assert result is None - - result = snippets.delete_queue(TEST_PROJECT_ID, TEST_LOCATION, QUEUE_NAME_2) - assert result is None - - -@pytest.mark.order10 -def test_retry_task(): - QUEUE_SIZE = 3 - QUEUE_NAME = [] - for i in range(QUEUE_SIZE): - QUEUE_NAME.append(f"queue-{uuid.uuid4()}") - - name = "projects/{}/locations/{}/queues/{}".format( - TEST_PROJECT_ID, TEST_LOCATION, QUEUE_NAME[2] - ) - result = snippets.retry_task( - TEST_PROJECT_ID, TEST_LOCATION, QUEUE_NAME[0], QUEUE_NAME[1], QUEUE_NAME[2] - ) - assert name in result.name - - for i in range(QUEUE_SIZE): - snippets.delete_queue( - project=TEST_PROJECT_ID, location=TEST_LOCATION, queue=QUEUE_NAME[i] - ) diff --git a/appengine/flexible_python37_and_earlier/tasks/templates/index.html b/appengine/flexible_python37_and_earlier/tasks/templates/index.html deleted file mode 100644 index 7e4efc7b336..00000000000 --- a/appengine/flexible_python37_and_earlier/tasks/templates/index.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - Tasks Sample - - -

Printed task payload: {{ payload }}

- - \ No newline at end of file From ead605a555c0e4495113e51e51a0999a5ed0ffe6 Mon Sep 17 00:00:00 2001 From: Jennifer Davis Date: Tue, 3 Feb 2026 19:07:01 -0800 Subject: [PATCH 35/67] fix: remove deprecated websockets instance (#13787) b/475331568 --- .../websockets/README.md | 11 --- .../websockets/app.yaml | 36 ------- .../websockets/main.py | 56 ----------- .../websockets/main_test.py | 82 ---------------- .../websockets/noxfile_config.py | 39 -------- .../websockets/requirements-test.txt | 3 - .../websockets/requirements.txt | 6 -- .../websockets/templates/index.html | 96 ------------------- 8 files changed, 329 deletions(-) delete mode 100644 appengine/flexible_python37_and_earlier/websockets/README.md delete mode 100644 appengine/flexible_python37_and_earlier/websockets/app.yaml delete mode 100644 appengine/flexible_python37_and_earlier/websockets/main.py delete mode 100644 appengine/flexible_python37_and_earlier/websockets/main_test.py delete mode 100644 appengine/flexible_python37_and_earlier/websockets/noxfile_config.py delete mode 100644 appengine/flexible_python37_and_earlier/websockets/requirements-test.txt delete mode 100644 appengine/flexible_python37_and_earlier/websockets/requirements.txt delete mode 100644 appengine/flexible_python37_and_earlier/websockets/templates/index.html diff --git a/appengine/flexible_python37_and_earlier/websockets/README.md b/appengine/flexible_python37_and_earlier/websockets/README.md deleted file mode 100644 index fabd0995a40..00000000000 --- a/appengine/flexible_python37_and_earlier/websockets/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Python websockets sample for Google App Engine Flexible Environment - -This sample demonstrates how to use websockets on [Google App Engine Flexible Environment](https://cloud.google.com/appengine). - -## Running locally - -Refer to the [top-level README](../README.md) for instructions on running and deploying. - -To run locally, you need to use gunicorn with the ``flask_socket`` worker: - - $ gunicorn -b 127.0.0.1:8080 -k flask_sockets.worker main:app diff --git a/appengine/flexible_python37_and_earlier/websockets/app.yaml b/appengine/flexible_python37_and_earlier/websockets/app.yaml deleted file mode 100644 index 8a323ffe30f..00000000000 --- a/appengine/flexible_python37_and_earlier/websockets/app.yaml +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright 2021 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -runtime: python -env: flex - -# Use a special gunicorn worker class to support websockets. -entrypoint: gunicorn -b :$PORT -k flask_sockets.worker main:app - -runtime_config: - python_version: 3 - -# Use only a single instance, so that this local-memory-only chat app will work -# consistently with multiple users. To work across multiple instances, an -# extra-instance messaging system or data store would be needed. -manual_scaling: - instances: 1 - - -# For applications which can take advantage of session affinity -# (where the load balancer will attempt to route multiple connections from -# the same user to the same App Engine instance), uncomment the folowing: - -# network: -# session_affinity: true diff --git a/appengine/flexible_python37_and_earlier/websockets/main.py b/appengine/flexible_python37_and_earlier/websockets/main.py deleted file mode 100644 index 132160d9ab5..00000000000 --- a/appengine/flexible_python37_and_earlier/websockets/main.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright 2018 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -# [START gae_flex_websockets_app] -from flask import Flask, render_template -from flask_sockets import Sockets - - -app = Flask(__name__) -sockets = Sockets(app) - - -@sockets.route("/chat") -def chat_socket(ws): - while not ws.closed: - message = ws.receive() - if message is None: # message is "None" if the client has closed. - continue - # Send the message to all clients connected to this webserver - # process. (To support multiple processes or instances, an - # extra-instance storage or messaging system would be required.) - clients = ws.handler.server.clients.values() - for client in clients: - client.ws.send(message) - - -# [END gae_flex_websockets_app] - - -@app.route("/") -def index(): - return render_template("index.html") - - -if __name__ == "__main__": - print( - """ -This can not be run directly because the Flask development server does not -support web sockets. Instead, use gunicorn: - -gunicorn -b 127.0.0.1:8080 -k flask_sockets.worker main:app - -""" - ) diff --git a/appengine/flexible_python37_and_earlier/websockets/main_test.py b/appengine/flexible_python37_and_earlier/websockets/main_test.py deleted file mode 100644 index 597f2416d1c..00000000000 --- a/appengine/flexible_python37_and_earlier/websockets/main_test.py +++ /dev/null @@ -1,82 +0,0 @@ -# Copyright 2018 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import socket -import subprocess - -import pytest -import requests -from retrying import retry -import websocket - - -@pytest.fixture(scope="module") -def server(): - """Provides the address of a test HTTP/websocket server. - The test server is automatically created before - a test and destroyed at the end. - """ - # Ask the OS to allocate a port. - sock = socket.socket() - sock.bind(("127.0.0.1", 0)) - port = sock.getsockname()[1] - - # Free the port and pass it to a subprocess. - sock.close() - - bind_to = f"127.0.0.1:{port}" - server = subprocess.Popen( - ["gunicorn", "-b", bind_to, "-k" "flask_sockets.worker", "main:app"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - - # With btlr, there can be many processes are spawned and the - # server might be in a tight memory situation, so let's wait for 2 - # mins. - # Wait until the server responds before proceeding. - @retry(wait_fixed=50, stop_max_delay=120000) - def check_server(url): - requests.get(url) - - check_server(f"http://{bind_to}/") - - yield bind_to - - server.kill() - - # Dump the logs for debugging - out, err = server.communicate() - print(f"gunicorn stdout: {out}") - print(f"gunicorn stderr: {err}") - - -def test_http(server): - result = requests.get(f"http://{server}/") - assert "Python Websockets Chat" in result.text - - -def test_websocket(server): - url = f"ws://{server}/chat" - ws_one = websocket.WebSocket() - ws_one.connect(url) - - ws_two = websocket.WebSocket() - ws_two.connect(url) - - message = "Hello, World" - ws_one.send(message) - - assert ws_one.recv() == message - assert ws_two.recv() == message diff --git a/appengine/flexible_python37_and_earlier/websockets/noxfile_config.py b/appengine/flexible_python37_and_earlier/websockets/noxfile_config.py deleted file mode 100644 index 1665dd736f8..00000000000 --- a/appengine/flexible_python37_and_earlier/websockets/noxfile_config.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Default TEST_CONFIG_OVERRIDE for python repos. - -# You can copy this file into your directory, then it will be imported from -# the noxfile.py. - -# The source of truth: -# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/noxfile_config.py - -TEST_CONFIG_OVERRIDE = { - # You can opt out from the test for specific Python versions. - # Skipping for Python 3.9 due to pyarrow compilation failure. - "ignored_versions": ["2.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"], - # Old samples are opted out of enforcing Python type hints - # All new samples should feature them - "enforce_type_hints": False, - # An envvar key for determining the project id to use. Change it - # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a - # build specific Cloud project. You can also use your own string - # to use your own Cloud project. - "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", - # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', - # A dictionary you want to inject into your test. Don't put any - # secrets here. These values will override predefined values. - "envs": {}, -} diff --git a/appengine/flexible_python37_and_earlier/websockets/requirements-test.txt b/appengine/flexible_python37_and_earlier/websockets/requirements-test.txt deleted file mode 100644 index 92b9194cf63..00000000000 --- a/appengine/flexible_python37_and_earlier/websockets/requirements-test.txt +++ /dev/null @@ -1,3 +0,0 @@ -pytest==8.2.0 -retrying==1.3.4 -websocket-client==1.7.0 diff --git a/appengine/flexible_python37_and_earlier/websockets/requirements.txt b/appengine/flexible_python37_and_earlier/websockets/requirements.txt deleted file mode 100644 index c1525d36077..00000000000 --- a/appengine/flexible_python37_and_earlier/websockets/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -Flask==1.1.4 # it seems like Flask-sockets doesn't play well with 2.0+ -Flask-Sockets==0.2.1 -gunicorn==23.0.0 -requests==2.31.0 -markupsafe==2.0.1 -Werkzeug==1.0.1; diff --git a/appengine/flexible_python37_and_earlier/websockets/templates/index.html b/appengine/flexible_python37_and_earlier/websockets/templates/index.html deleted file mode 100644 index af6d791f148..00000000000 --- a/appengine/flexible_python37_and_earlier/websockets/templates/index.html +++ /dev/null @@ -1,96 +0,0 @@ -{# -# Copyright 2018 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#} - - - - Google App Engine Flexible Environment - Python Websockets Chat - - - - - -

Chat demo

-
- - -
- -
-

Messages:

-
    -
    - -
    -

    Status:

    -
      -
      - - - - - - From 80079d4d85d30d9455a36fe31ebd5baa92a4efca Mon Sep 17 00:00:00 2001 From: David del Real Date: Fri, 6 Feb 2026 11:11:13 -0600 Subject: [PATCH 36/67] fix(django): update dependencies and settings for Django samples (#13742) * chore: updating Django and python versions in requirements, CI-CD/Nox files. b/470283288 Updated Django version to 6.0.1. Updated required python version to 3.12 (minimum version required by Django 6.0.1) * chore: Updated requirements for App Engine Django Flexible Environment b/470283244 - Updated Django and python version in requirements file. - Updated and added missing attributes for frameworks's execution. * chore: Updated requirements for Run Django b/470283502 - Updated Django and python version in requirements and nox files. * chore: Update requirements for Django Cloud SQL - Updated requirements and nox files for latest Django version. * Setting up Django to use signed URLs to workaourn UBLA for buckets. * Fixed linting issues. * Removed remnants from testing that shouldnt be here. * Removed changes to run/django to avoid issue with Pipeline test. Will attempt to merge other changes only. --- appengine/flexible/django_cloudsql/noxfile_config.py | 2 +- appengine/flexible/django_cloudsql/requirements.txt | 4 ++-- appengine/flexible/hello_world_django/app.yaml | 2 +- appengine/flexible/hello_world_django/noxfile_config.py | 2 +- .../flexible/hello_world_django/project_name/settings.py | 2 ++ appengine/flexible/hello_world_django/project_name/urls.py | 4 ++-- appengine/flexible/hello_world_django/requirements.txt | 2 +- kubernetes_engine/django_tutorial/requirements.txt | 5 ++--- 8 files changed, 12 insertions(+), 11 deletions(-) diff --git a/appengine/flexible/django_cloudsql/noxfile_config.py b/appengine/flexible/django_cloudsql/noxfile_config.py index 30010ba672d..60e19bd8a96 100644 --- a/appengine/flexible/django_cloudsql/noxfile_config.py +++ b/appengine/flexible/django_cloudsql/noxfile_config.py @@ -22,7 +22,7 @@ TEST_CONFIG_OVERRIDE = { # You can opt out from the test for specific Python versions. - "ignored_versions": ["2.7", "3.7", "3.8", "3.10", "3.12", "3.13"], + "ignored_versions": ["2.7", "3.7", "3.8", "3.9", "3.10", "3.11"], # An envvar key for determining the project id to use. Change it # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a # build specific Cloud project. You can also use your own string diff --git a/appengine/flexible/django_cloudsql/requirements.txt b/appengine/flexible/django_cloudsql/requirements.txt index e309f97d5bc..da90b09edaa 100644 --- a/appengine/flexible/django_cloudsql/requirements.txt +++ b/appengine/flexible/django_cloudsql/requirements.txt @@ -1,6 +1,6 @@ -Django==5.2.9 +Django==6.0.1; python_version >= "3.12" gunicorn==23.0.0 -psycopg2-binary==2.9.10 +psycopg2-binary==2.9.11 django-environ==0.12.0 google-cloud-secret-manager==2.21.1 django-storages[google]==1.14.6 diff --git a/appengine/flexible/hello_world_django/app.yaml b/appengine/flexible/hello_world_django/app.yaml index 62b74a9c27e..85096c4adc4 100644 --- a/appengine/flexible/hello_world_django/app.yaml +++ b/appengine/flexible/hello_world_django/app.yaml @@ -17,4 +17,4 @@ env: flex entrypoint: gunicorn -b :$PORT project_name.wsgi runtime_config: - python_version: 3 + operating_system: "ubuntu24" diff --git a/appengine/flexible/hello_world_django/noxfile_config.py b/appengine/flexible/hello_world_django/noxfile_config.py index 196376e7023..692b834f789 100644 --- a/appengine/flexible/hello_world_django/noxfile_config.py +++ b/appengine/flexible/hello_world_django/noxfile_config.py @@ -22,7 +22,7 @@ TEST_CONFIG_OVERRIDE = { # You can opt out from the test for specific Python versions. - "ignored_versions": ["2.7", "3.7"], + "ignored_versions": ["2.7", "3.7", "3.8", "3.9", "3.10", "3.11"], # Old samples are opted out of enforcing Python type hints # All new samples should feature them "enforce_type_hints": False, diff --git a/appengine/flexible/hello_world_django/project_name/settings.py b/appengine/flexible/hello_world_django/project_name/settings.py index f8b93099d56..bd094b5f576 100644 --- a/appengine/flexible/hello_world_django/project_name/settings.py +++ b/appengine/flexible/hello_world_django/project_name/settings.py @@ -114,3 +114,5 @@ # https://docs.djangoproject.com/en/stable/howto/static-files/ STATIC_URL = "/static/" + +STATIC_ROOT = os.path.join(BASE_DIR, 'static') diff --git a/appengine/flexible/hello_world_django/project_name/urls.py b/appengine/flexible/hello_world_django/project_name/urls.py index 9a393bb42d2..7d3a1e0f315 100644 --- a/appengine/flexible/hello_world_django/project_name/urls.py +++ b/appengine/flexible/hello_world_django/project_name/urls.py @@ -13,12 +13,12 @@ # limitations under the License. from django.contrib import admin -from django.urls import include, path +from django.urls import path import helloworld.views urlpatterns = [ - path("admin/", include(admin.site.urls)), + path("admin/", admin.site.urls), path("", helloworld.views.index), ] diff --git a/appengine/flexible/hello_world_django/requirements.txt b/appengine/flexible/hello_world_django/requirements.txt index 435ef2cb8ee..a7f029a554d 100644 --- a/appengine/flexible/hello_world_django/requirements.txt +++ b/appengine/flexible/hello_world_django/requirements.txt @@ -1,2 +1,2 @@ -Django==5.2.9 +Django==6.0.1; python_version >= "3.12" gunicorn==23.0.0 diff --git a/kubernetes_engine/django_tutorial/requirements.txt b/kubernetes_engine/django_tutorial/requirements.txt index 1ef339da5ba..df3b50126a0 100644 --- a/kubernetes_engine/django_tutorial/requirements.txt +++ b/kubernetes_engine/django_tutorial/requirements.txt @@ -1,5 +1,4 @@ -Django==5.2.9; python_version >= "3.10" -Django==4.2.24; python_version >= "3.8" and python_version < "3.10" +Django==6.0.1; python_version >= "3.12" # Uncomment the mysqlclient requirement if you are using MySQL rather than # PostgreSQL. You must also have a MySQL client installed in that case. #mysqlclient==1.4.1 @@ -7,4 +6,4 @@ wheel==0.40.0 gunicorn==23.0.0; python_version > '3.0' gunicorn==23.0.0; python_version < '3.0' # psycopg2==2.8.4 # uncomment if you prefer to build from source -psycopg2-binary==2.9.10 +psycopg2-binary==2.9.11 From 0bd8199b2970b84e9e70dd8372599abd5660e685 Mon Sep 17 00:00:00 2001 From: Chalmer Lowe Date: Fri, 6 Feb 2026 12:52:57 -0500 Subject: [PATCH 37/67] docs: migrate samples from googleapis/google-auth-library-python (#13793) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs(samples): add auth samples and tests (#1102) * docs(samples): add auth samples and tests * refactored verifying google token and lint fixed test file * Modified comment acc to review * renamed method acc to review comment * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * added comment acc to review * add samples tests as required checks * use GOOGLE_CLOUD_PROJECT * test new config 1 * adding refresh token for sys test * updating all py verion configs * update 3 * update 4 * update 5 - trimming nox * update 6 - fixing requirements.txt * update 7 - fixing pytest flags * update 8 - fixing sa test cred * update 9- reading sa path from env * update 10- testing explicit * update 11 - fix multi reference * update 12 - remove project id from client params * update 13 - use projectid from default * update 14 - remove project param * update 15- fix assert * update 16 - updating other py versions * update 17: try replacing compute with storage * update 18: fix assert and pass project * update 19: fixing comments * update 20: remove unused Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou Co-authored-by: Sai Sunder Srinivasan * chore(deps): update dependency google-cloud-compute to v1.5.1 (#1110) * chore(deps): update dependency google-cloud-compute to v1.5.1 * exclude templated .kokoro/samples files Co-authored-by: Anthonios Partheniou * chore(deps): update dependency google-auth to v2.11.0 (#1109) * chore(deps): update dependency google-cloud-storage to v2.5.0 (#1111) Co-authored-by: Anthonios Partheniou * fix: don't retry if error or error_description is not string (#1241) * fix: don't retry if error or error_description is not string * chore: fix flaky sample test * chore: update sys test cred * fix: misc fixes (#1316) * fix: misc fixes * update * feat: Add support for Python 3.12 (#1421) * feat: Add support for Python 3.12 * Add samples lint session * Add constraints file for python 3.12 * chore: Refresh system test creds. * only run specified nox session for samples --------- Co-authored-by: Carl Lundin * fix: explicitly declare support for Python 3.13 (#1741) * fix: explicitly declare support for Python 3.13 * Add config for samples * skip 2 tests due to oauth2client incompatibility * bump google-cloud-storage in samples test * bump google-auth in samples test * coverage --------- Co-authored-by: ohmayr * doc: Custom Credential Suppliers for AWS and Okta. (#1830) Documenting Custom Credential Suppliers for: 1. Aws Workload. 2. Okta Workload. The readme updates for these have already been made: [Link](https://github.com/googleapis/google-auth-library-python/pull/1496/files) --------- Co-authored-by: Chalmer Lowe Co-authored-by: Daniel Sanche * feat: support Python 3.14 (#1822) Co-authored-by: Anthonios Partheniou * chore(deps): update dependency requests to v2.32.4 [security] (#1871) This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [requests](https://requests.readthedocs.io) ([source](https://redirect.github.com/psf/requests), [changelog](https://redirect.github.com/psf/requests/blob/master/HISTORY.md)) | `==2.32.3` -> `==2.32.4` | ![age](https://developer.mend.io/api/mc/badges/age/pypi/requests/2.32.4?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/requests/2.32.3/2.32.4?slim=true) | ### GitHub Vulnerability Alerts #### [CVE-2024-47081](https://redirect.github.com/psf/requests/security/advisories/GHSA-9hjg-9r4m-mvj7) ### Impact Due to a URL parsing issue, Requests releases prior to 2.32.4 may leak .netrc credentials to third parties for specific maliciously-crafted URLs. ### Workarounds For older versions of Requests, use of the .netrc file can be disabled with `trust_env=False` on your Requests Session ([docs](https://requests.readthedocs.io/en/latest/api/#requests.Session.trust_env)). ### References [https://github.com/psf/requests/pull/6965](https://redirect.github.com/psf/requests/pull/6965) https://seclists.org/fulldisclosure/2025/Jun/2 --- ### Release Notes
      psf/requests (requests) ### [`v2.32.4`](https://redirect.github.com/psf/requests/blob/HEAD/HISTORY.md#2324-2025-06-10) [Compare Source](https://redirect.github.com/psf/requests/compare/v2.32.3...v2.32.4) **Security** - CVE-2024-47081 Fixed an issue where a maliciously crafted URL and trusted environment will retrieve credentials for the wrong hostname/machine from a netrc file. **Improvements** - Numerous documentation improvements **Deprecations** - Added support for pypy 3.11 for Linux and macOS. - Dropped support for pypy 3.9 following its end of support.
      --- ### Configuration 📅 **Schedule**: Branch creation - "" (UTC), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/googleapis/google-auth-library-python). Co-authored-by: Anthonios Partheniou * chore(deps): update all dependencies (#1894) > **Note:** This PR body was truncated due to platform limits. This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [google-auth](https://redirect.github.com/googleapis/google-auth-library-python) | `==2.41.1` -> `==2.45.0` | ![age](https://developer.mend.io/api/mc/badges/age/pypi/google-auth/2.45.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/google-auth/2.41.1/2.45.0?slim=true) | | [google-cloud-compute](https://redirect.github.com/googleapis/google-cloud-python/tree/main/packages/google-cloud-compute) ([source](https://redirect.github.com/googleapis/google-cloud-python)) | `==1.5.1` -> `==1.40.0` | ![age](https://developer.mend.io/api/mc/badges/age/pypi/google-cloud-compute/1.40.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/google-cloud-compute/1.5.1/1.40.0?slim=true) | | [google-cloud-storage](https://redirect.github.com/googleapis/python-storage) | `==3.1.0` -> `==3.7.0` | ![age](https://developer.mend.io/api/mc/badges/age/pypi/google-cloud-storage/3.7.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/google-cloud-storage/3.1.0/3.7.0?slim=true) | | [pytest](https://redirect.github.com/pytest-dev/pytest) ([changelog](https://docs.pytest.org/en/stable/changelog.html)) | `==8.4.2` -> `==9.0.2` | ![age](https://developer.mend.io/api/mc/badges/age/pypi/pytest/9.0.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/pytest/8.4.2/9.0.2?slim=true) | | [python-dotenv](https://redirect.github.com/theskumar/python-dotenv) | `==1.1.1` -> `==1.2.1` | ![age](https://developer.mend.io/api/mc/badges/age/pypi/python-dotenv/1.2.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/python-dotenv/1.1.1/1.2.1?slim=true) | | [requests](https://requests.readthedocs.io) ([source](https://redirect.github.com/psf/requests), [changelog](https://redirect.github.com/psf/requests/blob/master/HISTORY.md)) | `==2.32.4` -> `==2.32.5` | ![age](https://developer.mend.io/api/mc/badges/age/pypi/requests/2.32.5?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/requests/2.32.4/2.32.5?slim=true) | --- ### Release Notes
      googleapis/google-auth-library-python (google-auth) ### [`v2.45.0`](https://redirect.github.com/googleapis/google-auth-library-python/blob/HEAD/CHANGELOG.md#2450-2025-12-15) [Compare Source](https://redirect.github.com/googleapis/google-auth-library-python/compare/v2.44.0...v2.45.0) ##### Features - Adding Agent Identity bound token support and handling certificate mismatches with retries ([#​1890](https://redirect.github.com/googleapis/google-auth-library-python/issues/1890)) ([b32c934e6b0d09b94c467cd432a0a635e8b05f5c](https://redirect.github.com/googleapis/google-auth-library-python/commit/b32c934e6b0d09b94c467cd432a0a635e8b05f5c)) ### [`v2.44.0`](https://redirect.github.com/googleapis/google-auth-library-python/blob/HEAD/CHANGELOG.md#2440-2025-12-13) [Compare Source](https://redirect.github.com/googleapis/google-auth-library-python/compare/v2.43.0...v2.44.0) ##### Features - support Python 3.14 ([#​1822](https://redirect.github.com/googleapis/google-auth-library-python/issues/1822)) ([0f7097e78f247665b6ef0287d482033f7be2ed6d](https://redirect.github.com/googleapis/google-auth-library-python/commit/0f7097e78f247665b6ef0287d482033f7be2ed6d)) - add ecdsa p-384 support ([#​1872](https://redirect.github.com/googleapis/google-auth-library-python/issues/1872)) ([39c381a5f6881b590025f36d333d12eff8dc60fc](https://redirect.github.com/googleapis/google-auth-library-python/commit/39c381a5f6881b590025f36d333d12eff8dc60fc)) - MDS connections use mTLS ([#​1856](https://redirect.github.com/googleapis/google-auth-library-python/issues/1856)) ([0387bb95713653d47e846cad3a010eb55ef2db4c](https://redirect.github.com/googleapis/google-auth-library-python/commit/0387bb95713653d47e846cad3a010eb55ef2db4c)) - Implement token revocation in STS client and add revoke() metho… ([#​1849](https://redirect.github.com/googleapis/google-auth-library-python/issues/1849)) ([d5638986ca03ee95bfffa9ad821124ed7e903e63](https://redirect.github.com/googleapis/google-auth-library-python/commit/d5638986ca03ee95bfffa9ad821124ed7e903e63)) - Add shlex to correctly parse executable commands with spaces ([#​1855](https://redirect.github.com/googleapis/google-auth-library-python/issues/1855)) ([cf6fc3cced78bc1362a7fe596c32ebc9ce03c26b](https://redirect.github.com/googleapis/google-auth-library-python/commit/cf6fc3cced78bc1362a7fe596c32ebc9ce03c26b)) ##### Bug Fixes - Use public refresh method for source credentials in ImpersonatedCredentials ([#​1884](https://redirect.github.com/googleapis/google-auth-library-python/issues/1884)) ([e0c3296f471747258f6d98d2d9bfde636358ecde](https://redirect.github.com/googleapis/google-auth-library-python/commit/e0c3296f471747258f6d98d2d9bfde636358ecde)) - Add temporary patch to workload cert logic to accomodate Cloud Run mis-configuration ([#​1880](https://redirect.github.com/googleapis/google-auth-library-python/issues/1880)) ([78de7907b8bdb7b5510e3c6fa8a3f3721e2436d7](https://redirect.github.com/googleapis/google-auth-library-python/commit/78de7907b8bdb7b5510e3c6fa8a3f3721e2436d7)) - Delegate workload cert and key default lookup to helper function ([#​1877](https://redirect.github.com/googleapis/google-auth-library-python/issues/1877)) ([b0993c7edaba505d0fb0628af28760c43034c959](https://redirect.github.com/googleapis/google-auth-library-python/commit/b0993c7edaba505d0fb0628af28760c43034c959)) ### [`v2.43.0`](https://redirect.github.com/googleapis/google-auth-library-python/blob/HEAD/CHANGELOG.md#2430-2025-11-05) [Compare Source](https://redirect.github.com/googleapis/google-auth-library-python/compare/v2.42.1...v2.43.0) ##### Features - Add public wrapper for \_mtls\_helper.check\_use\_client\_cert which enables mTLS if GOOGLE\_API\_USE\_CLIENT\_CERTIFICATE is not set, when the MWID/X.509 cert sources detected ([#​1859](https://redirect.github.com/googleapis/google-auth-library-python/issues/1859)) Add public wrapper for check\_use\_client\_cert which enables mTLS if GOOGLE\_API\_USE\_CLIENT\_CERTIFICATE is not set, when the MWID/X.509 cert sources detected. Also, fix check\_use\_client\_cert to return boolean value. Change [#​1848](https://redirect.github.com/googleapis/google-auth-library-python/issues/1848) added the check\_use\_client\_cert method that helps know if client cert should be used for mTLS connection. However, that was in a private class, thus, created a public wrapper of the same function so that it can be used by python Client Libraries. Also, updated check\_use\_client\_cert to return a boolean value instead of existing string value for better readability and future scope. \--------- ([1535eccbff0ad8f3fd6a9775316ac8b77dca66ba](https://redirect.github.com/googleapis/google-cloud-python/commit/1535eccbff0ad8f3fd6a9775316ac8b77dca66ba)) - Enable mTLS if GOOGLE\_API\_USE\_CLIENT\_CERTIFICATE is not set, if the MWID/X.509 cert sources detected ([#​1848](https://redirect.github.com/googleapis/google-auth-library-python/issues/1848)) The Python SDK will use a hybrid approach for mTLS enablement: * If the GOOGLE\_API\_USE\_CLIENT\_CERTIFICATE environment variable is set (either true or false), the SDK will respect that setting. This is necessary for test scenarios and users who need to explicitly control mTLS behavior. * If the GOOGLE\_API\_USE\_CLIENT\_CERTIFICATE environment variable is not set, the SDK will automatically enable mTLS only if it detects Managed Workload Identity (MWID) or X.509 Workforce Identity Federation (WIF) certificate sources. In other cases where the variable is not set, mTLS will remain disabled. \*\* This change also adds the helper method `check_use_client_cert` and it's unit test, which will be used for checking the criteria for setting the mTLS to true \*\* This change is only for Auth-Library, other changes will be created for Client-Library use-cases. \--------- ([395e405b64b56ddb82ee639958c2e8056ad2e82b](https://redirect.github.com/googleapis/google-cloud-python/commit/395e405b64b56ddb82ee639958c2e8056ad2e82b)) - onboard `google-auth` to librarian ([#​1838](https://redirect.github.com/googleapis/google-auth-library-python/issues/1838)) This PR onboards `google-auth` library to the Librarian system. Wait for [#​1819](https://redirect.github.com/googleapis/google-auth-library-python/pull/1819). ([c503eaa511357d7a76cc1e1f1d3a3be2dabd5bca](https://redirect.github.com/googleapis/google-cloud-python/commit/c503eaa511357d7a76cc1e1f1d3a3be2dabd5bca)) ### [`v2.42.1`](https://redirect.github.com/googleapis/google-auth-library-python/blob/HEAD/CHANGELOG.md#2421-2025-10-30) [Compare Source](https://redirect.github.com/googleapis/google-auth-library-python/compare/v2.42.0...v2.42.1) ##### Bug Fixes - Catch ValueError for json.loads() ([#​1842](https://redirect.github.com/googleapis/google-auth-library-python/issues/1842)) ([b074cad](https://redirect.github.com/googleapis/google-auth-library-python/commit/b074cad460589633adfc6744c01726ae86f2aa2b)) ### [`v2.42.0`](https://redirect.github.com/googleapis/google-auth-library-python/blob/HEAD/CHANGELOG.md#2420-2025-10-24) [Compare Source](https://redirect.github.com/googleapis/google-auth-library-python/compare/v2.41.1...v2.42.0) ##### Features - Add trust boundary support for external accounts. ([#​1809](https://redirect.github.com/googleapis/google-auth-library-python/issues/1809)) ([36ecb1d](https://redirect.github.com/googleapis/google-auth-library-python/commit/36ecb1d65883477d27faf9c2281fc289659b9903)) ##### Bug Fixes - Read scopes from ADC json for impersoanted cred ([#​1820](https://redirect.github.com/googleapis/google-auth-library-python/issues/1820)) ([62c0fc8](https://redirect.github.com/googleapis/google-auth-library-python/commit/62c0fc82a3625542381f85c698595446fc99ddae))
      googleapis/google-cloud-python (google-cloud-compute) ### [`v1.40.0`](https://redirect.github.com/googleapis/google-cloud-python/releases/tag/google-cloud-compute-v1.40.0): google-cloud-compute 1.40.0 [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.39.0...google-cloud-compute-v1.40.0) ##### Features - Update Compute Engine v1 API to revision [`2025091`](https://redirect.github.com/googleapis/google-cloud-python/commit/20250916) ([#​1107](https://redirect.github.com/googleapis/google-cloud-python/issues/1107)) ([4dc7381c](https://redirect.github.com/googleapis/google-cloud-python/commit/4dc7381c)) ### [`v1.39.0`](https://redirect.github.com/googleapis/google-cloud-python/releases/tag/google-cloud-compute-v1.39.0): google-cloud-compute 1.39.0 [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.38.0...google-cloud-compute-v1.39.0) ##### Features - \[google-cloud-compute] Update Compute Engine v1 API to revision [`2025090`](https://redirect.github.com/googleapis/google-cloud-python/commit/20250909) ([4e5a0b5](https://redirect.github.com/googleapis/google-cloud-python/commit/4e5a0b5)) - \[google-cloud-compute] Update Compute Engine v1 API to revision [`2025090`](https://redirect.github.com/googleapis/google-cloud-python/commit/20250902) ([4e5a0b5](https://redirect.github.com/googleapis/google-cloud-python/commit/4e5a0b5)) ### [`v1.38.0`](https://redirect.github.com/googleapis/google-cloud-python/releases/tag/google-cloud-compute-v1.38.0): google-cloud-compute: v1.38.0 [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.37.0...google-cloud-compute-v1.38.0) ##### Features - \[google-cloud-compute] Update Compute Engine v1 API to revision [`2025090`](https://redirect.github.com/googleapis/google-cloud-python/commit/20250902) ([#​14434](https://redirect.github.com/googleapis/google-cloud-python/issues/14434)) ([daf5ab8](https://redirect.github.com/googleapis/google-cloud-python/commit/daf5ab81499ccb7bf29c74b616af9f6235c12526)) ### [`v1.37.0`](https://redirect.github.com/googleapis/google-cloud-python/releases/tag/google-cloud-compute-v1.37.0): google-cloud-compute: v1.37.0 [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.36.0...google-cloud-compute-v1.37.0) ##### Features - \[google-cloud-compute] Update Compute Engine v1 API to revision [`2025081`](https://redirect.github.com/googleapis/google-cloud-python/commit/20250810) ([#​1091](https://redirect.github.com/googleapis/google-cloud-python/issues/1091)) ([#​14324](https://redirect.github.com/googleapis/google-cloud-python/issues/14324)) ([6cdbcee](https://redirect.github.com/googleapis/google-cloud-python/commit/6cdbcee600286643cba8caf3c2219e9810b317fe)) ### [`v1.36.0`](https://redirect.github.com/googleapis/google-cloud-python/releases/tag/google-cloud-compute-v1.36.0): google-cloud-compute: v1.36.0 [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.35.0...google-cloud-compute-v1.36.0) ##### Features - \[google-cloud-compute] Update Compute Engine v1 API to revision [`2025080`](https://redirect.github.com/googleapis/google-cloud-python/commit/20250807) ([51732d3](https://redirect.github.com/googleapis/google-cloud-python/commit/51732d38da49f8a0598d58b204dc7d9c86ee0e52)) ### [`v1.35.0`](https://redirect.github.com/googleapis/google-cloud-python/releases/tag/google-cloud-compute-v1.35.0): google-cloud-compute: v1.35.0 [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.34.0...google-cloud-compute-v1.35.0) ##### Features - \[google-cloud-compute] Update Compute Engine v1 API to revision [`2025072`](https://redirect.github.com/googleapis/google-cloud-python/commit/20250728) ([#​1081](https://redirect.github.com/googleapis/google-cloud-python/issues/1081)) ([#​14196](https://redirect.github.com/googleapis/google-cloud-python/issues/14196)) ([9abaf2f](https://redirect.github.com/googleapis/google-cloud-python/commit/9abaf2fd0eb864f21f246a257ec796fe4e11717f)) - \[google-cloud-compute] Update Compute Engine v1 API to revision [`2025072`](https://redirect.github.com/googleapis/google-cloud-python/commit/20250729) ([#​1085](https://redirect.github.com/googleapis/google-cloud-python/issues/1085)) ([#​14199](https://redirect.github.com/googleapis/google-cloud-python/issues/14199)) ([17c935f](https://redirect.github.com/googleapis/google-cloud-python/commit/17c935fb57e5e76e44b5590dcf74b27cd6dfe285)) ### [`v1.34.0`](https://redirect.github.com/googleapis/google-cloud-python/releases/tag/google-cloud-compute-v1.34.0): google-cloud-compute: v1.34.0 [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.33.0...google-cloud-compute-v1.34.0) ##### Features - \[google-cloud-compute] Update Compute Engine v1 API to revision [`2025071`](https://redirect.github.com/googleapis/google-cloud-python/commit/20250717) ([#​1074](https://redirect.github.com/googleapis/google-cloud-python/issues/1074)) ([#​14169](https://redirect.github.com/googleapis/google-cloud-python/issues/14169)) ([0c26f13](https://redirect.github.com/googleapis/google-cloud-python/commit/0c26f13efca3b91fc1981f3cab67d8c9305ca3a9)) ### [`v1.33.0`](https://redirect.github.com/googleapis/google-cloud-python/releases/tag/google-cloud-compute-v1.33.0): google-cloud-compute: v1.33.0 [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.32.0...google-cloud-compute-v1.33.0) ##### Features - \[google-cloud-compute] Update Compute Engine v1 API to revision [`2025070`](https://redirect.github.com/googleapis/google-cloud-python/commit/20250708) ([#​1073](https://redirect.github.com/googleapis/google-cloud-python/issues/1073)) ([#​14107](https://redirect.github.com/googleapis/google-cloud-python/issues/14107)) ([723ac5d](https://redirect.github.com/googleapis/google-cloud-python/commit/723ac5deb793fa180bacb52841ff73893b58076e)) ### [`v1.32.0`](https://redirect.github.com/googleapis/google-cloud-python/releases/tag/google-cloud-compute-v1.32.0): google-cloud-compute: v1.32.0 [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.31.0...google-cloud-compute-v1.32.0) ##### Features - Update Compute Engine v1 API to revision [`2025062`](https://redirect.github.com/googleapis/google-cloud-python/commit/20250626) ([3ad8819](https://redirect.github.com/googleapis/google-cloud-python/commit/3ad8819a1967ba00374e6be26062d5c7e94200c8)) ### [`v1.31.0`](https://redirect.github.com/googleapis/google-cloud-python/releases/tag/google-cloud-compute-v1.31.0): google-cloud-compute: v1.31.0 [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.30.0...google-cloud-compute-v1.31.0) ##### Features - \[google-cloud-compute] Update Compute Engine v1 API to revision [`2025060`](https://redirect.github.com/googleapis/google-cloud-python/commit/20250601) ([#​13970](https://redirect.github.com/googleapis/google-cloud-python/issues/13970)) ([8083640](https://redirect.github.com/googleapis/google-cloud-python/commit/808364041835ed14e55839cfb33325c9d1cb1f15)) - Update Compute Engine v1 API to revision [`2025051`](https://redirect.github.com/googleapis/google-cloud-python/commit/20250511) ([#​1047](https://redirect.github.com/googleapis/google-cloud-python/issues/1047)) ([8083640](https://redirect.github.com/googleapis/google-cloud-python/commit/808364041835ed14e55839cfb33325c9d1cb1f15)) ### [`v1.30.0`](https://redirect.github.com/googleapis/google-cloud-python/releases/tag/google-cloud-compute-v1.30.0): google-cloud-compute: v1.30.0 [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.29.0...google-cloud-compute-v1.30.0) ##### Features - \[google-cloud-compute] Update Compute Engine API to revision [`2025041`](https://redirect.github.com/googleapis/google-cloud-python/commit/20250415) ([#​13800](https://redirect.github.com/googleapis/google-cloud-python/issues/13800)) ([ae6a7c9](https://redirect.github.com/googleapis/google-cloud-python/commit/ae6a7c9c7e6bf014db6347d6d6f0ad15183b7f9a)) ### [`v1.29.0`](https://redirect.github.com/googleapis/google-cloud-python/releases/tag/google-cloud-compute-v1.29.0): google-cloud-compute: v1.29.0 [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.28.0...google-cloud-compute-v1.29.0) ##### Features - \[google-cloud-compute] Update Compute Engine API to revision [`2025032`](https://redirect.github.com/googleapis/google-cloud-python/commit/20250320) ([#​13710](https://redirect.github.com/googleapis/google-cloud-python/issues/13710)) ([da0e811](https://redirect.github.com/googleapis/google-cloud-python/commit/da0e81122069ace8048471d9fffb6a1203574b36)) ### [`v1.28.0`](https://redirect.github.com/googleapis/google-cloud-python/releases/tag/google-cloud-compute-v1.28.0): google-cloud-compute: v1.28.0 [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.27.0...google-cloud-compute-v1.28.0) ##### Features - \[google-cloud-compute] Update Compute Engine API to revision ([9be352b](https://redirect.github.com/googleapis/google-cloud-python/commit/9be352b08daebce854628107672b62bf94893c4f)) - Update Compute Engine API to revision [`2025030`](https://redirect.github.com/googleapis/google-cloud-python/commit/20250302) ([#​13678](https://redirect.github.com/googleapis/google-cloud-python/issues/13678)) ([9be352b](https://redirect.github.com/googleapis/google-cloud-python/commit/9be352b08daebce854628107672b62bf94893c4f)) ### [`v1.27.0`](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.26.0...google-cloud-compute-v1.27.0) [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.26.0...google-cloud-compute-v1.27.0) ### [`v1.26.0`](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.25.0...google-cloud-compute-v1.26.0) [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.25.0...google-cloud-compute-v1.26.0) ### [`v1.25.0`](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.24.0...google-cloud-compute-v1.25.0) [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.24.0...google-cloud-compute-v1.25.0) ### [`v1.24.0`](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.23.0...google-cloud-compute-v1.24.0) [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.23.0...google-cloud-compute-v1.24.0) ### [`v1.23.0`](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.22.0...google-cloud-compute-v1.23.0) [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.22.0...google-cloud-compute-v1.23.0) ### [`v1.22.0`](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.21.0...google-cloud-compute-v1.22.0) [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.21.0...google-cloud-compute-v1.22.0) ### [`v1.21.0`](https://redirect.github.com/googleapis/google-cloud-python/releases/tag/google-cloud-os-config-v1.21.0): google-cloud-os-config: v1.21.0 [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.20.1...google-cloud-compute-v1.21.0) ##### Features - Add highest\_upgradable\_cve\_severity field to Vulnerability report ([e8feca7](https://redirect.github.com/googleapis/google-cloud-python/commit/e8feca76cfe1a2eaf7e96ff2341715545d15b5a8)) ##### Documentation - A comment for field `filter` in message `.google.cloud.osconfig.v1.ListVulnerabilityReportsRequest` is changed ([e8feca7](https://redirect.github.com/googleapis/google-cloud-python/commit/e8feca76cfe1a2eaf7e96ff2341715545d15b5a8)) - A comment for field `update_time` in message `.google.cloud.osconfig.v1.VulnerabilityReport` is changed ([e8feca7](https://redirect.github.com/googleapis/google-cloud-python/commit/e8feca76cfe1a2eaf7e96ff2341715545d15b5a8)) ### [`v1.20.1`](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.20.0...google-cloud-compute-v1.20.1) [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.20.0...google-cloud-compute-v1.20.1) ### [`v1.20.0`](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.19.2...google-cloud-compute-v1.20.0) [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.19.2...google-cloud-compute-v1.20.0) ### [`v1.19.2`](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.19.1...google-cloud-compute-v1.19.2) [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.19.1...google-cloud-compute-v1.19.2) ### [`v1.19.1`](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.19.0...google-cloud-compute-v1.19.1) [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.19.0...google-cloud-compute-v1.19.1) ### [`v1.19.0`](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.18.0...google-cloud-compute-v1.19.0) [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.18.0...google-cloud-compute-v1.19.0) ### [`v1.18.0`](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-bigquery-connection-v1.17.0...google-cloud-bigquery-connection-v1.18.0) [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.17.0...google-cloud-compute-v1.18.0) ### [`v1.17.0`](https://redirect.github.com/googleapis/google-cloud-python/releases/tag/google-cloud-trace-v1.17.0): google-cloud-trace 1.17.0 [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.16.1...google-cloud-compute-v1.17.0) ##### [1.17.0](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-trace-v1.16.2...google-cloud-trace-v1.17.0) (2025-10-20) ### [`v1.16.1`](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.16.0...google-cloud-compute-v1.16.1) [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.16.0...google-cloud-compute-v1.16.1) ### [`v1.16.0`](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-workflows-v1.15.1...google-cloud-workflows-v1.16.0) [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.15.0...google-cloud-compute-v1.16.0) ### [`v1.15.0`](https://redirect.github.com/googleapis/google-cloud-python/releases/tag/google-cloud-resource-manager-v1.15.0): google-cloud-resource-manager 1.15.0 [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.14.1...google-cloud-compute-v1.15.0) #### [1.15.0](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-resource-manager-v1.14.2...google-cloud-resource-manager-v1.15.0) (2025-10-20) ### [`v1.14.1`](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-workflows-v1.14.0...google-cloud-workflows-v1.14.1) ### [`v1.14.0`](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-trace-v1.13.5...google-cloud-trace-v1.14.0) ### [`v1.13.0`](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-workflows-v1.12.1...google-cloud-workflows-v1.13.0) ### [`v1.12.1`](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-orchestration-airflow-v1.12.0...google-cloud-orchestration-airflow-v1.12.1) ### [`v1.12.0`](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-workflows-v1.11.0...google-cloud-workflows-v1.12.0) ### [`v1.11.0`](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-workflows-v1.10.2...google-cloud-workflows-v1.11.0) ### [`v1.10.0`](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-orchestration-airflow-v1.9.2...google-cloud-orchestration-airflow-v1.10.0) ### [`v1.9.0`](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-orchestration-airflow-v1.8.0...google-cloud-orchestration-airflow-v1.9.0) ### [`v1.7.0`](https://redirect.github.com/googleapis/google-cloud-python/releases/tag/google-cloud-appengine-logging-v1.7.0): google-cloud-appengine-logging 1.7.0 #### [1.7.0](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-appengine-logging-v1.6.2...google-cloud-appengine-logging-v1.7.0) (2025-10-16) ### [`v1.6.1`](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-appengine-logging-v1.6.0...google-cloud-appengine-logging-v1.6.1) ### [`v1.6.0`](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-appengine-logging-v1.5.0...google-cloud-appengine-logging-v1.6.0)
      googleapis/python-storage (google-cloud-storage) ### [`v3.7.0`](https://redirect.github.com/googleapis/python-storage/blob/HEAD/CHANGELOG.md#370-2025-12-09) [Compare Source](https://redirect.github.com/googleapis/python-storage/compare/v3.6.0...v3.7.0) ##### Features - Auto enable mTLS when supported certificates are detected ([#​1637](https://redirect.github.com/googleapis/python-storage/issues/1637)) ([4e91c54](https://redirect.github.com/googleapis/python-storage/commit/4e91c541363f0e583bf9dd1b81a95ff2cb618bac)) - Send entire object checksum in the final api call of resumable upload ([#​1654](https://redirect.github.com/googleapis/python-storage/issues/1654)) ([ddce7e5](https://redirect.github.com/googleapis/python-storage/commit/ddce7e53a13e6c0487221bb14e88161da7ed9e08)) - Support urllib3 >= 2.6.0 ([#​1658](https://redirect.github.com/googleapis/python-storage/issues/1658)) ([57405e9](https://redirect.github.com/googleapis/python-storage/commit/57405e956a7ca579b20582bf6435cec42743c478)) ##### Bug Fixes - Fix for [move\_blob](https://redirect.github.com/googleapis/python-storage/blob/57405e956a7ca579b20582bf6435cec42743c478/google/cloud/storage/bucket.py#L2256) failure when the new blob name contains characters that need to be url encoded ([#​1605](https://redirect.github.com/googleapis/python-storage/issues/1605)) ([ec470a2](https://redirect.github.com/googleapis/python-storage/commit/ec470a270e189e137c7229cc359367d5a897cdb9)) ### [`v3.6.0`](https://redirect.github.com/googleapis/python-storage/blob/HEAD/CHANGELOG.md#360-2025-11-17) [Compare Source](https://redirect.github.com/googleapis/python-storage/compare/v3.5.0...v3.6.0) ##### Features - Add support for partial list buckets ([#​1606](https://redirect.github.com/googleapis/python-storage/issues/1606)) ([92fc2b0](https://redirect.github.com/googleapis/python-storage/commit/92fc2b00429415b9fbe7cba0167778eee60449e2)) - Make return\_partial\_success and unreachable fields public for list Bucket ([#​1601](https://redirect.github.com/googleapis/python-storage/issues/1601)) ([323cddd](https://redirect.github.com/googleapis/python-storage/commit/323cddd5d439e04e12614106eab1928fd4008c0b)) - **zb-experimental:** Add async write object stream ([5ab8103](https://redirect.github.com/googleapis/python-storage/commit/5ab81032268e875f82a66431d666fe61c9eb394b)) - **zb-experimental:** Add async write object stream ([#​1612](https://redirect.github.com/googleapis/python-storage/issues/1612)) ([5ab8103](https://redirect.github.com/googleapis/python-storage/commit/5ab81032268e875f82a66431d666fe61c9eb394b)) ##### Bug Fixes - Dont pass credentials to StorageClient ([#​1608](https://redirect.github.com/googleapis/python-storage/issues/1608)) ([195d644](https://redirect.github.com/googleapis/python-storage/commit/195d644c4d4feec98e9a9cd9fad67fc774c50dc8)) ### [`v3.5.0`](https://redirect.github.com/googleapis/python-storage/blob/HEAD/CHANGELOG.md#350-2025-11-05) [Compare Source](https://redirect.github.com/googleapis/python-storage/compare/v3.4.1...v3.5.0) ##### Features - **experimental:** Add base resumption strategy for bidi streams ([#​1594](https://redirect.github.com/googleapis/python-storage/issues/1594)) ([5fb85ea](https://redirect.github.com/googleapis/python-storage/commit/5fb85ea544dcc9ed9dca65957c872c3811f02b87)) - **experimental:** Add checksum for bidi reads operation ([#​1566](https://redirect.github.com/googleapis/python-storage/issues/1566)) ([93ce515](https://redirect.github.com/googleapis/python-storage/commit/93ce515d60f0ac77ab83680ba2b4d6a9f57e75d0)) - **experimental:** Add read resumption strategy ([#​1599](https://redirect.github.com/googleapis/python-storage/issues/1599)) ([5d5e895](https://redirect.github.com/googleapis/python-storage/commit/5d5e895e173075da557b58614fecc84086aaf9cb)) - **experimental:** Handle BidiReadObjectRedirectedError for bidi reads ([#​1600](https://redirect.github.com/googleapis/python-storage/issues/1600)) ([71b0f8a](https://redirect.github.com/googleapis/python-storage/commit/71b0f8a368a61bed9bd793a059f980562061223e)) - Indicate that md5 is used as a CRC ([#​1522](https://redirect.github.com/googleapis/python-storage/issues/1522)) ([961536c](https://redirect.github.com/googleapis/python-storage/commit/961536c7bf3652a824c207754317030526b9dd28)) - Provide option to update user\_agent ([#​1596](https://redirect.github.com/googleapis/python-storage/issues/1596)) ([02f1451](https://redirect.github.com/googleapis/python-storage/commit/02f1451aaa8dacd10a862e97abb62ae48249b9b4)) ##### Bug Fixes - Deprecate credentials\_file argument ([74415a2](https://redirect.github.com/googleapis/python-storage/commit/74415a2a120e9bfa42f4f5fc8bd2f8e0d4cf5d18)) - Flaky system tests for resumable\_media ([#​1592](https://redirect.github.com/googleapis/python-storage/issues/1592)) ([7fee3dd](https://redirect.github.com/googleapis/python-storage/commit/7fee3dd3390cfb5475a39d8f8272ea825dbda449)) - Make `download_ranges` compatible with `asyncio.create_task(..)` ([#​1591](https://redirect.github.com/googleapis/python-storage/issues/1591)) ([faf8b83](https://redirect.github.com/googleapis/python-storage/commit/faf8b83b1f0ac378f8f6f47ce33dc23a866090c9)) - Make `download_ranges` compatible with `asyncio.create_task(..)` ([#​1591](https://redirect.github.com/googleapis/python-storage/issues/1591)) ([faf8b83](https://redirect.github.com/googleapis/python-storage/commit/faf8b83b1f0ac378f8f6f47ce33dc23a866090c9)) - Redact sensitive data from OTEL traces and fix env var parsing ([#​1553](https://redirect.github.com/googleapis/python-storage/issues/1553)) ([a38ca19](https://redirect.github.com/googleapis/python-storage/commit/a38ca1977694def98f65ae7239e300a987bbd262)) - Redact sensitive data from OTEL traces and fix env var parsing ([#​1553](https://redirect.github.com/googleapis/python-storage/issues/1553)) ([a38ca19](https://redirect.github.com/googleapis/python-storage/commit/a38ca1977694def98f65ae7239e300a987bbd262)) - Use separate header object for each upload in Transfer Manager MPU ([#​1595](https://redirect.github.com/googleapis/python-storage/issues/1595)) ([0d867bd](https://redirect.github.com/googleapis/python-storage/commit/0d867bd4f405d2dbeca1edfc8072080c5a96c1cd)) ### [`v3.4.1`](https://redirect.github.com/googleapis/python-storage/blob/HEAD/CHANGELOG.md#341-2025-10-08) [Compare Source](https://redirect.github.com/googleapis/python-storage/compare/v3.4.0...v3.4.1) ##### Bug Fixes - Fixes [#​1561](https://redirect.github.com/googleapis/python-storage/issues/1561) by adding an option to specify the entire object checksum for resumable uploads via the `upload_from_string`, `upload_from_file`, and `upload_from_filename` methods ([acb918e](https://redirect.github.com/googleapis/python-storage/commit/acb918e20f7092e13d72fc63fe4ae2560bfecd40)) ### [`v3.4.0`](https://redirect.github.com/googleapis/python-storage/blob/HEAD/CHANGELOG.md#340-2025-09-15) [Compare Source](https://redirect.github.com/googleapis/python-storage/compare/v3.3.1...v3.4.0) ##### Features - **experimental:** Add async grpc client ([#​1537](https://redirect.github.com/googleapis/python-storage/issues/1537)) ([ac57b8d](https://redirect.github.com/googleapis/python-storage/commit/ac57b8d819a49aef0ed0cb5bb630bf11012f43e3)) - **experimental:** Add grpc client ([#​1533](https://redirect.github.com/googleapis/python-storage/issues/1533)) ([5674587](https://redirect.github.com/googleapis/python-storage/commit/5674587f2aa347ec2787f2bc1e847eaa294bc1ca)) ##### Bug Fixes - GAPIC generation failed with 'Directory not empty' ([#​1542](https://redirect.github.com/googleapis/python-storage/issues/1542)) ([c80d820](https://redirect.github.com/googleapis/python-storage/commit/c80d8207a8661b84c56cd66bb34de7b5704675b8)) ### [`v3.3.1`](https://redirect.github.com/googleapis/python-storage/blob/HEAD/CHANGELOG.md#331-2025-08-25) [Compare Source](https://redirect.github.com/googleapis/python-storage/compare/v3.3.0...v3.3.1) ##### Bug Fixes - Provide option to user to set entire object checksum at "initiate a resumable upload session" and send the same ([#​1525](https://redirect.github.com/googleapis/python-storage/issues/1525)) ([a8109e0](https://redirect.github.com/googleapis/python-storage/commit/a8109e0d02c62542f1bea20373b53864fb776caa)) - Send part's checksum for XML MPU part upload ([#​1529](https://redirect.github.com/googleapis/python-storage/issues/1529)) ([2ad77c7](https://redirect.github.com/googleapis/python-storage/commit/2ad77c7d949e84c515c051a0fd4b37b822788dd8)) ### [`v3.3.0`](https://redirect.github.com/googleapis/python-storage/blob/HEAD/CHANGELOG.md#330-2025-08-05) [Compare Source](https://redirect.github.com/googleapis/python-storage/compare/v3.2.0...v3.3.0) ##### Features - Add support for bucket IP filter ([#​1516](https://redirect.github.com/googleapis/python-storage/issues/1516)) ([a29073c](https://redirect.github.com/googleapis/python-storage/commit/a29073cf58df9c5667305e05c6378284057cda23)) ##### Bug Fixes - Add logs on AssertionError for issue [#​1512](https://redirect.github.com/googleapis/python-storage/issues/1512) ([#​1518](https://redirect.github.com/googleapis/python-storage/issues/1518)) ([6a9923e](https://redirect.github.com/googleapis/python-storage/commit/6a9923e4fc944f7a7c3906eb7800d23677bd2481)) ##### Documentation - Update the documentation of move\_blob function ([#​1507](https://redirect.github.com/googleapis/python-storage/issues/1507)) ([72252e9](https://redirect.github.com/googleapis/python-storage/commit/72252e940909ce2e3da9cfd80f5b7b43a026f45c)) ### [`v3.2.0`](https://redirect.github.com/googleapis/python-storage/blob/HEAD/CHANGELOG.md#320-2025-07-04) [Compare Source](https://redirect.github.com/googleapis/python-storage/compare/v3.1.1...v3.2.0) ##### Features - Adding support of single shot download ([#​1493](https://redirect.github.com/googleapis/python-storage/issues/1493)) ([61c5d5f](https://redirect.github.com/googleapis/python-storage/commit/61c5d5f62c88506f200bc6d86b399a2c28715bc4)) ### [`v3.1.1`](https://redirect.github.com/googleapis/python-storage/blob/HEAD/CHANGELOG.md#311-2025-06-13) [Compare Source](https://redirect.github.com/googleapis/python-storage/compare/v3.1.0...v3.1.1) ##### Bug Fixes - Add a check for partial response data ([#​1487](https://redirect.github.com/googleapis/python-storage/issues/1487)) ([7e0412a](https://redirect.github.com/googleapis/python-storage/commit/7e0412a4fdfedcaa4683d5ef7d9155d5d58efa11)) - Add trove classifier for Python 3.13 ([0100916](https://redirect.github.com/googleapis/python-storage/commit/01009164beaab8931a1e1684966e3060edcf77b7)) - **deps:** Require google-crc32c >= 1.1.3 ([0100916](https://redirect.github.com/googleapis/python-storage/commit/01009164beaab8931a1e1684966e3060edcf77b7)) - **deps:** Require protobuf >= 3.20.2, < 7.0.0 ([0100916](https://redirect.github.com/googleapis/python-storage/commit/01009164beaab8931a1e1684966e3060edcf77b7)) - **deps:** Require requests >= 2.22.0 ([0100916](https://redirect.github.com/googleapis/python-storage/commit/01009164beaab8931a1e1684966e3060edcf77b7)) - Remove setup.cfg configuration for creating universal wheels ([#​1448](https://redirect.github.com/googleapis/python-storage/issues/1448)) ([d3b6b3f](https://redirect.github.com/googleapis/python-storage/commit/d3b6b3f96a6f94aa7c371902f48d1363ae6bfb5c)) - Resolve issue where pre-release versions of dependencies are installed ([0100916](https://redirect.github.com/googleapis/python-storage/commit/01009164beaab8931a1e1684966e3060edcf77b7)) - Segmentation fault in tink while writing data ([#​1490](https://redirect.github.com/googleapis/python-storage/issues/1490)) ([2a46c0b](https://redirect.github.com/googleapis/python-storage/commit/2a46c0b9e6ec561ae3151d2a9a80c7452634487e)) ##### Documentation - Move quickstart to top of readme ([#​1451](https://redirect.github.com/googleapis/python-storage/issues/1451)) ([53257cf](https://redirect.github.com/googleapis/python-storage/commit/53257cf20a4de3810156ae9576a7092f5527df98)) - Update README to break infinite redirect loop ([#​1450](https://redirect.github.com/googleapis/python-storage/issues/1450)) ([03f1594](https://redirect.github.com/googleapis/python-storage/commit/03f1594eb90ea1298a3a23927537c86ac35d33d5))
      pytest-dev/pytest (pytest) ### [`v9.0.2`](https://redirect.github.com/pytest-dev/pytest/releases/tag/9.0.2) [Compare Source](https://redirect.github.com/pytest-dev/pytest/compare/9.0.1...9.0.2) ### pytest 9.0.2 (2025-12-06) #### Bug fixes - [#​13896](https://redirect.github.com/pytest-dev/pytest/issues/13896): The terminal progress feature added in pytest 9.0.0 has been disabled by default, except on Windows, due to compatibility issues with some terminal emulators. You may enable it again by passing `-p terminalprogress`. We may enable it by default again once compatibility improves in the future. Additionally, when the environment variable `TERM` is `dumb`, the escape codes are no longer emitted, even if the plugin is enabled. - [#​13904](https://redirect.github.com/pytest-dev/pytest/issues/13904): Fixed the TOML type of the `tmp_path_retention_count` settings in the API reference from number to string. - [#​13946](https://redirect.github.com/pytest-dev/pytest/issues/13946): The private `config.inicfg` attribute was changed in a breaking manner in pytest 9.0.0. Due to its usage in the ecosystem, it is now restored to working order using a compatibility shim. It will be deprecated in pytest 9.1 and removed in pytest 10. - [#​13965](https://redirect.github.com/pytest-dev/pytest/issues/13965): Fixed quadratic-time behavior when handling `unittest` subtests in Python 3.10. #### Improved documentation - [#​4492](https://redirect.github.com/pytest-dev/pytest/issues/4492): The API Reference now contains cross-reference-able documentation of `pytest's command-line flags `. ### [`v9.0.1`](https://redirect.github.com/pytest-dev/pytest/releases/tag/9.0.1) [Compare Source](https://redirect.github.com/pytest-dev/pytest/compare/9.0.0...9.0.1) ### pytest 9.0.1 (2025-11-12) #### Bug fixes - [#​13895](https://redirect.github.com/pytest-dev/pytest/issues/13895): Restore support for skipping tests via `raise unittest.SkipTest`. - [#​13896](https://redirect.github.com/pytest-dev/pytest/issues/13896): The terminal progress plugin added in pytest 9.0 is now automatically disabled when iTerm2 is detected, it generated desktop notifications instead of the desired functionality. - [#​13904](https://redirect.github.com/pytest-dev/pytest/issues/13904): Fixed the TOML type of the verbosity settings in the API reference from number to string. - [#​13910](https://redirect.github.com/pytest-dev/pytest/issues/13910): Fixed UserWarning: Do not expect file\_or\_dir on some earlier Python 3.12 and 3.13 point versions. #### Packaging updates and notes for downstreams - [#​13933](https://redirect.github.com/pytest-dev/pytest/issues/13933): The tox configuration has been adjusted to make sure the desired version string can be passed into its `package_env` through the `SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST` environment variable as a part of the release process -- by `webknjaz`. #### Contributor-facing changes - [#​13891](https://redirect.github.com/pytest-dev/pytest/issues/13891), [#​13942](https://redirect.github.com/pytest-dev/pytest/issues/13942): The CI/CD part of the release automation is now capable of creating GitHub Releases without having a Git checkout on disk -- by `bluetech` and `webknjaz`. - [#​13933](https://redirect.github.com/pytest-dev/pytest/issues/13933): The tox configuration has been adjusted to make sure the desired version string can be passed into its `package_env` through the `SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST` environment variable as a part of the release process -- by `webknjaz`. ### [`v9.0.0`](https://redirect.github.com/pytest-dev/pytest/releases/tag/9.0.0) [Compare Source](https://redirect.github.com/pytest-dev/pytest/compare/8.4.2...9.0.0) ### pytest 9.0.0 (2025-11-05) #### New features - [#​1367](https://redirect.github.com/pytest-dev/pytest/issues/1367): **Support for subtests** has been added. `subtests ` are an alternative to parametrization, useful in situations where the parametrization values are not all known at collection time. Example: ```python def contains_docstring(p: Path) -> bool: """Return True if the given Python file contains a top-level docstring.""" ... def test_py_files_contain_docstring(subtests: pytest.Subtests) -> None: for path in Path.cwd().glob("*.py"): with subtests.test(path=str(path)): assert contains_docstring(path) ``` Each assert failure or error is caught by the context manager and reported individually, giving a clear picture of all files that are missing a docstring. In addition, `unittest.TestCase.subTest` is now also supported. This feature was originally implemented as a separate plugin in [pytest-subtests](https://redirect.github.com/pytest-dev/pytest-subtests), but since then has been merged into the core. > \[!NOTE] > This feature is experimental and will likely evolve in future releases. By that we mean that we might change how subtests are reported on failure, but the functionality and how to use it are stable. - [#​13743](https://redirect.github.com/pytest-dev/pytest/issues/13743): Added support for **native TOML configuration files**. While pytest, since version 6, supports configuration in `pyproject.toml` files under `[tool.pytest.ini_options]`, it does so in an "INI compatibility mode", where all configuration values are treated as strings or list of strings. Now, pytest supports the native TOML data model. In `pyproject.toml`, the native TOML configuration is under the `[tool.pytest]` table. ```toml # pyproject.toml [tool.pytest] minversion = "9.0" addopts = ["-ra", "-q"] testpaths = [ "tests", "integration", ] ``` The `[tool.pytest.ini_options]` table remains supported, but both tables cannot be used at the same time. If you prefer to use a separate configuration file, or don't use `pyproject.toml`, you can use `pytest.toml` or `.pytest.toml`: ```toml # pytest.toml or .pytest.toml [pytest] minversion = "9.0" addopts = ["-ra", "-q"] testpaths = [ "tests", "integration", ] ``` The documentation now (sometimes) shows configuration snippets in both TOML and INI formats, in a tabbed interface. See `config file formats` for full details. - [#​13823](https://redirect.github.com/pytest-dev/pytest/issues/13823): Added a **"strict mode"** enabled by the `strict` configuration option. When set to `true`, the `strict` option currently enables - `strict_config` - `strict_markers` - `strict_parametrization_ids` - `strict_xfail` The individual strictness options can be explicitly set to override the global `strict` setting. The previously-deprecated `--strict` command-line flag now enables strict mode. If pytest adds new strictness options in the future, they will also be enabled in strict mode. Therefore, you should only enable strict mode if you use a pinned/locked version of pytest, or if you want to proactively adopt new strictness options as they are added. See `strict mode` for more details. - [#​13737](https://redirect.github.com/pytest-dev/pytest/issues/13737): Added the `strict_parametrization_ids` configuration option. When set, pytest emits an error if it detects non-unique parameter set IDs, rather than automatically making the IDs unique by adding 0, 1, ... to them. This can be particularly useful for catching unintended duplicates. - [#​13072](https://redirect.github.com/pytest-dev/pytest/issues/13072): Added support for displaying test session **progress in the terminal tab** using the [OSC 9;4;](https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC) ANSI sequence. When pytest runs in a supported terminal emulator like ConEmu, Gnome Terminal, Ptyxis, Windows Terminal, Kitty or Ghostty, you'll see the progress in the terminal tab or window, allowing you to monitor pytest's progress at a glance. This feature is automatically enabled when running in a TTY. It is implemented as an internal plugin. If needed, it can be disabled as follows: - On a user level, using `-p no:terminalprogress` on the command line or via an environment variable `PYTEST_ADDOPTS='-p no:terminalprogress'`. - On a project configuration level, using `addopts = "-p no:terminalprogress"`. - [#​478](https://redirect.github.com/pytest-dev/pytest/issues/478): Support PEP420 (implicit namespace packages) as --pyargs target when `consider_namespace_packages` is true in the config. Previously, this option only impacted package imports, now it also impacts tests discovery. - [#​13678](https://redirect.github.com/pytest-dev/pytest/issues/13678): Added a new `faulthandler_exit_on_timeout` configuration option set to "false" by default to let faulthandler interrupt the pytest process after a timeout in case of deadlock. Previously, a faulthandler timeout would only dump the traceback of all threads to stderr, but would not interrupt the pytest process. \-- by `ogrisel`. - [#​13829](https://redirect.github.com/pytest-dev/pytest/issues/13829): Added support for configuration option aliases via the `aliases` parameter in `Parser.addini() `. Plugins can now register alternative names for configuration options, allowing for more flexibility in configuration naming and supporting backward compatibility when renaming options. The canonical name always takes precedence if both the canonical name and an alias are specified in the configuration file. #### Improvements in existing functionality - [#​13330](https://redirect.github.com/pytest-dev/pytest/issues/13330): Having pytest configuration spread over more than one file (for example having both a `pytest.ini` file and `pyproject.toml` with a `[tool.pytest.ini_options]` table) will now print a warning to make it clearer to the user that only one of them is actually used. \-- by `sgaist` - [#​13574](https://redirect.github.com/pytest-dev/pytest/issues/13574): The single argument `--version` no longer loads the entire plugin infrastructure, making it faster and more reliable when displaying only the pytest version. Passing `--version` twice (e.g., `pytest --version --version`) retains the original behavior, showing both the pytest version and plugin information. > \[!NOTE] > Since `--version` is now processed early, it only takes effect when passed directly via the command line. It will not work if set through other mechanisms, such as `PYTEST_ADDOPTS` or `addopts`. - [#​13823](https://redirect.github.com/pytest-dev/pytest/issues/13823): Added `strict_xfail` as an alias to the `xfail_strict` option, `strict_config` as an alias to the `--strict-config` flag, and `strict_markers` as an alias to the `--strict-markers` flag. This makes all strictness options consistently have configuration options with the prefix `strict_`. - [#​13700](https://redirect.github.com/pytest-dev/pytest/issues/13700): --junitxml no longer prints the generated xml file summary at the end of the pytest session when --quiet is given. - [#​13732](https://redirect.github.com/pytest-dev/pytest/issues/13732): Previously, when filtering warnings, pytest would fail if the filter referenced a class that could not be imported. Now, this only outputs a message indicating the problem. - [#​13859](https://redirect.github.com/pytest-dev/pytest/issues/13859): Clarify the error message for pytest.raises() when a regex match fails. - [#​13861](https://redirect.github.com/pytest-dev/pytest/issues/13861): Better sentence structure in a test's expected error message. Previously, the error message would be "expected exception must be \, but got \". Now, it is "Expected \, but got \". #### Removals and backward incompatible breaking changes - [#​12083](https://redirect.github.com/pytest-dev/pytest/issues/12083): Fixed a bug where an invocation such as pytest a/ a/b would cause only tests from a/b to run, and not other tests under a/. The fix entails a few breaking changes to how such overlapping arguments and duplicates are handled: 1. pytest a/b a/ or pytest a/ a/b are equivalent to pytest a; if an argument overlaps another arguments, only the prefix remains. 2. pytest x.py x.py is equivalent to pytest x.py; previously such an invocation was taken as an explicit request to run the tests from the file twice. If you rely on these behaviors, consider using `--keep-duplicates `, which retains its existing behavior (including the bug). - [#​13719](https://redirect.github.com/pytest-dev/pytest/issues/13719): Support for Python 3.9 is dropped following its end of life. - [#​13766](https://redirect.github.com/pytest-dev/pytest/issues/13766): Previously, pytest would assume it was running in a CI/CD environment if either of the environment variables $CI or $BUILD\_NUMBER was defined; now, CI mode is only activated if at least one of those variables is defined and set to a *non-empty* value. - [#​13779](https://redirect.github.com/pytest-dev/pytest/issues/13779): **PytestRemovedIn9Warning deprecation warnings are now errors by default.** Following our plan to remove deprecated features with as little disruption as possible, all warnings of type `PytestRemovedIn9Warning` now generate errors instead of warning messages by default. **The affected features will be effectively removed in pytest 9.1**, so please consult the `deprecations` section in the docs for directions on how to update existing code. In the pytest `9.0.X` series, it is possible to change the errors back into warnings as a stopgap measure by adding this to your `pytest.ini` file: ```ini [pytest] filterwarnings = ignore::pytest.PytestRemovedIn9Warning ``` But this will stop working when pytest `9.1` is released. **If you have concerns** about the removal of a specific feature, please add a comment to `13779`. #### Deprecations (removal in next major release) - [#​13807](https://redirect.github.com/pytest-dev/pytest/issues/13807): `monkeypatch.syspath_prepend() ` now issues a deprecation warning when the prepended path contains legacy namespace packages (those using `pkg_resources.declare_namespace()`). Users should migrate to native namespace packages (`420`). See `monkeypatch-fixup-namespace-packages` for details. #### Bug fixes - [#​13445](https://redirect.github.com/pytest-dev/pytest/issues/13445): Made the type annotations of `pytest.skip` and friends more spec-complaint to have them work across more type checkers. - [#​13537](https://redirect.github.com/pytest-dev/pytest/issues/13537): Fixed a bug in which `ExceptionGroup` with only `Skipped` exceptions in teardown was not handled correctly and showed as error. - [#​13598](https://redirect.github.com/pytest-dev/pytest/issues/13598): Fixed possible collection confusion on Windows when short paths and symlinks are involved. - [#​13716](https://redirect.github.com/pytest-dev/pytest/issues/13716): Fixed a bug where a nonsensical invocation like `pytest x.py[a]` (a file cannot be parametrized)
      --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/googleapis/google-auth-library-python). --------- Co-authored-by: Anthonios Partheniou Co-authored-by: Chalmer Lowe * chore(deps): update all dependencies (#1923) This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [google-auth](https://redirect.github.com/googleapis/google-auth-library-python) | `==2.45.0` → `==2.47.0` | ![age](https://developer.mend.io/api/mc/badges/age/pypi/google-auth/2.47.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/google-auth/2.45.0/2.47.0?slim=true) | | [google-cloud-compute](https://redirect.github.com/googleapis/google-cloud-python/tree/main/packages/google-cloud-compute) ([source](https://redirect.github.com/googleapis/google-cloud-python)) | `==1.40.0` → `==1.41.0` | ![age](https://developer.mend.io/api/mc/badges/age/pypi/google-cloud-compute/1.41.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/google-cloud-compute/1.40.0/1.41.0?slim=true) | --- ### Release Notes
      googleapis/google-auth-library-python (google-auth) ### [`v2.47.0`](https://redirect.github.com/googleapis/google-auth-library-python/blob/HEAD/CHANGELOG.md#2470-2026-01-06) [Compare Source](https://redirect.github.com/googleapis/google-auth-library-python/compare/v2.46.0...v2.47.0) ##### Features - drop `cachetools` dependency in favor of simple local implementation ([#​1590](https://redirect.github.com/googleapis/google-auth-library-python/issues/1590)) ([5c07e1c4f52bc77a1b16fa3b7b3c5269c242f6f4](https://redirect.github.com/googleapis/google-auth-library-python/commit/5c07e1c4f52bc77a1b16fa3b7b3c5269c242f6f4)) ##### Bug Fixes - Python 3.8 support ([#​1918](https://redirect.github.com/googleapis/google-auth-library-python/issues/1918)) ([60dc20014a35ec4ba71e8065b9a33ecbdbeca97a](https://redirect.github.com/googleapis/google-auth-library-python/commit/60dc20014a35ec4ba71e8065b9a33ecbdbeca97a)) ### [`v2.46.0`](https://redirect.github.com/googleapis/google-auth-library-python/blob/HEAD/CHANGELOG.md#2460-2026-01-05) [Compare Source](https://redirect.github.com/googleapis/google-auth-library-python/compare/v2.45.0...v2.46.0) ##### Documentation - update urllib3 docstrings for v2 compatibility ([#​1903](https://redirect.github.com/googleapis/google-auth-library-python/issues/1903)) ([3f1aeea2d1014ea1d244a4c3470e52d74d55404b](https://redirect.github.com/googleapis/google-auth-library-python/commit/3f1aeea2d1014ea1d244a4c3470e52d74d55404b)) ##### Features - Recognize workload certificate config in has\_default\_client\_cert\_source for mTLS for Agentic Identities ([#​1907](https://redirect.github.com/googleapis/google-auth-library-python/issues/1907)) ([0b9107d573123e358c347ffa067637f992af61b4](https://redirect.github.com/googleapis/google-auth-library-python/commit/0b9107d573123e358c347ffa067637f992af61b4)) ##### Bug Fixes - add types to default and verify\_token and Request **init** based on comments in the source code. ([#​1588](https://redirect.github.com/googleapis/google-auth-library-python/issues/1588)) ([59a5f588f7793b59d923a4185c8c07738da618f7](https://redirect.github.com/googleapis/google-auth-library-python/commit/59a5f588f7793b59d923a4185c8c07738da618f7)) - fix the document of secure\_authorized\_session ([#​1536](https://redirect.github.com/googleapis/google-auth-library-python/issues/1536)) ([5d0014707fc359782df5ccfcaa75fd372fe9dce3](https://redirect.github.com/googleapis/google-auth-library-python/commit/5d0014707fc359782df5ccfcaa75fd372fe9dce3)) - remove setup.cfg configuration for creating universal wheels ([#​1693](https://redirect.github.com/googleapis/google-auth-library-python/issues/1693)) ([c767531ce05a89002d109f595187aff1fcaacfb7](https://redirect.github.com/googleapis/google-auth-library-python/commit/c767531ce05a89002d109f595187aff1fcaacfb7)) - use .read() instead of .content.read() in aiohttp transport ([#​1899](https://redirect.github.com/googleapis/google-auth-library-python/issues/1899)) ([12f4470f808809e8abf1141f98d88ab720c3899b](https://redirect.github.com/googleapis/google-auth-library-python/commit/12f4470f808809e8abf1141f98d88ab720c3899b)) - raise RefreshError for missing token in impersonated credentials ([#​1897](https://redirect.github.com/googleapis/google-auth-library-python/issues/1897)) ([94d04e090fdfc61926dd32bc1d65f8820b9cede5](https://redirect.github.com/googleapis/google-auth-library-python/commit/94d04e090fdfc61926dd32bc1d65f8820b9cede5)) - Fix test coverage for mtls\_helper ([#​1886](https://redirect.github.com/googleapis/google-auth-library-python/issues/1886)) ([02e71631fe275d93825c2e957e830773e75133f7](https://redirect.github.com/googleapis/google-auth-library-python/commit/02e71631fe275d93825c2e957e830773e75133f7))
      googleapis/google-cloud-python (google-cloud-compute) ### [`v1.41.0`](https://redirect.github.com/googleapis/google-cloud-python/releases/tag/google-cloud-compute-v1.41.0): google-cloud-compute 1.41.0 [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.40.0...google-cloud-compute-v1.41.0) #### [1.41.0](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.40.0...google-cloud-compute-v1.41.0) (2026-01-08)
      --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/googleapis/google-auth-library-python). Co-authored-by: Anthonios Partheniou * chore(deps): update dependency google-cloud-storage to v3.8.0 (#1935) This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [google-cloud-storage](https://redirect.github.com/googleapis/python-storage) | `==3.7.0` → `==3.8.0` | ![age](https://developer.mend.io/api/mc/badges/age/pypi/google-cloud-storage/3.8.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/google-cloud-storage/3.7.0/3.8.0?slim=true) | --- ### Release Notes
      googleapis/python-storage (google-cloud-storage) ### [`v3.8.0`](https://redirect.github.com/googleapis/python-storage/blob/HEAD/CHANGELOG.md#380-2026-01-13) [Compare Source](https://redirect.github.com/googleapis/python-storage/compare/v3.7.0...v3.8.0) ##### Features - flush the last chunk in append method ([#​1699](https://redirect.github.com/googleapis/python-storage/issues/1699)) ([89bfe7a5fcd0391da35e9ceccc185279782b5420](https://redirect.github.com/googleapis/python-storage/commit/89bfe7a5fcd0391da35e9ceccc185279782b5420)) - add write resumption strategy ([#​1663](https://redirect.github.com/googleapis/python-storage/issues/1663)) ([a57ea0ec786a84c7ae9ed82c6ae5d38ecadba4af](https://redirect.github.com/googleapis/python-storage/commit/a57ea0ec786a84c7ae9ed82c6ae5d38ecadba4af)) - add bidi stream retry manager. ([#​1632](https://redirect.github.com/googleapis/python-storage/issues/1632)) ([d90f0ee09902a21b186106bcf0a8cb0b81b34340](https://redirect.github.com/googleapis/python-storage/commit/d90f0ee09902a21b186106bcf0a8cb0b81b34340)) - implement "append\_from\_file" ([#​1686](https://redirect.github.com/googleapis/python-storage/issues/1686)) ([1333c956da18b4db753cda98c41c3619c84caf69](https://redirect.github.com/googleapis/python-storage/commit/1333c956da18b4db753cda98c41c3619c84caf69)) - make flush size configurable ([#​1677](https://redirect.github.com/googleapis/python-storage/issues/1677)) ([f7095faf0a81239894ff9d277849788b62eb6ac5](https://redirect.github.com/googleapis/python-storage/commit/f7095faf0a81239894ff9d277849788b62eb6ac5)) - compute chunk wise checksum for bidi\_writes ([#​1675](https://redirect.github.com/googleapis/python-storage/issues/1675)) ([139390cb01f93a2d61e7ec201e3637dffe0b2a34](https://redirect.github.com/googleapis/python-storage/commit/139390cb01f93a2d61e7ec201e3637dffe0b2a34)) - expose persisted size in mrd ([#​1671](https://redirect.github.com/googleapis/python-storage/issues/1671)) ([0e2961bef285fc064174a5c18e3db05c7a682521](https://redirect.github.com/googleapis/python-storage/commit/0e2961bef285fc064174a5c18e3db05c7a682521)) ##### Bug Fixes - add system test for opening with read\_handle ([#​1672](https://redirect.github.com/googleapis/python-storage/issues/1672)) ([6dc711dacd4d38c573aa4ca9ad71fe412c0e49c1](https://redirect.github.com/googleapis/python-storage/commit/6dc711dacd4d38c573aa4ca9ad71fe412c0e49c1)) - no state lookup while opening bidi-write stream ([#​1636](https://redirect.github.com/googleapis/python-storage/issues/1636)) ([2d5a7b16846a69f3a911844971241899f60cce14](https://redirect.github.com/googleapis/python-storage/commit/2d5a7b16846a69f3a911844971241899f60cce14)) - close write object stream always ([#​1661](https://redirect.github.com/googleapis/python-storage/issues/1661)) ([4a609a4b3f4ba1396825911cb02f8a9649135cd5](https://redirect.github.com/googleapis/python-storage/commit/4a609a4b3f4ba1396825911cb02f8a9649135cd5))
      --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/googleapis/google-auth-library-python). Co-authored-by: Anthonios Partheniou * chore(deps): update dependency google-cloud-compute to v1.42.0 (#1939) This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [google-cloud-compute](https://redirect.github.com/googleapis/google-cloud-python/tree/main/packages/google-cloud-compute) ([source](https://redirect.github.com/googleapis/google-cloud-python)) | `==1.41.0` → `==1.42.0` | ![age](https://developer.mend.io/api/mc/badges/age/pypi/google-cloud-compute/1.42.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/google-cloud-compute/1.41.0/1.42.0?slim=true) | --- ### Release Notes
      googleapis/google-cloud-python (google-cloud-compute) ### [`v1.42.0`](https://redirect.github.com/googleapis/google-cloud-python/releases/tag/google-cloud-compute-v1.42.0): google-cloud-compute 1.42.0 [Compare Source](https://redirect.github.com/googleapis/google-cloud-python/compare/google-cloud-compute-v1.41.0...google-cloud-compute-v1.42.0) ##### Features - Update Compute Engine v1 API to revision [`2025121`](https://redirect.github.com/googleapis/google-cloud-python/commit/20251210) ([#​1137](https://redirect.github.com/googleapis/google-cloud-python/issues/1137)) ([63cc1cdf](https://redirect.github.com/googleapis/google-cloud-python/commit/63cc1cdf)) - Update Compute Engine v1 API to revision [`2025103`](https://redirect.github.com/googleapis/google-cloud-python/commit/20251031) ([63cc1cdf](https://redirect.github.com/googleapis/google-cloud-python/commit/63cc1cdf))
      --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/googleapis/google-auth-library-python). Co-authored-by: Chalmer Lowe * updates import order and adds annotation authenticate_explicit_with_adc.py * adds annotations authenticate_implicit_with_adc.py * reorders imports and adds annotations custom_aws_supplier.py * reorders imports and adds annotations custom_okta_supplier.py * adds annotation idtoken_from_impersonated_credentials.py * reorders imports and adds annotation idtoken_from_metadata_server.py * adds annotation idtoken_from_service_account.py * reorders imports and adds annotation snippets_test.py * adds annotations verify_google_idtoken.py * updates import order authenticate_explicit_with_adc.py * updates import order custom_okta_supplier.py * updates line spacing * reorders import custom_okta_supplier.py * Apply suggestion from @gemini-code-assist[bot] Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Update licence to reflect Google LLC * updates credential demo to mask the access_key --------- Co-authored-by: Sita Lakshmi Sangameswaran Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou Co-authored-by: Sai Sunder Srinivasan Co-authored-by: WhiteSource Renovate Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Co-authored-by: Carl Lundin Co-authored-by: ohmayr Co-authored-by: werman Co-authored-by: Daniel Sanche Co-authored-by: Lingqing Gan Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../authenticate_explicit_with_adc.py | 55 +++++ .../authenticate_implicit_with_adc.py | 46 +++++ auth/cloud-client-temp/custom_aws_supplier.py | 119 +++++++++++ .../cloud-client-temp/custom_okta_supplier.py | 190 ++++++++++++++++++ .../idtoken_from_impersonated_credentials.py | 75 +++++++ .../idtoken_from_metadata_server.py | 50 +++++ .../idtoken_from_service_account.py | 50 +++++ auth/cloud-client-temp/noxfile.py | 85 ++++++++ auth/cloud-client-temp/noxfile_config.py | 38 ++++ auth/cloud-client-temp/requirements.txt | 8 + auth/cloud-client-temp/snippets_test.py | 76 +++++++ .../verify_google_idtoken.py | 62 ++++++ 12 files changed, 854 insertions(+) create mode 100644 auth/cloud-client-temp/authenticate_explicit_with_adc.py create mode 100644 auth/cloud-client-temp/authenticate_implicit_with_adc.py create mode 100644 auth/cloud-client-temp/custom_aws_supplier.py create mode 100644 auth/cloud-client-temp/custom_okta_supplier.py create mode 100644 auth/cloud-client-temp/idtoken_from_impersonated_credentials.py create mode 100644 auth/cloud-client-temp/idtoken_from_metadata_server.py create mode 100644 auth/cloud-client-temp/idtoken_from_service_account.py create mode 100644 auth/cloud-client-temp/noxfile.py create mode 100644 auth/cloud-client-temp/noxfile_config.py create mode 100644 auth/cloud-client-temp/requirements.txt create mode 100644 auth/cloud-client-temp/snippets_test.py create mode 100644 auth/cloud-client-temp/verify_google_idtoken.py diff --git a/auth/cloud-client-temp/authenticate_explicit_with_adc.py b/auth/cloud-client-temp/authenticate_explicit_with_adc.py new file mode 100644 index 00000000000..c9ce2f02af3 --- /dev/null +++ b/auth/cloud-client-temp/authenticate_explicit_with_adc.py @@ -0,0 +1,55 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START auth_cloud_explicit_adc] + + +import google.auth +from google.cloud import storage +import google.oauth2.credentials + + +def authenticate_explicit_with_adc() -> None: + """ + List storage buckets by authenticating with ADC. + + // TODO(Developer): + // 1. Before running this sample, + // set up ADC as described in https://cloud.google.com/docs/authentication/external/set-up-adc + // 2. Replace the project variable. + // 3. Make sure you have the necessary permission to list storage buckets: "storage.buckets.list" + """ + + # Construct the Google credentials object which obtains the default configuration from your + # working environment. + # google.auth.default() will give you ComputeEngineCredentials + # if you are on a GCE (or other metadata server supported environments). + credentials, project_id = google.auth.default() + # If you are authenticating to a Cloud API, you can let the library include the default scope, + # https://www.googleapis.com/auth/cloud-platform, because IAM is used to provide fine-grained + # permissions for Cloud. + # If you need to provide a scope, specify it as follows: + # credentials = google.auth.default(scopes=scope) + # For more information on scopes to use, + # see: https://developers.google.com/identity/protocols/oauth2/scopes + + # Construct the Storage client. + storage_client = storage.Client(credentials=credentials, project=project_id) + buckets = storage_client.list_buckets() + print("Buckets:") + for bucket in buckets: + print(bucket.name) + print("Listed all storage buckets.") + +# [END auth_cloud_explicit_adc] diff --git a/auth/cloud-client-temp/authenticate_implicit_with_adc.py b/auth/cloud-client-temp/authenticate_implicit_with_adc.py new file mode 100644 index 00000000000..ed967ab880a --- /dev/null +++ b/auth/cloud-client-temp/authenticate_implicit_with_adc.py @@ -0,0 +1,46 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START auth_cloud_implicit_adc] + +from google.cloud import storage + + +def authenticate_implicit_with_adc(project_id: str = "your-google-cloud-project-id") -> None: + """ + When interacting with Google Cloud Client libraries, the library can auto-detect the + credentials to use. + + // TODO(Developer): + // 1. Before running this sample, + // set up ADC as described in https://cloud.google.com/docs/authentication/external/set-up-adc + // 2. Replace the project variable. + // 3. Make sure that the user account or service account that you are using + // has the required permissions. For this sample, you must have "storage.buckets.list". + Args: + project_id: The project id of your Google Cloud project. + """ + + # This snippet demonstrates how to list buckets. + # *NOTE*: Replace the client created below with the client required for your application. + # Note that the credentials are not specified when constructing the client. + # Hence, the client library will look for credentials using ADC. + storage_client = storage.Client(project=project_id) + buckets = storage_client.list_buckets() + print("Buckets:") + for bucket in buckets: + print(bucket.name) + print("Listed all storage buckets.") + +# [END auth_cloud_implicit_adc] diff --git a/auth/cloud-client-temp/custom_aws_supplier.py b/auth/cloud-client-temp/custom_aws_supplier.py new file mode 100644 index 00000000000..abe858eb5b5 --- /dev/null +++ b/auth/cloud-client-temp/custom_aws_supplier.py @@ -0,0 +1,119 @@ +# Copyright 2025 Google LLC +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os +import sys + +import boto3 +from dotenv import load_dotenv +from google.auth.aws import AwsSecurityCredentials, AwsSecurityCredentialsSupplier +from google.auth.aws import Credentials as AwsCredentials +from google.auth.exceptions import GoogleAuthError +from google.auth.transport.requests import AuthorizedSession + +load_dotenv() + + +class CustomAwsSupplier(AwsSecurityCredentialsSupplier): + """Custom AWS Security Credentials Supplier.""" + + def __init__(self) -> None: + """Initializes the Boto3 session, prioritizing environment variables for region.""" + # Explicitly read the region from the environment first. This ensures that + # a value from a .env file is picked up reliably for local testing. + region = os.getenv("AWS_REGION") or os.getenv("AWS_DEFAULT_REGION") + + # If region is None, Boto3's discovery chain will be used when needed. + self.session = boto3.Session(region_name=region) + self._cached_region = None + print(f"[INFO] CustomAwsSupplier initialized. Region from env: {region}") + + def get_aws_region(self, context: object, request: object) -> str: + """Returns the AWS region using Boto3's default provider chain.""" + if self._cached_region: + return self._cached_region + + # Accessing region_name will use the value from the constructor if provided, + # otherwise it triggers Boto3's lazy-loading discovery (e.g., metadata service). + self._cached_region = self.session.region_name + + if not self._cached_region: + print("[ERROR] Boto3 was unable to resolve an AWS region.", file=sys.stderr) + raise GoogleAuthError("Boto3 was unable to resolve an AWS region.") + + print(f"[INFO] Boto3 resolved AWS Region: {self._cached_region}") + return self._cached_region + + def get_aws_security_credentials(self, context: object, request: object = None) -> AwsSecurityCredentials: + """Retrieves AWS security credentials using Boto3's default provider chain.""" + aws_credentials = self.session.get_credentials() + if not aws_credentials: + print("[ERROR] Unable to resolve AWS credentials.", file=sys.stderr) + raise GoogleAuthError("Unable to resolve AWS credentials from the provider chain.") + + # Instead of printing the whole key, mask everything but the last 4 characters + masked_access_key = f"{'*' * 16}{aws_credentials.access_key[-4:]}" + print(f"[INFO] Resolved AWS Access Key ID: {masked_access_key}") + + return AwsSecurityCredentials( + access_key_id=aws_credentials.access_key, + secret_access_key=aws_credentials.secret_key, + session_token=aws_credentials.token, + ) + + +def main() -> None: + """Main function to demonstrate the custom AWS supplier.""" + print("--- Starting Script ---") + + gcp_audience = os.getenv("GCP_WORKLOAD_AUDIENCE") + sa_impersonation_url = os.getenv("GCP_SERVICE_ACCOUNT_IMPERSONATION_URL") + gcs_bucket_name = os.getenv("GCS_BUCKET_NAME") + + print(f"GCP_WORKLOAD_AUDIENCE: {gcp_audience}") + print(f"GCS_BUCKET_NAME: {gcs_bucket_name}") + + if not all([gcp_audience, sa_impersonation_url, gcs_bucket_name]): + print("[ERROR] Missing required environment variables.", file=sys.stderr) + raise GoogleAuthError("Missing required environment variables.") + + custom_supplier = CustomAwsSupplier() + + credentials = AwsCredentials( + audience=gcp_audience, + subject_token_type="urn:ietf:params:aws:token-type:aws4_request", + service_account_impersonation_url=sa_impersonation_url, + aws_security_credentials_supplier=custom_supplier, + scopes=['https://www.googleapis.com/auth/devstorage.read_write'], + ) + + bucket_url = f"https://storage.googleapis.com/storage/v1/b/{gcs_bucket_name}" + print(f"Request URL: {bucket_url}") + + authed_session = AuthorizedSession(credentials) + try: + print("Attempting to make authenticated request to Google Cloud Storage...") + res = authed_session.get(bucket_url) + res.raise_for_status() + print("\n--- SUCCESS! ---") + print("Successfully authenticated and retrieved bucket data:") + print(json.dumps(res.json(), indent=2)) + except Exception as e: + print("--- FAILED --- ", file=sys.stderr) + print(e, file=sys.stderr) + exit(1) + + +if __name__ == "__main__": + main() diff --git a/auth/cloud-client-temp/custom_okta_supplier.py b/auth/cloud-client-temp/custom_okta_supplier.py new file mode 100644 index 00000000000..c2b35fd406f --- /dev/null +++ b/auth/cloud-client-temp/custom_okta_supplier.py @@ -0,0 +1,190 @@ +# Copyright 2025 Google LLC +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os +import time +import urllib.parse + +from dotenv import load_dotenv +from google.auth.exceptions import GoogleAuthError +from google.auth.identity_pool import Credentials as IdentityPoolClient +from google.auth.transport.requests import AuthorizedSession +import requests + +load_dotenv() + +# Workload Identity Pool Configuration +GCP_WORKLOAD_AUDIENCE = os.getenv("GCP_WORKLOAD_AUDIENCE") +SERVICE_ACCOUNT_IMPERSONATION_URL = os.getenv("GCP_SERVICE_ACCOUNT_IMPERSONATION_URL") +GCS_BUCKET_NAME = os.getenv("GCS_BUCKET_NAME") + +# Okta Configuration +OKTA_DOMAIN = os.getenv("OKTA_DOMAIN") +OKTA_CLIENT_ID = os.getenv("OKTA_CLIENT_ID") +OKTA_CLIENT_SECRET = os.getenv("OKTA_CLIENT_SECRET") + +# Constants +TOKEN_URL = "https://sts.googleapis.com/v1/token" +SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:jwt" + + +class OktaClientCredentialsSupplier: + """A custom SubjectTokenSupplier that authenticates with Okta. + + This supplier uses the Client Credentials grant flow for machine-to-machine + (M2M) authentication with Okta. + """ + + def __init__(self, domain: str, client_id: str, client_secret: str) -> None: + self.okta_token_url = f"{domain}/oauth2/default/v1/token" + self.client_id = client_id + self.client_secret = client_secret + self.access_token = None + self.expiry_time = 0 + print("OktaClientCredentialsSupplier initialized.") + + def get_subject_token(self, context: object, request: object = None) -> str: + """Fetches a new token if the current one is expired or missing. + + Args: + context: The context object, not used in this implementation. + + Returns: + The Okta Access token. + """ + # Check if the current token is still valid (with a 60-second buffer). + is_token_valid = self.access_token and time.time() < self.expiry_time - 60 + + if is_token_valid: + print("[Supplier] Returning cached Okta Access token.") + return self.access_token + + print( + "[Supplier] Token is missing or expired. Fetching new Okta Access token..." + ) + self._fetch_okta_access_token() + return self.access_token + + def _fetch_okta_access_token(self) -> None: + """Performs the Client Credentials grant flow with Okta.""" + headers = { + "Content-Type": "application/x-www-form-urlencoded", + "Accept": "application/json", + } + data = { + "grant_type": "client_credentials", + "scope": "gcp.test.read", + } + encoded_data = urllib.parse.urlencode(data) + + try: + response = requests.post( + self.okta_token_url, + headers=headers, + data=encoded_data, + auth=(self.client_id, self.client_secret), + ) + response.raise_for_status() + token_data = response.json() + + if "access_token" in token_data and "expires_in" in token_data: + self.access_token = token_data["access_token"] + self.expiry_time = time.time() + token_data["expires_in"] + print( + f"[Supplier] Successfully received Access Token from Okta. " + f"Expires in {token_data['expires_in']} seconds." + ) + else: + raise GoogleAuthError( + "Access token or expires_in not found in Okta response." + ) + except requests.exceptions.RequestException as e: + print(f"[Supplier] Error fetching token from Okta: {e}") + if e.response: + print(f"[Supplier] Okta response: {e.response.text}") + raise GoogleAuthError( + "Failed to authenticate with Okta using Client Credentials grant." + ) from e + + +def main() -> None: + """Main function to demonstrate the custom Okta supplier. + + TODO(Developer): + 1. Before running this sample, set up your environment variables. You can do + this by creating a .env file in the same directory as this script and + populating it with the following variables: + - GCP_WORKLOAD_AUDIENCE: The audience for the GCP workload identity pool. + - GCP_SERVICE_ACCOUNT_IMPERSONATION_URL: The URL for service account impersonation (optional). + - GCS_BUCKET_NAME: The name of the GCS bucket to access. + - OKTA_DOMAIN: Your Okta domain (e.g., https://dev-12345.okta.com). + - OKTA_CLIENT_ID: The Client ID of your Okta M2M application. + - OKTA_CLIENT_SECRET: The Client Secret of your Okta M2M application. + """ + if not all( + [ + GCP_WORKLOAD_AUDIENCE, + GCS_BUCKET_NAME, + OKTA_DOMAIN, + OKTA_CLIENT_ID, + OKTA_CLIENT_SECRET, + ] + ): + raise GoogleAuthError( + "Missing required environment variables. Please check your .env file." + ) + + # 1. Instantiate the custom supplier with Okta credentials. + okta_supplier = OktaClientCredentialsSupplier( + OKTA_DOMAIN, OKTA_CLIENT_ID, OKTA_CLIENT_SECRET + ) + + # 2. Instantiate an IdentityPoolClient. + client = IdentityPoolClient( + audience=GCP_WORKLOAD_AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + subject_token_supplier=okta_supplier, + # If you choose to provide explicit scopes: use the `scopes` parameter. + default_scopes=['https://www.googleapis.com/auth/cloud-platform'], + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + ) + + # 3. Construct the URL for the Cloud Storage JSON API. + bucket_url = f"https://storage.googleapis.com/storage/v1/b/{GCS_BUCKET_NAME}" + print(f"[Test] Getting metadata for bucket: {GCS_BUCKET_NAME}...") + print(f"[Test] Request URL: {bucket_url}") + + # 4. Use the client to make an authenticated request. + authed_session = AuthorizedSession(client) + try: + res = authed_session.get(bucket_url) + res.raise_for_status() + print("\n--- SUCCESS! ---") + print("Successfully authenticated and retrieved bucket data:") + print(json.dumps(res.json(), indent=2)) + except requests.exceptions.RequestException as e: + print("\n--- FAILED ---") + print(f"Request failed: {e}") + if e.response: + print(f"Response: {e.response.text}") + exit(1) + except GoogleAuthError as e: + print("\n--- FAILED ---") + print(f"Authentication or request failed: {e}") + exit(1) + + +if __name__ == "__main__": + main() diff --git a/auth/cloud-client-temp/idtoken_from_impersonated_credentials.py b/auth/cloud-client-temp/idtoken_from_impersonated_credentials.py new file mode 100644 index 00000000000..7819072d927 --- /dev/null +++ b/auth/cloud-client-temp/idtoken_from_impersonated_credentials.py @@ -0,0 +1,75 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [auth_cloud_idtoken_impersonated_credentials] + +import google +from google.auth import impersonated_credentials +import google.auth.transport.requests + + +def idtoken_from_impersonated_credentials( + impersonated_service_account: str, scope: str, target_audience: str) -> None: + """ + Use a service account (SA1) to impersonate as another service account (SA2) and obtain id token + for the impersonated account. + To obtain token for SA2, SA1 should have the "roles/iam.serviceAccountTokenCreator" permission + on SA2. + + Args: + impersonated_service_account: The name of the privilege-bearing service account for whom the credential is created. + Examples: name@project.service.gserviceaccount.com + + scope: Provide the scopes that you might need to request to access Google APIs, + depending on the level of access you need. + For this example, we use the cloud-wide scope and use IAM to narrow the permissions. + https://cloud.google.com/docs/authentication#authorization_for_services + For more information, see: https://developers.google.com/identity/protocols/oauth2/scopes + + target_audience: The service name for which the id token is requested. Service name refers to the + logical identifier of an API service, such as "iap.googleapis.com". + Examples: iap.googleapis.com + """ + + # Construct the GoogleCredentials object which obtains the default configuration from your + # working environment. + credentials, project_id = google.auth.default() + + # Create the impersonated credential. + target_credentials = impersonated_credentials.Credentials( + source_credentials=credentials, + target_principal=impersonated_service_account, + # delegates: The chained list of delegates required to grant the final accessToken. + # For more information, see: + # https://cloud.google.com/iam/docs/create-short-lived-credentials-direct#sa-credentials-permissions + # Delegate is NOT USED here. + delegates=[], + target_scopes=[scope], + lifetime=300) + + # Set the impersonated credential, target audience and token options. + id_creds = impersonated_credentials.IDTokenCredentials( + target_credentials, + target_audience=target_audience, + include_email=True) + + # Get the ID token. + # Once you've obtained the ID token, use it to make an authenticated call + # to the target audience. + request = google.auth.transport.requests.Request() + id_creds.refresh(request) + # token = id_creds.token + print("Generated ID token.") + +# [auth_cloud_idtoken_impersonated_credentials] diff --git a/auth/cloud-client-temp/idtoken_from_metadata_server.py b/auth/cloud-client-temp/idtoken_from_metadata_server.py new file mode 100644 index 00000000000..7c9277f349e --- /dev/null +++ b/auth/cloud-client-temp/idtoken_from_metadata_server.py @@ -0,0 +1,50 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START auth_cloud_idtoken_metadata_server] + +import google +from google.auth import compute_engine +import google.auth.transport.requests +import google.oauth2.credentials + + +def idtoken_from_metadata_server(url: str) -> None: + """ + Use the Google Cloud metadata server in the Cloud Run (or AppEngine or Kubernetes etc.,) + environment to create an identity token and add it to the HTTP request as part of an + Authorization header. + + Args: + url: The url or target audience to obtain the ID token for. + Examples: http://www.example.com + """ + + request = google.auth.transport.requests.Request() + # Set the target audience. + # Setting "use_metadata_identity_endpoint" to "True" will make the request use the default application + # credentials. Optionally, you can also specify a specific service account to use by mentioning + # the service_account_email. + credentials = compute_engine.IDTokenCredentials( + request=request, target_audience=url, use_metadata_identity_endpoint=True + ) + + # Get the ID token. + # Once you've obtained the ID token, use it to make an authenticated call + # to the target audience. + credentials.refresh(request) + # print(credentials.token) + print("Generated ID token.") + +# [END auth_cloud_idtoken_metadata_server] diff --git a/auth/cloud-client-temp/idtoken_from_service_account.py b/auth/cloud-client-temp/idtoken_from_service_account.py new file mode 100644 index 00000000000..d96a4862a8b --- /dev/null +++ b/auth/cloud-client-temp/idtoken_from_service_account.py @@ -0,0 +1,50 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START auth_cloud_idtoken_service_account] + +import google.auth +import google.auth.transport.requests + +from google.oauth2 import service_account + + +def get_idToken_from_serviceaccount(json_credential_path: str, target_audience: str) -> None: + """ + TODO(Developer): Replace the below variables before running the code. + + *NOTE*: + Using service account keys introduces risk; they are long-lived, and can be used by anyone + that obtains the key. Proper rotation and storage reduce this risk but do not eliminate it. + For these reasons, you should consider an alternative approach that + does not use a service account key. Several alternatives to service account keys + are described here: + https://cloud.google.com/docs/authentication/external/set-up-adc + + Args: + json_credential_path: Path to the service account json credential file. + target_audience: The url or target audience to obtain the ID token for. + Examples: http://www.abc.com + """ + + # Obtain the id token by providing the json file path and target audience. + credentials = service_account.IDTokenCredentials.from_service_account_file( + filename=json_credential_path, + target_audience=target_audience) + + request = google.auth.transport.requests.Request() + credentials.refresh(request) + print("Generated ID token.") + +# [END auth_cloud_idtoken_service_account] diff --git a/auth/cloud-client-temp/noxfile.py b/auth/cloud-client-temp/noxfile.py new file mode 100644 index 00000000000..3cdf3cf3bdb --- /dev/null +++ b/auth/cloud-client-temp/noxfile.py @@ -0,0 +1,85 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pathlib + +import nox + +CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() + +# https://github.com/psf/black/issues/2964, pin click version to 8.0.4 to +# avoid incompatiblity with black. +CLICK_VERSION = "click==8.0.4" +BLACK_VERSION = "black==19.3b0" +BLACK_PATHS = [ + "google", + "tests", + "tests_async", + "noxfile.py", + "setup.py", + "docs/conf.py", +] + + +# Error if a python version is missing +nox.options.error_on_missing_interpreters = True + +# +# Style Checks +# + + +# Linting with flake8. +# +# We ignore the following rules: +# E203: whitespace before ‘:’ +# E266: too many leading ‘#’ for block comment +# E501: line too long +# I202: Additional newline in a section of imports +# +# We also need to specify the rules which are ignored by default: +# ['E226', 'W504', 'E126', 'E123', 'W503', 'E24', 'E704', 'E121'] +FLAKE8_COMMON_ARGS = [ + "--show-source", + "--builtin=gettext", + "--max-complexity=20", + "--exclude=.nox,.cache,env,lib,generated_pb2,*_pb2.py,*_pb2_grpc.py", + "--ignore=E121,E123,E126,E203,E226,E24,E266,E501,E704,W503,W504,I202", + "--max-line-length=88", +] + + +@nox.session(python=["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]) +def unit(session): + # constraints_path = str( + # CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" + # ) + session.install("-r", "requirements.txt") + # session.install("-e", ".") + session.run( + "pytest", + f"--junitxml=unit_{session.python}_sponge_log.xml", + "snippets_test.py", + # "tests_async", + ) + + +@nox.session +def lint(session: nox.sessions.Session) -> None: + session.install("flake8") + + args = FLAKE8_COMMON_ARGS + [ + ".", + ] + session.run("flake8", *args) diff --git a/auth/cloud-client-temp/noxfile_config.py b/auth/cloud-client-temp/noxfile_config.py new file mode 100644 index 00000000000..e892b338fce --- /dev/null +++ b/auth/cloud-client-temp/noxfile_config.py @@ -0,0 +1,38 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Default TEST_CONFIG_OVERRIDE for python repos. + +# You can copy this file into your directory, then it will be inported from +# the noxfile.py. + +# The source of truth: +# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/noxfile_config.py + +TEST_CONFIG_OVERRIDE = { + # You can opt out from the test for specific Python versions. + "ignored_versions": ["2.7"], + # Old samples are opted out of enforcing Python type hints + # All new samples should feature them + "enforce_type_hints": True, + # An envvar key for determining the project id to use. Change it + # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a + # build specific Cloud project. You can also use your own string + # to use your own Cloud project. + # "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", + "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", + # A dictionary you want to inject into your test. Don't put any + # secrets here. These values will override predefined values. + "envs": {}, +} diff --git a/auth/cloud-client-temp/requirements.txt b/auth/cloud-client-temp/requirements.txt new file mode 100644 index 00000000000..8dafe853ea0 --- /dev/null +++ b/auth/cloud-client-temp/requirements.txt @@ -0,0 +1,8 @@ +google-cloud-compute==1.42.0 +google-cloud-storage==3.8.0 +google-auth==2.47.0 +pytest===8.4.2; python_version == '3.9' +pytest==9.0.2; python_version > '3.9' +boto3>=1.26.0 +requests==2.32.5 +python-dotenv==1.2.1 diff --git a/auth/cloud-client-temp/snippets_test.py b/auth/cloud-client-temp/snippets_test.py new file mode 100644 index 00000000000..940f27e553c --- /dev/null +++ b/auth/cloud-client-temp/snippets_test.py @@ -0,0 +1,76 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os +import re + +from _pytest.capture import CaptureFixture +import google +import google.auth.transport.requests +from google.oauth2 import service_account + +import authenticate_explicit_with_adc +import authenticate_implicit_with_adc +import idtoken_from_metadata_server +import idtoken_from_service_account +# from system_tests.noxfile import SERVICE_ACCOUNT_FILE +import verify_google_idtoken + +CREDENTIALS, PROJECT = google.auth.default() +SERVICE_ACCOUNT_FILE = os.getenv("GOOGLE_APPLICATION_CREDENTIALS") + + +def test_authenticate_explicit_with_adc(capsys: CaptureFixture) -> None: + authenticate_explicit_with_adc.authenticate_explicit_with_adc() + out, err = capsys.readouterr() + assert re.search("Listed all storage buckets.", out) + + +def test_authenticate_implicit_with_adc(capsys: CaptureFixture) -> None: + authenticate_implicit_with_adc.authenticate_implicit_with_adc(PROJECT) + out, err = capsys.readouterr() + assert re.search("Listed all storage buckets.", out) + + +def test_idtoken_from_metadata_server(capsys: CaptureFixture) -> None: + idtoken_from_metadata_server.idtoken_from_metadata_server("https://www.google.com") + out, err = capsys.readouterr() + assert re.search("Generated ID token.", out) + + +def test_idtoken_from_service_account(capsys: CaptureFixture) -> None: + idtoken_from_service_account.get_idToken_from_serviceaccount( + SERVICE_ACCOUNT_FILE, + "iap.googleapis.com") + out, err = capsys.readouterr() + assert re.search("Generated ID token.", out) + + +def test_verify_google_idtoken() -> None: + idtoken = get_idtoken_from_service_account(SERVICE_ACCOUNT_FILE, "iap.googleapis.com") + + verify_google_idtoken.verify_google_idtoken( + idtoken, + "iap.googleapis.com", + "https://www.googleapis.com/oauth2/v3/certs" + ) + + +def get_idtoken_from_service_account(json_credential_path: str, target_audience: str) -> str: + credentials = service_account.IDTokenCredentials.from_service_account_file( + filename=json_credential_path, + target_audience=target_audience) + + request = google.auth.transport.requests.Request() + credentials.refresh(request) + return credentials.token diff --git a/auth/cloud-client-temp/verify_google_idtoken.py b/auth/cloud-client-temp/verify_google_idtoken.py new file mode 100644 index 00000000000..8bb4c075fd7 --- /dev/null +++ b/auth/cloud-client-temp/verify_google_idtoken.py @@ -0,0 +1,62 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START auth_cloud_verify_google_idtoken] + +import google +import google.auth.transport.requests +from google.oauth2 import id_token + + +def verify_google_idtoken(idtoken: str, audience: str = "iap.googleapis.com", + jwk_url: str = "https://www.googleapis.com/oauth2/v3/certs") -> None: + """ + Verifies the obtained Google id token. This is done at the receiving end of the OIDC endpoint. + The most common use case for verifying the ID token is when you are protecting + your own APIs with IAP. Google services already verify credentials as a platform, + so verifying ID tokens before making Google API calls is usually unnecessary. + + Args: + idtoken: The Google ID token to verify. + + audience: The service name for which the id token is requested. Service name refers to the + logical identifier of an API service, such as "iap.googleapis.com". + + jwk_url: To verify id tokens, get the Json Web Key endpoint (jwk). + OpenID Connect allows the use of a "Discovery document," a JSON document found at a + well-known location containing key-value pairs which provide details about the + OpenID Connect provider's configuration. + For more information on validating the jwt, see: + https://developers.google.com/identity/protocols/oauth2/openid-connect#validatinganidtoken + + Here, we validate Google's token using Google's OpenID Connect service (jwkUrl). + For more information on jwk,see: + https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-key-sets + """ + + request = google.auth.transport.requests.Request() + # Set the parameters and verify the token. + # Setting "certs_url" is optional. When verifying a Google ID token, this is set by default. + result = id_token.verify_token(idtoken, request, audience, clock_skew_in_seconds=10) + + # Verify that the token contains subject and email claims. + # Get the User id. + if not result["sub"] is None: + print(f"User id: {result['sub']}") + # Optionally, if "INCLUDE_EMAIL" was set in the token options, check if the + # email was verified. + if result.get('email_verified'): + print(f"Email verified {result['email']}") + +# [END auth_cloud_verify_google_idtoken] From eeb7fe7554d035ef216213ecbaa233f4721d2507 Mon Sep 17 00:00:00 2001 From: Anayeli Date: Fri, 6 Feb 2026 11:55:11 -0600 Subject: [PATCH 38/67] chore(bigquery): Remove unused sample code for delete transfer (#13789) --- bigquery-datatransfer/snippets/conftest.py | 9 +++++---- .../snippets/manage_transfer_configs.py | 20 ------------------- .../snippets/manage_transfer_configs_test.py | 6 ------ 3 files changed, 5 insertions(+), 30 deletions(-) diff --git a/bigquery-datatransfer/snippets/conftest.py b/bigquery-datatransfer/snippets/conftest.py index 1248a9407f7..30dd52f3ce6 100644 --- a/bigquery-datatransfer/snippets/conftest.py +++ b/bigquery-datatransfer/snippets/conftest.py @@ -123,7 +123,7 @@ def transfer_client(default_credentials, project_id): @pytest.fixture(scope="session") def transfer_config_name(transfer_client, project_id, dataset_id, service_account_name): - from . import manage_transfer_configs, scheduled_query + from . import scheduled_query # Use the transfer_client fixture so we know quota is attributed to the # correct project. @@ -140,9 +140,10 @@ def transfer_config_name(transfer_client, project_id, dataset_id, service_accoun } ) yield transfer_config.name - manage_transfer_configs.delete_config( - {"transfer_config_name": transfer_config.name} - ) + try: + transfer_client.delete_transfer_config(name=transfer_config.name) + except google.api_core.exceptions.NotFound: + pass @pytest.fixture diff --git a/bigquery-datatransfer/snippets/manage_transfer_configs.py b/bigquery-datatransfer/snippets/manage_transfer_configs.py index cfc344705a1..57da430afb5 100644 --- a/bigquery-datatransfer/snippets/manage_transfer_configs.py +++ b/bigquery-datatransfer/snippets/manage_transfer_configs.py @@ -138,23 +138,3 @@ def schedule_backfill_manual_transfer(override_values={}): print(f"backfill: {run.run_time} run: {run.name}") # [END bigquerydatatransfer_schedule_backfill] return response.runs - - -def delete_config(override_values={}): - import google.api_core.exceptions - from google.cloud import bigquery_datatransfer - - transfer_client = bigquery_datatransfer.DataTransferServiceClient() - - transfer_config_name = "projects/1234/locations/us/transferConfigs/abcd" - # To facilitate testing, we replace values with alternatives - # provided by the testing harness. - transfer_config_name = override_values.get( - "transfer_config_name", transfer_config_name - ) - try: - transfer_client.delete_transfer_config(name=transfer_config_name) - except google.api_core.exceptions.NotFound: - print("Transfer config not found.") - else: - print(f"Deleted transfer config: {transfer_config_name}") diff --git a/bigquery-datatransfer/snippets/manage_transfer_configs_test.py b/bigquery-datatransfer/snippets/manage_transfer_configs_test.py index 5aa68cc10a9..0110fe29fd5 100644 --- a/bigquery-datatransfer/snippets/manage_transfer_configs_test.py +++ b/bigquery-datatransfer/snippets/manage_transfer_configs_test.py @@ -53,9 +53,3 @@ def test_schedule_backfill_manual_transfer(capsys, transfer_config_name): assert transfer_config_name in out # Check that there are three runs for between 2 and 5 days ago. assert len(runs) == 3 - - -def test_delete_config(capsys, transfer_config_name): - # transfer_config_name fixture in conftest.py calls the delete config - # sample. To conserve limited BQ-DTS quota we only make basic checks. - assert len(transfer_config_name) != 0 From ba237cf06bf3aa6d3934f4efd6d2caa4a6be91a7 Mon Sep 17 00:00:00 2001 From: Sampath Kumar Date: Fri, 6 Feb 2026 23:35:32 +0530 Subject: [PATCH 39/67] feat(genai): add new code-execution code samples (#13763) * feat(genai): Add 3 new CodeExecutions(new folder) samples. These code samples demonstrate usage of Gemini's ability to dynamically generate required code and then execute it. * fix(genai): update the annotate code sample prompts * chore(genai): add example response & input image referrence. * chore(genai): add example out for barplot example. * chore(genai): add example out for cropimage example. * fix(genai): fix region tags * cleanup(genai): cleanup old files * fix(genai): lint error * chore(genai): update confi to limit tests to just python 3.12 * fix: add Pillow to requirements.txt * fix: change copyright licensed year to 2026 Also update template folder files. * chore: remove the images_folder Simplify the code footprint by using samples available in GCS bucket. * chore: rollback year-update for templatefolder files. --------- Co-authored-by: Jennifer Davis --- ...execution_annotateimage_with_txt_gcsimg.py | 150 +++++++++++++++++ .../codeexecution_barplot_with_txt_img.py | 156 ++++++++++++++++++ .../codeexecution_cropimage_with_txt_img.py | 95 +++++++++++ genai/code_execution/noxfile_config.py | 42 +++++ genai/code_execution/requirements-test.txt | 4 + genai/code_execution/requirements.txt | 2 + genai/code_execution/test_codeexecution.py | 35 ++++ 7 files changed, 484 insertions(+) create mode 100644 genai/code_execution/codeexecution_annotateimage_with_txt_gcsimg.py create mode 100644 genai/code_execution/codeexecution_barplot_with_txt_img.py create mode 100644 genai/code_execution/codeexecution_cropimage_with_txt_img.py create mode 100644 genai/code_execution/noxfile_config.py create mode 100644 genai/code_execution/requirements-test.txt create mode 100644 genai/code_execution/requirements.txt create mode 100644 genai/code_execution/test_codeexecution.py diff --git a/genai/code_execution/codeexecution_annotateimage_with_txt_gcsimg.py b/genai/code_execution/codeexecution_annotateimage_with_txt_gcsimg.py new file mode 100644 index 00000000000..a81f62c8491 --- /dev/null +++ b/genai/code_execution/codeexecution_annotateimage_with_txt_gcsimg.py @@ -0,0 +1,150 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def generate_content() -> bool: + # [START googlegenaisdk_codeexecution_annotateimage_with_txt_gcsimg] + import io + from PIL import Image + from google import genai + from google.genai import types + + client = genai.Client() + + response = client.models.generate_content( + model="gemini-3-flash-preview", + contents=[ + types.Part.from_uri( + file_uri="https://storage.googleapis.com/cloud-samples-data/generative-ai/image/robotic.jpeg", + mime_type="image/png", + ), + "Annotate on the image with arrows of different colors, which object should go into which bin.", + ], + config=types.GenerateContentConfig(tools=[types.Tool(code_execution=types.ToolCodeExecution)]), + ) + + img_count = 0 + for part in response.candidates[0].content.parts: + if part.text is not None: + print(part.text) + if part.executable_code is not None: + print("####################### 1. Generate Python Code #######################") + print(part.executable_code.code) + if part.code_execution_result is not None: + print("####################### 2. Executing Python Code #######################") + print(part.code_execution_result.output) + # For local executions, save the output to a local filename + if part.as_image() is not None: + print("####################### 3. Save Output #######################") + img_count += 1 + output_location = f"robotic-annotate-output-{img_count}.jpg" + image_data = part.as_image().image_bytes + image = Image.open(io.BytesIO(image_data)) + image = image.convert("RGB") + image.save(output_location) + print(f"Output is saved to {output_location}") + # Example response: + # ####################### 1. Generate Python Code ####################### + # import PIL.Image + # import PIL.ImageDraw + # + # # Load the image to get dimensions + # img = PIL.Image.open('f_https___storage.googleapis.com_cloud_samples_data_generative_ai_image_robotic.jpeg') + # width, height = img.size + # + # # Define objects and bins with normalized coordinates [ymin, xmin, ymax, xmax] + # bins = { + # 'light_blue': [118, 308, 338, 436], + # 'green': [248, 678, 458, 831], + # 'black': [645, 407, 898, 578] + # } + # + # objects = [ + # {'name': 'green pepper', 'box': [256, 482, 296, 546], 'target': 'green'}, + # {'name': 'red pepper', 'box': [317, 478, 349, 544], 'target': 'green'}, + # {'name': 'grapes', 'box': [584, 555, 664, 593], 'target': 'green'}, + # {'name': 'cherries', 'box': [463, 671, 511, 718], 'target': 'green'}, + # {'name': 'soda can', 'box': [397, 524, 489, 605], 'target': 'light_blue'}, + # {'name': 'brown snack', 'box': [397, 422, 475, 503], 'target': 'black'}, + # {'name': 'welch snack', 'box': [520, 466, 600, 543], 'target': 'black'}, + # {'name': 'paper towel', 'box': [179, 564, 250, 607], 'target': 'black'}, + # {'name': 'plastic cup', 'box': [271, 587, 346, 643], 'target': 'black'}, + # ] + # + # # Helper to get center of a normalized box + # def get_center(box): + # ymin, xmin, ymax, xmax = box + # return ((xmin + xmax) / 2000 * width, (ymin + ymax) / 2000 * height) + # + # draw = PIL.ImageDraw.Draw(img) + # + # # Define arrow colors based on target bin + # colors = { + # 'green': 'green', + # 'light_blue': 'blue', + # 'black': 'red' + # } + # + # for obj in objects: + # start_point = get_center(obj['box']) + # end_point = get_center(bins[obj['target']]) + # color = colors[obj['target']] + # # Drawing a line with an arrow head (simulated with a few extra lines) + # draw.line([start_point, end_point], fill=color, width=5) + # # Simple arrowhead + # import math + # angle = math.atan2(end_point[1] - start_point[1], end_point[0] - start_point[0]) + # arrow_len = 20 + # p1 = (end_point[0] - arrow_len * math.cos(angle - math.pi / 6), + # end_point[1] - arrow_len * math.sin(angle - math.pi / 6)) + # p2 = (end_point[0] - arrow_len * math.cos(angle + math.pi / 6), + # end_point[1] - arrow_len * math.sin(angle + math.pi / 6)) + # draw.line([end_point, p1], fill=color, width=5) + # draw.line([end_point, p2], fill=color, width=5) + # + # img.save('annotated_robotic.jpeg') + # + # # Also list detections for confirmation + # # [ + # # {"box_2d": [118, 308, 338, 436], "label": "light blue bin"}, + # # {"box_2d": [248, 678, 458, 831], "label": "green bin"}, + # # {"box_2d": [645, 407, 898, 578], "label": "black bin"}, + # # {"box_2d": [256, 482, 296, 546], "label": "green pepper"}, + # # {"box_2d": [317, 478, 349, 544], "label": "red pepper"}, + # # {"box_2d": [584, 555, 664, 593], "label": "grapes"}, + # # {"box_2d": [463, 671, 511, 718], "label": "cherries"}, + # # {"box_2d": [397, 524, 489, 605], "label": "soda can"}, + # # {"box_2d": [397, 422, 475, 503], "label": "brown snack"}, + # # {"box_2d": [520, 466, 600, 543], "label": "welch snack"}, + # # {"box_2d": [179, 564, 250, 607], "label": "paper towel"}, + # # {"box_2d": [271, 587, 346, 643], "label": "plastic cup"} + # # ] + # + # ####################### 2. Executing Python Code ####################### + # None + # ####################### 3. Save Output ####################### + # Output is saved to output-annotate-image-1.jpg + # The image has been annotated with arrows indicating the appropriate bin for each object based on standard waste sorting practices: + # + # - **Green Arrows (Compost):** Organic items such as the green pepper, red pepper, grapes, and cherries are directed to the **green bin**. + # - **Blue Arrow (Recycling):** The crushed soda can is directed to the **light blue bin**. + # - **Red Arrows (Trash/Landfill):** Non-recyclable or contaminated items like the snack wrappers (brown and Welch's), the white paper towel, and the small plastic cup are directed to the **black bin**. + # + # These categorizations follow common sorting rules where green is for organics, blue for recyclables, and black for general waste. + # [END googlegenaisdk_codeexecution_annotateimage_with_txt_gcsimg] + return True + + +if __name__ == "__main__": + generate_content() diff --git a/genai/code_execution/codeexecution_barplot_with_txt_img.py b/genai/code_execution/codeexecution_barplot_with_txt_img.py new file mode 100644 index 00000000000..7542282e4be --- /dev/null +++ b/genai/code_execution/codeexecution_barplot_with_txt_img.py @@ -0,0 +1,156 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def generate_content() -> bool: + # [START googlegenaisdk_codeexecution_barplot_with_txt_img] + import io + from PIL import Image + from google import genai + from google.genai import types + + # Use to the benchmark image in Cloud Storage + image = types.Part.from_uri( + file_uri="https://storage.googleapis.com/cloud-samples-data/generative-ai/image/benchmark.jpeg", + mime_type="image/jpeg", + ) + + client = genai.Client() + + response = client.models.generate_content( + model="gemini-3-flash-preview", + contents=[ + image, + "Make a bar chart of per-category performance, normalize prior SOTA as 1.0 for each task," + "then take average per-category. Plot using matplotlib with nice style.", + ], + config=types.GenerateContentConfig(tools=[types.Tool(code_execution=types.ToolCodeExecution)]), + ) + + img_count = 0 + for part in response.candidates[0].content.parts: + if part.text is not None: + print(part.text) + if part.executable_code is not None: + print("####################### 1. Generate Python Code #######################") + print(part.executable_code.code) + if part.code_execution_result is not None: + print("####################### 2. Executing Python Code #######################") + print(part.code_execution_result.output) + # For local executions, save the output to a local filename + if part.as_image() is not None: + print("####################### 3. Save Output #######################") + img_count += 1 + output_location = f"output-barplot-{img_count}.jpg" + image_data = part.as_image().image_bytes + image = Image.open(io.BytesIO(image_data)) + image = image.convert("RGB") + image.save(output_location) + print(f"Output is saved to {output_location}") + # Example response: + # ####################### 1. Generate Python Code ####################### + # import matplotlib.pyplot as plt + # import numpy as np + # + # data = [ + # # Category, Benchmark, G3P, G2.5P, C4.5, GPT5.1, lower_is_better + # ("Visual Reasoning", "MMMU Pro", 81.0, 68.0, 72.0, 76.0, False), + # ("Visual Reasoning", "VLMsAreBiased", 50.6, 24.3, 32.7, 21.7, False), + # ("Document", "CharXiv Reasoning", 81.4, 69.6, 67.2, 69.5, False), + # ("Document", "OmniDocBench1.5*", 0.115, 0.145, 0.120, 0.147, True), + # ("Spatial", "ERQA", 70.5, 56.0, 51.3, 60.0, False), + # ("Spatial", "Point-Bench", 85.5, 62.7, 38.5, 41.8, False), + # ("Spatial", "RefSpatial", 65.5, 33.6, 19.5, 28.2, False), + # ("Spatial", "CV-Bench", 92.0, 85.9, 83.8, 84.6, False), + # ("Spatial", "MindCube", 77.7, 57.5, 58.5, 61.7, False), + # ("Screen", "ScreenSpot Pro", 72.7, 11.4, 49.9, 3.50, False), + # ("Screen", "Gui-World QA", 68.0, 42.8, 44.9, 38.7, False), + # ("Video", "Video-MMMU", 87.6, 83.6, 84.4, 80.4, False), + # ("Video", "Video-MME", 88.4, 86.9, 84.1, 86.3, False), + # ("Video", "1H-VideoQA", 81.8, 79.4, 52.0, 61.5, False), + # ("Video", "Perception Test", 80.0, 78.4, 74.1, 77.8, False), + # ("Video", "YouCook2", 222.7, 188.3, 145.8, 132.4, False), + # ("Video", "Vatex", 77.4, 71.3, 60.1, 62.9, False), + # ("Video", "Motion Bench", 70.3, 66.3, 65.9, 61.1, False), + # ("Education", "Math Kangaroo", 84.4, 77.4, 68.9, 79.9, False), + # ("Biomedical", "MedXpertQA-MM", 77.8, 65.9, 62.2, 65.5, False), + # ("Biomedical", "VQA-RAD", 81.9, 71.4, 76.0, 72.2, False), + # ("Biomedical", "MicroVQA", 68.8, 63.5, 61.4, 61.5, False), + # ] + # + # normalized_scores = [] + # for cat, bench, g3p, g25p, c45, gpt, lib in data: + # others = [g25p, c45, gpt] + # if lib: + # sota = min(others) + # norm_score = sota / g3p + # else: + # sota = max(others) + # norm_score = g3p / sota + # normalized_scores.append((cat, norm_score)) + # + # categories = {} + # for cat, score in normalized_scores: + # if cat not in categories: + # categories[cat] = [] + # categories[cat].append(score) + # + # avg_per_category = {cat: np.mean(scores) for cat, scores in categories.items()} + # + # # Plotting + # cats = list(avg_per_category.keys()) + # values = [avg_per_category[c] for c in cats] + # + # # Sort categories for better visualization if needed, or keep order from data + # plt.figure(figsize=(10, 6)) + # plt.style.use('ggplot') + # bars = plt.bar(cats, values, color='skyblue', edgecolor='navy') + # + # plt.axhline(y=1.0, color='red', linestyle='--', label='Prior SOTA (1.0)') + # plt.ylabel('Normalized Performance (SOTA = 1.0)') + # plt.title('Gemini 3 Pro Performance relative to Prior SOTA (Normalized)', fontsize=14) + # plt.xticks(rotation=45, ha='right') + # plt.ylim(0, max(values) * 1.2) + # + # for bar in bars: + # yval = bar.get_height() + # plt.text(bar.get_x() + bar.get_width()/2, yval + 0.02, f'{yval:.2f}x', ha='center', va='bottom') + # + # plt.legend() + # plt.tight_layout() + # plt.savefig('performance_chart.png') + # plt.show() + # + # print(avg_per_category) + # + # ####################### 2. Executing Python Code ####################### + # {'Visual Reasoning': np.float64(1.3065950426525028), 'Document': np.float64(1.1065092453773113), 'Spatial': np.float64(1.3636746436001959), 'Screen': np.float64(1.4856952211773211), 'Video': np.float64(1.0620548283943443), 'Education': np.float64(1.0563204005006257), 'Biomedical': np.float64(1.1138909257119955)} + # + # ####################### 3. Save Output ####################### + # Output is saved to output-barplot-1.jpg + # ####################### 3. Save Output ####################### + # Output is saved to output-barplot-2.jpg + # Based on the data provided in the table, I have calculated the per-category performance of Gemini 3 Pro normalized against the prior state-of-the-art (SOTA), which is defined as the best performance among Gemini 2.5 Pro, Claude Opus 4.5, and GPT-5.1 for each benchmark. + # + # For benchmarks where lower values are better (indicated by an asterisk, e.g., OmniDocBench1.5*), the normalization was calculated as $\text{Prior SOTA} / \text{Gemini 3 Pro Score}$. For all other benchmarks, it was calculated as $\text{Gemini 3 Pro Score} / \text{Prior SOTA}$. The values were then averaged within each category. + # + # The resulting bar chart below shows that Gemini 3 Pro outperforms the prior SOTA across all categories, with the most significant gains in **Screen** (1.49x), **Spatial** (1.36x), and **Visual Reasoning** (1.31x) benchmarks. + # + # ![Gemini 3 Pro Performance Chart](performance_chart.png) + # [END googlegenaisdk_codeexecution_barplot_with_txt_img] + return True + + +if __name__ == "__main__": + generate_content() diff --git a/genai/code_execution/codeexecution_cropimage_with_txt_img.py b/genai/code_execution/codeexecution_cropimage_with_txt_img.py new file mode 100644 index 00000000000..9acfae1f93f --- /dev/null +++ b/genai/code_execution/codeexecution_cropimage_with_txt_img.py @@ -0,0 +1,95 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def generate_content() -> bool: + # [START googlegenaisdk_codeexecution_cropimage_with_txt_img] + import io + import requests + from PIL import Image + from google import genai + from google.genai import types + + # Download the input image + image_path = "https://storage.googleapis.com/cloud-samples-data/generative-ai/image/chips.jpeg" + image_bytes = requests.get(image_path).content + image = types.Part.from_bytes(data=image_bytes, mime_type="image/jpeg") + + client = genai.Client() + + response = client.models.generate_content( + model="gemini-3-flash-preview", + contents=[ + image, + "Locate the ESMT chip. What are the numbers on the chip?", + ], + config=types.GenerateContentConfig(tools=[types.Tool(code_execution=types.ToolCodeExecution)]), + ) + + for part in response.candidates[0].content.parts: + if part.text is not None: + print(part.text) + if part.executable_code is not None: + print("####################### 1. Generate Python Code #######################") + print(part.executable_code.code) + if part.code_execution_result is not None: + print("####################### 2. Executing Python Code #######################") + print(part.code_execution_result.output) + # For local executions, save the output to a local filename + if part.as_image() is not None: + print("####################### 3. Save Output #######################") + image_data = part.as_image().image_bytes + image = Image.open(io.BytesIO(image_data)) + output_location = "ESMT-chip-output.jpg" + image.save(output_location) + print(f"Output is saved to {output_location}") + # Example response: + # ####################### 1. Generate Python Code ####################### + # import PIL.Image + # import PIL.ImageDraw + # + # # Load the image to get dimensions + # img = PIL.Image.open('input_file_0.jpeg') + # width, height = img.size + # + # # Define the region for expression pedals + # # They are roughly in the center + # # Normalized coordinates roughly: [ymin, xmin, ymax, xmax] + # expression_pedals_box = [460, 465, 615, 615] + # + # # Convert normalized to pixel coordinates + # def norm_to_pixel(norm_box, w, h): + # ymin, xmin, ymax, xmax = norm_box + # return [int(ymin * h / 1000), int(xmin * w / 1000), int(ymax * h / 1000), int(xmax * w / 1000)] + # + # pedals_pixel_box = norm_to_pixel(expression_pedals_box, width, height) + # + # # Crop and save + # pedals_crop = img.crop((pedals_pixel_box[1], pedals_pixel_box[0], pedals_pixel_box[3], pedals_pixel_box[2])) + # pedals_crop.save('expression_pedals_zoom.png') + # + # # Output objects for verification (optional but helpful for internal tracking) + # # [{box_2d: [460, 465, 615, 615], label: "expression pedals"}] + # + # ####################### 2. Executing Python Code ####################### + # None + # ####################### 3. Save Output ####################### + # Output is saved to instrument-img-output.jpg + # Based on the zoomed-in image, there are 4 expression pedals located in the center of the organ console, above the pedalboard. + # [END googlegenaisdk_codeexecution_cropimage_with_txt_img] + return True + + +if __name__ == "__main__": + generate_content() diff --git a/genai/code_execution/noxfile_config.py b/genai/code_execution/noxfile_config.py new file mode 100644 index 00000000000..29d9e7911eb --- /dev/null +++ b/genai/code_execution/noxfile_config.py @@ -0,0 +1,42 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Default TEST_CONFIG_OVERRIDE for python repos. + +# You can copy this file into your directory, then it will be imported from +# the noxfile.py. + +# The source of truth: +# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/noxfile_config.py + +TEST_CONFIG_OVERRIDE = { + # You can opt out from the test for specific Python versions. + "ignored_versions": ["2.7", "3.7", "3.8", "3.9", "3.10", "3.11", "3.13", "3.14"], + # Old samples are opted out of enforcing Python type hints + # All new samples should feature them + "enforce_type_hints": True, + # An envvar key for determining the project id to use. Change it + # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a + # build specific Cloud project. You can also use your own string + # to use your own Cloud project. + "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", + # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', + # If you need to use a specific version of pip, + # change pip_version_override to the string representation + # of the version number, for example, "20.2.4" + "pip_version_override": None, + # A dictionary you want to inject into your test. Don't put any + # secrets here. These values will override predefined values. + "envs": {}, +} diff --git a/genai/code_execution/requirements-test.txt b/genai/code_execution/requirements-test.txt new file mode 100644 index 00000000000..8d10ef87035 --- /dev/null +++ b/genai/code_execution/requirements-test.txt @@ -0,0 +1,4 @@ +backoff==2.2.1 +google-api-core==2.29.0 +pytest==9.0.2 +pytest-asyncio==1.3.0 diff --git a/genai/code_execution/requirements.txt b/genai/code_execution/requirements.txt new file mode 100644 index 00000000000..7365e0b937d --- /dev/null +++ b/genai/code_execution/requirements.txt @@ -0,0 +1,2 @@ +google-genai==1.60.0 +pillow==11.1.0 diff --git a/genai/code_execution/test_codeexecution.py b/genai/code_execution/test_codeexecution.py new file mode 100644 index 00000000000..e3a8bfb7944 --- /dev/null +++ b/genai/code_execution/test_codeexecution.py @@ -0,0 +1,35 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os + +import codeexecution_annotateimage_with_txt_gcsimg +import codeexecution_barplot_with_txt_img +import codeexecution_cropimage_with_txt_img + +os.environ["GOOGLE_GENAI_USE_VERTEXAI"] = "True" +os.environ["GOOGLE_CLOUD_LOCATION"] = "global" # "us-central1" +# The project name is included in the CICD pipeline +# os.environ['GOOGLE_CLOUD_PROJECT'] = "add-your-project-name" + + +def test_codeexecution_annotateimage_with_txt_gcsimg() -> None: + assert codeexecution_annotateimage_with_txt_gcsimg.generate_content() + + +def test_codeexecution_barplot_with_txt_img() -> None: + assert codeexecution_barplot_with_txt_img.generate_content() + + +def test_codeexecution_cropimage_with_txt_img() -> None: + assert codeexecution_cropimage_with_txt_img.generate_content() From f8d3f26590e3617e027bd999242b22c0fb6c1931 Mon Sep 17 00:00:00 2001 From: madhavmadupu <134821170+madhavmadupu@users.noreply.github.com> Date: Fri, 6 Feb 2026 23:45:39 +0530 Subject: [PATCH 40/67] fix: ensure output folder exists before saving image (#13695) (#13737) * fix: ensure output folder exists before saving image (#13695) * refactor: apply bot suggestions for PEP8 imports and path handling --------- Co-authored-by: Jennifer Davis --- genai/image_generation/imggen_mmflash_with_txt.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/genai/image_generation/imggen_mmflash_with_txt.py b/genai/image_generation/imggen_mmflash_with_txt.py index 0ee371b7e84..cd6c458a757 100644 --- a/genai/image_generation/imggen_mmflash_with_txt.py +++ b/genai/image_generation/imggen_mmflash_with_txt.py @@ -15,10 +15,12 @@ def generate_content() -> str: # [START googlegenaisdk_imggen_mmflash_with_txt] + import os + from io import BytesIO + from google import genai from google.genai.types import GenerateContentConfig, Modality from PIL import Image - from io import BytesIO client = genai.Client() @@ -34,7 +36,10 @@ def generate_content() -> str: print(part.text) elif part.inline_data: image = Image.open(BytesIO((part.inline_data.data))) - image.save("output_folder/example-image-eiffel-tower.png") + # Ensure the output directory exists + output_dir = "output_folder" + os.makedirs(output_dir, exist_ok=True) + image.save(os.path.join(output_dir, "example-image-eiffel-tower.png")) # [END googlegenaisdk_imggen_mmflash_with_txt] return True From 5a70eaf0a8c1f507c682274a093a3a9b1c7ca75f Mon Sep 17 00:00:00 2001 From: David del Real Date: Mon, 9 Feb 2026 11:08:14 -0600 Subject: [PATCH 41/67] chore(genai) Update Gemini model version for live_websocket_audioscript_with_txt (#13800) b/481470123 --- genai/live/live_websocket_audiotranscript_with_txt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/genai/live/live_websocket_audiotranscript_with_txt.py b/genai/live/live_websocket_audiotranscript_with_txt.py index 0ed03b8638d..8b6ce59fb79 100644 --- a/genai/live/live_websocket_audiotranscript_with_txt.py +++ b/genai/live/live_websocket_audiotranscript_with_txt.py @@ -47,7 +47,7 @@ async def generate_content() -> str: # Configuration Constants PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT") LOCATION = "us-central1" - GEMINI_MODEL_NAME = "gemini-2.0-flash-live-preview-04-09" + GEMINI_MODEL_NAME = "gemini-live-2.5-flash-native-audio" # To generate a bearer token in CLI, use: # $ gcloud auth application-default print-access-token # It's recommended to fetch this token dynamically rather than hardcoding. From a7d723567cc7f2e09881ecb4fbc4c5e156078611 Mon Sep 17 00:00:00 2001 From: Angel Caamal Date: Mon, 9 Feb 2026 11:14:08 -0600 Subject: [PATCH 42/67] chore(genai): Update gemini model version to 2.5 in live websocket audiogen (#13799) --- genai/live/live_websocket_audiogen_with_txt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/genai/live/live_websocket_audiogen_with_txt.py b/genai/live/live_websocket_audiogen_with_txt.py index 5fdeee44299..d81c685cf0e 100644 --- a/genai/live/live_websocket_audiogen_with_txt.py +++ b/genai/live/live_websocket_audiogen_with_txt.py @@ -47,7 +47,7 @@ async def generate_content() -> str: # Configuration Constants PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT") LOCATION = "us-central1" - GEMINI_MODEL_NAME = "gemini-2.0-flash-live-preview-04-09" + GEMINI_MODEL_NAME = "gemini-live-2.5-flash-native-audio" # To generate a bearer token in CLI, use: # $ gcloud auth application-default print-access-token # It's recommended to fetch this token dynamically rather than hardcoding. From bcdc9a5a8ccada41dda445bb24b17fe2c69ac356 Mon Sep 17 00:00:00 2001 From: Angel Caamal Date: Mon, 9 Feb 2026 11:14:41 -0600 Subject: [PATCH 43/67] chore(genai): Update live_audio_with_txt to gemini-2.5-flash-native-audio (#13792) * chore(genai): Update live_audio_with_txt to gemini-2.5-flash-native-audio * chore(genai) Update gimini to live-2.5 --- genai/live/live_audio_with_txt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/genai/live/live_audio_with_txt.py b/genai/live/live_audio_with_txt.py index 5d4e82cef85..3860b9f0128 100644 --- a/genai/live/live_audio_with_txt.py +++ b/genai/live/live_audio_with_txt.py @@ -37,7 +37,7 @@ def play_audio(audio_array: np.ndarray, sample_rate: int = 24000) -> None: client = genai.Client() voice_name = "Aoede" - model = "gemini-2.0-flash-live-preview-04-09" + model = "gemini-live-2.5-flash-native-audio" config = LiveConnectConfig( response_modalities=[Modality.AUDIO], From 4c2155039d4f4aeb4829c07b1ab9aa4c104a3c08 Mon Sep 17 00:00:00 2001 From: David del Real Date: Mon, 9 Feb 2026 11:15:40 -0600 Subject: [PATCH 44/67] fix: Update firestore samples for python to use recurssive delete (#13777) b/470275866 Modified samples for Firestore Client and Asyn Firestore Client. --- firestore/cloud-async-client/snippets.py | 20 ++++++------------- firestore/cloud-client/snippets.py | 25 +++++++----------------- 2 files changed, 13 insertions(+), 32 deletions(-) diff --git a/firestore/cloud-async-client/snippets.py b/firestore/cloud-async-client/snippets.py index b0a97962cc5..3a7b9476941 100644 --- a/firestore/cloud-async-client/snippets.py +++ b/firestore/cloud-async-client/snippets.py @@ -693,24 +693,16 @@ async def delete_full_collection(): db = firestore.AsyncClient() # [START firestore_data_delete_collection_async] - async def delete_collection(coll_ref, batch_size): - docs = coll_ref.limit(batch_size).stream() - deleted = 0 + async def delete_collection(coll_ref): - async for doc in docs: - print(f"Deleting doc {doc.id} => {doc.to_dict()}") - await doc.reference.delete() - deleted = deleted + 1 - - if deleted >= batch_size: - return delete_collection(coll_ref, batch_size) + await db.recursive_delete(coll_ref) # [END firestore_data_delete_collection_async] - await delete_collection(db.collection("cities"), 10) - await delete_collection(db.collection("data"), 10) - await delete_collection(db.collection("objects"), 10) - await delete_collection(db.collection("users"), 10) + await delete_collection(db.collection("cities")) + await delete_collection(db.collection("data")) + await delete_collection(db.collection("objects")) + await delete_collection(db.collection("users")) async def collection_group_query(db): diff --git a/firestore/cloud-client/snippets.py b/firestore/cloud-client/snippets.py index 09dff308a50..9bc64d1c383 100644 --- a/firestore/cloud-client/snippets.py +++ b/firestore/cloud-client/snippets.py @@ -839,28 +839,17 @@ def delete_full_collection(): db = firestore.Client() # [START firestore_data_delete_collection] - def delete_collection(coll_ref, batch_size): - if batch_size == 0: - return + def delete_collection(coll_ref): - docs = coll_ref.list_documents(page_size=batch_size) - deleted = 0 - - for doc in docs: - print(f"Deleting doc {doc.id} => {doc.get().to_dict()}") - doc.delete() - deleted = deleted + 1 - - if deleted >= batch_size: - return delete_collection(coll_ref, batch_size) + print(f"Recursively deleting collection: {coll_ref}") + db.recursive_delete(coll_ref) # [END firestore_data_delete_collection] - delete_collection(db.collection("cities"), 10) - delete_collection(db.collection("data"), 10) - delete_collection(db.collection("objects"), 10) - delete_collection(db.collection("users"), 10) - delete_collection(db.collection("users"), 0) + delete_collection(db.collection("cities")) + delete_collection(db.collection("data")) + delete_collection(db.collection("objects")) + delete_collection(db.collection("users")) def collection_group_query(db): From 44c0d8ae94a0310b53393b7651cdee00092de8df Mon Sep 17 00:00:00 2001 From: "Leah E. Cole" <6719667+leahecole@users.noreply.github.com> Date: Mon, 9 Feb 2026 17:18:40 +0000 Subject: [PATCH 45/67] increase number of retries for flaky test (#11786) --- .../data_analytics_process_expansion_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer/2022_airflow_summit/data_analytics_process_expansion_test.py b/composer/2022_airflow_summit/data_analytics_process_expansion_test.py index 466a546391d..ffd4b46b7c5 100644 --- a/composer/2022_airflow_summit/data_analytics_process_expansion_test.py +++ b/composer/2022_airflow_summit/data_analytics_process_expansion_test.py @@ -214,7 +214,7 @@ def bq_dataset(test_bucket): print(f"Ignoring NotFound on cleanup, details: {e}") -@backoff.on_exception(backoff.expo, AssertionError, max_tries=3) +@backoff.on_exception(backoff.expo, AssertionError, max_tries=5) def test_process(test_dataproc_batch): print(test_dataproc_batch) From 563e5bd497540e54aa512ceb3917b57dd8f41fad Mon Sep 17 00:00:00 2001 From: Jennifer Davis Date: Mon, 9 Feb 2026 09:21:39 -0800 Subject: [PATCH 46/67] fix: remove zombie forced runs (#13805) --- .github/sync-repo-settings.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/sync-repo-settings.yaml b/.github/sync-repo-settings.yaml index bf76c480c47..1403afe718c 100644 --- a/.github/sync-repo-settings.yaml +++ b/.github/sync-repo-settings.yaml @@ -43,7 +43,6 @@ branchProtectionRules: # List of required status check contexts that must pass for commits to be accepted to matching branches. requiredStatusCheckContexts: - "Kokoro CI - Lint" - - "Kokoro CI - Python 2.7 (App Engine Standard Only)" - "Kokoro CI - Python 3.9" - "Kokoro CI - Python 3.13" - "cla/google" From 03b248c1b6c9e92d39ed6e39ba4ce1f701247ae7 Mon Sep 17 00:00:00 2001 From: Angel Caamal Date: Tue, 10 Feb 2026 10:53:22 -0600 Subject: [PATCH 47/67] chore(genai): Update gemini model to gemini-2.5-flash (#13810) --- genai/live/live_structured_output_with_txt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/genai/live/live_structured_output_with_txt.py b/genai/live/live_structured_output_with_txt.py index b743c87f064..2727fbcb08e 100644 --- a/genai/live/live_structured_output_with_txt.py +++ b/genai/live/live_structured_output_with_txt.py @@ -59,7 +59,7 @@ def generate_content() -> CalendarEvent: ) completion = client.beta.chat.completions.parse( - model="google/gemini-2.0-flash-001", + model="google/gemini-2.5-flash", messages=[ ChatCompletionSystemMessageParam( role="system", content="Extract the event information." From 6566734a3ab19dbd8276c979d766f328545ec678 Mon Sep 17 00:00:00 2001 From: Angel Caamal Date: Tue, 10 Feb 2026 23:37:20 -0600 Subject: [PATCH 48/67] chore(discoveryengine): update gemini to 2.5-flash in answer query sample (#13790) --- discoveryengine/answer_query_sample.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discoveryengine/answer_query_sample.py b/discoveryengine/answer_query_sample.py index 80e02e0c7c5..fcb47bff6b8 100644 --- a/discoveryengine/answer_query_sample.py +++ b/discoveryengine/answer_query_sample.py @@ -69,7 +69,7 @@ def answer_query_sample( ignore_non_answer_seeking_query=False, # Optional: Ignore non-answer seeking query ignore_low_relevant_content=False, # Optional: Return fallback answer when content is not relevant model_spec=discoveryengine.AnswerQueryRequest.AnswerGenerationSpec.ModelSpec( - model_version="gemini-2.0-flash-001/answer_gen/v1", # Optional: Model to use for answer generation + model_version="gemini-2.5-flash/answer_gen/v1", # Optional: Model to use for answer generation ), prompt_spec=discoveryengine.AnswerQueryRequest.AnswerGenerationSpec.PromptSpec( preamble="Give a detailed answer.", # Optional: Natural language instructions for customizing the answer. From e6e937de5f5ebbaaa91e48290d2eba0c4832c325 Mon Sep 17 00:00:00 2001 From: Angel Caamal Date: Tue, 10 Feb 2026 23:38:00 -0600 Subject: [PATCH 49/67] chore(genai): Update live_audiogen_with_txt to gemini-2.5-flash-native-audio (#13791) * chore(genai): Update live_audiogen_with_txt to gemini-2.5-flash-native-audio * chore(genai) Update gimini to live-2.5 --- genai/live/live_audiogen_with_txt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/genai/live/live_audiogen_with_txt.py b/genai/live/live_audiogen_with_txt.py index a6fc09f2e2a..29e20e8d661 100644 --- a/genai/live/live_audiogen_with_txt.py +++ b/genai/live/live_audiogen_with_txt.py @@ -29,7 +29,7 @@ async def generate_content() -> None: VoiceConfig) client = genai.Client() - model = "gemini-2.0-flash-live-preview-04-09" + model = "gemini-live-2.5-flash-native-audio" # For more Voice options, check https://cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/2-5-flash#live-api-native-audio voice_name = "Aoede" From 243e99a2dcd243d9467b94feb46d9cf3b3c254a0 Mon Sep 17 00:00:00 2001 From: David del Real Date: Wed, 11 Feb 2026 10:52:07 -0600 Subject: [PATCH 50/67] chore(genai) Update gemini model live conv audio with audio (#13820) * chore(genai) Update Gemini model version for live conversation audio with audio b/481443183 * added missing blank line * updated comment --- genai/live/live_conversation_audio_with_audio.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/genai/live/live_conversation_audio_with_audio.py b/genai/live/live_conversation_audio_with_audio.py index fb39dc36615..5d5b5a05445 100644 --- a/genai/live/live_conversation_audio_with_audio.py +++ b/genai/live/live_conversation_audio_with_audio.py @@ -32,7 +32,7 @@ # The number of audio frames to send in each chunk. CHUNK = 4200 CHANNELS = 1 -MODEL = "gemini-live-2.5-flash-preview-native-audio-09-2025" +MODEL = "gemini-live-2.5-flash-native-audio" # The audio sample rate expected by the model. INPUT_RATE = 16000 @@ -118,7 +118,7 @@ async def receive() -> None: receive_task = asyncio.create_task(receive()) await asyncio.gather(send_task, receive_task) # Example response: - # gemini-2.0-flash-live-preview-04-09 + # gemini-live-2.5-flash-native-audio # {'input_transcription': {'text': 'Hello.'}} # {'output_transcription': {}} # {'output_transcription': {'text': 'Hi'}} From aa8403c71da29a9410e450031b8b8f4a07d415bf Mon Sep 17 00:00:00 2001 From: Anayeli Date: Thu, 12 Feb 2026 14:57:52 -0600 Subject: [PATCH 51/67] chore(appengine): Update flex hello-world app outdated dependencies (#13807) --- appengine/flexible/hello_world/app.yaml | 1 + appengine/flexible/hello_world/requirements.txt | 7 ++----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/appengine/flexible/hello_world/app.yaml b/appengine/flexible/hello_world/app.yaml index 78198c8821a..8a9b1e1763b 100644 --- a/appengine/flexible/hello_world/app.yaml +++ b/appengine/flexible/hello_world/app.yaml @@ -18,6 +18,7 @@ entrypoint: gunicorn -b :$PORT main:app runtime_config: operating_system: ubuntu24 + runtime_version: 3.12 # This sample incurs costs to run on the App Engine flexible environment. # The settings below are to reduce costs during testing and are not appropriate diff --git a/appengine/flexible/hello_world/requirements.txt b/appengine/flexible/hello_world/requirements.txt index 068ea0acdfc..bdb61ec2417 100644 --- a/appengine/flexible/hello_world/requirements.txt +++ b/appengine/flexible/hello_world/requirements.txt @@ -1,5 +1,2 @@ -Flask==3.0.3; python_version > '3.6' -Flask==2.3.3; python_version < '3.7' -Werkzeug==3.0.3; python_version > '3.6' -Werkzeug==2.3.8; python_version < '3.7' -gunicorn==23.0.0 \ No newline at end of file +Flask==3.0.3 +gunicorn==22.0.0 From 7752b240a3552d71b03b67f24e6579aaf35ae76a Mon Sep 17 00:00:00 2001 From: Katie McLaughlin Date: Tue, 17 Feb 2026 13:21:32 +1100 Subject: [PATCH 52/67] fix(run/django): use k8s-skaffold pack for image building (#13834) --- run/django/cloudmigrate.yaml | 2 +- run/django/e2e_test_setup.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/run/django/cloudmigrate.yaml b/run/django/cloudmigrate.yaml index a054e0fc93c..8a6fc01b92b 100644 --- a/run/django/cloudmigrate.yaml +++ b/run/django/cloudmigrate.yaml @@ -15,7 +15,7 @@ # [START cloudrun_django_cloudmigrate_yaml_python] steps: - id: "Build Container Image" - name: buildpacksio/pack + name: gcr.io/k8s-skaffold/pack args: ["build", "${_IMAGE_NAME}", "--builder=gcr.io/buildpacks/builder"] - id: "Push Container Image" diff --git a/run/django/e2e_test_setup.yaml b/run/django/e2e_test_setup.yaml index 6ee69ce3f9f..dac968f03f6 100644 --- a/run/django/e2e_test_setup.yaml +++ b/run/django/e2e_test_setup.yaml @@ -69,7 +69,7 @@ steps: --project ${PROJECT_ID}" - id: "Build Container Image" - name: buildpacksio/pack + name: gcr.io/k8s-skaffold/pack args: ["build", "${_IMAGE_NAME}", "--builder=gcr.io/buildpacks/builder", "--env", "GOOGLE_PYTHON_VERSION=${_PYTHON_VERSION}"] From bc500193d4861d0c2eb5f231a8e484870a74a721 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 19 Feb 2026 02:31:55 +0000 Subject: [PATCH 53/67] fix(deps): update dependency fastmcp to v3 (#13839) --- run/mcp-server/pyproject.toml | 2 +- run/mcp-server/uv.lock | 469 ++++++++++++++-------------------- 2 files changed, 199 insertions(+), 272 deletions(-) diff --git a/run/mcp-server/pyproject.toml b/run/mcp-server/pyproject.toml index 397102c991c..32fdf3743bb 100644 --- a/run/mcp-server/pyproject.toml +++ b/run/mcp-server/pyproject.toml @@ -5,5 +5,5 @@ description = "Example of deploying an MCP server on Cloud Run" readme = "README.md" requires-python = ">=3.10" dependencies = [ - "fastmcp==2.13.0", + "fastmcp==3.0.0", ] diff --git a/run/mcp-server/uv.lock b/run/mcp-server/uv.lock index c2f5c2de885..517177da22d 100644 --- a/run/mcp-server/uv.lock +++ b/run/mcp-server/uv.lock @@ -2,6 +2,18 @@ version = 1 revision = 3 requires-python = ">=3.10" +[[package]] +name = "aiofile" +version = "3.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "caio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/e2/d7cb819de8df6b5c1968a2756c3cb4122d4fa2b8fc768b53b7c9e5edb646/aiofile-3.9.0.tar.gz", hash = "sha256:e5ad718bb148b265b6df1b3752c4d1d83024b93da9bd599df74b9d9ffcf7919b", size = 17943, upload-time = "2024-10-08T10:39:35.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/25/da1f0b4dd970e52bf5a36c204c107e11a0c6d3ed195eba0bfbc664c312b2/aiofile-3.9.0-py3-none-any.whl", hash = "sha256:ce2f6c1571538cbdfa0143b04e16b208ecb0e9cb4148e528af8a640ed51cc8aa", size = 19539, upload-time = "2024-10-08T10:39:32.955Z" }, +] + [[package]] name = "annotated-types" version = "0.7.0" @@ -37,14 +49,14 @@ wheels = [ [[package]] name = "authlib" -version = "1.6.0" +version = "1.6.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a2/9d/b1e08d36899c12c8b894a44a5583ee157789f26fc4b176f8e4b6217b56e1/authlib-1.6.0.tar.gz", hash = "sha256:4367d32031b7af175ad3a323d571dc7257b7099d55978087ceae4a0d88cd3210", size = 158371, upload-time = "2025-05-23T00:21:45.011Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/6c/c88eac87468c607f88bc24df1f3b31445ee6fc9ba123b09e666adf687cd9/authlib-1.6.8.tar.gz", hash = "sha256:41ae180a17cf672bc784e4a518e5c82687f1fe1e98b0cafaeda80c8e4ab2d1cb", size = 165074, upload-time = "2026-02-14T04:02:17.941Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/29/587c189bbab1ccc8c86a03a5d0e13873df916380ef1be461ebe6acebf48d/authlib-1.6.0-py2.py3-none-any.whl", hash = "sha256:91685589498f79e8655e8a8947431ad6288831d643f11c55c2143ffcc738048d", size = 239981, upload-time = "2025-05-23T00:21:43.075Z" }, + { url = "https://files.pythonhosted.org/packages/9b/73/f7084bf12755113cd535ae586782ff3a6e710bfbe6a0d13d1c2f81ffbbfa/authlib-1.6.8-py2.py3-none-any.whl", hash = "sha256:97286fd7a15e6cfefc32771c8ef9c54f0ed58028f1322de6a2a7c969c3817888", size = 244116, upload-time = "2026-02-14T04:02:15.579Z" }, ] [[package]] @@ -74,6 +86,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/96/c5/1e741d26306c42e2bf6ab740b2202872727e0f606033c9dd713f8b93f5a8/cachetools-6.2.1-py3-none-any.whl", hash = "sha256:09868944b6dde876dfd44e1d47e18484541eaf12f26f29b7af91b26cc892d701", size = 11280, upload-time = "2025-10-12T14:55:28.382Z" }, ] +[[package]] +name = "caio" +version = "0.9.25" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/92/88/b8527e1b00c1811db339a1df8bd1ae49d146fcea9d6a5c40e3a80aaeb38d/caio-0.9.25.tar.gz", hash = "sha256:16498e7f81d1d0f5a4c0ad3f2540e65fe25691376e0a5bd367f558067113ed10", size = 26781, upload-time = "2025-12-26T15:21:36.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/80/ea4ead0c5d52a9828692e7df20f0eafe8d26e671ce4883a0a146bb91049e/caio-0.9.25-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ca6c8ecda611478b6016cb94d23fd3eb7124852b985bdec7ecaad9f3116b9619", size = 36836, upload-time = "2025-12-26T15:22:04.662Z" }, + { url = "https://files.pythonhosted.org/packages/17/b9/36715c97c873649d1029001578f901b50250916295e3dddf20c865438865/caio-0.9.25-cp310-cp310-manylinux2010_x86_64.manylinux2014_x86_64.manylinux_2_12_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:db9b5681e4af8176159f0d6598e73b2279bb661e718c7ac23342c550bd78c241", size = 79695, upload-time = "2025-12-26T15:22:18.818Z" }, + { url = "https://files.pythonhosted.org/packages/ec/90/543f556fcfcfa270713eef906b6352ab048e1e557afec12925c991dc93c2/caio-0.9.25-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d6956d9e4a27021c8bd6c9677f3a59eb1d820cc32d0343cea7961a03b1371965", size = 36839, upload-time = "2025-12-26T15:21:40.267Z" }, + { url = "https://files.pythonhosted.org/packages/51/3b/36f3e8ec38dafe8de4831decd2e44c69303d2a3892d16ceda42afed44e1b/caio-0.9.25-cp311-cp311-manylinux2010_x86_64.manylinux2014_x86_64.manylinux_2_12_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bf84bfa039f25ad91f4f52944452a5f6f405e8afab4d445450978cd6241d1478", size = 80255, upload-time = "2025-12-26T15:22:20.271Z" }, + { url = "https://files.pythonhosted.org/packages/d3/25/79c98ebe12df31548ba4eaf44db11b7cad6b3e7b4203718335620939083c/caio-0.9.25-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fb7ff95af4c31ad3f03179149aab61097a71fd85e05f89b4786de0359dffd044", size = 36983, upload-time = "2025-12-26T15:21:36.075Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2b/21288691f16d479945968a0a4f2856818c1c5be56881d51d4dac9b255d26/caio-0.9.25-cp312-cp312-manylinux2010_x86_64.manylinux2014_x86_64.manylinux_2_12_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:97084e4e30dfa598449d874c4d8e0c8d5ea17d2f752ef5e48e150ff9d240cd64", size = 82012, upload-time = "2025-12-26T15:22:20.983Z" }, + { url = "https://files.pythonhosted.org/packages/31/57/5e6ff127e6f62c9f15d989560435c642144aa4210882f9494204bc892305/caio-0.9.25-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d6c2a3411af97762a2b03840c3cec2f7f728921ff8adda53d7ea2315a8563451", size = 36979, upload-time = "2025-12-26T15:21:35.484Z" }, + { url = "https://files.pythonhosted.org/packages/a3/9f/f21af50e72117eb528c422d4276cbac11fb941b1b812b182e0a9c70d19c5/caio-0.9.25-cp313-cp313-manylinux2010_x86_64.manylinux2014_x86_64.manylinux_2_12_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0998210a4d5cd5cb565b32ccfe4e53d67303f868a76f212e002a8554692870e6", size = 81900, upload-time = "2025-12-26T15:22:21.919Z" }, + { url = "https://files.pythonhosted.org/packages/69/ca/a08fdc7efdcc24e6a6131a93c85be1f204d41c58f474c42b0670af8c016b/caio-0.9.25-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fab6078b9348e883c80a5e14b382e6ad6aabbc4429ca034e76e730cf464269db", size = 36978, upload-time = "2025-12-26T15:21:41.055Z" }, + { url = "https://files.pythonhosted.org/packages/5e/6c/d4d24f65e690213c097174d26eda6831f45f4734d9d036d81790a27e7b78/caio-0.9.25-cp314-cp314-manylinux2010_x86_64.manylinux2014_x86_64.manylinux_2_12_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:44a6b58e52d488c75cfaa5ecaa404b2b41cc965e6c417e03251e868ecd5b6d77", size = 81832, upload-time = "2025-12-26T15:22:22.757Z" }, + { url = "https://files.pythonhosted.org/packages/86/93/1f76c8d1bafe3b0614e06b2195784a3765bbf7b0a067661af9e2dd47fc33/caio-0.9.25-py3-none-any.whl", hash = "sha256:06c0bb02d6b929119b1cfbe1ca403c768b2013a369e2db46bfa2a5761cf82e40", size = 19087, upload-time = "2025-12-26T15:22:00.221Z" }, +] + [[package]] name = "certifi" version = "2025.4.26" @@ -314,15 +345,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/63/8e5ab2a38281432f568f6b981ad4bf46093c3adbbedb979bc5b6e589e2d1/cyclopts-4.1.0-py3-none-any.whl", hash = "sha256:6468e7e7467af4b6378bf17d0aaf204b713ddc5df383d9ffa7cae6e285da1329", size = 182506, upload-time = "2025-10-28T18:23:15.007Z" }, ] -[[package]] -name = "diskcache" -version = "5.6.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3f/21/1c1ffc1a039ddcc459db43cc108658f32c57d271d7289a2794e401d0fdb6/diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc", size = 67916, upload-time = "2023-08-31T06:12:00.316Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19", size = 45550, upload-time = "2023-08-31T06:11:58.822Z" }, -] - [[package]] name = "dnspython" version = "2.8.0" @@ -377,27 +399,33 @@ wheels = [ [[package]] name = "fastmcp" -version = "2.13.0" +version = "3.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "authlib" }, { name = "cyclopts" }, { name = "exceptiongroup" }, { name = "httpx" }, + { name = "jsonref" }, + { name = "jsonschema-path" }, { name = "mcp" }, - { name = "openapi-core" }, { name = "openapi-pydantic" }, + { name = "opentelemetry-api" }, + { name = "packaging" }, { name = "platformdirs" }, - { name = "py-key-value-aio", extra = ["disk", "keyring", "memory"] }, + { name = "py-key-value-aio", extra = ["filetree", "keyring", "memory"] }, { name = "pydantic", extra = ["email"] }, { name = "pyperclip" }, { name = "python-dotenv" }, + { name = "pyyaml" }, { name = "rich" }, + { name = "uvicorn" }, + { name = "watchfiles" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bc/3b/c30af894db2c3ec439d0e4168ba7ce705474cabdd0a599033ad9a19ad977/fastmcp-2.13.0.tar.gz", hash = "sha256:57f7b7503363e1babc0d1a13af18252b80366a409e1de85f1256cce66a4bee35", size = 7767346, upload-time = "2025-10-25T12:54:10.957Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/be/beb5d3e485983b9dd122f3f74772bcceeb085ca824e11c52c14ba71cf21a/fastmcp-3.0.0.tar.gz", hash = "sha256:f3b0cfa012f6b2b50b877da181431c6f9a551197f466b0bb7de7f39ceae159a1", size = 16093079, upload-time = "2026-02-18T21:25:34.461Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/7f/09942135f506953fc61bb81b9e5eaf50a8eea923b83d9135bd959168ef2d/fastmcp-2.13.0-py3-none-any.whl", hash = "sha256:bdff1399d3b7ebb79286edfd43eb660182432514a5ab8e4cbfb45f1d841d2aa0", size = 367134, upload-time = "2025-10-25T12:54:09.284Z" }, + { url = "https://files.pythonhosted.org/packages/12/14/05bebaf3764ea71ce6fa9d3fcf870610bbc8b1e7be2505e870d709375316/fastmcp-3.0.0-py3-none-any.whl", hash = "sha256:561d537cb789f995174c5591f1b54f758ce3f82da3cd951ffe51ce18609569e9", size = 603327, upload-time = "2026-02-18T21:25:36.701Z" }, ] [[package]] @@ -467,15 +495,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, ] -[[package]] -name = "isodate" -version = "0.7.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, -] - [[package]] name = "jaraco-classes" version = "3.4.0" @@ -521,6 +540,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010, upload-time = "2025-02-27T18:51:00.104Z" }, ] +[[package]] +name = "jsonref" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/0d/c1f3277e90ccdb50d33ed5ba1ec5b3f0a242ed8c1b1a85d3afeb68464dca/jsonref-1.1.0.tar.gz", hash = "sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552", size = 8814, upload-time = "2023-01-16T16:10:04.455Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/ec/e1db9922bceb168197a558a2b8c03a7963f1afe93517ddd3cf99f202f996/jsonref-1.1.0-py3-none-any.whl", hash = "sha256:590dc7773df6c21cbf948b5dac07a72a251db28b0238ceecce0a2abfa8ec30a9", size = 9425, upload-time = "2023-01-16T16:10:02.255Z" }, +] + [[package]] name = "jsonschema" version = "4.25.1" @@ -581,51 +609,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d3/32/da7f44bcb1105d3e88a0b74ebdca50c59121d2ddf71c9e34ba47df7f3a56/keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd", size = 39085, upload-time = "2024-12-25T15:26:44.377Z" }, ] -[[package]] -name = "lazy-object-proxy" -version = "1.12.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/08/a2/69df9c6ba6d316cfd81fe2381e464db3e6de5db45f8c43c6a23504abf8cb/lazy_object_proxy-1.12.0.tar.gz", hash = "sha256:1f5a462d92fd0cfb82f1fab28b51bfb209fabbe6aabf7f0d51472c0c124c0c61", size = 43681, upload-time = "2025-08-22T13:50:06.783Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/2b/d5e8915038acbd6c6a9fcb8aaf923dc184222405d3710285a1fec6e262bc/lazy_object_proxy-1.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61d5e3310a4aa5792c2b599a7a78ccf8687292c8eb09cf187cca8f09cf6a7519", size = 26658, upload-time = "2025-08-22T13:42:23.373Z" }, - { url = "https://files.pythonhosted.org/packages/da/8f/91fc00eeea46ee88b9df67f7c5388e60993341d2a406243d620b2fdfde57/lazy_object_proxy-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1ca33565f698ac1aece152a10f432415d1a2aa9a42dfe23e5ba2bc255ab91f6", size = 68412, upload-time = "2025-08-22T13:42:24.727Z" }, - { url = "https://files.pythonhosted.org/packages/07/d2/b7189a0e095caedfea4d42e6b6949d2685c354263bdf18e19b21ca9b3cd6/lazy_object_proxy-1.12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d01c7819a410f7c255b20799b65d36b414379a30c6f1684c7bd7eb6777338c1b", size = 67559, upload-time = "2025-08-22T13:42:25.875Z" }, - { url = "https://files.pythonhosted.org/packages/a3/ad/b013840cc43971582ff1ceaf784d35d3a579650eb6cc348e5e6ed7e34d28/lazy_object_proxy-1.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:029d2b355076710505c9545aef5ab3f750d89779310e26ddf2b7b23f6ea03cd8", size = 66651, upload-time = "2025-08-22T13:42:27.427Z" }, - { url = "https://files.pythonhosted.org/packages/7e/6f/b7368d301c15612fcc4cd00412b5d6ba55548bde09bdae71930e1a81f2ab/lazy_object_proxy-1.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc6e3614eca88b1c8a625fc0a47d0d745e7c3255b21dac0e30b3037c5e3deeb8", size = 66901, upload-time = "2025-08-22T13:42:28.585Z" }, - { url = "https://files.pythonhosted.org/packages/61/1b/c6b1865445576b2fc5fa0fbcfce1c05fee77d8979fd1aa653dd0f179aefc/lazy_object_proxy-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:be5fe974e39ceb0d6c9db0663c0464669cf866b2851c73971409b9566e880eab", size = 26536, upload-time = "2025-08-22T13:42:29.636Z" }, - { url = "https://files.pythonhosted.org/packages/01/b3/4684b1e128a87821e485f5a901b179790e6b5bc02f89b7ee19c23be36ef3/lazy_object_proxy-1.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1cf69cd1a6c7fe2dbcc3edaa017cf010f4192e53796538cc7d5e1fedbfa4bcff", size = 26656, upload-time = "2025-08-22T13:42:30.605Z" }, - { url = "https://files.pythonhosted.org/packages/3a/03/1bdc21d9a6df9ff72d70b2ff17d8609321bea4b0d3cffd2cea92fb2ef738/lazy_object_proxy-1.12.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:efff4375a8c52f55a145dc8487a2108c2140f0bec4151ab4e1843e52eb9987ad", size = 68832, upload-time = "2025-08-22T13:42:31.675Z" }, - { url = "https://files.pythonhosted.org/packages/3d/4b/5788e5e8bd01d19af71e50077ab020bc5cce67e935066cd65e1215a09ff9/lazy_object_proxy-1.12.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1192e8c2f1031a6ff453ee40213afa01ba765b3dc861302cd91dbdb2e2660b00", size = 69148, upload-time = "2025-08-22T13:42:32.876Z" }, - { url = "https://files.pythonhosted.org/packages/79/0e/090bf070f7a0de44c61659cb7f74c2fe02309a77ca8c4b43adfe0b695f66/lazy_object_proxy-1.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3605b632e82a1cbc32a1e5034278a64db555b3496e0795723ee697006b980508", size = 67800, upload-time = "2025-08-22T13:42:34.054Z" }, - { url = "https://files.pythonhosted.org/packages/cf/d2/b320325adbb2d119156f7c506a5fbfa37fcab15c26d13cf789a90a6de04e/lazy_object_proxy-1.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a61095f5d9d1a743e1e20ec6d6db6c2ca511961777257ebd9b288951b23b44fa", size = 68085, upload-time = "2025-08-22T13:42:35.197Z" }, - { url = "https://files.pythonhosted.org/packages/6a/48/4b718c937004bf71cd82af3713874656bcb8d0cc78600bf33bb9619adc6c/lazy_object_proxy-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:997b1d6e10ecc6fb6fe0f2c959791ae59599f41da61d652f6c903d1ee58b7370", size = 26535, upload-time = "2025-08-22T13:42:36.521Z" }, - { url = "https://files.pythonhosted.org/packages/0d/1b/b5f5bd6bda26f1e15cd3232b223892e4498e34ec70a7f4f11c401ac969f1/lazy_object_proxy-1.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ee0d6027b760a11cc18281e702c0309dd92da458a74b4c15025d7fc490deede", size = 26746, upload-time = "2025-08-22T13:42:37.572Z" }, - { url = "https://files.pythonhosted.org/packages/55/64/314889b618075c2bfc19293ffa9153ce880ac6153aacfd0a52fcabf21a66/lazy_object_proxy-1.12.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4ab2c584e3cc8be0dfca422e05ad30a9abe3555ce63e9ab7a559f62f8dbc6ff9", size = 71457, upload-time = "2025-08-22T13:42:38.743Z" }, - { url = "https://files.pythonhosted.org/packages/11/53/857fc2827fc1e13fbdfc0ba2629a7d2579645a06192d5461809540b78913/lazy_object_proxy-1.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:14e348185adbd03ec17d051e169ec45686dcd840a3779c9d4c10aabe2ca6e1c0", size = 71036, upload-time = "2025-08-22T13:42:40.184Z" }, - { url = "https://files.pythonhosted.org/packages/2b/24/e581ffed864cd33c1b445b5763d617448ebb880f48675fc9de0471a95cbc/lazy_object_proxy-1.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c4fcbe74fb85df8ba7825fa05eddca764138da752904b378f0ae5ab33a36c308", size = 69329, upload-time = "2025-08-22T13:42:41.311Z" }, - { url = "https://files.pythonhosted.org/packages/78/be/15f8f5a0b0b2e668e756a152257d26370132c97f2f1943329b08f057eff0/lazy_object_proxy-1.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:563d2ec8e4d4b68ee7848c5ab4d6057a6d703cb7963b342968bb8758dda33a23", size = 70690, upload-time = "2025-08-22T13:42:42.51Z" }, - { url = "https://files.pythonhosted.org/packages/5d/aa/f02be9bbfb270e13ee608c2b28b8771f20a5f64356c6d9317b20043c6129/lazy_object_proxy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:53c7fd99eb156bbb82cbc5d5188891d8fdd805ba6c1e3b92b90092da2a837073", size = 26563, upload-time = "2025-08-22T13:42:43.685Z" }, - { url = "https://files.pythonhosted.org/packages/f4/26/b74c791008841f8ad896c7f293415136c66cc27e7c7577de4ee68040c110/lazy_object_proxy-1.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:86fd61cb2ba249b9f436d789d1356deae69ad3231dc3c0f17293ac535162672e", size = 26745, upload-time = "2025-08-22T13:42:44.982Z" }, - { url = "https://files.pythonhosted.org/packages/9b/52/641870d309e5d1fb1ea7d462a818ca727e43bfa431d8c34b173eb090348c/lazy_object_proxy-1.12.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:81d1852fb30fab81696f93db1b1e55a5d1ff7940838191062f5f56987d5fcc3e", size = 71537, upload-time = "2025-08-22T13:42:46.141Z" }, - { url = "https://files.pythonhosted.org/packages/47/b6/919118e99d51c5e76e8bf5a27df406884921c0acf2c7b8a3b38d847ab3e9/lazy_object_proxy-1.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be9045646d83f6c2664c1330904b245ae2371b5c57a3195e4028aedc9f999655", size = 71141, upload-time = "2025-08-22T13:42:47.375Z" }, - { url = "https://files.pythonhosted.org/packages/e5/47/1d20e626567b41de085cf4d4fb3661a56c159feaa73c825917b3b4d4f806/lazy_object_proxy-1.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:67f07ab742f1adfb3966c40f630baaa7902be4222a17941f3d85fd1dae5565ff", size = 69449, upload-time = "2025-08-22T13:42:48.49Z" }, - { url = "https://files.pythonhosted.org/packages/58/8d/25c20ff1a1a8426d9af2d0b6f29f6388005fc8cd10d6ee71f48bff86fdd0/lazy_object_proxy-1.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:75ba769017b944fcacbf6a80c18b2761a1795b03f8899acdad1f1c39db4409be", size = 70744, upload-time = "2025-08-22T13:42:49.608Z" }, - { url = "https://files.pythonhosted.org/packages/c0/67/8ec9abe15c4f8a4bcc6e65160a2c667240d025cbb6591b879bea55625263/lazy_object_proxy-1.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:7b22c2bbfb155706b928ac4d74c1a63ac8552a55ba7fff4445155523ea4067e1", size = 26568, upload-time = "2025-08-22T13:42:57.719Z" }, - { url = "https://files.pythonhosted.org/packages/23/12/cd2235463f3469fd6c62d41d92b7f120e8134f76e52421413a0ad16d493e/lazy_object_proxy-1.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4a79b909aa16bde8ae606f06e6bbc9d3219d2e57fb3e0076e17879072b742c65", size = 27391, upload-time = "2025-08-22T13:42:50.62Z" }, - { url = "https://files.pythonhosted.org/packages/60/9e/f1c53e39bbebad2e8609c67d0830cc275f694d0ea23d78e8f6db526c12d3/lazy_object_proxy-1.12.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:338ab2f132276203e404951205fe80c3fd59429b3a724e7b662b2eb539bb1be9", size = 80552, upload-time = "2025-08-22T13:42:51.731Z" }, - { url = "https://files.pythonhosted.org/packages/4c/b6/6c513693448dcb317d9d8c91d91f47addc09553613379e504435b4cc8b3e/lazy_object_proxy-1.12.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c40b3c9faee2e32bfce0df4ae63f4e73529766893258eca78548bac801c8f66", size = 82857, upload-time = "2025-08-22T13:42:53.225Z" }, - { url = "https://files.pythonhosted.org/packages/12/1c/d9c4aaa4c75da11eb7c22c43d7c90a53b4fca0e27784a5ab207768debea7/lazy_object_proxy-1.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:717484c309df78cedf48396e420fa57fc8a2b1f06ea889df7248fdd156e58847", size = 80833, upload-time = "2025-08-22T13:42:54.391Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ae/29117275aac7d7d78ae4f5a4787f36ff33262499d486ac0bf3e0b97889f6/lazy_object_proxy-1.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b7ea5ea1ffe15059eb44bcbcb258f97bcb40e139b88152c40d07b1a1dfc9ac", size = 79516, upload-time = "2025-08-22T13:42:55.812Z" }, - { url = "https://files.pythonhosted.org/packages/19/40/b4e48b2c38c69392ae702ae7afa7b6551e0ca5d38263198b7c79de8b3bdf/lazy_object_proxy-1.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:08c465fb5cd23527512f9bd7b4c7ba6cec33e28aad36fbbe46bf7b858f9f3f7f", size = 27656, upload-time = "2025-08-22T13:42:56.793Z" }, - { url = "https://files.pythonhosted.org/packages/ef/3a/277857b51ae419a1574557c0b12e0d06bf327b758ba94cafc664cb1e2f66/lazy_object_proxy-1.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c9defba70ab943f1df98a656247966d7729da2fe9c2d5d85346464bf320820a3", size = 26582, upload-time = "2025-08-22T13:49:49.366Z" }, - { url = "https://files.pythonhosted.org/packages/1a/b6/c5e0fa43535bb9c87880e0ba037cdb1c50e01850b0831e80eb4f4762f270/lazy_object_proxy-1.12.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6763941dbf97eea6b90f5b06eb4da9418cc088fce0e3883f5816090f9afcde4a", size = 71059, upload-time = "2025-08-22T13:49:50.488Z" }, - { url = "https://files.pythonhosted.org/packages/06/8a/7dcad19c685963c652624702f1a968ff10220b16bfcc442257038216bf55/lazy_object_proxy-1.12.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fdc70d81235fc586b9e3d1aeef7d1553259b62ecaae9db2167a5d2550dcc391a", size = 71034, upload-time = "2025-08-22T13:49:54.224Z" }, - { url = "https://files.pythonhosted.org/packages/12/ac/34cbfb433a10e28c7fd830f91c5a348462ba748413cbb950c7f259e67aa7/lazy_object_proxy-1.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0a83c6f7a6b2bfc11ef3ed67f8cbe99f8ff500b05655d8e7df9aab993a6abc95", size = 69529, upload-time = "2025-08-22T13:49:55.29Z" }, - { url = "https://files.pythonhosted.org/packages/6f/6a/11ad7e349307c3ca4c0175db7a77d60ce42a41c60bcb11800aabd6a8acb8/lazy_object_proxy-1.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:256262384ebd2a77b023ad02fbcc9326282bcfd16484d5531154b02bc304f4c5", size = 70391, upload-time = "2025-08-22T13:49:56.35Z" }, - { url = "https://files.pythonhosted.org/packages/59/97/9b410ed8fbc6e79c1ee8b13f8777a80137d4bc189caf2c6202358e66192c/lazy_object_proxy-1.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:7601ec171c7e8584f8ff3f4e440aa2eebf93e854f04639263875b8c2971f819f", size = 26988, upload-time = "2025-08-22T13:49:57.302Z" }, - { url = "https://files.pythonhosted.org/packages/41/a0/b91504515c1f9a299fc157967ffbd2f0321bce0516a3d5b89f6f4cad0355/lazy_object_proxy-1.12.0-pp39.pp310.pp311.graalpy311-none-any.whl", hash = "sha256:c3b2e0af1f7f77c4263759c4824316ce458fabe0fceadcd24ef8ca08b2d1e402", size = 15072, upload-time = "2025-08-22T13:50:05.498Z" }, -] - [[package]] name = "markdown-it-py" version = "3.0.0" @@ -638,94 +621,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, ] -[[package]] -name = "markupsafe" -version = "3.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, - { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, - { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, - { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, - { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, - { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, - { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, - { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, - { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, - { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, - { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, - { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, - { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, - { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, - { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, - { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, - { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, - { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, - { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, - { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, - { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, - { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, - { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, - { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, - { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, - { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, - { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, - { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, - { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, - { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, - { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, - { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, - { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, - { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, - { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, - { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, - { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, - { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, - { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, - { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, - { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, - { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, - { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, - { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, - { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, - { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, - { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, - { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, - { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, - { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, - { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, - { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, - { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, - { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, - { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, - { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, - { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, - { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, - { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, - { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, - { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, - { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, - { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, - { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, - { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, - { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, - { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, - { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, - { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, - { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, - { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, -] - [[package]] name = "mcp" -version = "1.19.0" +version = "1.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -734,15 +632,18 @@ dependencies = [ { name = "jsonschema" }, { name = "pydantic" }, { name = "pydantic-settings" }, + { name = "pyjwt", extra = ["crypto"] }, { name = "python-multipart" }, { name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "sse-starlette" }, { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/69/2b/916852a5668f45d8787378461eaa1244876d77575ffef024483c94c0649c/mcp-1.19.0.tar.gz", hash = "sha256:213de0d3cd63f71bc08ffe9cc8d4409cc87acffd383f6195d2ce0457c021b5c1", size = 444163, upload-time = "2025-10-24T01:11:15.839Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/6d/62e76bbb8144d6ed86e202b5edd8a4cb631e7c8130f3f4893c3f90262b10/mcp-1.26.0.tar.gz", hash = "sha256:db6e2ef491eecc1a0d93711a76f28dec2e05999f93afd48795da1c1137142c66", size = 608005, upload-time = "2026-01-24T19:40:32.468Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/a3/3e71a875a08b6a830b88c40bc413bff01f1650f1efe8a054b5e90a9d4f56/mcp-1.19.0-py3-none-any.whl", hash = "sha256:f5907fe1c0167255f916718f376d05f09a830a215327a3ccdd5ec8a519f2e572", size = 170105, upload-time = "2025-10-24T01:11:14.151Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d9/eaa1f80170d2b7c5ba23f3b59f766f3a0bb41155fbc32a69adfa1adaaef9/mcp-1.26.0-py3-none-any.whl", hash = "sha256:904a21c33c25aa98ddbeb47273033c435e595bbacfdb177f4bd87f6dceebe1ca", size = 233615, upload-time = "2026-01-24T19:40:30.652Z" }, ] [[package]] @@ -754,7 +655,7 @@ dependencies = [ ] [package.metadata] -requires-dist = [{ name = "fastmcp", specifier = "==2.13.0" }] +requires-dist = [{ name = "fastmcp", specifier = "==3.0.0" }] [[package]] name = "mdurl" @@ -774,26 +675,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" }, ] -[[package]] -name = "openapi-core" -version = "0.19.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "isodate" }, - { name = "jsonschema" }, - { name = "jsonschema-path" }, - { name = "more-itertools" }, - { name = "openapi-schema-validator" }, - { name = "openapi-spec-validator" }, - { name = "parse" }, - { name = "typing-extensions" }, - { name = "werkzeug" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b1/35/1acaa5f2fcc6e54eded34a2ec74b479439c4e469fc4e8d0e803fda0234db/openapi_core-0.19.5.tar.gz", hash = "sha256:421e753da56c391704454e66afe4803a290108590ac8fa6f4a4487f4ec11f2d3", size = 103264, upload-time = "2025-03-20T20:17:28.193Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/6f/83ead0e2e30a90445ee4fc0135f43741aebc30cca5b43f20968b603e30b6/openapi_core-0.19.5-py3-none-any.whl", hash = "sha256:ef7210e83a59394f46ce282639d8d26ad6fc8094aa904c9c16eb1bac8908911f", size = 106595, upload-time = "2025-03-20T20:17:26.77Z" }, -] - [[package]] name = "openapi-pydantic" version = "0.5.1" @@ -807,41 +688,25 @@ wheels = [ ] [[package]] -name = "openapi-schema-validator" -version = "0.6.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jsonschema" }, - { name = "jsonschema-specifications" }, - { name = "rfc3339-validator" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8b/f3/5507ad3325169347cd8ced61c232ff3df70e2b250c49f0fe140edb4973c6/openapi_schema_validator-0.6.3.tar.gz", hash = "sha256:f37bace4fc2a5d96692f4f8b31dc0f8d7400fd04f3a937798eaf880d425de6ee", size = 11550, upload-time = "2025-01-10T18:08:22.268Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/21/c6/ad0fba32775ae749016829dace42ed80f4407b171da41313d1a3a5f102e4/openapi_schema_validator-0.6.3-py3-none-any.whl", hash = "sha256:f3b9870f4e556b5a62a1c39da72a6b4b16f3ad9c73dc80084b1b11e74ba148a3", size = 8755, upload-time = "2025-01-10T18:08:19.758Z" }, -] - -[[package]] -name = "openapi-spec-validator" -version = "0.7.2" +name = "opentelemetry-api" +version = "1.39.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "jsonschema" }, - { name = "jsonschema-path" }, - { name = "lazy-object-proxy" }, - { name = "openapi-schema-validator" }, + { name = "importlib-metadata" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/82/af/fe2d7618d6eae6fb3a82766a44ed87cd8d6d82b4564ed1c7cfb0f6378e91/openapi_spec_validator-0.7.2.tar.gz", hash = "sha256:cc029309b5c5dbc7859df0372d55e9d1ff43e96d678b9ba087f7c56fc586f734", size = 36855, upload-time = "2025-06-07T14:48:56.299Z" } +sdist = { url = "https://files.pythonhosted.org/packages/97/b9/3161be15bb8e3ad01be8be5a968a9237c3027c5be504362ff800fca3e442/opentelemetry_api-1.39.1.tar.gz", hash = "sha256:fbde8c80e1b937a2c61f20347e91c0c18a1940cecf012d62e65a7caf08967c9c", size = 65767, upload-time = "2025-12-11T13:32:39.182Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/dd/b3fd642260cb17532f66cc1e8250f3507d1e580483e209dc1e9d13bd980d/openapi_spec_validator-0.7.2-py3-none-any.whl", hash = "sha256:4bbdc0894ec85f1d1bea1d6d9c8b2c3c8d7ccaa13577ef40da9c006c9fd0eb60", size = 39713, upload-time = "2025-06-07T14:48:54.077Z" }, + { url = "https://files.pythonhosted.org/packages/cf/df/d3f1ddf4bb4cb50ed9b1139cc7b1c54c34a1e7ce8fd1b9a37c0d1551a6bd/opentelemetry_api-1.39.1-py3-none-any.whl", hash = "sha256:2edd8463432a7f8443edce90972169b195e7d6a05500cd29e6d13898187c9950", size = 66356, upload-time = "2025-12-11T13:32:17.304Z" }, ] [[package]] -name = "parse" -version = "1.20.2" +name = "packaging" +version = "26.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4f/78/d9b09ba24bb36ef8b83b71be547e118d46214735b6dfb39e4bfde0e9b9dd/parse-1.20.2.tar.gz", hash = "sha256:b41d604d16503c79d81af5165155c0b20f6c8d6c559efa66b4b695c3e5a0a0ce", size = 29391, upload-time = "2024-06-11T04:41:57.34Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/31/ba45bf0b2aa7898d81cbbfac0e88c267befb59ad91a19e36e1bc5578ddb1/parse-1.20.2-py2.py3-none-any.whl", hash = "sha256:967095588cb802add9177d0c0b6133b5ba33b1ea9007ca800e526f42a85af558", size = 20126, upload-time = "2024-06-11T04:41:55.057Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] [[package]] @@ -853,15 +718,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7d/eb/b6260b31b1a96386c0a880edebe26f89669098acea8e0318bff6adb378fd/pathable-0.4.4-py3-none-any.whl", hash = "sha256:5ae9e94793b6ef5a4cbe0a7ce9dbbefc1eec38df253763fd0aeeacf2762dbbc2", size = 9592, upload-time = "2025-01-10T18:43:11.88Z" }, ] -[[package]] -name = "pathvalidate" -version = "3.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fa/2a/52a8da6fe965dea6192eb716b357558e103aea0a1e9a8352ad575a8406ca/pathvalidate-3.3.1.tar.gz", hash = "sha256:b18c07212bfead624345bb8e1d6141cdcf15a39736994ea0b94035ad2b1ba177", size = 63262, upload-time = "2025-06-15T09:07:20.736Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/70/875f4a23bfc4731703a5835487d0d2fb999031bd415e7d17c0ae615c18b7/pathvalidate-3.3.1-py3-none-any.whl", hash = "sha256:5263baab691f8e1af96092fa5137ee17df5bdfbd6cff1fcac4d6ef4bc2e1735f", size = 24305, upload-time = "2025-06-15T09:07:19.117Z" }, -] - [[package]] name = "platformdirs" version = "4.5.0" @@ -873,21 +729,21 @@ wheels = [ [[package]] name = "py-key-value-aio" -version = "0.2.8" +version = "0.4.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beartype" }, - { name = "py-key-value-shared" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ca/35/65310a4818acec0f87a46e5565e341c5a96fc062a9a03495ad28828ff4d7/py_key_value_aio-0.2.8.tar.gz", hash = "sha256:c0cfbb0bd4e962a3fa1a9fa6db9ba9df812899bd9312fa6368aaea7b26008b36", size = 32853, upload-time = "2025-10-24T13:31:04.688Z" } +sdist = { url = "https://files.pythonhosted.org/packages/04/3c/0397c072a38d4bc580994b42e0c90c5f44f679303489e4376289534735e5/py_key_value_aio-0.4.4.tar.gz", hash = "sha256:e3012e6243ed7cc09bb05457bd4d03b1ba5c2b1ca8700096b3927db79ffbbe55", size = 92300, upload-time = "2026-02-16T21:21:43.245Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/5a/e56747d87a97ad2aff0f3700d77f186f0704c90c2da03bfed9e113dae284/py_key_value_aio-0.2.8-py3-none-any.whl", hash = "sha256:561565547ce8162128fd2bd0b9d70ce04a5f4586da8500cce79a54dfac78c46a", size = 69200, upload-time = "2025-10-24T13:31:03.81Z" }, + { url = "https://files.pythonhosted.org/packages/32/69/f1b537ee70b7def42d63124a539ed3026a11a3ffc3086947a1ca6e861868/py_key_value_aio-0.4.4-py3-none-any.whl", hash = "sha256:18e17564ecae61b987f909fc2cd41ee2012c84b4b1dcb8c055cf8b4bc1bf3f5d", size = 152291, upload-time = "2026-02-16T21:21:44.241Z" }, ] [package.optional-dependencies] -disk = [ - { name = "diskcache" }, - { name = "pathvalidate" }, +filetree = [ + { name = "aiofile" }, + { name = "anyio" }, ] keyring = [ { name = "keyring" }, @@ -896,19 +752,6 @@ memory = [ { name = "cachetools" }, ] -[[package]] -name = "py-key-value-shared" -version = "0.2.8" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "beartype" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/26/79/05a1f9280cfa0709479319cbfd2b1c5beb23d5034624f548c83fb65b0b61/py_key_value_shared-0.2.8.tar.gz", hash = "sha256:703b4d3c61af124f0d528ba85995c3c8d78f8bd3d2b217377bd3278598070cc1", size = 8216, upload-time = "2025-10-24T13:31:03.601Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/84/7a/1726ceaa3343874f322dd83c9ec376ad81f533df8422b8b1e1233a59f8ce/py_key_value_shared-0.2.8-py3-none-any.whl", hash = "sha256:aff1bbfd46d065b2d67897d298642e80e5349eae588c6d11b48452b46b8d46ba", size = 14586, upload-time = "2025-10-24T13:31:02.838Z" }, -] - [[package]] name = "pycparser" version = "2.22" @@ -1075,6 +918,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, ] +[[package]] +name = "pyjwt" +version = "2.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5a/b46fa56bf322901eee5b0454a34343cdbdae202cd421775a8ee4e42fd519/pyjwt-2.11.0.tar.gz", hash = "sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623", size = 98019, upload-time = "2026-01-30T19:59:55.694Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224, upload-time = "2026-01-30T19:59:54.539Z" }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + [[package]] name = "pyperclip" version = "1.11.0" @@ -1226,18 +1083,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] -[[package]] -name = "rfc3339-validator" -version = "0.1.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513, upload-time = "2021-05-12T16:37:54.178Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490, upload-time = "2021-05-12T16:37:52.536Z" }, -] - [[package]] name = "rich" version = "14.0.0" @@ -1400,15 +1245,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/91/ff/2e2eed29e02c14a5cb6c57f09b2d5b40e65d6cc71f45b52e0be295ccbc2f/secretstorage-3.4.0-py3-none-any.whl", hash = "sha256:0e3b6265c2c63509fb7415717607e4b2c9ab767b7f344a57473b779ca13bd02e", size = 15272, upload-time = "2025-09-09T16:42:12.744Z" }, ] -[[package]] -name = "six" -version = "1.17.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, -] - [[package]] name = "sniffio" version = "1.3.1" @@ -1523,16 +1359,119 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.34.3" +version = "0.41.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/ad/713be230bcda622eaa35c28f0d328c3675c371238470abdea52417f17a8e/uvicorn-0.34.3.tar.gz", hash = "sha256:35919a9a979d7a59334b6b10e05d77c1d0d574c50e0fc98b8b1a0f165708b55a", size = 76631, upload-time = "2025-06-01T07:48:17.531Z" } +sdist = { url = "https://files.pythonhosted.org/packages/32/ce/eeb58ae4ac36fe09e3842eb02e0eb676bf2c53ae062b98f1b2531673efdd/uvicorn-0.41.0.tar.gz", hash = "sha256:09d11cf7008da33113824ee5a1c6422d89fbc2ff476540d69a34c87fab8b571a", size = 82633, upload-time = "2026-02-16T23:07:24.1Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/0d/8adfeaa62945f90d19ddc461c55f4a50c258af7662d34b6a3d5d1f8646f6/uvicorn-0.34.3-py3-none-any.whl", hash = "sha256:16246631db62bdfbf069b0645177d6e8a77ba950cfedbfd093acef9444e4d885", size = 62431, upload-time = "2025-06-01T07:48:15.664Z" }, + { url = "https://files.pythonhosted.org/packages/83/e4/d04a086285c20886c0daad0e026f250869201013d18f81d9ff5eada73a88/uvicorn-0.41.0-py3-none-any.whl", hash = "sha256:29e35b1d2c36a04b9e180d4007ede3bcb32a85fbdfd6c6aeb3f26839de088187", size = 68783, upload-time = "2026-02-16T23:07:22.357Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/1a/206e8cf2dd86fddf939165a57b4df61607a1e0add2785f170a3f616b7d9f/watchfiles-1.1.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:eef58232d32daf2ac67f42dea51a2c80f0d03379075d44a587051e63cc2e368c", size = 407318, upload-time = "2025-10-14T15:04:18.753Z" }, + { url = "https://files.pythonhosted.org/packages/b3/0f/abaf5262b9c496b5dad4ed3c0e799cbecb1f8ea512ecb6ddd46646a9fca3/watchfiles-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03fa0f5237118a0c5e496185cafa92878568b652a2e9a9382a5151b1a0380a43", size = 394478, upload-time = "2025-10-14T15:04:20.297Z" }, + { url = "https://files.pythonhosted.org/packages/b1/04/9cc0ba88697b34b755371f5ace8d3a4d9a15719c07bdc7bd13d7d8c6a341/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca65483439f9c791897f7db49202301deb6e15fe9f8fe2fed555bf986d10c31", size = 449894, upload-time = "2025-10-14T15:04:21.527Z" }, + { url = "https://files.pythonhosted.org/packages/d2/9c/eda4615863cd8621e89aed4df680d8c3ec3da6a4cf1da113c17decd87c7f/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f0ab1c1af0cb38e3f598244c17919fb1a84d1629cc08355b0074b6d7f53138ac", size = 459065, upload-time = "2025-10-14T15:04:22.795Z" }, + { url = "https://files.pythonhosted.org/packages/84/13/f28b3f340157d03cbc8197629bc109d1098764abe1e60874622a0be5c112/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bc570d6c01c206c46deb6e935a260be44f186a2f05179f52f7fcd2be086a94d", size = 488377, upload-time = "2025-10-14T15:04:24.138Z" }, + { url = "https://files.pythonhosted.org/packages/86/93/cfa597fa9389e122488f7ffdbd6db505b3b915ca7435ecd7542e855898c2/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e84087b432b6ac94778de547e08611266f1f8ffad28c0ee4c82e028b0fc5966d", size = 595837, upload-time = "2025-10-14T15:04:25.057Z" }, + { url = "https://files.pythonhosted.org/packages/57/1e/68c1ed5652b48d89fc24d6af905d88ee4f82fa8bc491e2666004e307ded1/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:620bae625f4cb18427b1bb1a2d9426dc0dd5a5ba74c7c2cdb9de405f7b129863", size = 473456, upload-time = "2025-10-14T15:04:26.497Z" }, + { url = "https://files.pythonhosted.org/packages/d5/dc/1a680b7458ffa3b14bb64878112aefc8f2e4f73c5af763cbf0bd43100658/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:544364b2b51a9b0c7000a4b4b02f90e9423d97fbbf7e06689236443ebcad81ab", size = 455614, upload-time = "2025-10-14T15:04:27.539Z" }, + { url = "https://files.pythonhosted.org/packages/61/a5/3d782a666512e01eaa6541a72ebac1d3aae191ff4a31274a66b8dd85760c/watchfiles-1.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bbe1ef33d45bc71cf21364df962af171f96ecaeca06bd9e3d0b583efb12aec82", size = 630690, upload-time = "2025-10-14T15:04:28.495Z" }, + { url = "https://files.pythonhosted.org/packages/9b/73/bb5f38590e34687b2a9c47a244aa4dd50c56a825969c92c9c5fc7387cea1/watchfiles-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1a0bb430adb19ef49389e1ad368450193a90038b5b752f4ac089ec6942c4dff4", size = 622459, upload-time = "2025-10-14T15:04:29.491Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ac/c9bb0ec696e07a20bd58af5399aeadaef195fb2c73d26baf55180fe4a942/watchfiles-1.1.1-cp310-cp310-win32.whl", hash = "sha256:3f6d37644155fb5beca5378feb8c1708d5783145f2a0f1c4d5a061a210254844", size = 272663, upload-time = "2025-10-14T15:04:30.435Z" }, + { url = "https://files.pythonhosted.org/packages/11/a0/a60c5a7c2ec59fa062d9a9c61d02e3b6abd94d32aac2d8344c4bdd033326/watchfiles-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:a36d8efe0f290835fd0f33da35042a1bb5dc0e83cbc092dcf69bce442579e88e", size = 287453, upload-time = "2025-10-14T15:04:31.53Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/2c5f479fb531ce2f0564eda479faecf253d886b1ab3630a39b7bf7362d46/watchfiles-1.1.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f57b396167a2565a4e8b5e56a5a1c537571733992b226f4f1197d79e94cf0ae5", size = 406529, upload-time = "2025-10-14T15:04:32.899Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cd/f515660b1f32f65df671ddf6f85bfaca621aee177712874dc30a97397977/watchfiles-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:421e29339983e1bebc281fab40d812742268ad057db4aee8c4d2bce0af43b741", size = 394384, upload-time = "2025-10-14T15:04:33.761Z" }, + { url = "https://files.pythonhosted.org/packages/7b/c3/28b7dc99733eab43fca2d10f55c86e03bd6ab11ca31b802abac26b23d161/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e43d39a741e972bab5d8100b5cdacf69db64e34eb19b6e9af162bccf63c5cc6", size = 448789, upload-time = "2025-10-14T15:04:34.679Z" }, + { url = "https://files.pythonhosted.org/packages/4a/24/33e71113b320030011c8e4316ccca04194bf0cbbaeee207f00cbc7d6b9f5/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f537afb3276d12814082a2e9b242bdcf416c2e8fd9f799a737990a1dbe906e5b", size = 460521, upload-time = "2025-10-14T15:04:35.963Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c3/3c9a55f255aa57b91579ae9e98c88704955fa9dac3e5614fb378291155df/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2cd9e04277e756a2e2d2543d65d1e2166d6fd4c9b183f8808634fda23f17b14", size = 488722, upload-time = "2025-10-14T15:04:37.091Z" }, + { url = "https://files.pythonhosted.org/packages/49/36/506447b73eb46c120169dc1717fe2eff07c234bb3232a7200b5f5bd816e9/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f3f58818dc0b07f7d9aa7fe9eb1037aecb9700e63e1f6acfed13e9fef648f5d", size = 596088, upload-time = "2025-10-14T15:04:38.39Z" }, + { url = "https://files.pythonhosted.org/packages/82/ab/5f39e752a9838ec4d52e9b87c1e80f1ee3ccdbe92e183c15b6577ab9de16/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb9f66367023ae783551042d31b1d7fd422e8289eedd91f26754a66f44d5cff", size = 472923, upload-time = "2025-10-14T15:04:39.666Z" }, + { url = "https://files.pythonhosted.org/packages/af/b9/a419292f05e302dea372fa7e6fda5178a92998411f8581b9830d28fb9edb/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aebfd0861a83e6c3d1110b78ad54704486555246e542be3e2bb94195eabb2606", size = 456080, upload-time = "2025-10-14T15:04:40.643Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c3/d5932fd62bde1a30c36e10c409dc5d54506726f08cb3e1d8d0ba5e2bc8db/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5fac835b4ab3c6487b5dbad78c4b3724e26bcc468e886f8ba8cc4306f68f6701", size = 629432, upload-time = "2025-10-14T15:04:41.789Z" }, + { url = "https://files.pythonhosted.org/packages/f7/77/16bddd9779fafb795f1a94319dc965209c5641db5bf1edbbccace6d1b3c0/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:399600947b170270e80134ac854e21b3ccdefa11a9529a3decc1327088180f10", size = 623046, upload-time = "2025-10-14T15:04:42.718Z" }, + { url = "https://files.pythonhosted.org/packages/46/ef/f2ecb9a0f342b4bfad13a2787155c6ee7ce792140eac63a34676a2feeef2/watchfiles-1.1.1-cp311-cp311-win32.whl", hash = "sha256:de6da501c883f58ad50db3a32ad397b09ad29865b5f26f64c24d3e3281685849", size = 271473, upload-time = "2025-10-14T15:04:43.624Z" }, + { url = "https://files.pythonhosted.org/packages/94/bc/f42d71125f19731ea435c3948cad148d31a64fccde3867e5ba4edee901f9/watchfiles-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:35c53bd62a0b885bf653ebf6b700d1bf05debb78ad9292cf2a942b23513dc4c4", size = 287598, upload-time = "2025-10-14T15:04:44.516Z" }, + { url = "https://files.pythonhosted.org/packages/57/c9/a30f897351f95bbbfb6abcadafbaca711ce1162f4db95fc908c98a9165f3/watchfiles-1.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:57ca5281a8b5e27593cb7d82c2ac927ad88a96ed406aa446f6344e4328208e9e", size = 277210, upload-time = "2025-10-14T15:04:45.883Z" }, + { url = "https://files.pythonhosted.org/packages/74/d5/f039e7e3c639d9b1d09b07ea412a6806d38123f0508e5f9b48a87b0a76cc/watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d", size = 404745, upload-time = "2025-10-14T15:04:46.731Z" }, + { url = "https://files.pythonhosted.org/packages/a5/96/a881a13aa1349827490dab2d363c8039527060cfcc2c92cc6d13d1b1049e/watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610", size = 391769, upload-time = "2025-10-14T15:04:48.003Z" }, + { url = "https://files.pythonhosted.org/packages/4b/5b/d3b460364aeb8da471c1989238ea0e56bec24b6042a68046adf3d9ddb01c/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af", size = 449374, upload-time = "2025-10-14T15:04:49.179Z" }, + { url = "https://files.pythonhosted.org/packages/b9/44/5769cb62d4ed055cb17417c0a109a92f007114a4e07f30812a73a4efdb11/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2edc3553362b1c38d9f06242416a5d8e9fe235c204a4072e988ce2e5bb1f69f6", size = 459485, upload-time = "2025-10-14T15:04:50.155Z" }, + { url = "https://files.pythonhosted.org/packages/19/0c/286b6301ded2eccd4ffd0041a1b726afda999926cf720aab63adb68a1e36/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30f7da3fb3f2844259cba4720c3fc7138eb0f7b659c38f3bfa65084c7fc7abce", size = 488813, upload-time = "2025-10-14T15:04:51.059Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2b/8530ed41112dd4a22f4dcfdb5ccf6a1baad1ff6eed8dc5a5f09e7e8c41c7/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8979280bdafff686ba5e4d8f97840f929a87ed9cdf133cbbd42f7766774d2aa", size = 594816, upload-time = "2025-10-14T15:04:52.031Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d2/f5f9fb49489f184f18470d4f99f4e862a4b3e9ac2865688eb2099e3d837a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcc5c24523771db3a294c77d94771abcfcb82a0e0ee8efd910c37c59ec1b31bb", size = 475186, upload-time = "2025-10-14T15:04:53.064Z" }, + { url = "https://files.pythonhosted.org/packages/cf/68/5707da262a119fb06fbe214d82dd1fe4a6f4af32d2d14de368d0349eb52a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db5d7ae38ff20153d542460752ff397fcf5c96090c1230803713cf3147a6803", size = 456812, upload-time = "2025-10-14T15:04:55.174Z" }, + { url = "https://files.pythonhosted.org/packages/66/ab/3cbb8756323e8f9b6f9acb9ef4ec26d42b2109bce830cc1f3468df20511d/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:28475ddbde92df1874b6c5c8aaeb24ad5be47a11f87cde5a28ef3835932e3e94", size = 630196, upload-time = "2025-10-14T15:04:56.22Z" }, + { url = "https://files.pythonhosted.org/packages/78/46/7152ec29b8335f80167928944a94955015a345440f524d2dfe63fc2f437b/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:36193ed342f5b9842edd3532729a2ad55c4160ffcfa3700e0d54be496b70dd43", size = 622657, upload-time = "2025-10-14T15:04:57.521Z" }, + { url = "https://files.pythonhosted.org/packages/0a/bf/95895e78dd75efe9a7f31733607f384b42eb5feb54bd2eb6ed57cc2e94f4/watchfiles-1.1.1-cp312-cp312-win32.whl", hash = "sha256:859e43a1951717cc8de7f4c77674a6d389b106361585951d9e69572823f311d9", size = 272042, upload-time = "2025-10-14T15:04:59.046Z" }, + { url = "https://files.pythonhosted.org/packages/87/0a/90eb755f568de2688cb220171c4191df932232c20946966c27a59c400850/watchfiles-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:91d4c9a823a8c987cce8fa2690923b069966dabb196dd8d137ea2cede885fde9", size = 288410, upload-time = "2025-10-14T15:05:00.081Z" }, + { url = "https://files.pythonhosted.org/packages/36/76/f322701530586922fbd6723c4f91ace21364924822a8772c549483abed13/watchfiles-1.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:a625815d4a2bdca61953dbba5a39d60164451ef34c88d751f6c368c3ea73d404", size = 278209, upload-time = "2025-10-14T15:05:01.168Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/f750b29225fe77139f7ae5de89d4949f5a99f934c65a1f1c0b248f26f747/watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18", size = 404321, upload-time = "2025-10-14T15:05:02.063Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/f07a295cde762644aa4c4bb0f88921d2d141af45e735b965fb2e87858328/watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a", size = 391783, upload-time = "2025-10-14T15:05:03.052Z" }, + { url = "https://files.pythonhosted.org/packages/bc/11/fc2502457e0bea39a5c958d86d2cb69e407a4d00b85735ca724bfa6e0d1a/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219", size = 449279, upload-time = "2025-10-14T15:05:04.004Z" }, + { url = "https://files.pythonhosted.org/packages/e3/1f/d66bc15ea0b728df3ed96a539c777acfcad0eb78555ad9efcaa1274688f0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428", size = 459405, upload-time = "2025-10-14T15:05:04.942Z" }, + { url = "https://files.pythonhosted.org/packages/be/90/9f4a65c0aec3ccf032703e6db02d89a157462fbb2cf20dd415128251cac0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0", size = 488976, upload-time = "2025-10-14T15:05:05.905Z" }, + { url = "https://files.pythonhosted.org/packages/37/57/ee347af605d867f712be7029bb94c8c071732a4b44792e3176fa3c612d39/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150", size = 595506, upload-time = "2025-10-14T15:05:06.906Z" }, + { url = "https://files.pythonhosted.org/packages/a8/78/cc5ab0b86c122047f75e8fc471c67a04dee395daf847d3e59381996c8707/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae", size = 474936, upload-time = "2025-10-14T15:05:07.906Z" }, + { url = "https://files.pythonhosted.org/packages/62/da/def65b170a3815af7bd40a3e7010bf6ab53089ef1b75d05dd5385b87cf08/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d", size = 456147, upload-time = "2025-10-14T15:05:09.138Z" }, + { url = "https://files.pythonhosted.org/packages/57/99/da6573ba71166e82d288d4df0839128004c67d2778d3b566c138695f5c0b/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b", size = 630007, upload-time = "2025-10-14T15:05:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/a8/51/7439c4dd39511368849eb1e53279cd3454b4a4dbace80bab88feeb83c6b5/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374", size = 622280, upload-time = "2025-10-14T15:05:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/95/9c/8ed97d4bba5db6fdcdb2b298d3898f2dd5c20f6b73aee04eabe56c59677e/watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0", size = 272056, upload-time = "2025-10-14T15:05:12.156Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/c14e28429f744a260d8ceae18bf58c1d5fa56b50d006a7a9f80e1882cb0d/watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42", size = 288162, upload-time = "2025-10-14T15:05:13.208Z" }, + { url = "https://files.pythonhosted.org/packages/dc/61/fe0e56c40d5cd29523e398d31153218718c5786b5e636d9ae8ae79453d27/watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18", size = 277909, upload-time = "2025-10-14T15:05:14.49Z" }, + { url = "https://files.pythonhosted.org/packages/79/42/e0a7d749626f1e28c7108a99fb9bf524b501bbbeb9b261ceecde644d5a07/watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da", size = 403389, upload-time = "2025-10-14T15:05:15.777Z" }, + { url = "https://files.pythonhosted.org/packages/15/49/08732f90ce0fbbc13913f9f215c689cfc9ced345fb1bcd8829a50007cc8d/watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051", size = 389964, upload-time = "2025-10-14T15:05:16.85Z" }, + { url = "https://files.pythonhosted.org/packages/27/0d/7c315d4bd5f2538910491a0393c56bf70d333d51bc5b34bee8e68e8cea19/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e", size = 448114, upload-time = "2025-10-14T15:05:17.876Z" }, + { url = "https://files.pythonhosted.org/packages/c3/24/9e096de47a4d11bc4df41e9d1e61776393eac4cb6eb11b3e23315b78b2cc/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70", size = 460264, upload-time = "2025-10-14T15:05:18.962Z" }, + { url = "https://files.pythonhosted.org/packages/cc/0f/e8dea6375f1d3ba5fcb0b3583e2b493e77379834c74fd5a22d66d85d6540/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261", size = 487877, upload-time = "2025-10-14T15:05:20.094Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/df24cfc6424a12deb41503b64d42fbea6b8cb357ec62ca84a5a3476f654a/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620", size = 595176, upload-time = "2025-10-14T15:05:21.134Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b5/853b6757f7347de4e9b37e8cc3289283fb983cba1ab4d2d7144694871d9c/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04", size = 473577, upload-time = "2025-10-14T15:05:22.306Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f7/0a4467be0a56e80447c8529c9fce5b38eab4f513cb3d9bf82e7392a5696b/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77", size = 455425, upload-time = "2025-10-14T15:05:23.348Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e0/82583485ea00137ddf69bc84a2db88bd92ab4a6e3c405e5fb878ead8d0e7/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef", size = 628826, upload-time = "2025-10-14T15:05:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/28/9a/a785356fccf9fae84c0cc90570f11702ae9571036fb25932f1242c82191c/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf", size = 622208, upload-time = "2025-10-14T15:05:25.45Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f4/0872229324ef69b2c3edec35e84bd57a1289e7d3fe74588048ed8947a323/watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5", size = 404315, upload-time = "2025-10-14T15:05:26.501Z" }, + { url = "https://files.pythonhosted.org/packages/7b/22/16d5331eaed1cb107b873f6ae1b69e9ced582fcf0c59a50cd84f403b1c32/watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd", size = 390869, upload-time = "2025-10-14T15:05:27.649Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7e/5643bfff5acb6539b18483128fdc0ef2cccc94a5b8fbda130c823e8ed636/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb", size = 449919, upload-time = "2025-10-14T15:05:28.701Z" }, + { url = "https://files.pythonhosted.org/packages/51/2e/c410993ba5025a9f9357c376f48976ef0e1b1aefb73b97a5ae01a5972755/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5", size = 460845, upload-time = "2025-10-14T15:05:30.064Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a4/2df3b404469122e8680f0fcd06079317e48db58a2da2950fb45020947734/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3", size = 489027, upload-time = "2025-10-14T15:05:31.064Z" }, + { url = "https://files.pythonhosted.org/packages/ea/84/4587ba5b1f267167ee715b7f66e6382cca6938e0a4b870adad93e44747e6/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33", size = 595615, upload-time = "2025-10-14T15:05:32.074Z" }, + { url = "https://files.pythonhosted.org/packages/6a/0f/c6988c91d06e93cd0bb3d4a808bcf32375ca1904609835c3031799e3ecae/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510", size = 474836, upload-time = "2025-10-14T15:05:33.209Z" }, + { url = "https://files.pythonhosted.org/packages/b4/36/ded8aebea91919485b7bbabbd14f5f359326cb5ec218cd67074d1e426d74/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05", size = 455099, upload-time = "2025-10-14T15:05:34.189Z" }, + { url = "https://files.pythonhosted.org/packages/98/e0/8c9bdba88af756a2fce230dd365fab2baf927ba42cd47521ee7498fd5211/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6", size = 630626, upload-time = "2025-10-14T15:05:35.216Z" }, + { url = "https://files.pythonhosted.org/packages/2a/84/a95db05354bf2d19e438520d92a8ca475e578c647f78f53197f5a2f17aaf/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81", size = 622519, upload-time = "2025-10-14T15:05:36.259Z" }, + { url = "https://files.pythonhosted.org/packages/1d/ce/d8acdc8de545de995c339be67711e474c77d643555a9bb74a9334252bd55/watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b", size = 272078, upload-time = "2025-10-14T15:05:37.63Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c9/a74487f72d0451524be827e8edec251da0cc1fcf111646a511ae752e1a3d/watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a", size = 287664, upload-time = "2025-10-14T15:05:38.95Z" }, + { url = "https://files.pythonhosted.org/packages/df/b8/8ac000702cdd496cdce998c6f4ee0ca1f15977bba51bdf07d872ebdfc34c/watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02", size = 277154, upload-time = "2025-10-14T15:05:39.954Z" }, + { url = "https://files.pythonhosted.org/packages/47/a8/e3af2184707c29f0f14b1963c0aace6529f9d1b8582d5b99f31bbf42f59e/watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21", size = 403820, upload-time = "2025-10-14T15:05:40.932Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/e47e307c2f4bd75f9f9e8afbe3876679b18e1bcec449beca132a1c5ffb2d/watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5", size = 390510, upload-time = "2025-10-14T15:05:41.945Z" }, + { url = "https://files.pythonhosted.org/packages/d5/a0/ad235642118090f66e7b2f18fd5c42082418404a79205cdfca50b6309c13/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7", size = 448408, upload-time = "2025-10-14T15:05:43.385Z" }, + { url = "https://files.pythonhosted.org/packages/df/85/97fa10fd5ff3332ae17e7e40e20784e419e28521549780869f1413742e9d/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101", size = 458968, upload-time = "2025-10-14T15:05:44.404Z" }, + { url = "https://files.pythonhosted.org/packages/47/c2/9059c2e8966ea5ce678166617a7f75ecba6164375f3b288e50a40dc6d489/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44", size = 488096, upload-time = "2025-10-14T15:05:45.398Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/d90a9ec8ac309bc26db808a13e7bfc0e4e78b6fc051078a554e132e80160/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c", size = 596040, upload-time = "2025-10-14T15:05:46.502Z" }, + { url = "https://files.pythonhosted.org/packages/95/68/4e3479b20ca305cfc561db3ed207a8a1c745ee32bf24f2026a129d0ddb6e/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc", size = 473847, upload-time = "2025-10-14T15:05:47.484Z" }, + { url = "https://files.pythonhosted.org/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", size = 455072, upload-time = "2025-10-14T15:05:48.928Z" }, + { url = "https://files.pythonhosted.org/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", size = 629104, upload-time = "2025-10-14T15:05:49.908Z" }, + { url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" }, + { url = "https://files.pythonhosted.org/packages/ba/4c/a888c91e2e326872fa4705095d64acd8aa2fb9c1f7b9bd0588f33850516c/watchfiles-1.1.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:17ef139237dfced9da49fb7f2232c86ca9421f666d78c264c7ffca6601d154c3", size = 409611, upload-time = "2025-10-14T15:06:05.809Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c7/5420d1943c8e3ce1a21c0a9330bcf7edafb6aa65d26b21dbb3267c9e8112/watchfiles-1.1.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:672b8adf25b1a0d35c96b5888b7b18699d27d4194bac8beeae75be4b7a3fc9b2", size = 396889, upload-time = "2025-10-14T15:06:07.035Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e5/0072cef3804ce8d3aaddbfe7788aadff6b3d3f98a286fdbee9fd74ca59a7/watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77a13aea58bc2b90173bc69f2a90de8e282648939a00a602e1dc4ee23e26b66d", size = 451616, upload-time = "2025-10-14T15:06:08.072Z" }, + { url = "https://files.pythonhosted.org/packages/83/4e/b87b71cbdfad81ad7e83358b3e447fedd281b880a03d64a760fe0a11fc2e/watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b495de0bb386df6a12b18335a0285dda90260f51bdb505503c02bcd1ce27a8b", size = 458413, upload-time = "2025-10-14T15:06:09.209Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8e/e500f8b0b77be4ff753ac94dc06b33d8f0d839377fee1b78e8c8d8f031bf/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:db476ab59b6765134de1d4fe96a1a9c96ddf091683599be0f26147ea1b2e4b88", size = 408250, upload-time = "2025-10-14T15:06:10.264Z" }, + { url = "https://files.pythonhosted.org/packages/bd/95/615e72cd27b85b61eec764a5ca51bd94d40b5adea5ff47567d9ebc4d275a/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:89eef07eee5e9d1fda06e38822ad167a044153457e6fd997f8a858ab7564a336", size = 396117, upload-time = "2025-10-14T15:06:11.28Z" }, + { url = "https://files.pythonhosted.org/packages/c9/81/e7fe958ce8a7fb5c73cc9fb07f5aeaf755e6aa72498c57d760af760c91f8/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce19e06cbda693e9e7686358af9cd6f5d61312ab8b00488bc36f5aabbaf77e24", size = 450493, upload-time = "2025-10-14T15:06:12.321Z" }, + { url = "https://files.pythonhosted.org/packages/6e/d4/ed38dd3b1767193de971e694aa544356e63353c33a85d948166b5ff58b9e/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e6f39af2eab0118338902798b5aa6664f46ff66bc0280de76fca67a7f262a49", size = 457546, upload-time = "2025-10-14T15:06:13.372Z" }, ] [[package]] @@ -1594,18 +1533,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, ] -[[package]] -name = "werkzeug" -version = "3.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markupsafe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/32/af/d4502dc713b4ccea7175d764718d5183caf8d0867a4f0190d5d4a45cea49/werkzeug-3.1.1.tar.gz", hash = "sha256:8cd39dfbdfc1e051965f156163e2974e52c210f130810e9ad36858f0fd3edad4", size = 806453, upload-time = "2024-11-01T16:40:45.462Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/ea/c67e1dee1ba208ed22c06d1d547ae5e293374bfc43e0eb0ef5e262b68561/werkzeug-3.1.1-py3-none-any.whl", hash = "sha256:a71124d1ef06008baafa3d266c02f56e1836a5984afd6dd6c9230669d60d9fb5", size = 224371, upload-time = "2024-11-01T16:40:43.994Z" }, -] - [[package]] name = "zipp" version = "3.23.0" From 1dcb59b5d3e683884dce3073d978f970111200ea Mon Sep 17 00:00:00 2001 From: Chalmer Lowe Date: Thu, 19 Feb 2026 14:23:11 -0500 Subject: [PATCH 54/67] migrate code from googleapis/python-logging (#13844) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update logging samples to fit new style guide and match Node.js samples. [(#435)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/435) * Fix test conflict. Change-Id: I67e149dc43ebdb11144ac3839e062aa4668ebb2e * Fix logging test. Change-Id: I866edcc956fda2265dd7af7b774336930ec6a151 * Auto-update dependencies. [(#456)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/456) * Fix import order lint errors Change-Id: Ieaf7237fc6f925daec46a07d2e81a452b841198a * Auto-update dependencies. [(#540)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/540) * Auto-update dependencies. [(#542)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/542) * Move to google-cloud [(#544)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/544) * Add new "quickstart" samples [(#547)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/547) * Quickstart tests [(#569)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/569) * Add tests for quickstarts * Update secrets * Generate readmes for most service samples [(#599)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/599) * Update samples to support latest Google Cloud Python [(#656)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/656) * Auto-update dependencies. [(#715)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/715) * Remove cloud config fixture [(#887)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/887) * Remove cloud config fixture * Fix client secrets * Fix bigtable instance * Fix reference to our testing tools * Auto-update dependencies. [(#914)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/914) * Auto-update dependencies. * xfail the error reporting test * Fix lint * Re-generate all readmes * Fix README rst links [(#962)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/962) * Fix README rst links * Update all READMEs * Auto-update dependencies. [(#1004)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/1004) * Auto-update dependencies. * Fix natural language samples * Fix pubsub iam samples * Fix language samples * Fix bigquery samples * Auto-update dependencies. [(#1055)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/1055) * Auto-update dependencies. * Explicitly use latest bigtable client Change-Id: Id71e9e768f020730e4ca9514a0d7ebaa794e7d9e * Revert language update for now Change-Id: I8867f154e9a5aae00d0047c9caf880e5e8f50c53 * Remove pdb. smh Change-Id: I5ff905fadc026eebbcd45512d4e76e003e3b2b43 * Fix logging tests Change-Id: I6691c70912b1e1b5993e962a4827a846642feac3 * Auto-update dependencies. [(#1093)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/1093) * Auto-update dependencies. * Fix storage notification poll sample Change-Id: I6afbc79d15e050531555e4c8e51066996717a0f3 * Fix spanner samples Change-Id: I40069222c60d57e8f3d3878167591af9130895cb * Drop coverage because it's not useful Change-Id: Iae399a7083d7866c3c7b9162d0de244fbff8b522 * Try again to fix flaky logging test Change-Id: I6225c074701970c17c426677ef1935bb6d7e36b4 * Update all generated readme auth instructions [(#1121)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/1121) Change-Id: I03b5eaef8b17ac3dc3c0339fd2c7447bd3e11bd2 * Added Link to Python Setup Guide [(#1158)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/1158) * Update Readme.rst to add Python setup guide As requested in b/64770713. This sample is linked in documentation https://cloud.google.com/bigtable/docs/scaling, and it would make more sense to update the guide here than in the documentation. * Update README.rst * Update README.rst * Update README.rst * Update README.rst * Update README.rst * Update install_deps.tmpl.rst * Updated readmegen scripts and re-generated related README files * Fixed the lint error * Fix a few more lint issues Change-Id: I0d420f3053f391fa225e4b8179e45fd1138f5c65 * Auto-update dependencies. [(#1186)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/1186) * Add sample for standard library logging handler configuration [(#1233)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/1233) * Add sample for standard library logging handler configuration * Add handler.py to readme * Added "Open in Cloud Shell" buttons to README files [(#1254)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/1254) * Auto-update dependencies. [(#1359)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/1359) * Auto-update dependencies. [(#1377)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/1377) * Auto-update dependencies. * Update requirements.txt * Regenerate the README files and fix the Open in Cloud Shell link for some samples [(#1441)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/1441) * Update READMEs to fix numbering and add git clone [(#1464)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/1464) * Update logging doc tags. [(#1634)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/1634) * Fix deprecation warning [(#1801)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/1801) logging.warn -> logging.warning to fix "DeprecationWarning: The 'warn' function is deprecated, use 'warning' instead" * Auto-update dependencies. [(#1846)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/1846) ACK, merging. * Auto-update dependencies. [(#1980)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/1980) * Auto-update dependencies. * Update requirements.txt * Update requirements.txt * Adds split updates for Firebase ... opencensus [(#2438)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/2438) * Auto-update dependencies. [(#2005)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/2005) * Auto-update dependencies. * Revert update of appengine/flexible/datastore. * revert update of appengine/flexible/scipy * revert update of bigquery/bqml * revert update of bigquery/cloud-client * revert update of bigquery/datalab-migration * revert update of bigtable/quickstart * revert update of compute/api * revert update of container_registry/container_analysis * revert update of dataflow/run_template * revert update of datastore/cloud-ndb * revert update of dialogflow/cloud-client * revert update of dlp * revert update of functions/imagemagick * revert update of functions/ocr/app * revert update of healthcare/api-client/fhir * revert update of iam/api-client * revert update of iot/api-client/gcs_file_to_device * revert update of iot/api-client/mqtt_example * revert update of language/automl * revert update of run/image-processing * revert update of vision/automl * revert update testing/requirements.txt * revert update of vision/cloud-client/detect * revert update of vision/cloud-client/product_search * revert update of jobs/v2/api_client * revert update of jobs/v3/api_client * revert update of opencensus * revert update of translate/cloud-client * revert update to speech/cloud-client Co-authored-by: Kurtis Van Gent <31518063+kurtisvg@users.noreply.github.com> Co-authored-by: Doug Mahugh * chore(deps): update dependency google-cloud-logging to v1.15.0 [(#3161)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/3161) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [google-cloud-logging](https://togithub.com/googleapis/python-logging) | minor | `==1.14.0` -> `==1.15.0` | --- ### Release Notes
      googleapis/python-logging ### [`v1.15.0`](https://togithub.com/googleapis/python-logging/blob/master/CHANGELOG.md#​1150httpswwwgithubcomgoogleapispython-loggingcomparev1140v1150-2020-02-26) [Compare Source](https://togithub.com/googleapis/python-logging/compare/v1.14.0...v1.15.0) ##### Features - add support for cmek settings; undeprecate resource name helper methods; bump copyright year to 2020 ([#​22](https://www.github.com/googleapis/python-logging/issues/22)) ([1c687c1](https://www.github.com/googleapis/python-logging/commit/1c687c168cdc1f5ebc74d2380ad87335a42209a2)) ##### Bug Fixes - **logging:** deprecate resource name helper methods (via synth) ([#​9837](https://www.github.com/googleapis/python-logging/issues/9837)) ([335af9e](https://www.github.com/googleapis/python-logging/commit/335af9e909eb7fb4696ba906a82176611653531d)) - **logging:** update test assertion and core version pins ([#​10087](https://www.github.com/googleapis/python-logging/issues/10087)) ([4aedea8](https://www.github.com/googleapis/python-logging/commit/4aedea80e2bccb5ba3c41fae7a0ee46cc07eefa9)) - replace unsafe six.PY3 with PY2 for better future compatibility with Python 4 ([#​10081](https://www.github.com/googleapis/python-logging/issues/10081)) ([c6eb601](https://www.github.com/googleapis/python-logging/commit/c6eb60179d674dfd5137d90d209094c9369b3581))
      --- ### Renovate configuration :date: **Schedule**: At any time (no schedule defined). :vertical_traffic_light: **Automerge**: Disabled by config. Please merge this manually once you are satisfied. :recycle: **Rebasing**: Never, or you tick the rebase/retry checkbox. :no_bell: **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#GoogleCloudPlatform/python-docs-samples). * Simplify noxfile setup. [(#2806)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/2806) * chore(deps): update dependency requests to v2.23.0 * Simplify noxfile and add version control. * Configure appengine/standard to only test Python 2.7. * Update Kokokro configs to match noxfile. * Add requirements-test to each folder. * Remove Py2 versions from everything execept appengine/standard. * Remove conftest.py. * Remove appengine/standard/conftest.py * Remove 'no-sucess-flaky-report' from pytest.ini. * Add GAE SDK back to appengine/standard tests. * Fix typo. * Roll pytest to python 2 version. * Add a bunch of testing requirements. * Remove typo. * Add appengine lib directory back in. * Add some additional requirements. * Fix issue with flake8 args. * Even more requirements. * Readd appengine conftest.py. * Add a few more requirements. * Even more Appengine requirements. * Add webtest for appengine/standard/mailgun. * Add some additional requirements. * Add workaround for issue with mailjet-rest. * Add responses for appengine/standard/mailjet. Co-authored-by: Renovate Bot * [logging] chore: remove gcp-devrel-py-tools [(#3477)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/3477) * Update logging example to retrieve the default handler [(#3691)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/3691) * Update handler.py * Update README.rst * Update handler.py Co-authored-by: Kurtis Van Gent <31518063+kurtisvg@users.noreply.github.com> * testing: various cleanups [(#3877)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/3877) * testing: various cleanups * [iap]: only run iap tests on Kokoro * [vision/automl]: use temporary directory for temporary files * [appengine/flexible/scipy]: use temporary directory * [bigtable/snippets/reads]: update pytest snapshot * [texttospeech/cloud-client]: added output.mp3 to .gitignore * [iot/api-client/gcs_file_to_device]: use temporary directory * [iot/api-client/mqtt_example]: use temporary directory * [logging/cloud-client]: use uuid and add backoff * use project directory with Trampoline V1 * chore(deps): update dependency pytest to v5.4.3 [(#4279)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/4279) * chore(deps): update dependency pytest to v5.4.3 * specify pytest for python 2 in appengine Co-authored-by: Leah Cole * Update dependency pytest to v6 [(#4390)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/4390) * chore(deps): update dependency google-cloud-logging to v1.15.1 [(#4458)](https://github.com/GoogleCloudPlatform/python-docs-samples/issues/4458) * chore: update templates * feat!: use microgenerator (#94) The bulk of the changes are to the handwritten code (`gogle/cloud/logging_v2/*.py`). Changes are listed in `UPGRADING.md`. * `_gapic` uses the microgenerated surface * Add support for parent resources that are not `project` (`folder`, `billingAccount`, `organization`) where appropriate for log entries and sinks. * Use google-style docstrings * Optional params can only be passed as kwargs * chore(deps): update dependency google-cloud-logging to v2 (#95) * chore: Re-generated to pick up changes from synthtool. (#98) * changes without context autosynth cannot find the source of changes triggered by earlier changes in this repository, or by version upgrades to tools such as linters. * fix: address lint issues Source-Author: Leah E. Cole <6719667+leahecole@users.noreply.github.com> Source-Date: Thu Nov 12 11:30:49 2020 -0800 Source-Repo: googleapis/synthtool Source-Sha: e89175cf074dccc4babb4eca66ae913696e47a71 Source-Link: https://github.com/googleapis/synthtool/commit/e89175cf074dccc4babb4eca66ae913696e47a71 * docs(python): update intersphinx for grpc and auth * docs(python): update intersphinx for grpc and auth * use https for python intersphinx Co-authored-by: Tim Swast Source-Author: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Source-Date: Wed Nov 18 14:37:25 2020 -0700 Source-Repo: googleapis/synthtool Source-Sha: 9a7d9fbb7045c34c9d3d22c1ff766eeae51f04c9 Source-Link: https://github.com/googleapis/synthtool/commit/9a7d9fbb7045c34c9d3d22c1ff766eeae51f04c9 * docs(python): fix intersphinx link for google-auth Source-Author: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Source-Date: Thu Nov 19 10:16:05 2020 -0700 Source-Repo: googleapis/synthtool Source-Sha: a073c873f3928c561bdf87fdfbf1d081d1998984 Source-Link: https://github.com/googleapis/synthtool/commit/a073c873f3928c561bdf87fdfbf1d081d1998984 * chore: Re-generated to pick up changes from googleapis. (#97) * changes without context autosynth cannot find the source of changes triggered by earlier changes in this repository, or by version upgrades to tools such as linters. * feat: Makes remaining LogBucket and LogViews methods public PiperOrigin-RevId: 342353190 Source-Author: Google APIs Source-Date: Fri Nov 13 15:44:35 2020 -0800 Source-Repo: googleapis/googleapis Source-Sha: be0bdf86cd31aa7c1a7b30a9a2e9f2fd53ee3d91 Source-Link: https://github.com/googleapis/googleapis/commit/be0bdf86cd31aa7c1a7b30a9a2e9f2fd53ee3d91 * fixed lint issue Co-authored-by: Daniel Sanche * chore(deps): update dependency google-cloud-logging to v2.0.1 (#109) * chore: Re-generated to pick up changes from synthtool. (#127) * changes without context autosynth cannot find the source of changes triggered by earlier changes in this repository, or by version upgrades to tools such as linters. * chore: add config / docs for 'pre-commit' support Source-Author: Tres Seaver Source-Date: Tue Dec 1 16:01:20 2020 -0500 Source-Repo: googleapis/synthtool Source-Sha: 32af6da519a6b042e3da62008e2a75e991efb6b4 Source-Link: https://github.com/googleapis/synthtool/commit/32af6da519a6b042e3da62008e2a75e991efb6b4 * chore(deps): update precommit hook pre-commit/pre-commit-hooks to v3.3.0 Source-Author: WhiteSource Renovate Source-Date: Wed Dec 2 17:18:24 2020 +0100 Source-Repo: googleapis/synthtool Source-Sha: 69629b64b83c6421d616be2b8e11795738ec8a6c Source-Link: https://github.com/googleapis/synthtool/commit/69629b64b83c6421d616be2b8e11795738ec8a6c * test(python): give filesystem paths to pytest-cov https://pytest-cov.readthedocs.io/en/latest/config.html The pytest-cov docs seem to suggest a filesystem path is expected. Source-Author: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Source-Date: Wed Dec 2 09:28:04 2020 -0700 Source-Repo: googleapis/synthtool Source-Sha: f94318521f63085b9ccb43d42af89f153fb39f15 Source-Link: https://github.com/googleapis/synthtool/commit/f94318521f63085b9ccb43d42af89f153fb39f15 * chore: update noxfile.py.j2 * Update noxfile.py.j2 add changes from @glasnt to the template template to ensure that enforcing type hinting doesn't fail for repos with the sample noxfile (aka all samples repos) See https://github.com/GoogleCloudPlatform/python-docs-samples/pull/4869/files for context * fix typo Source-Author: Leah E. Cole <6719667+leahecole@users.noreply.github.com> Source-Date: Thu Dec 3 13:44:30 2020 -0800 Source-Repo: googleapis/synthtool Source-Sha: 18c5dbdb4ac8cf75d4d8174e7b4558f48e76f8a1 Source-Link: https://github.com/googleapis/synthtool/commit/18c5dbdb4ac8cf75d4d8174e7b4558f48e76f8a1 * docs: fix usage guide (#140) * chore(deps): update dependency google-cloud-bigquery to v2.6.2 (#150) * chore(deps): update dependency google-cloud-logging to v2.1.1 (#152) * chore(deps): update dependency google-cloud-logging to v2.2.0 (#164) * chore(deps): update dependency google-cloud-bigquery to v2.7.0 (#166) * docs: add python std_logging to sample browser (#173) * docs: add existing region tag to python std logging This is so this existing Python example will show up here: https://cloud.google.com/logging/docs/samples/logging-stdlogging * chore: update templates (#168) * changes without context autosynth cannot find the source of changes triggered by earlier changes in this repository, or by version upgrades to tools such as linters. * chore: add 3.9 to noxfile template Since the python-docs-samples noxfile-template doesn't sync with this, I wanted to make sure the noxfile template matched the most recent change [here](https://github.com/GoogleCloudPlatform/python-docs-samples/pull/4968/files) cc @tmatsuo Source-Author: Leah E. Cole <6719667+leahecole@users.noreply.github.com> Source-Date: Fri Jan 15 17:24:05 2021 -0800 Source-Repo: googleapis/synthtool Source-Sha: 56ddc68f36b32341e9f22c2c59b4ce6aa3ba635f Source-Link: https://github.com/googleapis/synthtool/commit/56ddc68f36b32341e9f22c2c59b4ce6aa3ba635f * build(python): make `NOX_SESSION` optional I added this accidentally in #889. `NOX_SESSION` should be passed down if it is set but not marked required. Source-Author: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Source-Date: Tue Jan 19 09:38:04 2021 -0700 Source-Repo: googleapis/synthtool Source-Sha: ba960d730416fe05c50547e975ce79fcee52c671 Source-Link: https://github.com/googleapis/synthtool/commit/ba960d730416fe05c50547e975ce79fcee52c671 * chore: Add header checker config to python library synth Now that we have it working in [python-docs-samples](https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/.github/header-checker-lint.yml) we should consider adding it to the 🐍 libraries :) Source-Author: Leah E. Cole <6719667+leahecole@users.noreply.github.com> Source-Date: Mon Jan 25 13:24:08 2021 -0800 Source-Repo: googleapis/synthtool Source-Sha: 573f7655311b553a937f9123bee17bf78497db95 Source-Link: https://github.com/googleapis/synthtool/commit/573f7655311b553a937f9123bee17bf78497db95 * chore: add noxfile parameters for extra dependencies Also, add tests for some noxfile parameters for assurance that the template generates valid Python. Co-authored-by: Jeffrey Rennie Source-Author: Tim Swast Source-Date: Tue Jan 26 12:26:57 2021 -0600 Source-Repo: googleapis/synthtool Source-Sha: 778d8beae28d6d87eb01fdc839a4b4d966ed2ebe Source-Link: https://github.com/googleapis/synthtool/commit/778d8beae28d6d87eb01fdc839a4b4d966ed2ebe * chore(deps): update dependency google-cloud-storage to v1.35.1 (#172) * chore(deps): update dependency google-cloud-pubsub to v2.3.0 (#176) * chore(deps): update dependency google-cloud-pubsub to v2.4.0 (#191) * chore(deps): update dependency google-cloud-storage to v1.36.1 (#182) * chore(deps): update dependency google-cloud-bigquery to v2.10.0 (#180) Co-authored-by: Daniel Sanche * chore(deps): update dependency google-cloud-storage to v1.36.2 (#215) * chore(deps): update dependency google-cloud-bigquery to v2.11.0 (#212) * chore(deps): update dependency google-cloud-logging to v2.3.0 (#221) * chore(deps): update dependency google-cloud-bigquery to v2.12.0 (#224) * chore(deps): update dependency google-cloud-logging to v2.3.1 (#242) * chore(deps): update dependency google-cloud-bigquery to v2.13.1 (#232) Co-authored-by: Daniel Sanche * chore(deps): update dependency google-cloud-pubsub to v2.4.1 (#246) * chore(deps): update dependency google-cloud-storage to v1.37.0 (#243) Co-authored-by: Daniel Sanche * chore(deps): update dependency google-cloud-storage to v1.37.1 (#255) * chore: migrate to owl bot (#270) * chore: migrate to owl bot * chore: copy files from googleapis-gen 130ce904e5d546c312943d10f48799590f9c0f66 * chore: run the post processor * 🦉 Updates from OwlBot Co-authored-by: Owl Bot * chore(deps): update dependency google-cloud-bigquery to v2.14.0 (#271) * chore(deps): update dependency google-cloud-bigquery to v2.15.0 (#277) * chore(deps): update dependency pytest to v6.2.3 (#273) * chore(deps): update dependency google-cloud-storage to v1.38.0 (#272) Co-authored-by: Daniel Sanche * chore(deps): update dependency pytest to v6.2.4 (#280) * chore(deps): update dependency google-cloud-bigquery to v2.16.0 (#282) * chore(deps): update dependency google-cloud-pubsub to v2.4.2 (#288) * chore(deps): update dependency google-cloud-storage to v1.37.0 (#243) Co-authored-by: Daniel Sanche * chore(deps): update dependency google-cloud-storage to v1.37.1 (#255) * chore: migrate to owl bot (#270) * chore: migrate to owl bot * chore: copy files from googleapis-gen 130ce904e5d546c312943d10f48799590f9c0f66 * chore: run the post processor * 🦉 Updates from OwlBot Co-authored-by: Owl Bot * chore(deps): update dependency google-cloud-bigquery to v2.14.0 (#271) * chore(deps): update dependency google-cloud-bigquery to v2.15.0 (#277) * chore(deps): update dependency pytest to v6.2.3 (#273) * chore(deps): update dependency google-cloud-storage to v1.38.0 (#272) Co-authored-by: Daniel Sanche * chore(deps): update dependency pytest to v6.2.4 (#280) * chore(deps): update dependency google-cloud-bigquery to v2.16.0 (#282) * chore(deps): update dependency google-cloud-pubsub to v2.4.2 (#288) * chore(deps): update dependency google-cloud-logging to v2.4.0 (#296) * chore(deps): update dependency google-cloud-bigquery to v2.16.1 (#297) * chore(deps): update dependency google-cloud-pubsub to v2.5.0 (#303) * chore: new owl bot post processor docker image (#308) gcr.io/repo-automation-bots/owlbot-python:latest@sha256:3c3a445b3ddc99ccd5d31edc4b4519729635d20693900db32c4f587ed51f7479 * chore(deps): update dependency google-cloud-bigquery to v2.17.0 (#306) * chore(deps): update dependency google-cloud-bigquery to v2.18.0 (#312) * chore(deps): update dependency google-cloud-bigquery to v2.20.0 (#317) * test: clean up extra topics from snippet tests (#324) * added code to remove extra topics in snippet tests * fixed lint issue * chore(deps): update dependency google-cloud-logging to v2.5.0 (#326) [![WhiteSource Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [google-cloud-logging](https://togithub.com/googleapis/python-logging) | `==2.4.0` -> `==2.5.0` | [![age](https://badges.renovateapi.com/packages/pypi/google-cloud-logging/2.5.0/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/pypi/google-cloud-logging/2.5.0/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/pypi/google-cloud-logging/2.5.0/compatibility-slim/2.4.0)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/pypi/google-cloud-logging/2.5.0/confidence-slim/2.4.0)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
      googleapis/python-logging ### [`v2.5.0`](https://togithub.com/googleapis/python-logging/blob/master/CHANGELOG.md#​250-httpswwwgithubcomgoogleapispython-loggingcomparev240v250-2021-06-10) [Compare Source](https://togithub.com/googleapis/python-logging/compare/v2.4.0...v2.5.0) ##### Features - support AuditLog and RequestLog protos ([#​274](https://www.github.com/googleapis/python-logging/issues/274)) ([5d91be9](https://www.github.com/googleapis/python-logging/commit/5d91be9f121c364cbd53c6a9fffc4fb6ca6bd324)) ##### Bug Fixes - **deps:** add packaging requirement ([#​300](https://www.github.com/googleapis/python-logging/issues/300)) ([68c5cec](https://www.github.com/googleapis/python-logging/commit/68c5ceced3288253af8e3c6013a35fa3954b37bc)) - structured log handler formatting issues ([#​319](https://www.github.com/googleapis/python-logging/issues/319)) ([db9da37](https://www.github.com/googleapis/python-logging/commit/db9da3700511b5a24c3c44c9f4377705937caf46))
      --- ### Configuration 📅 **Schedule**: At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box. --- This PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/python-logging). * chore(deps): update dependency google-cloud-storage to v1.39.0 (#335) [![WhiteSource Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [google-cloud-storage](https://togithub.com/googleapis/python-storage) | `==1.38.0` -> `==1.39.0` | [![age](https://badges.renovateapi.com/packages/pypi/google-cloud-storage/1.39.0/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/pypi/google-cloud-storage/1.39.0/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/pypi/google-cloud-storage/1.39.0/compatibility-slim/1.38.0)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/pypi/google-cloud-storage/1.39.0/confidence-slim/1.38.0)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
      googleapis/python-storage ### [`v1.39.0`](https://togithub.com/googleapis/python-storage/blob/master/CHANGELOG.md#​1390-httpswwwgithubcomgoogleapispython-storagecomparev1380v1390-2021-06-21) [Compare Source](https://togithub.com/googleapis/python-storage/compare/v1.38.0...v1.39.0) ##### Features - media operation retries can be configured using the same interface as with non-media operation ([#​447](https://www.github.com/googleapis/python-storage/issues/447)) ([0dbbb8a](https://www.github.com/googleapis/python-storage/commit/0dbbb8ac17a4b632707485ee6c7cc15e4670efaa)) ##### Bug Fixes - add ConnectionError to default retry ([#​445](https://www.github.com/googleapis/python-storage/issues/445)) ([8344253](https://www.github.com/googleapis/python-storage/commit/8344253a1969b9d04b81f87a6d7bddd3ddb55006)) - apply idempotency policies for ACLs ([#​458](https://www.github.com/googleapis/python-storage/issues/458)) ([2232f38](https://www.github.com/googleapis/python-storage/commit/2232f38933dbdfeb4f6585291794d332771ffdf2)) - replace python lifecycle action parsing ValueError with warning ([#​437](https://www.github.com/googleapis/python-storage/issues/437)) ([2532d50](https://www.github.com/googleapis/python-storage/commit/2532d506b44fc1ef0fa0a996822d29e7459c465a)) - revise blob.compose query parameters `if_generation_match` ([#​454](https://www.github.com/googleapis/python-storage/issues/454)) ([70d19e7](https://www.github.com/googleapis/python-storage/commit/70d19e72831dee112bb07f38b50beef4890c1155)) ##### Documentation - streamline 'timeout' / 'retry' docs in docstrings ([#​461](https://www.github.com/googleapis/python-storage/issues/461)) ([78b2eba](https://www.github.com/googleapis/python-storage/commit/78b2eba81003b437cd24f2b8d269ea2455682507)) - streamline docstrings for conditional parmas ([#​464](https://www.github.com/googleapis/python-storage/issues/464)) ([6999370](https://www.github.com/googleapis/python-storage/commit/69993702390322df07cc2e818003186a47524c2b))
      --- ### Configuration 📅 **Schedule**: At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box. --- This PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/python-logging). * chore(deps): update dependency google-cloud-bigquery to v2.21.0 (#349) [![WhiteSource Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [google-cloud-bigquery](https://togithub.com/googleapis/python-bigquery) | `==2.20.0` -> `==2.21.0` | [![age](https://badges.renovateapi.com/packages/pypi/google-cloud-bigquery/2.21.0/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/pypi/google-cloud-bigquery/2.21.0/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/pypi/google-cloud-bigquery/2.21.0/compatibility-slim/2.20.0)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/pypi/google-cloud-bigquery/2.21.0/confidence-slim/2.20.0)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
      googleapis/python-bigquery ### [`v2.21.0`](https://togithub.com/googleapis/python-bigquery/blob/master/CHANGELOG.md#​2210-httpswwwgithubcomgoogleapispython-bigquerycomparev2200v2210-2021-07-12) [Compare Source](https://togithub.com/googleapis/python-bigquery/compare/v2.20.0...v2.21.0) ##### Features - Add max_results parameter to some of the `QueryJob` methods. ([#​698](https://www.github.com/googleapis/python-bigquery/issues/698)) ([2a9618f](https://www.github.com/googleapis/python-bigquery/commit/2a9618f4daaa4a014161e1a2f7376844eec9e8da)) - Add support for decimal target types. ([#​735](https://www.github.com/googleapis/python-bigquery/issues/735)) ([7d2d3e9](https://www.github.com/googleapis/python-bigquery/commit/7d2d3e906a9eb161911a198fb925ad79de5df934)) - Add support for table snapshots. ([#​740](https://www.github.com/googleapis/python-bigquery/issues/740)) ([ba86b2a](https://www.github.com/googleapis/python-bigquery/commit/ba86b2a6300ae5a9f3c803beeb42bda4c522e34c)) - Enable unsetting policy tags on schema fields. ([#​703](https://www.github.com/googleapis/python-bigquery/issues/703)) ([18bb443](https://www.github.com/googleapis/python-bigquery/commit/18bb443c7acd0a75dcb57d9aebe38b2d734ff8c7)) - Make it easier to disable best-effort deduplication with streaming inserts. ([#​734](https://www.github.com/googleapis/python-bigquery/issues/734)) ([1246da8](https://www.github.com/googleapis/python-bigquery/commit/1246da86b78b03ca1aa2c45ec71649e294cfb2f1)) - Support passing struct data to the DB API. ([#​718](https://www.github.com/googleapis/python-bigquery/issues/718)) ([38b3ef9](https://www.github.com/googleapis/python-bigquery/commit/38b3ef96c3dedc139b84f0ff06885141ae7ce78c)) ##### Bug Fixes - Inserting non-finite floats with `insert_rows()`. ([#​728](https://www.github.com/googleapis/python-bigquery/issues/728)) ([d047419](https://www.github.com/googleapis/python-bigquery/commit/d047419879e807e123296da2eee89a5253050166)) - Use `pandas` function to check for `NaN`. ([#​750](https://www.github.com/googleapis/python-bigquery/issues/750)) ([67bc5fb](https://www.github.com/googleapis/python-bigquery/commit/67bc5fbd306be7cdffd216f3791d4024acfa95b3)) ##### Documentation - Add docs for all enums in module. ([#​745](https://www.github.com/googleapis/python-bigquery/issues/745)) ([145944f](https://www.github.com/googleapis/python-bigquery/commit/145944f24fedc4d739687399a8309f9d51d43dfd)) - Omit mention of Python 2.7 in `CONTRIBUTING.rst`. ([#​706](https://www.github.com/googleapis/python-bigquery/issues/706)) ([27d6839](https://www.github.com/googleapis/python-bigquery/commit/27d6839ee8a40909e4199cfa0da8b6b64705b2e9))
      --- ### Configuration 📅 **Schedule**: At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box. --- This PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/python-logging). * chore(deps): update dependency google-cloud-bigquery to v2.22.0 (#355) * feat: add Samples section to CONTRIBUTING.rst (#357) Source-Link: https://github.com/googleapis/synthtool/commit/52e4e46eff2a0b70e3ff5506a02929d089d077d4 Post-Processor: gcr.io/repo-automation-bots/owlbot-python:latest@sha256:6186535cbdbf6b9fe61f00294929221d060634dae4a0795c1cefdbc995b2d605 * chore(deps): update dependency google-cloud-pubsub to v2.6.1 (#332) * chore(deps): update dependency google-cloud-bigquery to v2.22.1 (#358) * chore(deps): update dependency backoff to v1.11.1 (#348) * chore(deps): update dependency google-cloud-storage to v1.41.1 (#344) * chore(deps): update dependency google-cloud-pubsub to v2.7.0 (#364) * chore(deps): update dependency google-cloud-bigquery to v2.23.1 (#362) * chore(deps): update dependency google-cloud-logging to v2.6.0 (#365) * chore(deps): update dependency google-cloud-bigquery to v2.23.2 (#366) * chore(deps): update dependency google-cloud-bigquery to v2.23.3 (#370) * chore: fix INSTALL_LIBRARY_FROM_SOURCE in noxfile.py (#371) Source-Link: https://github.com/googleapis/synthtool/commit/6252f2cd074c38f37b44abe5e96d128733eb1b61 Post-Processor: gcr.io/repo-automation-bots/owlbot-python:latest@sha256:50e35228649c47b6ca82aa0be3ff9eb2afce51c82b66c4a03fe4afeb5ff6c0fc * chore(deps): update dependency google-cloud-bigquery to v2.24.0 (#374) Co-authored-by: Leah E. Cole <6719667+leahecole@users.noreply.github.com> * chore(deps): update dependency google-cloud-storage to v1.42.0 (#373) * chore(deps): update dependency google-cloud-bigquery to v2.24.1 (#377) [![WhiteSource Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [google-cloud-bigquery](https://togithub.com/googleapis/python-bigquery) | `==2.24.0` -> `==2.24.1` | [![age](https://badges.renovateapi.com/packages/pypi/google-cloud-bigquery/2.24.1/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/pypi/google-cloud-bigquery/2.24.1/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/pypi/google-cloud-bigquery/2.24.1/compatibility-slim/2.24.0)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/pypi/google-cloud-bigquery/2.24.1/confidence-slim/2.24.0)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
      googleapis/python-bigquery ### [`v2.24.1`](https://togithub.com/googleapis/python-bigquery/blob/master/CHANGELOG.md#​2241-httpswwwgithubcomgoogleapispython-bigquerycomparev2240v2241-2021-08-13) [Compare Source](https://togithub.com/googleapis/python-bigquery/compare/v2.24.0...v2.24.1)
      --- ### Configuration 📅 **Schedule**: At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Renovate will not automatically rebase this PR, because other commits have been found. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box. --- This PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/python-logging). * chore(deps): update dependency google-cloud-bigquery to v2.25.0 (#379) * chore(deps): update dependency google-cloud-bigquery to v2.25.1 (#381) * chore(deps): update dependency pytest to v6.2.5 (#383) Co-authored-by: Anthonios Partheniou * chore(deps): update dependency google-cloud-bigquery to v2.25.2 (#386) * chore(deps): update dependency google-cloud-bigquery to v2.26.0 (#388) * chore(deps): update dependency google-cloud-pubsub to v2.8.0 (#391) * chore(deps): update dependency google-cloud-storage to v1.42.1 (#393) * chore(deps): update dependency google-cloud-storage to v1.42.2 (#398) * chore(python): fix formatting issue in noxfile.py.j2 (#417) fix: improper types in pagers generation fix: add 'dict' annotation type to 'request' fix(deps): require proto-plus==1.15.0 * chore(deps): update all dependencies (#418) * chore(docs): remove get_default_handler call from setup docs (#421) * chore(docs): added audit log sample to usage guide (#428) * chore(deps): update all dependencies (#436) * chore(deps): update dependency google-cloud-bigquery to v2.30.0 (#437) * chore(deps): update all dependencies (#438) * chore(deps): update all dependencies * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Owl Bot * chore(deps): update dependency google-cloud-pubsub to v2.9.0 (#441) * chore(deps): update dependency google-cloud-storage to v1.43.0 (#449) * chore(deps): update dependency google-cloud-bigquery to v2.31.0 (#451) * chore: use python-samples-reviewers (#461) * chore(samples): Add check for tests in directory (#463) Source-Link: https://github.com/googleapis/synthtool/commit/52aef91f8d25223d9dbdb4aebd94ba8eea2101f3 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:36a95b8f494e4674dc9eee9af98961293b51b86b3649942aac800ae6c1f796d4 Co-authored-by: Owl Bot * chore(deps): update dependency google-cloud-storage to v1.44.0 (#460) Co-authored-by: Anthonios Partheniou * chore(deps): update all dependencies (#466) * chore(python): Noxfile recognizes that tests can live in a folder (#468) Source-Link: https://github.com/googleapis/synthtool/commit/4760d8dce1351d93658cb11d02a1b7ceb23ae5d7 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:f0e4b51deef56bed74d3e2359c583fc104a8d6367da3984fc5c66938db738828 Co-authored-by: Owl Bot * docs: update usage guide for v3.0.0 (#456) * chore(deps): update dependency google-cloud-storage to v2.1.0 (#469) * chore(deps): update all dependencies (#477) * chore(deps): update dependency pytest to v7.0.1 (#490) * chore(deps): update all dependencies (#492) [![WhiteSource Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [google-cloud-bigquery](https://togithub.com/googleapis/python-bigquery) | `==2.32.0` -> `==2.34.2` | [![age](https://badges.renovateapi.com/packages/pypi/google-cloud-bigquery/2.34.2/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/pypi/google-cloud-bigquery/2.34.2/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/pypi/google-cloud-bigquery/2.34.2/compatibility-slim/2.32.0)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/pypi/google-cloud-bigquery/2.34.2/confidence-slim/2.32.0)](https://docs.renovatebot.com/merge-confidence/) | | [google-cloud-pubsub](https://togithub.com/googleapis/python-pubsub) | `==2.9.0` -> `==2.10.0` | [![age](https://badges.renovateapi.com/packages/pypi/google-cloud-pubsub/2.10.0/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/pypi/google-cloud-pubsub/2.10.0/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/pypi/google-cloud-pubsub/2.10.0/compatibility-slim/2.9.0)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/pypi/google-cloud-pubsub/2.10.0/confidence-slim/2.9.0)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
      googleapis/python-bigquery ### [`v2.34.2`](https://togithub.com/googleapis/python-bigquery/blob/HEAD/CHANGELOG.md#​2342-httpsgithubcomgoogleapispython-bigquerycomparev2341v2342-2022-03-05) [Compare Source](https://togithub.com/googleapis/python-bigquery/compare/v2.34.1...v2.34.2) ### [`v2.34.1`](https://togithub.com/googleapis/python-bigquery/blob/HEAD/CHANGELOG.md#​2341-httpsgithubcomgoogleapispython-bigquerycomparev2340v2341-2022-03-02) [Compare Source](https://togithub.com/googleapis/python-bigquery/compare/v2.34.0...v2.34.1) ### [`v2.34.0`](https://togithub.com/googleapis/python-bigquery/blob/HEAD/CHANGELOG.md#​2340-httpsgithubcomgoogleapispython-bigquerycomparev2330v2340-2022-02-18) [Compare Source](https://togithub.com/googleapis/python-bigquery/compare/v2.33.0...v2.34.0) ##### Features - support BI Engine statistics in query job ([#​1144](https://togithub.com/googleapis/python-bigquery/issues/1144)) ([7482549](https://togithub.com/googleapis/python-bigquery/commit/7482549cb42ed5302634ab4fb7b4efcd97b35c68)) ### [`v2.33.0`](https://togithub.com/googleapis/python-bigquery/blob/HEAD/CHANGELOG.md#​2330-httpsgithubcomgoogleapispython-bigquerycomparev2320v2330-2022-02-16) [Compare Source](https://togithub.com/googleapis/python-bigquery/compare/v2.32.0...v2.33.0) ##### Features - add `--no_query_cache` option to `%%bigquery` magics to disable query cache ([#​1141](https://togithub.com/googleapis/python-bigquery/issues/1141)) ([7dd30af](https://togithub.com/googleapis/python-bigquery/commit/7dd30af41b8a595b96176c964ba14aa41645ef0d)) ##### Bug Fixes - return 403 when VPC-SC violation happens ([#​1131](https://togithub.com/googleapis/python-bigquery/issues/1131)) ([f5daa9b](https://togithub.com/googleapis/python-bigquery/commit/f5daa9b41377a58cb3220bb2ab7c72adc6462196)) ##### Documentation - reference BigQuery REST API defaults in `LoadJobConfig` descrip… ([#​1132](https://togithub.com/googleapis/python-bigquery/issues/1132)) ([18d9580](https://togithub.com/googleapis/python-bigquery/commit/18d958062721d6be81e7bd7a5bd66f277344a864)) - show common job properties in `get_job` and `cancel_job` samples ([#​1137](https://togithub.com/googleapis/python-bigquery/issues/1137)) ([8edc10d](https://togithub.com/googleapis/python-bigquery/commit/8edc10d019bd96defebc4f92a47774901e9b956f))
      googleapis/python-pubsub ### [`v2.10.0`](https://togithub.com/googleapis/python-pubsub/blob/HEAD/CHANGELOG.md#​2100-httpsgithubcomgoogleapispython-pubsubcomparev290v2100-2022-03-04) [Compare Source](https://togithub.com/googleapis/python-pubsub/compare/v2.9.0...v2.10.0) ##### Features - add api key support ([#​571](https://togithub.com/googleapis/python-pubsub/issues/571)) ([cdda762](https://togithub.com/googleapis/python-pubsub/commit/cdda762f6d15d96f5e2d7fac975f3494dc49eaa9)) - add exactly once delivery flag ([#​577](https://togithub.com/googleapis/python-pubsub/issues/577)) ([d6614e2](https://togithub.com/googleapis/python-pubsub/commit/d6614e274328c58449e67dfc788e2e7986c0c10b)) - add support for exactly once delivery ([#​578](https://togithub.com/googleapis/python-pubsub/issues/578)) ([95a86fa](https://togithub.com/googleapis/python-pubsub/commit/95a86fa5f528701b760064f0cece0efa4e60cd44)) - exactly-once delivery support ([#​550](https://togithub.com/googleapis/python-pubsub/issues/550)) ([2fb6e15](https://togithub.com/googleapis/python-pubsub/commit/2fb6e1533192ae81dceee5c71283169a0a85a015)) ##### Bug Fixes - **deps:** move libcst to extras ([#​585](https://togithub.com/googleapis/python-pubsub/issues/585)) ([0846762](https://togithub.com/googleapis/python-pubsub/commit/084676243ca4afd54cda601e589b80883f9703a3)) - refactor client classes for safer type checking ([#​552](https://togithub.com/googleapis/python-pubsub/issues/552)) ([7f705be](https://togithub.com/googleapis/python-pubsub/commit/7f705beb927383f14b9d56f0341ee0de101f7c05)) - resolve DuplicateCredentialArgs error when using credentials_file ([8ca8cf2](https://togithub.com/googleapis/python-pubsub/commit/8ca8cf27333baf823a1dffd081e63079f1a12625)) ##### Samples - samples: create subscription with filtering enabled [#​580](https://togithub.com/googleapis/python-pubsub/pull/580) - samples: handle empty response in sync pull samples [#​586](https://togithub.com/googleapis/python-pubsub/pull/586) - samples: sample for receiving messages with exactly-once delivery enabled [#​588](https://togithub.com/googleapis/python-pubsub/pull/588) - samples: create subscription with exactly once delivery [#​592](https://togithub.com/googleapis/python-pubsub/pull/592) [https://github.com/googleapis/python-pubsub/pull/588](https://togithub.com/googleapis/python-pubsub/pull/588)/588 ##### Documentation - add autogenerated code snippets ([aa3754c](https://togithub.com/googleapis/python-pubsub/commit/aa3754cf432bd02be2734a23a32d5b36cd216aee)) - Docs have inconsistent default values for max_latency and max_bytes ([#​572](https://togithub.com/googleapis/python-pubsub/issues/572)) ([d136dfd](https://togithub.com/googleapis/python-pubsub/commit/d136dfdb69ebeebd1411a1415f863b94d07078f0))
      --- ### Configuration 📅 **Schedule**: At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://togithub.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] If you want to rebase/retry this PR, click this checkbox. --- This PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/python-logging). * chore: Adding support for pytest-xdist and pytest-parallel (#496) Source-Link: https://github.com/googleapis/synthtool/commit/38e11ad1104dcc1e63b52691ddf2fe4015d06955 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:4e1991042fe54b991db9ca17c8fb386e61b22fe4d1472a568bf0fcac85dcf5d3 * chore(deps): update dependency google-cloud-pubsub to v2.11.0 (#499) [![WhiteSource Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [google-cloud-pubsub](https://togithub.com/googleapis/python-pubsub) | `==2.10.0` -> `==2.11.0` | [![age](https://badges.renovateapi.com/packages/pypi/google-cloud-pubsub/2.11.0/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/pypi/google-cloud-pubsub/2.11.0/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/pypi/google-cloud-pubsub/2.11.0/compatibility-slim/2.10.0)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/pypi/google-cloud-pubsub/2.11.0/confidence-slim/2.10.0)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
      googleapis/python-pubsub ### [`v2.11.0`](https://togithub.com/googleapis/python-pubsub/blob/HEAD/CHANGELOG.md#​2110-httpsgithubcomgoogleapispython-pubsubcomparev2100v2110-2022-03-09) [Compare Source](https://togithub.com/googleapis/python-pubsub/compare/v2.10.0...v2.11.0) ##### Features - retry temporary GRPC statuses for ack/modack/nack when exactly-once delivery is enabled ([#​607](https://togithub.com/googleapis/python-pubsub/issues/607)) ([a91bed8](https://togithub.com/googleapis/python-pubsub/commit/a91bed829c9040fcc6c1e70b99b66188ac4ded40)) - return singleton success future for exactly-once methods in Message ([#​608](https://togithub.com/googleapis/python-pubsub/issues/608)) ([253ced2](https://togithub.com/googleapis/python-pubsub/commit/253ced28f308450c7a1a93cc38f6d101ecd7d4c0)) ##### Bug Fixes - **deps:** require google-api-core>=1.31.5, >=2.3.2 ([#​600](https://togithub.com/googleapis/python-pubsub/issues/600)) ([1608b7f](https://togithub.com/googleapis/python-pubsub/commit/1608b7ffdd5b5db87e1e55fde763440ca9a4086e)) - **deps:** require proto-plus>=1.15.0 ([1608b7f](https://togithub.com/googleapis/python-pubsub/commit/1608b7ffdd5b5db87e1e55fde763440ca9a4086e))
      --- ### Configuration 📅 **Schedule**: At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, click this checkbox. --- This PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/python-logging). * chore(deps): update all dependencies (#502) [![WhiteSource Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [google-cloud-storage](https://togithub.com/googleapis/python-storage) | `==2.1.0` -> `==2.2.0` | [![age](https://badges.renovateapi.com/packages/pypi/google-cloud-storage/2.2.0/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/pypi/google-cloud-storage/2.2.0/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/pypi/google-cloud-storage/2.2.0/compatibility-slim/2.1.0)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/pypi/google-cloud-storage/2.2.0/confidence-slim/2.1.0)](https://docs.renovatebot.com/merge-confidence/) | | [pytest](https://docs.pytest.org/en/latest/) ([source](https://togithub.com/pytest-dev/pytest), [changelog](https://docs.pytest.org/en/stable/changelog.html)) | `==7.0.1` -> `==7.1.0` | [![age](https://badges.renovateapi.com/packages/pypi/pytest/7.1.0/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/pypi/pytest/7.1.0/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/pypi/pytest/7.1.0/compatibility-slim/7.0.1)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/pypi/pytest/7.1.0/confidence-slim/7.0.1)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
      googleapis/python-storage ### [`v2.2.0`](https://togithub.com/googleapis/python-storage/blob/HEAD/CHANGELOG.md#​220-httpsgithubcomgoogleapispython-storagecomparev210v220-2022-03-14) [Compare Source](https://togithub.com/googleapis/python-storage/compare/v2.1.0...v2.2.0) ##### Features - allow no project in client methods using storage emulator ([#​703](https://togithub.com/googleapis/python-storage/issues/703)) ([bcde0ec](https://togithub.com/googleapis/python-storage/commit/bcde0ec619d7d303892bcc0863b7f977c79f7649)) ##### Bug Fixes - add user agent in python-storage when calling resumable media ([c7bf615](https://togithub.com/googleapis/python-storage/commit/c7bf615909a04f3bab3efb1047a9f4ba659bba19)) - **deps:** require google-api-core>=1.31.5, >=2.3.2 ([#​722](https://togithub.com/googleapis/python-storage/issues/722)) ([e9aab38](https://togithub.com/googleapis/python-storage/commit/e9aab389f868799d4425133954bad4f1cbb85786)) - Fix BlobReader handling of interleaved reads and seeks ([#​721](https://togithub.com/googleapis/python-storage/issues/721)) ([5d1cfd2](https://togithub.com/googleapis/python-storage/commit/5d1cfd2050321481a3bc4acbe80537ea666506fa)) - retry client side requests timeout ([#​727](https://togithub.com/googleapis/python-storage/issues/727)) ([e0b3b35](https://togithub.com/googleapis/python-storage/commit/e0b3b354d51e4be7c563d7f2f628a7139df842c0)) ##### Documentation - fixed download_blob_to_file example ([#​704](https://togithub.com/googleapis/python-storage/issues/704)) ([2c94d98](https://togithub.com/googleapis/python-storage/commit/2c94d98ed21cc768cfa54fac3d734254fc4d8480))
      pytest-dev/pytest ### [`v7.1.0`](https://togithub.com/pytest-dev/pytest/releases/7.1.0) [Compare Source](https://togithub.com/pytest-dev/pytest/compare/7.0.1...7.1.0) # pytest 7.1.0 (2022-03-13) ## Breaking Changes - [#​8838](https://togithub.com/pytest-dev/pytest/issues/8838): As per our policy, the following features have been deprecated in the 6.X series and are now removed: - `pytest._fillfuncargs` function. - `pytest_warning_captured` hook - use `pytest_warning_recorded` instead. - `-k -foobar` syntax - use `-k 'not foobar'` instead. - `-k foobar:` syntax. - `pytest.collect` module - import from `pytest` directly. For more information consult [Deprecations and Removals](https://docs.pytest.org/en/latest/deprecations.html) in the docs. - [#​9437](https://togithub.com/pytest-dev/pytest/issues/9437): Dropped support for Python 3.6, which reached [end-of-life](https://devguide.python.org/#status-of-python-branches) at 2021-12-23. ## Improvements - [#​5192](https://togithub.com/pytest-dev/pytest/issues/5192): Fixed test output for some data types where `-v` would show less information. Also, when showing diffs for sequences, `-q` would produce full diffs instead of the expected diff. - [#​9362](https://togithub.com/pytest-dev/pytest/issues/9362): pytest now avoids specialized assert formatting when it is detected that the default `__eq__` is overridden in `attrs` or `dataclasses`. - [#​9536](https://togithub.com/pytest-dev/pytest/issues/9536): When `-vv` is given on command line, show skipping and xfail reasons in full instead of truncating them to fit the terminal width. - [#​9644](https://togithub.com/pytest-dev/pytest/issues/9644): More information about the location of resources that led Python to raise `ResourceWarning`{.interpreted-text role="class"} can now be obtained by enabling `tracemalloc`{.interpreted-text role="mod"}. See `resource-warnings`{.interpreted-text role="ref"} for more information. - [#​9678](https://togithub.com/pytest-dev/pytest/issues/9678): More types are now accepted in the `ids` argument to `@pytest.mark.parametrize`. Previously only \[str]{.title-ref}, \[float]{.title-ref}, \[int]{.title-ref} and \[bool]{.title-ref} were accepted; now \[bytes]{.title-ref}, \[complex]{.title-ref}, \[re.Pattern]{.title-ref}, \[Enum]{.title-ref} and anything with a \[\__name\_\_]{.title-ref} are also accepted. - [#​9692](https://togithub.com/pytest-dev/pytest/issues/9692): `pytest.approx`{.interpreted-text role="func"} now raises a `TypeError`{.interpreted-text role="class"} when given an unordered sequence (such as `set`{.interpreted-text role="class"}). Note that this implies that custom classes which only implement `__iter__` and `__len__` are no longer supported as they don't guarantee order. ## Bug Fixes - [#​8242](https://togithub.com/pytest-dev/pytest/issues/8242): The deprecation of raising `unittest.SkipTest`{.interpreted-text role="class"} to skip collection of tests during the pytest collection phase is reverted - this is now a supported feature again. - [#​9493](https://togithub.com/pytest-dev/pytest/issues/9493): Symbolic link components are no longer resolved in conftest paths. This means that if a conftest appears twice in collection tree, using symlinks, it will be executed twice. For example, given > tests/real/conftest.py > tests/real/test_it.py > tests/link -> tests/real running `pytest tests` now imports the conftest twice, once as `tests/real/conftest.py` and once as `tests/link/conftest.py`. This is a fix to match a similar change made to test collection itself in pytest 6.0 (see `6523`{.interpreted-text role="pull"} for details). - [#​9626](https://togithub.com/pytest-dev/pytest/issues/9626): Fixed count of selected tests on terminal collection summary when there were errors or skipped modules. If there were errors or skipped modules on collection, pytest would mistakenly subtract those from the selected count. - [#​9645](https://togithub.com/pytest-dev/pytest/issues/9645): Fixed regression where `--import-mode=importlib` used together with `PYTHONPATH`{.interpreted-text role="envvar"} or `pythonpath`{.interpreted-text role="confval"} would cause import errors in test suites. - [#​9708](https://togithub.com/pytest-dev/pytest/issues/9708): `pytester`{.interpreted-text role="fixture"} now requests a `monkeypatch`{.interpreted-text role="fixture"} fixture instead of creating one internally. This solves some issues with tests that involve pytest environment variables. - [#​9730](https://togithub.com/pytest-dev/pytest/issues/9730): Malformed `pyproject.toml` files now produce a clearer error message.
      --- ### Configuration 📅 **Schedule**: At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Renovate will not automatically rebase this PR, because other commits have been found. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://togithub.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] If you want to rebase/retry this PR, click this checkbox. --- This PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/python-logging). * chore(deps): update dependency google-cloud-storage to v2.2.1 (#506) * chore(deps): update dependency pytest to v7.1.1 (#509) Co-authored-by: Drew Brown * chore(python): use black==22.3.0 (#513) Source-Link: https://github.com/googleapis/synthtool/commit/6fab84af09f2cf89a031fd8671d1def6b2931b11 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:7cffbc10910c3ab1b852c05114a08d374c195a81cdec1d4a67a1d129331d0bfe * chore(deps): update dependency google-cloud-bigquery to v2.34.3 (#514) * chore(deps): update dependency google-cloud-bigquery to v3 (#520) Co-authored-by: Anthonios Partheniou * chore(python): add nox session to sort python imports (#538) Source-Link: https://github.com/googleapis/synthtool/commit/1b71c10e20de7ed3f97f692f99a0e3399b67049f Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:00c9d764fd1cd56265f12a5ef4b99a0c9e87cf261018099141e2ca5158890416 Co-authored-by: Owl Bot * chore(deps): update all dependencies (#532) * chore(deps): update all dependencies * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Owl Bot Co-authored-by: Drew Brown Co-authored-by: Anthonios Partheniou * fix: require python 3.7+ (#580) * chore(python): drop python 3.6 Source-Link: https://github.com/googleapis/synthtool/commit/4f89b13af10d086458f9b379e56a614f9d6dab7b Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:e7bb19d47c13839fe8c147e50e02e8b6cf5da8edd1af8b82208cd6f66cc2829c * add api_description to .repo-metadata.json * require python 3.7+ in setup.py * revert templated README * remove python 3.6 sample configs * exclude templated README Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou * chore(deps): update all dependencies (#591) * chore(deps): update all dependencies * revert Co-authored-by: Anthonios Partheniou * chore(deps): update all dependencies (#593) * chore(deps): update all dependencies * revert Co-authored-by: Anthonios Partheniou * chore(deps): update all dependencies (#596) * chore(deps): update all dependencies * revert Co-authored-by: Anthonios Partheniou * chore(deps): update all dependencies (#599) Co-authored-by: Daniel Sanche Co-authored-by: Anthonios Partheniou * chore(deps): update dependency google-cloud-pubsub to v2.13.6 (#604) * chore(deps): update dependency google-cloud-logging to v3.2.2 (#608) * chore(deps): update dependency google-cloud-bigquery to v3.3.2 (#609) * chore(deps): update dependency pytest to v7.1.3 (#619) * chore: detect samples tests in nested directories (#628) Source-Link: https://github.com/googleapis/synthtool/commit/50db768f450a50d7c1fd62513c113c9bb96fd434 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:e09366bdf0fd9c8976592988390b24d53583dd9f002d476934da43725adbb978 * fix: mimic the pubsub test to clean up bigquery datasets (#639) * fix: mimic the pubsub test to clean up bigquery datasets * fix: add explanatory comment * chore(deps): update all dependencies (#630) * chore(deps): update dependency google-cloud-logging to v3.2.4 (#642) * chore(deps): update dependency backoff to v2.2.1 (#643) * chore(deps): update all dependencies (#647) Co-authored-by: Anthonios Partheniou * chore(deps): update dependency pytest to v7.2.0 (#655) Co-authored-by: Anthonios Partheniou * chore(deps): update all dependencies (#661) * chore(python): drop flake8-import-order in samples noxfile (#676) Source-Link: https://github.com/googleapis/synthtool/commit/6ed3a831cb9ff69ef8a504c353e098ec0192ad93 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:3abfa0f1886adaf0b83f07cb117b24a639ea1cb9cffe56d43280b977033563eb Co-authored-by: Owl Bot * chore(deps): update dependency google-cloud-logging to v3.3.0 (#678) * chore(deps): update all dependencies (#685) * fix(deps): Require google-api-core >=1.34.0, >=2.11.0 (#659) * chore: update to gapic-generator-python 1.5.0 feat: add support for `google.cloud..__version__` PiperOrigin-RevId: 484665853 Source-Link: https://github.com/googleapis/googleapis/commit/8eb249a19db926c2fbc4ecf1dc09c0e521a88b22 Source-Link: https://github.com/googleapis/googleapis-gen/commit/c8aa327b5f478865fc3fd91e3c2768e54e26ad44 Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiYzhhYTMyN2I1ZjQ3ODg2NWZjM2ZkOTFlM2MyNzY4ZTU0ZTI2YWQ0NCJ9 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * update version in gapic_version.py * feat: new APIs added to reflect updates to the filestore service - Add ENTERPRISE Tier - Add snapshot APIs: RevertInstance, ListSnapshots, CreateSnapshot, DeleteSnapshot, UpdateSnapshot - Add multi-share APIs: ListShares, GetShare, CreateShare, DeleteShare, UpdateShare - Add ConnectMode to NetworkConfig (for Private Service Access support) - New status codes (SUSPENDED/SUSPENDING, REVERTING/RESUMING) - Add SuspensionReason (for KMS related suspension) - Add new fields to Instance information: max_capacity_gb, capacity_step_size_gb, max_share_count, capacity_gb, multi_share_enabled PiperOrigin-RevId: 487492758 Source-Link: https://github.com/googleapis/googleapis/commit/5be5981f50322cf0c7388595e0f31ac5d0693469 Source-Link: https://github.com/googleapis/googleapis-gen/commit/ab0e217f560cc2c1afc11441c2eab6b6950efd2b Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiYWIwZTIxN2Y1NjBjYzJjMWFmYzExNDQxYzJlYWI2YjY5NTBlZmQyYiJ9 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * chore: Update gapic-generator-python to v1.6.1 PiperOrigin-RevId: 488036204 Source-Link: https://github.com/googleapis/googleapis/commit/08f275f5c1c0d99056e1cb68376323414459ee19 Source-Link: https://github.com/googleapis/googleapis-gen/commit/555c0945e60649e38739ae64bc45719cdf72178f Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiNTU1YzA5NDVlNjA2NDllMzg3MzlhZTY0YmM0NTcxOWNkZjcyMTc4ZiJ9 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * move version to gapic_version.py; clean up setup.py and owlbot.py * customize testing/constraints-3.7.txt * fix(deps): Require google-api-core >=1.34.0, >=2.11.0 fix: Drop usage of pkg_resources fix: Fix timeout default values docs(samples): Snippetgen should call await on the operation coroutine before calling result PiperOrigin-RevId: 493260409 Source-Link: https://github.com/googleapis/googleapis/commit/fea43879f83a8d0dacc9353b3f75f8f46d37162f Source-Link: https://github.com/googleapis/googleapis-gen/commit/387b7344c7529ee44be84e613b19a820508c612b Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiMzg3YjczNDRjNzUyOWVlNDRiZTg0ZTYxM2IxOWE4MjA1MDhjNjEyYiJ9 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * update version * restore test Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou Co-authored-by: Daniel Sanche * chore(deps): update dependency google-cloud-bigquery to v3.4.1 (#689) Co-authored-by: Anthonios Partheniou * chore(deps): update dependency google-cloud-logging to v3.4.0 (#697) * chore(python): add support for python 3.11 (#698) Source-Link: https://github.com/googleapis/synthtool/commit/7197a001ffb6d8ce7b0b9b11c280f0c536c1033a Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:c43f1d918bcf817d337aa29ff833439494a158a0831508fda4ec75dc4c0d0320 Co-authored-by: Owl Bot * chore(deps): update dependency google-cloud-pubsub to v2.13.12 (#702) * chore(deps): update dependency pytest to v7.2.1 (#706) * chore(deps): update dependency google-cloud-bigquery to v3.4.2 (#707) * chore(deps): update dependency google-cloud-pubsub to v2.14.0 (#708) Co-authored-by: Anthonios Partheniou * chore(deps): update dependency google-cloud-logging to v3.5.0 (#714) * chore(deps): update dependency google-cloud-bigquery to v3.5.0 (#719) Co-authored-by: Anthonios Partheniou * chore(deps): update dependency google-cloud-pubsub to v2.14.1 (#724) * chore(deps): update all dependencies (#726) * chore(deps): update all dependencies (#732) * chore(deps): update all dependencies (#738) * chore(deps): update dependency google-cloud-bigquery to v3.10.0 (#745) * chore(deps): update all dependencies (#747) Co-authored-by: Anthonios Partheniou * chore(deps): update dependency google-cloud-bigquery to v3.11.0 (#754) Co-authored-by: Anthonios Partheniou * chore(deps): update all dependencies (#759) Co-authored-by: Anthonios Partheniou * chore(deps): update all dependencies (#763) * chore(deps): update dependency google-cloud-bigquery to v3.11.4 (#766) Co-authored-by: Anthonios Partheniou * chore(deps): update dependency google-cloud-pubsub to v2.18.1 (#772) * chore(deps): update dependency google-cloud-pubsub to v2.18.2 (#776) * chore(deps): update dependency google-cloud-pubsub to v2.18.3 (#777) * fix: add severity to structured log write (#783) * feat: Log Analytics features of the Cloud Logging API (#746) * docs: Minor formatting chore: Update gapic-generator-python to v1.11.5 build: Update rules_python to 0.24.0 PiperOrigin-RevId: 563436317 Source-Link: https://github.com/googleapis/googleapis/commit/42fd37b18d706f6f51f52f209973b3b2c28f509a Source-Link: https://github.com/googleapis/googleapis-gen/commit/280264ca02fb9316b4237a96d0af1a2343a81a56 Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiMjgwMjY0Y2EwMmZiOTMxNmI0MjM3YTk2ZDBhZjFhMjM0M2E4MWE1NiJ9 chore: Update gapic-generator-python to v1.11.2 PiperOrigin-RevId: 546510849 Source-Link: https://github.com/googleapis/googleapis/commit/736073ad9a9763a170eceaaa54519bcc0ea55a5e Source-Link: https://github.com/googleapis/googleapis-gen/commit/deb64e8ec19d141e31089fe932b3a997ad541c4d Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiZGViNjRlOGVjMTlkMTQxZTMxMDg5ZmU5MzJiM2E5OTdhZDU0MWM0ZCJ9 fix: Add async context manager return types chore: Mock return_value should not populate oneof message fields chore: Support snippet generation for services that only support REST transport chore: Update gapic-generator-python to v1.11.0 PiperOrigin-RevId: 545430278 Source-Link: https://github.com/googleapis/googleapis/commit/601b5326107eeb74800b426d1f9933faa233258a Source-Link: https://github.com/googleapis/googleapis-gen/commit/b3f18d0f6560a855022fd058865e7620479d7af9 Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiYjNmMThkMGY2NTYwYTg1NTAyMmZkMDU4ODY1ZTc2MjA0NzlkN2FmOSJ9 feat: Log Analytics features of the Cloud Logging API feat: Add ConfigServiceV2.CreateBucketAsync method for creating Log Buckets asynchronously feat: Add ConfigServiceV2.UpdateBucketAsync method for creating Log Buckets asynchronously feat: Add ConfigServiceV2.CreateLink method for creating linked datasets for Log Analytics Buckets feat: Add ConfigServiceV2.DeleteLink method for deleting linked datasets feat: Add ConfigServiceV2.ListLinks method for listing linked datasets feat: Add ConfigServiceV2.GetLink methods for describing linked datasets feat: Add LogBucket.analytics_enabled field that specifies whether Log Bucket's Analytics features are enabled feat: Add LogBucket.index_configs field that contains a list of Log Bucket's indexed fields and related configuration data docs: Documentation for the Log Analytics features of the Cloud Logging API PiperOrigin-RevId: 529851525 Source-Link: https://github.com/googleapis/googleapis/commit/1c7ee99d19adf8e444e2d73c5dd52884eab9862d Source-Link: https://github.com/googleapis/googleapis-gen/commit/4a2a3a05b91804333a1b39b635d8fe2243d4b4fd Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiNGEyYTNhMDViOTE4MDQzMzNhMWIzOWI2MzVkOGZlMjI0M2Q0YjRmZCJ9 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot * chore(deps): update all dependencies (#781) * chore(deps): update all dependencies * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: meredithslota Co-authored-by: Owl Bot * chore(deps): update dependency google-cloud-storage to v2.11.0 (#785) Co-authored-by: Daniel Sanche * chore(deps): update dependency google-cloud-logging to v3.7.0 (#787) Co-authored-by: Daniel Sanche * chore(deps): update dependency google-cloud-bigquery to v3.12.0 (#792) * chore(deps): update dependency google-cloud-logging to v3.8.0 (#793) * chore(deps): update all dependencies (#796) * feat: Add support for Python 3.12 (#813) * chore(python): Add Python 3.12 Source-Link: https://github.com/googleapis/synthtool/commit/af16e6d4672cc7b400f144de2fc3068b54ff47d2 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:bacc3af03bff793a03add584537b36b5644342931ad989e3ba1171d3bd5399f5 * add trove classifier for python 3.12 * add python 3.12, and older, as a required check --------- Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou * build: treat warnings as errors (#819) * build: treat warnings as errors * resolve warning Client.dataset is deprecated and will be removed in a future version * See https://github.com/googleapis/python-logging/issues/820 * address warning @pytest.yield_fixture is deprecated. Use @pytest.fixture instead; they are the same. * filter warnings from grpcio * revert * update comment * chore(deps): update all dependencies (#828) * chore(deps): update all dependencies (#829) * chore(deps): update dependency pytest to v7.4.4 (#834) * chore(deps): update dependency google-cloud-bigquery to v3.15.0 (#836) * chore(deps): update dependency google-cloud-bigquery to v3.16.0 (#839) Co-authored-by: Kevin Zheng <147537668+gkevinzheng@users.noreply.github.com> * chore(deps): update all dependencies (#843) * chore(deps): update all dependencies * Update requirements-test.txt for Python 3.7 compatibility --------- Co-authored-by: Kevin Zheng <147537668+gkevinzheng@users.noreply.github.com> * docs: Added documentation for Django/Flask integrations and dictConfig (#848) * docs: Added documentation for Django/Flask integrations and dictConfig * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Added product prefix to new snippet * Added client setup in sample + link to settings in documentation * Changed django links to point to `/stable/` links --------- Co-authored-by: Owl Bot * chore(deps): update all dependencies (#853) * chore(deps): update all dependencies * pin pytest for python 3.7 --------- Co-authored-by: Anthonios Partheniou * docs: Update `dictConfig` snippet (#885) * docs: Update `dictConfig` snippet to add line that applies the config * Added `import logging.config` into snippet * Update root handlers dict entry in dictConfig * Update usage_guide.py * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Remove propagate config option from loggers * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Added test for dictConfig snippet * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot * chore(deps): update all dependencies (#914) * test: Added timestamp to sink names + autodelete sinks older than 2 hours in export_test.py (#925) * chore: Running this to remove all sinks * readd try block * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * added timestamp to sink name * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Fixed regex string * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot * chore(python): update dependencies in .kokoro/docker/docs (#954) * chore(python): update dependencies in .kokoro/docker/docs Source-Link: https://github.com/googleapis/synthtool/commit/59171c8f83f3522ce186e4d110d27e772da4ba7a Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:2ed982f884312e4883e01b5ab8af8b6935f0216a5a2d82928d273081fc3be562 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * add constraints file for Python 3.13 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Remove obsolete release configs and script --------- Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou * docs: Added documentation on log_level and excluded_loggers params in setup_logging (#971) * docs: Added documentation on log_level and excluded_loggers params in setup_logging * Added product prefix to new region tags. * test: Added cleanup of old sink storage buckets (#991) * test: Added cleanup of old sink storage buckets * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * add list_buckets threshold --------- Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou * feat: Add support for python 3.14 (#1065) This PR adds python 3.14 support to the python-logging package. Expanding off of Omair's work here: https://github.com/googleapis/python-logging/pull/1055 --------- Co-authored-by: ohmayr Co-authored-by: Owl Bot Co-authored-by: Kevin Zheng Co-authored-by: Anthonios Partheniou Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Delete logging/samples/snippets/noxfile.py removing noxfile to avoid conflicts. --------- Co-authored-by: Jon Wayne Parrott Co-authored-by: DPE bot Co-authored-by: Jason Dobry Co-authored-by: Bill Prin Co-authored-by: michaelawyu Co-authored-by: Andrew Gorcester Co-authored-by: Frank Natividad Co-authored-by: Jeffrey Rennie Co-authored-by: Gus Class Co-authored-by: Kurtis Van Gent <31518063+kurtisvg@users.noreply.github.com> Co-authored-by: Doug Mahugh Co-authored-by: WhiteSource Renovate Co-authored-by: Takashi Matsuo Co-authored-by: Darren Carlton Co-authored-by: Leah Cole Co-authored-by: Bu Sun Kim Co-authored-by: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Co-authored-by: Yoshi Automation Bot Co-authored-by: Daniel Sanche Co-authored-by: Daniel Sanche Co-authored-by: Nicole Zhu <69952136+nicoleczhu@users.noreply.github.com> Co-authored-by: Anthonios Partheniou Co-authored-by: Owl Bot Co-authored-by: gcf-owl-bot[bot] <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Co-authored-by: Leah E. Cole <6719667+leahecole@users.noreply.github.com> Co-authored-by: Nicholas Lee Co-authored-by: Drew Brown Co-authored-by: minherz Co-authored-by: meredithslota Co-authored-by: Kevin Zheng <147537668+gkevinzheng@users.noreply.github.com> Co-authored-by: ohmayr Co-authored-by: Kevin Zheng Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- logging/samples/AUTHORING_GUIDE.md | 1 + logging/samples/CONTRIBUTING.md | 1 + logging/samples/snippets/README.rst.in | 28 + logging/samples/snippets/export.py | 138 +++++ logging/samples/snippets/export_test.py | 135 +++++ logging/samples/snippets/handler.py | 50 ++ logging/samples/snippets/handler_test.py | 22 + logging/samples/snippets/quickstart.py | 42 ++ logging/samples/snippets/quickstart_test.py | 22 + .../samples/snippets/requirements-test.txt | 3 + logging/samples/snippets/requirements.txt | 4 + logging/samples/snippets/snippets.py | 109 ++++ logging/samples/snippets/snippets_test.py | 56 ++ logging/samples/snippets/usage_guide.py | 564 ++++++++++++++++++ logging/samples/snippets/usage_guide_test.py | 96 +++ 15 files changed, 1271 insertions(+) create mode 100644 logging/samples/AUTHORING_GUIDE.md create mode 100644 logging/samples/CONTRIBUTING.md create mode 100644 logging/samples/snippets/README.rst.in create mode 100644 logging/samples/snippets/export.py create mode 100644 logging/samples/snippets/export_test.py create mode 100644 logging/samples/snippets/handler.py create mode 100644 logging/samples/snippets/handler_test.py create mode 100644 logging/samples/snippets/quickstart.py create mode 100644 logging/samples/snippets/quickstart_test.py create mode 100644 logging/samples/snippets/requirements-test.txt create mode 100644 logging/samples/snippets/requirements.txt create mode 100644 logging/samples/snippets/snippets.py create mode 100644 logging/samples/snippets/snippets_test.py create mode 100644 logging/samples/snippets/usage_guide.py create mode 100644 logging/samples/snippets/usage_guide_test.py diff --git a/logging/samples/AUTHORING_GUIDE.md b/logging/samples/AUTHORING_GUIDE.md new file mode 100644 index 00000000000..8249522ffc2 --- /dev/null +++ b/logging/samples/AUTHORING_GUIDE.md @@ -0,0 +1 @@ +See https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/AUTHORING_GUIDE.md \ No newline at end of file diff --git a/logging/samples/CONTRIBUTING.md b/logging/samples/CONTRIBUTING.md new file mode 100644 index 00000000000..f5fe2e6baf1 --- /dev/null +++ b/logging/samples/CONTRIBUTING.md @@ -0,0 +1 @@ +See https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/CONTRIBUTING.md \ No newline at end of file diff --git a/logging/samples/snippets/README.rst.in b/logging/samples/snippets/README.rst.in new file mode 100644 index 00000000000..ff243c1ce81 --- /dev/null +++ b/logging/samples/snippets/README.rst.in @@ -0,0 +1,28 @@ +# This file is used to generate README.rst + +product: + name: Cloud Logging + short_name: Cloud Logging + url: https://cloud.google.com/logging/docs + description: > + `Cloud Logging`_ allows you to store, search, analyze, monitor, + and alert on log data and events from Google Cloud Platform and Amazon + Web Services. + +setup: +- auth +- install_deps + +samples: +- name: Quickstart + file: quickstart.py +- name: Snippets + file: snippets.py + show_help: true +- name: Export + file: export.py + show_help: true + +cloud_client_library: true + +folder: logging/cloud-client \ No newline at end of file diff --git a/logging/samples/snippets/export.py b/logging/samples/snippets/export.py new file mode 100644 index 00000000000..9a0673ee72d --- /dev/null +++ b/logging/samples/snippets/export.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python + +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse + +from google.cloud import logging + + +# [START logging_list_sinks] +def list_sinks(): + """Lists all sinks.""" + logging_client = logging.Client() + + sinks = list(logging_client.list_sinks()) + + if not sinks: + print("No sinks.") + + for sink in sinks: + print("{}: {} -> {}".format(sink.name, sink.filter_, sink.destination)) + + +# [END logging_list_sinks] + + +# [START logging_create_sink] +def create_sink(sink_name, destination_bucket, filter_): + """Creates a sink to export logs to the given Cloud Storage bucket. + + The filter determines which logs this sink matches and will be exported + to the destination. For example a filter of 'severity>=INFO' will send + all logs that have a severity of INFO or greater to the destination. + See https://cloud.google.com/logging/docs/view/advanced_filters for more + filter information. + """ + logging_client = logging.Client() + + # The destination can be a Cloud Storage bucket, a Cloud Pub/Sub topic, + # or a BigQuery dataset. In this case, it is a Cloud Storage Bucket. + # See https://cloud.google.com/logging/docs/api/tasks/exporting-logs for + # information on the destination format. + destination = "storage.googleapis.com/{bucket}".format(bucket=destination_bucket) + + sink = logging_client.sink(sink_name, filter_=filter_, destination=destination) + + if sink.exists(): + print("Sink {} already exists.".format(sink.name)) + return + + sink.create() + print("Created sink {}".format(sink.name)) + + +# [END logging_create_sink] + + +# [START logging_update_sink] +def update_sink(sink_name, filter_): + """Changes a sink's filter. + + The filter determines which logs this sink matches and will be exported + to the destination. For example a filter of 'severity>=INFO' will send + all logs that have a severity of INFO or greater to the destination. + See https://cloud.google.com/logging/docs/view/advanced_filters for more + filter information. + """ + logging_client = logging.Client() + sink = logging_client.sink(sink_name) + + sink.reload() + + sink.filter_ = filter_ + print("Updated sink {}".format(sink.name)) + sink.update() + + +# [END logging_update_sink] + + +# [START logging_delete_sink] +def delete_sink(sink_name): + """Deletes a sink.""" + logging_client = logging.Client() + sink = logging_client.sink(sink_name) + + sink.delete() + + print("Deleted sink {}".format(sink.name)) + + +# [END logging_delete_sink] + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter + ) + + subparsers = parser.add_subparsers(dest="command") + subparsers.add_parser("list", help=list_sinks.__doc__) + + create_parser = subparsers.add_parser("create", help=list_sinks.__doc__) + create_parser.add_argument("sink_name", help="Name of the log export sink.") + create_parser.add_argument( + "destination_bucket", help="Cloud Storage bucket where logs will be exported." + ) + create_parser.add_argument("filter", help="The filter used to match logs.") + + update_parser = subparsers.add_parser("update", help=update_sink.__doc__) + update_parser.add_argument("sink_name", help="Name of the log export sink.") + update_parser.add_argument("filter", help="The filter used to match logs.") + + delete_parser = subparsers.add_parser("delete", help=delete_sink.__doc__) + delete_parser.add_argument("sink_name", help="Name of the log export sink.") + + args = parser.parse_args() + + if args.command == "list": + list_sinks() + elif args.command == "create": + create_sink(args.sink_name, args.destination_bucket, args.filter) + elif args.command == "update": + update_sink(args.sink_name, args.filter) + elif args.command == "delete": + delete_sink(args.sink_name) diff --git a/logging/samples/snippets/export_test.py b/logging/samples/snippets/export_test.py new file mode 100644 index 00000000000..c9eb18efb85 --- /dev/null +++ b/logging/samples/snippets/export_test.py @@ -0,0 +1,135 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import re +import random +import string +import time + +import backoff +from google.cloud import logging, storage +import pytest + +import export + + +BUCKET = os.environ["CLOUD_STORAGE_BUCKET"] +TEST_SINK_NAME_TMPL = "example_sink_{}_{}" +TEST_SINK_FILTER = "severity>=CRITICAL" +TIMESTAMP = int(time.time()) + +# Threshold beyond which the cleanup_old_sinks fixture will delete +# old sink, in seconds +CLEANUP_THRESHOLD = 7200 # 2 hours + +# Max buckets to delete at a time, to mitigate operation timeout +# issues. To turn off in the future, set to None. +MAX_BUCKETS = 1500 + + +def _random_id(): + return "".join( + random.choice(string.ascii_uppercase + string.digits) for _ in range(6) + ) + + +def _create_sink_name(): + return TEST_SINK_NAME_TMPL.format(TIMESTAMP, _random_id()) + + +@backoff.on_exception(backoff.expo, Exception, max_time=60, raise_on_giveup=False) +def _delete_object(obj, **kwargs): + obj.delete(**kwargs) + + +# Runs once for entire test suite +@pytest.fixture(scope="module") +def cleanup_old_sinks(): + client = logging.Client() + test_sink_name_regex = ( + r"^" + TEST_SINK_NAME_TMPL.format(r"(\d+)", r"[A-Z0-9]{6}") + r"$" + ) + for sink in client.list_sinks(): + match = re.match(test_sink_name_regex, sink.name) + if match: + sink_timestamp = int(match.group(1)) + if TIMESTAMP - sink_timestamp > CLEANUP_THRESHOLD: + _delete_object(sink) + + storage_client = storage.Client() + + # See _sink_storage_setup in usage_guide.py for details about how + # sinks are named. + test_bucket_name_regex = r"^sink\-storage\-(\d+)$" + for bucket in storage_client.list_buckets(max_results=MAX_BUCKETS): + match = re.match(test_bucket_name_regex, bucket.name) + if match: + # Bucket timestamp is int(time.time() * 1000) + bucket_timestamp = int(match.group(1)) + if TIMESTAMP - bucket_timestamp // 1000 > CLEANUP_THRESHOLD: + _delete_object(bucket, force=True) + + +@pytest.fixture +def example_sink(cleanup_old_sinks): + client = logging.Client() + + sink = client.sink( + _create_sink_name(), + filter_=TEST_SINK_FILTER, + destination="storage.googleapis.com/{bucket}".format(bucket=BUCKET), + ) + + sink.create() + + yield sink + + _delete_object(sink) + + +def test_list(example_sink, capsys): + @backoff.on_exception(backoff.expo, AssertionError, max_time=60) + def eventually_consistent_test(): + export.list_sinks() + out, _ = capsys.readouterr() + assert example_sink.name in out + + eventually_consistent_test() + + +def test_create(capsys): + sink_name = _create_sink_name() + + try: + export.create_sink(sink_name, BUCKET, TEST_SINK_FILTER) + # Clean-up the temporary sink. + finally: + _delete_object(logging.Client().sink(sink_name)) + + out, _ = capsys.readouterr() + assert sink_name in out + + +def test_update(example_sink, capsys): + updated_filter = "severity>=INFO" + export.update_sink(example_sink.name, updated_filter) + + example_sink.reload() + assert example_sink.filter_ == updated_filter + + +def test_delete(example_sink, capsys): + export.delete_sink(example_sink.name) + assert not example_sink.exists() diff --git a/logging/samples/snippets/handler.py b/logging/samples/snippets/handler.py new file mode 100644 index 00000000000..49d2578984f --- /dev/null +++ b/logging/samples/snippets/handler.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python + +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def use_logging_handler(): + # [START logging_stdlogging] + # [START logging_handler_setup] + # Imports the Cloud Logging client library + import google.cloud.logging + + # Instantiates a client + client = google.cloud.logging.Client() + + # Retrieves a Cloud Logging handler based on the environment + # you're running in and integrates the handler with the + # Python logging module. By default this captures all logs + # at INFO level and higher + client.setup_logging() + # [END logging_handler_setup] + + # [START logging_handler_usage] + # Imports Python standard library logging + import logging + + # The data to log + text = "Hello, world!" + + # Emits the data using the standard logging module + logging.warning(text) + # [END logging_handler_usage] + + print("Logged: {}".format(text)) + # [END logging_stdlogging] + + +if __name__ == "__main__": + use_logging_handler() diff --git a/logging/samples/snippets/handler_test.py b/logging/samples/snippets/handler_test.py new file mode 100644 index 00000000000..9d635806ae1 --- /dev/null +++ b/logging/samples/snippets/handler_test.py @@ -0,0 +1,22 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import handler + + +def test_handler(capsys): + handler.use_logging_handler() + out, _ = capsys.readouterr() + assert "Logged" in out diff --git a/logging/samples/snippets/quickstart.py b/logging/samples/snippets/quickstart.py new file mode 100644 index 00000000000..7c38ea6fa82 --- /dev/null +++ b/logging/samples/snippets/quickstart.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python + +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def run_quickstart(): + # [START logging_quickstart] + # Imports the Google Cloud client library + from google.cloud import logging + + # Instantiates a client + logging_client = logging.Client() + + # The name of the log to write to + log_name = "my-log" + # Selects the log to write to + logger = logging_client.logger(log_name) + + # The data to log + text = "Hello, world!" + + # Writes the log entry + logger.log_text(text) + + print("Logged: {}".format(text)) + # [END logging_quickstart] + + +if __name__ == "__main__": + run_quickstart() diff --git a/logging/samples/snippets/quickstart_test.py b/logging/samples/snippets/quickstart_test.py new file mode 100644 index 00000000000..d8ace2cbcf3 --- /dev/null +++ b/logging/samples/snippets/quickstart_test.py @@ -0,0 +1,22 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import quickstart + + +def test_quickstart(capsys): + quickstart.run_quickstart() + out, _ = capsys.readouterr() + assert "Logged" in out diff --git a/logging/samples/snippets/requirements-test.txt b/logging/samples/snippets/requirements-test.txt new file mode 100644 index 00000000000..37eb1f9aa7a --- /dev/null +++ b/logging/samples/snippets/requirements-test.txt @@ -0,0 +1,3 @@ +backoff==2.2.1 +pytest===7.4.4; python_version == '3.7' +pytest==8.2.2; python_version >= '3.8' diff --git a/logging/samples/snippets/requirements.txt b/logging/samples/snippets/requirements.txt new file mode 100644 index 00000000000..8a52ee5c680 --- /dev/null +++ b/logging/samples/snippets/requirements.txt @@ -0,0 +1,4 @@ +google-cloud-logging==3.10.0 +google-cloud-bigquery==3.25.0 +google-cloud-storage==2.17.0 +google-cloud-pubsub==2.22.0 diff --git a/logging/samples/snippets/snippets.py b/logging/samples/snippets/snippets.py new file mode 100644 index 00000000000..736311e0f44 --- /dev/null +++ b/logging/samples/snippets/snippets.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python + +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This application demonstrates how to perform basic operations on logs and +log entries with Cloud Logging. + +For more information, see the README.md under /logging and the +documentation at https://cloud.google.com/logging/docs. +""" + +import argparse + +from google.cloud import logging + + +# [START logging_write_log_entry] +def write_entry(logger_name): + """Writes log entries to the given logger.""" + logging_client = logging.Client() + + # This log can be found in the Cloud Logging console under 'Custom Logs'. + logger = logging_client.logger(logger_name) + + # Make a simple text log + logger.log_text("Hello, world!") + + # Simple text log with severity. + logger.log_text("Goodbye, world!", severity="WARNING") + + # Struct log. The struct can be any JSON-serializable dictionary. + logger.log_struct( + { + "name": "King Arthur", + "quest": "Find the Holy Grail", + "favorite_color": "Blue", + }, + severity="INFO", + ) + + print("Wrote logs to {}.".format(logger.name)) + + +# [END logging_write_log_entry] + + +# [START logging_list_log_entries] +def list_entries(logger_name): + """Lists the most recent entries for a given logger.""" + logging_client = logging.Client() + logger = logging_client.logger(logger_name) + + print("Listing entries for logger {}:".format(logger.name)) + + for entry in logger.list_entries(): + timestamp = entry.timestamp.isoformat() + print("* {}: {}".format(timestamp, entry.payload)) + + +# [END logging_list_log_entries] + + +# [START logging_delete_log] +def delete_logger(logger_name): + """Deletes a logger and all its entries. + + Note that a deletion can take several minutes to take effect. + """ + logging_client = logging.Client() + logger = logging_client.logger(logger_name) + + logger.delete() + + print("Deleted all logging entries for {}".format(logger.name)) + + +# [END logging_delete_log] + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter + ) + parser.add_argument("logger_name", help="Logger name", default="example_log") + subparsers = parser.add_subparsers(dest="command") + subparsers.add_parser("list", help=list_entries.__doc__) + subparsers.add_parser("write", help=write_entry.__doc__) + subparsers.add_parser("delete", help=delete_logger.__doc__) + + args = parser.parse_args() + + if args.command == "list": + list_entries(args.logger_name) + elif args.command == "write": + write_entry(args.logger_name) + elif args.command == "delete": + delete_logger(args.logger_name) diff --git a/logging/samples/snippets/snippets_test.py b/logging/samples/snippets/snippets_test.py new file mode 100644 index 00000000000..479f742ae5c --- /dev/null +++ b/logging/samples/snippets/snippets_test.py @@ -0,0 +1,56 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import uuid + +import backoff +from google.api_core.exceptions import NotFound +from google.cloud import logging +import pytest + +import snippets + + +TEST_LOGGER_NAME = "example_log_{}".format(uuid.uuid4().hex) + + +@pytest.fixture +def example_log(): + client = logging.Client() + logger = client.logger(TEST_LOGGER_NAME) + text = "Hello, world." + logger.log_text(text) + return text + + +def test_list(example_log, capsys): + @backoff.on_exception(backoff.expo, AssertionError, max_time=120) + def eventually_consistent_test(): + snippets.list_entries(TEST_LOGGER_NAME) + out, _ = capsys.readouterr() + assert example_log in out + + eventually_consistent_test() + + +def test_write(): + snippets.write_entry(TEST_LOGGER_NAME) + + +def test_delete(example_log, capsys): + @backoff.on_exception(backoff.expo, NotFound, max_time=120) + def eventually_consistent_test(): + snippets.delete_logger(TEST_LOGGER_NAME) + out, _ = capsys.readouterr() + assert TEST_LOGGER_NAME in out diff --git a/logging/samples/snippets/usage_guide.py b/logging/samples/snippets/usage_guide.py new file mode 100644 index 00000000000..6dee33798b1 --- /dev/null +++ b/logging/samples/snippets/usage_guide.py @@ -0,0 +1,564 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Samples embedded in the Usage Guide (docs/usage.rst) + +Each example function takes a ``client`` argument (which must be an instance +of :class:`google.cloud.logging.client.Client`) and uses it to perform a task +with the API. + +To facilitate running the examples as system tests, each example is also passed +a ``to_delete`` list; the function adds to the list any objects created which +need to be deleted during teardown. +""" + +import os +import time + +from google.cloud.logging import Client + + +def snippet(func): + """Mark ``func`` as a snippet example function.""" + func._snippet = True + return func + + +def _millis(): + return time.time() * 1000 + + +def do_something_with(item): # pylint: disable=unused-argument + pass + + +# pylint: enable=reimported,unused-variable,unused-argument + + +@snippet +def client_list_entries(client, to_delete): # pylint: disable=unused-argument + """List entries via client.""" + + # [START client_list_entries_default] + for entry in client.list_entries(): # API call(s) + do_something_with(entry) + # [END client_list_entries_default] + break + + # [START client_list_entries_filter] + filter_str = "logName:log_name AND textPayload:simple" + for entry in client.list_entries(filter_=filter_str): # API call(s) + do_something_with(entry) + # [END client_list_entries_filter] + break + + # [START client_list_entries_order_by] + from google.cloud.logging import DESCENDING + + for entry in client.list_entries(order_by=DESCENDING): # API call(s) + do_something_with(entry) + # [END client_list_entries_order_by] + break + + # [START logging_list_gke_audit_logs] + import google.cloud.logging + from datetime import datetime, timedelta, timezone + import os + + # pull your project id from an environment variable + project_id = os.environ["GOOGLE_CLOUD_PROJECT"] + # construct a date object representing yesterday + yesterday = datetime.now(timezone.utc) - timedelta(days=1) + # Cloud Logging expects a timestamp in RFC3339 UTC "Zulu" format + # https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry + time_format = "%Y-%m-%dT%H:%M:%S.%f%z" + # build a filter that returns GKE Admin Activity audit Logs from + # the past 24 hours + # https://cloud.google.com/kubernetes-engine/docs/how-to/audit-logging + filter_str = ( + f'logName="projects/{project_id}/logs/cloudaudit.googleapis.com%2Factivity"' + f' AND resource.type="k8s_cluster"' + f' AND timestamp>="{yesterday.strftime(time_format)}"' + ) + # query and print all matching logs + client = google.cloud.logging.Client() + for entry in client.list_entries(filter_=filter_str): + print(entry) + # [END logging_list_gke_audit_logs] + break # we don't really need to print them all + + +@snippet +def client_setup(client2, to_delete): + """Client setup.""" + + # [START usage_client_setup] + import google.cloud.logging + + # if project not given, it will be inferred from the environment + client = google.cloud.logging.Client(project="my-project") + # [END usage_client_setup] + to_delete.append(client) + + # [START usage_http_client_setup] + http_client = google.cloud.logging.Client(_use_grpc=False) + # [END usage_http_client_setup] + to_delete.append(http_client) + + +@snippet +def logger_usage(client_true, to_delete): + """Logger usage.""" + import google.cloud.logging + + # [START logger_create] + client = google.cloud.logging.Client(project="my-project") + logger = client.logger(name="log_id") + # logger will bind to logName "projects/my_project/logs/log_id" + # [END logger_create] + client = client_true + + log_id = "logger_usage_%d" % (_millis()) + # [START logger_custom_labels] + custom_labels = {"my-key": "my-value"} + label_logger = client.logger(log_id, labels=custom_labels) + # [END logger_custom_labels] + to_delete.append(label_logger) + # [START logger_custom_resource] + from google.cloud.logging_v2.resource import Resource + + resource = Resource(type="global", labels={}) + global_logger = client.logger(log_id, resource=resource) + # [END logger_custom_resource] + to_delete.append(global_logger) + + logger = client_true.logger(log_id) + to_delete.append(logger) + + # [START logger_log_basic] + logger.log("A simple entry") # API call + # [END logger_log_basic] + + # [START logger_log_fields] + logger.log( + "an entry with fields set", + severity="ERROR", + insert_id="0123", + labels={"my-label": "my-value"}, + ) # API call + # [END logger_log_fields] + + # [START logger_log_text] + logger.log_text("A simple entry") # API call + # [END logger_log_text] + + # [START logger_log_struct] + logger.log_struct( + {"message": "My second entry", "weather": "partly cloudy"} + ) # API call + # [END logger_log_struct] + + # [START logger_log_resource_text] + from google.cloud.logging import Resource + + res = Resource( + type="generic_node", + labels={ + "location": "us-central1-a", + "namespace": "default", + "node_id": "10.10.10.1", + }, + ) + logger.log_struct( + {"message": "My first entry", "weather": "partly cloudy"}, resource=res + ) + # [END logger_log_resource_text] + + # [START logger_log_batch] + batch = logger.batch() + batch.log("first log") + batch.log("second log") + batch.commit() + # [END logger_log_batch] + + # [START logger_log_batch_context] + with logger.batch() as batch: + batch.log("first log") + # do work + batch.log("last log") + # [END logger_log_batch_context] + + # [START logger_list_entries] + from google.cloud.logging import DESCENDING + + for entry in logger.list_entries(order_by=DESCENDING): # API call(s) + do_something_with(entry) + # [END logger_list_entries] + + def _logger_delete(): + # [START logger_delete] + logger.delete() # API call + # [END logger_delete] + + _backoff_not_found(_logger_delete) + to_delete.remove(logger) + + +@snippet +def metric_crud(client, to_delete): + """Metric CRUD.""" + metric_name = "robots-%d" % (_millis(),) + description = "Robots all up in your server" + filter = "logName:apache-access AND textPayload:robot" + updated_filter = "textPayload:robot" + updated_description = "Danger, Will Robinson!" + + # [START client_list_metrics] + for metric in client.list_metrics(): # API call(s) + do_something_with(metric) + # [END client_list_metrics] + + # [START metric_create] + metric = client.metric(metric_name, filter_=filter, description=description) + assert not metric.exists() # API call + metric.create() # API call + assert metric.exists() # API call + # [END metric_create] + to_delete.append(metric) + + # [START metric_reload] + existing_metric = client.metric(metric_name) + existing_metric.reload() # API call + # [END metric_reload] + assert existing_metric.filter_ == filter + assert existing_metric.description == description + + # [START metric_update] + existing_metric.filter_ = updated_filter + existing_metric.description = updated_description + existing_metric.update() # API call + # [END metric_update] + existing_metric.reload() + assert existing_metric.filter_ == updated_filter + assert existing_metric.description == updated_description + + def _metric_delete(): + # [START metric_delete] + metric.delete() + # [END metric_delete] + + _backoff_not_found(_metric_delete) + to_delete.remove(metric) + + +def _sink_storage_setup(client): + from google.cloud import storage + + bucket_name = "sink-storage-%d" % (_millis(),) + client = storage.Client() + bucket = client.bucket(bucket_name) + bucket.create() + + # [START sink_bucket_permissions] + bucket.acl.reload() # API call + logs_group = bucket.acl.group("cloud-logs@google.com") + logs_group.grant_owner() + bucket.acl.add_entity(logs_group) + bucket.acl.save() # API call + # [END sink_bucket_permissions] + + return bucket + + +@snippet +def sink_storage(client, to_delete): + """Sink log entries to storage.""" + bucket = _sink_storage_setup(client) + to_delete.append(bucket) + sink_name = "robots-storage-%d" % (_millis(),) + filter = "textPayload:robot" + + # [START sink_storage_create] + destination = "storage.googleapis.com/%s" % (bucket.name,) + sink = client.sink(sink_name, filter_=filter, destination=destination) + assert not sink.exists() # API call + sink.create() # API call + assert sink.exists() # API call + # [END sink_storage_create] + to_delete.insert(0, sink) # delete sink before bucket + + +def _sink_bigquery_setup(client): + from google.cloud import bigquery + + dataset_name = "sink_bigquery_%d" % (_millis(),) + client = bigquery.Client() + dataset = client.create_dataset(dataset_name) + + # [START sink_dataset_permissions] + from google.cloud.bigquery.dataset import AccessEntry + + entry_list = dataset.access_entries + entry_list.append(AccessEntry("WRITER", "groupByEmail", "cloud-logs@google.com")) + dataset.access_entries = entry_list + client.update_dataset(dataset, ["access_entries"]) # API call + # [END sink_dataset_permissions] + + # create callback wrapper to delete dataset when done + class DatasetDeleter: + def delete(self): + client.delete_dataset(dataset, delete_contents=True) + + return dataset, DatasetDeleter() + + +@snippet +def sink_bigquery(client, to_delete): + """Sink log entries to bigquery.""" + dataset, dataset_deleter = _sink_bigquery_setup(client) + to_delete.append(dataset_deleter) + sink_name = "robots-bigquery-%d" % (_millis(),) + filter_str = "textPayload:robot" + + # [START sink_bigquery_create] + destination = "bigquery.googleapis.com%s" % (dataset.path,) + sink = client.sink(sink_name, filter_=filter_str, destination=destination) + assert not sink.exists() # API call + sink.create() # API call + assert sink.exists() # API call + # [END sink_bigquery_create] + to_delete.insert(0, sink) # delete sink before dataset + + +def _sink_pubsub_setup(client): + from google.cloud import pubsub + + client = pubsub.PublisherClient() + + project_id = os.environ["GOOGLE_CLOUD_PROJECT"] + topic_id = "sink-pubsub-%d" % (_millis(),) + + # [START sink_topic_permissions] + topic_path = client.topic_path(project_id, topic_id) + topic = client.create_topic(request={"name": topic_path}) + + policy = client.get_iam_policy(request={"resource": topic_path}) # API call + policy.bindings.add(role="roles/owner", members=["group:cloud-logs@google.com"]) + + client.set_iam_policy( + request={"resource": topic_path, "policy": policy} + ) # API call + # [END sink_topic_permissions] + + # create callback wrapper to delete topic when done + class TopicDeleter: + def delete(self): + client.delete_topic(request={"topic": topic_path}) + + return topic, TopicDeleter() + + +@snippet +def sink_pubsub(client, to_delete): + """Sink log entries to pubsub.""" + topic, topic_deleter = _sink_pubsub_setup(client) + to_delete.append(topic_deleter) + sink_name = "robots-pubsub-%d" % (_millis(),) + filter_str = "logName:apache-access AND textPayload:robot" + updated_filter = "textPayload:robot" + + # [START sink_pubsub_create] + destination = "pubsub.googleapis.com/%s" % (topic.name,) + sink = client.sink(sink_name, filter_=filter_str, destination=destination) + assert not sink.exists() # API call + sink.create() # API call + assert sink.exists() # API call + # [END sink_pubsub_create] + to_delete.append(sink) + created_sink = sink + + # [START client_list_sinks] + for sink in client.list_sinks(): # API call(s) + do_something_with(sink) + # [END client_list_sinks] + + # [START sink_reload] + existing_sink = client.sink(sink_name) + existing_sink.reload() + # [END sink_reload] + assert existing_sink.filter_ == filter_str + assert existing_sink.destination == destination + + # [START sink_update] + existing_sink.filter_ = updated_filter + existing_sink.update() + # [END sink_update] + existing_sink.reload() + assert existing_sink.filter_ == updated_filter + + sink = created_sink + # [START sink_delete] + sink.delete() + # [END sink_delete] + + +@snippet +def logging_handler(client): + # [START create_default_handler] + import logging + + handler = client.get_default_handler() + cloud_logger = logging.getLogger("cloudLogger") + cloud_logger.setLevel(logging.INFO) + cloud_logger.addHandler(handler) + cloud_logger.error("bad news") + # [END create_default_handler] + + # [START create_cloud_handler] + from google.cloud.logging.handlers import CloudLoggingHandler + from google.cloud.logging_v2.handlers import setup_logging + + handler = CloudLoggingHandler(client) + setup_logging(handler) + # [END create_cloud_handler] + + # [START create_named_handler] + handler = CloudLoggingHandler(client, name="mycustomlog") + # [END create_named_handler] + + +@snippet +def logging_json(client): + # [START logging_json_dumps] + import logging + import json + + data_dict = {"hello": "world"} + logging.info(json.dumps(data_dict)) + # [END logging_json_dumps] + + # [START logging_extra_json_fields] + import logging + + data_dict = {"hello": "world"} + logging.info("message field", extra={"json_fields": data_dict}) + # [END logging_extra_json_fields] + + +@snippet +def using_extras(client): + import logging + + # [START logging_extras] + my_labels = {"foo": "bar"} + my_http = {"requestUrl": "localhost"} + my_trace = "01234" + + logging.info( + "hello", extra={"labels": my_labels, "http_request": my_http, "trace": my_trace} + ) + # [END logging_extras] + + +@snippet +def setup_logging(client): + import logging + + # [START logging_setup_logging] + client.setup_logging(log_level=logging.INFO) + # [END logging_setup_logging] + + # [START logging_setup_logging_excludes] + client.setup_logging(log_level=logging.INFO, excluded_loggers=("werkzeug",)) + # [END logging_setup_logging_excludes] + + +@snippet +def logging_dict_config(client): + # [START logging_dict_config] + import logging.config + + import google.cloud.logging + + client = google.cloud.logging.Client() + + LOGGING = { + "version": 1, + "handlers": { + "cloud_logging_handler": { + "class": "google.cloud.logging.handlers.CloudLoggingHandler", + "client": client, + }, + "structured_log_handler": { + "class": "google.cloud.logging.handlers.StructuredLogHandler" + }, + }, + "root": {"handlers": [], "level": "WARNING"}, + "loggers": { + "cloud_logger": {"handlers": ["cloud_logging_handler"], "level": "INFO"}, + "structured_logger": { + "handlers": ["structured_log_handler"], + "level": "INFO", + }, + }, + } + + logging.config.dictConfig(LOGGING) + # [END logging_dict_config] + + +def _line_no(func): + return func.__code__.co_firstlineno + + +def _find_examples(): + funcs = [obj for obj in globals().values() if getattr(obj, "_snippet", False)] + for func in sorted(funcs, key=_line_no): + yield func + + +def _name_and_doc(func): + return func.__name__, func.__doc__ + + +def _backoff_not_found(deleter): + from google.cloud.exceptions import NotFound + + timeouts = [1, 2, 4, 8, 16] + while timeouts: + try: + deleter() + except NotFound: + time.sleep(timeouts.pop(0)) + else: + break + + +def main(): + client = Client() + for example in _find_examples(): + to_delete = [] + print("%-25s: %s" % _name_and_doc(example)) + try: + example(client, to_delete) + except AssertionError as failure: + print(" FAIL: %s" % (failure,)) + except Exception as error: # pylint: disable=broad-except + print(" ERROR: %r" % (error,)) + for item in to_delete: + _backoff_not_found(item.delete) + + +if __name__ == "__main__": + main() diff --git a/logging/samples/snippets/usage_guide_test.py b/logging/samples/snippets/usage_guide_test.py new file mode 100644 index 00000000000..3f606dd656b --- /dev/null +++ b/logging/samples/snippets/usage_guide_test.py @@ -0,0 +1,96 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from google.cloud.logging import Client + +import usage_guide + + +def test_logger_usage(): + client = Client() + + to_delete = [] + usage_guide.logger_usage(client, to_delete) + + for item in to_delete: + usage_guide._backoff_not_found(item.delete) + + +def test_metric_crud(): + client = Client() + + to_delete = [] + usage_guide.metric_crud(client, to_delete) + + for item in to_delete: + usage_guide._backoff_not_found(item.delete) + + +def test_sink_storage(): + client = Client() + + to_delete = [] + usage_guide.sink_storage(client, to_delete) + + for item in to_delete: + usage_guide._backoff_not_found(item.delete) + + +def test_sink_bigquery(): + client = Client() + + to_delete = [] + usage_guide.sink_bigquery(client, to_delete) + + for item in to_delete: + usage_guide._backoff_not_found(item.delete) + + +def test_sink_pubsub(): + client = Client() + + to_delete = [] + usage_guide.sink_pubsub(client, to_delete) + + for item in to_delete: + usage_guide._backoff_not_found(item.delete) + + +def test_logging_handler(): + client = Client() + + usage_guide.logging_handler(client) + + +def test_setup_logging(): + client = Client() + + usage_guide.setup_logging(client) + + +def test_client_list_entries(): + client = Client() + + to_delete = [] + usage_guide.client_list_entries(client, to_delete) + + for item in to_delete: + usage_guide._backoff_not_found(item.delete) + + +def test_dict_config(): + client = Client() + + usage_guide.logging_dict_config(client) From e767d22f6ae0acbde72d027da658c8c1b80c9514 Mon Sep 17 00:00:00 2001 From: Jennifer Davis Date: Thu, 19 Feb 2026 11:33:07 -0800 Subject: [PATCH 55/67] fix: no longer ture (#13846) --- logging/cloud-client/README.rst | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 logging/cloud-client/README.rst diff --git a/logging/cloud-client/README.rst b/logging/cloud-client/README.rst deleted file mode 100644 index 4ddc91a754f..00000000000 --- a/logging/cloud-client/README.rst +++ /dev/null @@ -1,3 +0,0 @@ -These samples have been moved. - -https://github.com/googleapis/python-logging/tree/main/samples From bec3672d2ec8e499ad2747841a3716ec0d45961d Mon Sep 17 00:00:00 2001 From: David del Real Date: Thu, 19 Feb 2026 14:39:37 -0600 Subject: [PATCH 56/67] Chore(appengine) Remove deprecated samples (#13833) * Remove deprecated samples Removal of appengine/flexible_python37_and_earlier path for deprecation. As per my search on Code Search, there are no samples using these deprecated code snippets anymore. b/475332253 * Remove reference to python 3.7 earlier samples from README.md file --- appengine/flexible/README.md | 2 - .../flexible_python37_and_earlier/README.md | 70 ----------- .../analytics/README.md | 25 ---- .../analytics/app.yaml | 25 ---- .../analytics/main.py | 77 ------------ .../analytics/main_test.py | 45 ------- .../analytics/noxfile_config.py | 39 ------ .../analytics/requirements-test.txt | 3 - .../analytics/requirements.txt | 5 - .../hello_world/app.yaml | 31 ----- .../hello_world/main.py | 35 ------ .../hello_world/main_test.py | 24 ---- .../hello_world/noxfile_config.py | 39 ------ .../hello_world/requirements-test.txt | 1 - .../hello_world/requirements.txt | 4 - .../hello_world_django/.gitignore | 1 - .../hello_world_django/README.md | 66 ---------- .../hello_world_django/app.yaml | 20 --- .../hello_world_django/helloworld/__init__.py | 0 .../hello_world_django/helloworld/views.py | 21 ---- .../hello_world_django/manage.py | 24 ---- .../hello_world_django/noxfile_config.py | 39 ------ .../project_name/__init__.py | 0 .../project_name/settings.py | 116 ------------------ .../hello_world_django/project_name/urls.py | 24 ---- .../hello_world_django/project_name/wsgi.py | 30 ----- .../hello_world_django/requirements-test.txt | 1 - .../hello_world_django/requirements.txt | 2 - .../metadata/app.yaml | 20 --- .../metadata/main.py | 87 ------------- .../metadata/main_test.py | 26 ---- .../metadata/noxfile_config.py | 39 ------ .../metadata/requirements-test.txt | 1 - .../metadata/requirements.txt | 5 - .../multiple_services/README.md | 63 ---------- .../gateway-service/app.yaml | 24 ---- .../multiple_services/gateway-service/main.py | 58 --------- .../gateway-service/requirements-test.txt | 1 - .../gateway-service/requirements.txt | 5 - .../gateway-service/services_config.py | 53 -------- .../multiple_services/noxfile_config.py | 39 ------ .../multiple_services/static-service/app.yaml | 24 ---- .../multiple_services/static-service/main.py | 46 ------- .../static-service/requirements-test.txt | 1 - .../static-service/requirements.txt | 5 - .../static-service/static/index.html | 32 ----- .../static-service/static/index.js | 36 ------ .../static-service/static/style.css | 19 --- .../numpy/app.yaml | 20 --- .../numpy/main.py | 59 --------- .../numpy/main_test.py | 24 ---- .../numpy/noxfile_config.py | 39 ------ .../numpy/requirements-test.txt | 1 - .../numpy/requirements.txt | 8 -- .../pubsub/README.md | 75 ----------- .../pubsub/app.yaml | 28 ----- .../pubsub/main.py | 98 --------------- .../pubsub/main_test.py | 65 ---------- .../pubsub/noxfile_config.py | 39 ------ .../pubsub/requirements-test.txt | 1 - .../pubsub/requirements.txt | 5 - .../pubsub/sample_message.json | 5 - .../pubsub/templates/index.html | 36 ------ .../scipy/.gitignore | 1 - .../scipy/README.md | 9 -- .../scipy/app.yaml | 20 --- .../scipy/assets/google_logo.jpg | Bin 21568 -> 0 bytes .../scipy/main.py | 63 ---------- .../scipy/main_test.py | 29 ----- .../scipy/noxfile_config.py | 39 ------ .../scipy/requirements-test.txt | 1 - .../scipy/requirements.txt | 11 -- .../static_files/README.md | 12 -- .../static_files/app.yaml | 20 --- .../static_files/main.py | 52 -------- .../static_files/main_test.py | 26 ---- .../static_files/noxfile_config.py | 39 ------ .../static_files/requirements-test.txt | 1 - .../static_files/requirements.txt | 4 - .../static_files/static/main.css | 21 ---- .../static_files/templates/index.html | 29 ----- .../twilio/README.md | 34 ----- .../twilio/app.yaml | 27 ---- .../twilio/main.py | 96 --------------- .../twilio/main_test.py | 75 ----------- .../twilio/noxfile_config.py | 39 ------ .../twilio/requirements-test.txt | 3 - .../twilio/requirements.txt | 6 - 88 files changed, 2513 deletions(-) delete mode 100644 appengine/flexible_python37_and_earlier/README.md delete mode 100644 appengine/flexible_python37_and_earlier/analytics/README.md delete mode 100644 appengine/flexible_python37_and_earlier/analytics/app.yaml delete mode 100644 appengine/flexible_python37_and_earlier/analytics/main.py delete mode 100644 appengine/flexible_python37_and_earlier/analytics/main_test.py delete mode 100644 appengine/flexible_python37_and_earlier/analytics/noxfile_config.py delete mode 100644 appengine/flexible_python37_and_earlier/analytics/requirements-test.txt delete mode 100644 appengine/flexible_python37_and_earlier/analytics/requirements.txt delete mode 100644 appengine/flexible_python37_and_earlier/hello_world/app.yaml delete mode 100644 appengine/flexible_python37_and_earlier/hello_world/main.py delete mode 100644 appengine/flexible_python37_and_earlier/hello_world/main_test.py delete mode 100644 appengine/flexible_python37_and_earlier/hello_world/noxfile_config.py delete mode 100644 appengine/flexible_python37_and_earlier/hello_world/requirements-test.txt delete mode 100644 appengine/flexible_python37_and_earlier/hello_world/requirements.txt delete mode 100644 appengine/flexible_python37_and_earlier/hello_world_django/.gitignore delete mode 100644 appengine/flexible_python37_and_earlier/hello_world_django/README.md delete mode 100644 appengine/flexible_python37_and_earlier/hello_world_django/app.yaml delete mode 100644 appengine/flexible_python37_and_earlier/hello_world_django/helloworld/__init__.py delete mode 100644 appengine/flexible_python37_and_earlier/hello_world_django/helloworld/views.py delete mode 100755 appengine/flexible_python37_and_earlier/hello_world_django/manage.py delete mode 100644 appengine/flexible_python37_and_earlier/hello_world_django/noxfile_config.py delete mode 100644 appengine/flexible_python37_and_earlier/hello_world_django/project_name/__init__.py delete mode 100644 appengine/flexible_python37_and_earlier/hello_world_django/project_name/settings.py delete mode 100644 appengine/flexible_python37_and_earlier/hello_world_django/project_name/urls.py delete mode 100644 appengine/flexible_python37_and_earlier/hello_world_django/project_name/wsgi.py delete mode 100644 appengine/flexible_python37_and_earlier/hello_world_django/requirements-test.txt delete mode 100644 appengine/flexible_python37_and_earlier/hello_world_django/requirements.txt delete mode 100644 appengine/flexible_python37_and_earlier/metadata/app.yaml delete mode 100644 appengine/flexible_python37_and_earlier/metadata/main.py delete mode 100644 appengine/flexible_python37_and_earlier/metadata/main_test.py delete mode 100644 appengine/flexible_python37_and_earlier/metadata/noxfile_config.py delete mode 100644 appengine/flexible_python37_and_earlier/metadata/requirements-test.txt delete mode 100644 appengine/flexible_python37_and_earlier/metadata/requirements.txt delete mode 100644 appengine/flexible_python37_and_earlier/multiple_services/README.md delete mode 100644 appengine/flexible_python37_and_earlier/multiple_services/gateway-service/app.yaml delete mode 100644 appengine/flexible_python37_and_earlier/multiple_services/gateway-service/main.py delete mode 100644 appengine/flexible_python37_and_earlier/multiple_services/gateway-service/requirements-test.txt delete mode 100644 appengine/flexible_python37_and_earlier/multiple_services/gateway-service/requirements.txt delete mode 100644 appengine/flexible_python37_and_earlier/multiple_services/gateway-service/services_config.py delete mode 100644 appengine/flexible_python37_and_earlier/multiple_services/noxfile_config.py delete mode 100644 appengine/flexible_python37_and_earlier/multiple_services/static-service/app.yaml delete mode 100644 appengine/flexible_python37_and_earlier/multiple_services/static-service/main.py delete mode 100644 appengine/flexible_python37_and_earlier/multiple_services/static-service/requirements-test.txt delete mode 100644 appengine/flexible_python37_and_earlier/multiple_services/static-service/requirements.txt delete mode 100644 appengine/flexible_python37_and_earlier/multiple_services/static-service/static/index.html delete mode 100644 appengine/flexible_python37_and_earlier/multiple_services/static-service/static/index.js delete mode 100644 appengine/flexible_python37_and_earlier/multiple_services/static-service/static/style.css delete mode 100644 appengine/flexible_python37_and_earlier/numpy/app.yaml delete mode 100644 appengine/flexible_python37_and_earlier/numpy/main.py delete mode 100644 appengine/flexible_python37_and_earlier/numpy/main_test.py delete mode 100644 appengine/flexible_python37_and_earlier/numpy/noxfile_config.py delete mode 100644 appengine/flexible_python37_and_earlier/numpy/requirements-test.txt delete mode 100644 appengine/flexible_python37_and_earlier/numpy/requirements.txt delete mode 100644 appengine/flexible_python37_and_earlier/pubsub/README.md delete mode 100644 appengine/flexible_python37_and_earlier/pubsub/app.yaml delete mode 100644 appengine/flexible_python37_and_earlier/pubsub/main.py delete mode 100644 appengine/flexible_python37_and_earlier/pubsub/main_test.py delete mode 100644 appengine/flexible_python37_and_earlier/pubsub/noxfile_config.py delete mode 100644 appengine/flexible_python37_and_earlier/pubsub/requirements-test.txt delete mode 100644 appengine/flexible_python37_and_earlier/pubsub/requirements.txt delete mode 100644 appengine/flexible_python37_and_earlier/pubsub/sample_message.json delete mode 100644 appengine/flexible_python37_and_earlier/pubsub/templates/index.html delete mode 100644 appengine/flexible_python37_and_earlier/scipy/.gitignore delete mode 100644 appengine/flexible_python37_and_earlier/scipy/README.md delete mode 100644 appengine/flexible_python37_and_earlier/scipy/app.yaml delete mode 100644 appengine/flexible_python37_and_earlier/scipy/assets/google_logo.jpg delete mode 100644 appengine/flexible_python37_and_earlier/scipy/main.py delete mode 100644 appengine/flexible_python37_and_earlier/scipy/main_test.py delete mode 100644 appengine/flexible_python37_and_earlier/scipy/noxfile_config.py delete mode 100644 appengine/flexible_python37_and_earlier/scipy/requirements-test.txt delete mode 100644 appengine/flexible_python37_and_earlier/scipy/requirements.txt delete mode 100644 appengine/flexible_python37_and_earlier/static_files/README.md delete mode 100644 appengine/flexible_python37_and_earlier/static_files/app.yaml delete mode 100644 appengine/flexible_python37_and_earlier/static_files/main.py delete mode 100644 appengine/flexible_python37_and_earlier/static_files/main_test.py delete mode 100644 appengine/flexible_python37_and_earlier/static_files/noxfile_config.py delete mode 100644 appengine/flexible_python37_and_earlier/static_files/requirements-test.txt delete mode 100644 appengine/flexible_python37_and_earlier/static_files/requirements.txt delete mode 100644 appengine/flexible_python37_and_earlier/static_files/static/main.css delete mode 100644 appengine/flexible_python37_and_earlier/static_files/templates/index.html delete mode 100644 appengine/flexible_python37_and_earlier/twilio/README.md delete mode 100644 appengine/flexible_python37_and_earlier/twilio/app.yaml delete mode 100644 appengine/flexible_python37_and_earlier/twilio/main.py delete mode 100644 appengine/flexible_python37_and_earlier/twilio/main_test.py delete mode 100644 appengine/flexible_python37_and_earlier/twilio/noxfile_config.py delete mode 100644 appengine/flexible_python37_and_earlier/twilio/requirements-test.txt delete mode 100644 appengine/flexible_python37_and_earlier/twilio/requirements.txt diff --git a/appengine/flexible/README.md b/appengine/flexible/README.md index 0cc851a437e..8f6a03a894f 100644 --- a/appengine/flexible/README.md +++ b/appengine/flexible/README.md @@ -7,8 +7,6 @@ These are samples for using Python on Google App Engine Flexible Environment. These samples are typically referenced from the [docs](https://cloud.google.com/appengine/docs). -For code samples of Python version 3.7 and earlier, please check -https://github.com/GoogleCloudPlatform/python-docs-samples/tree/main/appengine/flexible_python37_and_earlier See our other [Google Cloud Platform github repos](https://github.com/GoogleCloudPlatform) for sample applications and scaffolding for other frameworks and use cases. diff --git a/appengine/flexible_python37_and_earlier/README.md b/appengine/flexible_python37_and_earlier/README.md deleted file mode 100644 index 41927a35c3d..00000000000 --- a/appengine/flexible_python37_and_earlier/README.md +++ /dev/null @@ -1,70 +0,0 @@ -## Google App Engine Flexible Environment Python Samples - -[![Open in Cloud Shell][shell_img]][shell_link] - -[shell_img]: http://gstatic.com/cloudssh/images/open-btn.png -[shell_link]: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=appengine/flexible_python37_and_earlier/README.md - -These are samples for using Python on Google App Engine Flexible Environment. These samples are typically referenced from the [docs](https://cloud.google.com/appengine/docs). - -See our other [Google Cloud Platform github repos](https://github.com/GoogleCloudPlatform) for sample applications and -scaffolding for other frameworks and use cases. - -## Run Locally - -Some samples have specific instructions. If there is a README in the sample folder, please refer to it for any additional steps required to run the sample. - -In general, the samples typically require: - -1. Install the [Google Cloud SDK](https://cloud.google.com/sdk/), including the [gcloud tool](https://cloud.google.com/sdk/gcloud/), and [gcloud app component](https://cloud.google.com/sdk/gcloud-app). - -2. Setup the gcloud tool. This provides authentication to Google Cloud APIs and services. - - ``` - gcloud init - ``` - -3. Clone this repo. - - ``` - git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git - cd python-docs-samples/appengine/flexible_python37_and_earlier - ``` - -4. Follow https://cloud.google.com/python/docs/setup to set up a Python development environment. Then run: - - ``` - pip install -r requirements.txt - python main.py - ``` - -5. Visit the application at [http://localhost:8080](http://localhost:8080). - - -## Deploying - -Some samples in this repositories may have special deployment instructions. Refer to the readme in the sample directory. - -1. Use the [Google Developers Console](https://console.developer.google.com) to create a project/app id. (App id and project id are identical) - -2. Setup the gcloud tool, if you haven't already. - - ``` - gcloud init - ``` - -3. Use gcloud to deploy your app. - - ``` - gcloud app deploy - ``` - -4. Congratulations! Your application is now live at `your-app-id.appspot.com` - -## Contributing changes - -* See [CONTRIBUTING.md](../../CONTRIBUTING.md) - -## Licensing - -* See [LICENSE](../../LICENSE) diff --git a/appengine/flexible_python37_and_earlier/analytics/README.md b/appengine/flexible_python37_and_earlier/analytics/README.md deleted file mode 100644 index d4fa88bef8b..00000000000 --- a/appengine/flexible_python37_and_earlier/analytics/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# Google Analytics Measurement Protocol sample for Google App Engine Flexible - -[![Open in Cloud Shell][shell_img]][shell_link] - -[shell_img]: http://gstatic.com/cloudssh/images/open-btn.png -[shell_link]: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=appengine/flexible_python37_and_earlier/analytics/README.md - -This sample demonstrates how to use the [Google Analytics Measurement Protocol](https://developers.google.com/analytics/devguides/collection/protocol/v1/) (or any other SQL server) on [Google App Engine Flexible Environment](https://cloud.google.com/appengine). - -## Setup - -Before you can run or deploy the sample, you will need to do the following: - -1. Create a Google Analytics Property and obtain the Tracking ID. - -2. Update the environment variables in in ``app.yaml`` with your Tracking ID. - -## Running locally - -Refer to the [top-level README](../README.md) for instructions on running and deploying. - -You will need to set the following environment variables via your shell before running the sample: - - $ export GA_TRACKING_ID=[your Tracking ID] - $ python main.py diff --git a/appengine/flexible_python37_and_earlier/analytics/app.yaml b/appengine/flexible_python37_and_earlier/analytics/app.yaml deleted file mode 100644 index 0f5590d7058..00000000000 --- a/appengine/flexible_python37_and_earlier/analytics/app.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2021 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -runtime: python -env: flex -entrypoint: gunicorn -b :$PORT main:app - -runtime_config: - python_version: 3 - -#[START gae_flex_analytics_env_variables] -env_variables: - GA_TRACKING_ID: your-tracking-id -#[END gae_flex_analytics_env_variables] diff --git a/appengine/flexible_python37_and_earlier/analytics/main.py b/appengine/flexible_python37_and_earlier/analytics/main.py deleted file mode 100644 index c07ab9b4703..00000000000 --- a/appengine/flexible_python37_and_earlier/analytics/main.py +++ /dev/null @@ -1,77 +0,0 @@ -# Copyright 2015 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# [START gae_flex_analytics_track_event] -import logging -import os - -from flask import Flask -import requests - - -app = Flask(__name__) - - -# Environment variables are defined in app.yaml. -GA_TRACKING_ID = os.environ["GA_TRACKING_ID"] - - -def track_event(category, action, label=None, value=0): - data = { - "v": "1", # API Version. - "tid": GA_TRACKING_ID, # Tracking ID / Property ID. - # Anonymous Client Identifier. Ideally, this should be a UUID that - # is associated with particular user, device, or browser instance. - "cid": "555", - "t": "event", # Event hit type. - "ec": category, # Event category. - "ea": action, # Event action. - "el": label, # Event label. - "ev": value, # Event value, must be an integer - "ua": "Opera/9.80 (Windows NT 6.0) Presto/2.12.388 Version/12.14", - } - - response = requests.post("https://www.google-analytics.com/collect", data=data) - - # If the request fails, this will raise a RequestException. Depending - # on your application's needs, this may be a non-error and can be caught - # by the caller. - response.raise_for_status() - - -@app.route("/") -def track_example(): - track_event(category="Example", action="test action") - return "Event tracked." - - -@app.errorhandler(500) -def server_error(e): - logging.exception("An error occurred during a request.") - return ( - """ - An internal error occurred:
      {}
      - See logs for full stacktrace. - """.format( - e - ), - 500, - ) - - -if __name__ == "__main__": - # This is used when running locally. Gunicorn is used to run the - # application on Google App Engine. See entrypoint in app.yaml. - app.run(host="127.0.0.1", port=8080, debug=True) -# [END gae_flex_analytics_track_event] diff --git a/appengine/flexible_python37_and_earlier/analytics/main_test.py b/appengine/flexible_python37_and_earlier/analytics/main_test.py deleted file mode 100644 index 02914bda79d..00000000000 --- a/appengine/flexible_python37_and_earlier/analytics/main_test.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright 2016 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import re - -import pytest -import responses - - -@pytest.fixture -def app(monkeypatch): - monkeypatch.setenv("GA_TRACKING_ID", "1234") - - import main - - main.app.testing = True - return main.app.test_client() - - -@responses.activate -def test_tracking(app): - responses.add( - responses.POST, re.compile(r".*"), body="{}", content_type="application/json" - ) - - r = app.get("/") - - assert r.status_code == 200 - assert "Event tracked" in r.data.decode("utf-8") - - assert len(responses.calls) == 1 - request_body = responses.calls[0].request.body - assert "tid=1234" in request_body - assert "ea=test+action" in request_body diff --git a/appengine/flexible_python37_and_earlier/analytics/noxfile_config.py b/appengine/flexible_python37_and_earlier/analytics/noxfile_config.py deleted file mode 100644 index 1665dd736f8..00000000000 --- a/appengine/flexible_python37_and_earlier/analytics/noxfile_config.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Default TEST_CONFIG_OVERRIDE for python repos. - -# You can copy this file into your directory, then it will be imported from -# the noxfile.py. - -# The source of truth: -# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/noxfile_config.py - -TEST_CONFIG_OVERRIDE = { - # You can opt out from the test for specific Python versions. - # Skipping for Python 3.9 due to pyarrow compilation failure. - "ignored_versions": ["2.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"], - # Old samples are opted out of enforcing Python type hints - # All new samples should feature them - "enforce_type_hints": False, - # An envvar key for determining the project id to use. Change it - # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a - # build specific Cloud project. You can also use your own string - # to use your own Cloud project. - "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", - # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', - # A dictionary you want to inject into your test. Don't put any - # secrets here. These values will override predefined values. - "envs": {}, -} diff --git a/appengine/flexible_python37_and_earlier/analytics/requirements-test.txt b/appengine/flexible_python37_and_earlier/analytics/requirements-test.txt deleted file mode 100644 index e89f6031ad7..00000000000 --- a/appengine/flexible_python37_and_earlier/analytics/requirements-test.txt +++ /dev/null @@ -1,3 +0,0 @@ -pytest==8.2.0 -responses==0.17.0; python_version < '3.7' -responses==0.23.1; python_version > '3.6' diff --git a/appengine/flexible_python37_and_earlier/analytics/requirements.txt b/appengine/flexible_python37_and_earlier/analytics/requirements.txt deleted file mode 100644 index 9bfb6dcc546..00000000000 --- a/appengine/flexible_python37_and_earlier/analytics/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -Flask==3.0.3; python_version > '3.6' -Flask==2.3.3; python_version < '3.7' -gunicorn==23.0.0 -requests[security]==2.31.0 -Werkzeug==3.0.3 diff --git a/appengine/flexible_python37_and_earlier/hello_world/app.yaml b/appengine/flexible_python37_and_earlier/hello_world/app.yaml deleted file mode 100644 index 7aa7a47e159..00000000000 --- a/appengine/flexible_python37_and_earlier/hello_world/app.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright 2021 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -runtime: python -env: flex -entrypoint: gunicorn -b :$PORT main:app - -runtime_config: - python_version: 3 - -# This sample incurs costs to run on the App Engine flexible environment. -# The settings below are to reduce costs during testing and are not appropriate -# for production use. For more information, see: -# https://cloud.google.com/appengine/docs/flexible/python/configuring-your-app-with-app-yaml -manual_scaling: - instances: 1 -resources: - cpu: 1 - memory_gb: 0.5 - disk_size_gb: 10 diff --git a/appengine/flexible_python37_and_earlier/hello_world/main.py b/appengine/flexible_python37_and_earlier/hello_world/main.py deleted file mode 100644 index eba195ed4fd..00000000000 --- a/appengine/flexible_python37_and_earlier/hello_world/main.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2015 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# [START gae_flex_quickstart] -from flask import Flask - -app = Flask(__name__) - - -@app.route("/") -def hello(): - """Return a friendly HTTP greeting. - - Returns: - A string with the words 'Hello World!'. - """ - return "Hello World!" - - -if __name__ == "__main__": - # This is used when running locally only. When deploying to Google App - # Engine, a webserver process such as Gunicorn will serve the app. - app.run(host="127.0.0.1", port=8080, debug=True) -# [END gae_flex_quickstart] diff --git a/appengine/flexible_python37_and_earlier/hello_world/main_test.py b/appengine/flexible_python37_and_earlier/hello_world/main_test.py deleted file mode 100644 index a6049b094f9..00000000000 --- a/appengine/flexible_python37_and_earlier/hello_world/main_test.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright 2015 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import main - - -def test_index(): - main.app.testing = True - client = main.app.test_client() - - r = client.get("/") - assert r.status_code == 200 - assert "Hello World" in r.data.decode("utf-8") diff --git a/appengine/flexible_python37_and_earlier/hello_world/noxfile_config.py b/appengine/flexible_python37_and_earlier/hello_world/noxfile_config.py deleted file mode 100644 index 1665dd736f8..00000000000 --- a/appengine/flexible_python37_and_earlier/hello_world/noxfile_config.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Default TEST_CONFIG_OVERRIDE for python repos. - -# You can copy this file into your directory, then it will be imported from -# the noxfile.py. - -# The source of truth: -# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/noxfile_config.py - -TEST_CONFIG_OVERRIDE = { - # You can opt out from the test for specific Python versions. - # Skipping for Python 3.9 due to pyarrow compilation failure. - "ignored_versions": ["2.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"], - # Old samples are opted out of enforcing Python type hints - # All new samples should feature them - "enforce_type_hints": False, - # An envvar key for determining the project id to use. Change it - # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a - # build specific Cloud project. You can also use your own string - # to use your own Cloud project. - "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", - # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', - # A dictionary you want to inject into your test. Don't put any - # secrets here. These values will override predefined values. - "envs": {}, -} diff --git a/appengine/flexible_python37_and_earlier/hello_world/requirements-test.txt b/appengine/flexible_python37_and_earlier/hello_world/requirements-test.txt deleted file mode 100644 index 15d066af319..00000000000 --- a/appengine/flexible_python37_and_earlier/hello_world/requirements-test.txt +++ /dev/null @@ -1 +0,0 @@ -pytest==8.2.0 diff --git a/appengine/flexible_python37_and_earlier/hello_world/requirements.txt b/appengine/flexible_python37_and_earlier/hello_world/requirements.txt deleted file mode 100644 index 055e4c6a13d..00000000000 --- a/appengine/flexible_python37_and_earlier/hello_world/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -Flask==3.0.3; python_version > '3.6' -Flask==3.0.3; python_version < '3.7' -gunicorn==23.0.0 -Werkzeug==3.0.3 diff --git a/appengine/flexible_python37_and_earlier/hello_world_django/.gitignore b/appengine/flexible_python37_and_earlier/hello_world_django/.gitignore deleted file mode 100644 index 49ef2557b16..00000000000 --- a/appengine/flexible_python37_and_earlier/hello_world_django/.gitignore +++ /dev/null @@ -1 +0,0 @@ -db.sqlite3 diff --git a/appengine/flexible_python37_and_earlier/hello_world_django/README.md b/appengine/flexible_python37_and_earlier/hello_world_django/README.md deleted file mode 100644 index d6705b131a3..00000000000 --- a/appengine/flexible_python37_and_earlier/hello_world_django/README.md +++ /dev/null @@ -1,66 +0,0 @@ -# Django sample for Google App Engine Flexible Environment - -[![Open in Cloud Shell][shell_img]][shell_link] - -[shell_img]: http://gstatic.com/cloudssh/images/open-btn.png -[shell_link]: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=appengine/flexible_python37_and_earlier/hello_world_django/README.md - -This is a basic hello world [Django](https://www.djangoproject.com/) example -for [Google App Engine Flexible Environment](https://cloud.google.com/appengine). - -## Running locally - -You can run locally using django's `manage.py`: - - $ python manage.py runserver - -## Deployment & how the application runs on Google App Engine. - -Follow the standard deployment instructions in -[the top-level README](../README.md). Google App Engine runs the application -using [gunicorn](http://gunicorn.org/) as defined by `entrypoint` in -[`app.yaml`](app.yaml). You can use a different WSGI container if you want, as -long as it listens for web traffic on port `$PORT` and is declared in -[`requirements.txt`](requirements.txt). - -## How this was created - -To set up Python development environment, please follow -https://cloud.google.com/python/docs/setup. - -This project was created using standard Django commands: - - $ virtualenv env - $ source env/bin/activate - $ pip install django gunicorn - $ pip freeze > requirements.txt - $ django-admin startproject project_name - $ python manage.py startapp helloworld - -Then, we added a simple view in `hellworld.views`, added the app to -`project_name.settings.INSTALLED_APPS`, and finally added a URL rule to -`project_name.urls`. - -In order to deploy to Google App Engine, we created a simple -[`app.yaml`](app.yaml). - -## Database notice - -This sample project uses Django's default sqlite database. This isn't suitable -for production as your application can run multiple instances and each will -have a different sqlite database. Additionally, instance disks are ephemeral, -so data will not survive restarts. - -For production applications running on Google Cloud Platform, you have -the following options: - -* Use [Cloud SQL](https://cloud.google.com/sql), a fully-managed MySQL database. - There is a [Flask CloudSQL](../cloudsql) sample that should be straightforward - to adapt to Django. -* Use any database of your choice hosted on - [Google Compute Engine](https://cloud.google.com/compute). The - [Cloud Launcher](https://cloud.google.com/launcher/) can be used to easily - deploy common databases. -* Use third-party database services, or services hosted by other providers, - provided you have configured access. - diff --git a/appengine/flexible_python37_and_earlier/hello_world_django/app.yaml b/appengine/flexible_python37_and_earlier/hello_world_django/app.yaml deleted file mode 100644 index 62b74a9c27e..00000000000 --- a/appengine/flexible_python37_and_earlier/hello_world_django/app.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2021 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -runtime: python -env: flex -entrypoint: gunicorn -b :$PORT project_name.wsgi - -runtime_config: - python_version: 3 diff --git a/appengine/flexible_python37_and_earlier/hello_world_django/helloworld/__init__.py b/appengine/flexible_python37_and_earlier/hello_world_django/helloworld/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/appengine/flexible_python37_and_earlier/hello_world_django/helloworld/views.py b/appengine/flexible_python37_and_earlier/hello_world_django/helloworld/views.py deleted file mode 100644 index 71c0106bda1..00000000000 --- a/appengine/flexible_python37_and_earlier/hello_world_django/helloworld/views.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python -# Copyright 2015 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -from django.http import HttpResponse - - -def index(request): - return HttpResponse("Hello, World. This is Django running on Google App Engine") diff --git a/appengine/flexible_python37_and_earlier/hello_world_django/manage.py b/appengine/flexible_python37_and_earlier/hello_world_django/manage.py deleted file mode 100755 index c213c77eca6..00000000000 --- a/appengine/flexible_python37_and_earlier/hello_world_django/manage.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python -# Copyright 2021 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project_name.settings") - - from django.core.management import execute_from_command_line - - execute_from_command_line(sys.argv) diff --git a/appengine/flexible_python37_and_earlier/hello_world_django/noxfile_config.py b/appengine/flexible_python37_and_earlier/hello_world_django/noxfile_config.py deleted file mode 100644 index 1665dd736f8..00000000000 --- a/appengine/flexible_python37_and_earlier/hello_world_django/noxfile_config.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Default TEST_CONFIG_OVERRIDE for python repos. - -# You can copy this file into your directory, then it will be imported from -# the noxfile.py. - -# The source of truth: -# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/noxfile_config.py - -TEST_CONFIG_OVERRIDE = { - # You can opt out from the test for specific Python versions. - # Skipping for Python 3.9 due to pyarrow compilation failure. - "ignored_versions": ["2.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"], - # Old samples are opted out of enforcing Python type hints - # All new samples should feature them - "enforce_type_hints": False, - # An envvar key for determining the project id to use. Change it - # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a - # build specific Cloud project. You can also use your own string - # to use your own Cloud project. - "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", - # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', - # A dictionary you want to inject into your test. Don't put any - # secrets here. These values will override predefined values. - "envs": {}, -} diff --git a/appengine/flexible_python37_and_earlier/hello_world_django/project_name/__init__.py b/appengine/flexible_python37_and_earlier/hello_world_django/project_name/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/appengine/flexible_python37_and_earlier/hello_world_django/project_name/settings.py b/appengine/flexible_python37_and_earlier/hello_world_django/project_name/settings.py deleted file mode 100644 index f8b93099d56..00000000000 --- a/appengine/flexible_python37_and_earlier/hello_world_django/project_name/settings.py +++ /dev/null @@ -1,116 +0,0 @@ -# Copyright 2015 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Django settings for project_name project. - -Generated by 'django-admin startproject' using Django 1.8.4. - -For more information on this file, see -https://docs.djangoproject.com/en/stable/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/stable/ref/settings/ -""" - -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) -import os - -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/stable/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = "qgw!j*bpxo7g&o1ux-(2ph818ojfj(3c#-#*_8r^8&hq5jg$3@" - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - -ALLOWED_HOSTS = [] - - -# Application definition - -INSTALLED_APPS = ( - "django.contrib.admin", - "django.contrib.auth", - "django.contrib.contenttypes", - "django.contrib.sessions", - "django.contrib.messages", - "django.contrib.staticfiles", - "helloworld", -) - -MIDDLEWARE = ( - "django.middleware.security.SecurityMiddleware", - "django.contrib.sessions.middleware.SessionMiddleware", - "django.middleware.common.CommonMiddleware", - "django.middleware.csrf.CsrfViewMiddleware", - "django.contrib.auth.middleware.AuthenticationMiddleware", - "django.contrib.messages.middleware.MessageMiddleware", - "django.middleware.clickjacking.XFrameOptionsMiddleware", -) - -ROOT_URLCONF = "project_name.urls" - -TEMPLATES = [ - { - "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [], - "APP_DIRS": True, - "OPTIONS": { - "context_processors": [ - "django.template.context_processors.debug", - "django.template.context_processors.request", - "django.contrib.auth.context_processors.auth", - "django.contrib.messages.context_processors.messages", - ], - }, - }, -] - -WSGI_APPLICATION = "project_name.wsgi.application" - - -# Database -# https://docs.djangoproject.com/en/stable/ref/settings/#databases - -DATABASES = { - "default": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": os.path.join(BASE_DIR, "db.sqlite3"), - } -} - - -# Internationalization -# https://docs.djangoproject.com/en/stable/topics/i18n/ - -LANGUAGE_CODE = "en-us" - -TIME_ZONE = "UTC" - -USE_I18N = True - -USE_L10N = True - -USE_TZ = True - - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/stable/howto/static-files/ - -STATIC_URL = "/static/" diff --git a/appengine/flexible_python37_and_earlier/hello_world_django/project_name/urls.py b/appengine/flexible_python37_and_earlier/hello_world_django/project_name/urls.py deleted file mode 100644 index 9a393bb42d2..00000000000 --- a/appengine/flexible_python37_and_earlier/hello_world_django/project_name/urls.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright 2015 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from django.contrib import admin -from django.urls import include, path - -import helloworld.views - - -urlpatterns = [ - path("admin/", include(admin.site.urls)), - path("", helloworld.views.index), -] diff --git a/appengine/flexible_python37_and_earlier/hello_world_django/project_name/wsgi.py b/appengine/flexible_python37_and_earlier/hello_world_django/project_name/wsgi.py deleted file mode 100644 index c069a496999..00000000000 --- a/appengine/flexible_python37_and_earlier/hello_world_django/project_name/wsgi.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright 2021 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -WSGI config for project_name project. - -It exposes the WSGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/stable/howto/deployment/wsgi/ -""" - -import os - -from django.core.wsgi import get_wsgi_application - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project_name.settings") - -application = get_wsgi_application() diff --git a/appengine/flexible_python37_and_earlier/hello_world_django/requirements-test.txt b/appengine/flexible_python37_and_earlier/hello_world_django/requirements-test.txt deleted file mode 100644 index 15d066af319..00000000000 --- a/appengine/flexible_python37_and_earlier/hello_world_django/requirements-test.txt +++ /dev/null @@ -1 +0,0 @@ -pytest==8.2.0 diff --git a/appengine/flexible_python37_and_earlier/hello_world_django/requirements.txt b/appengine/flexible_python37_and_earlier/hello_world_django/requirements.txt deleted file mode 100644 index 435ef2cb8ee..00000000000 --- a/appengine/flexible_python37_and_earlier/hello_world_django/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -Django==5.2.9 -gunicorn==23.0.0 diff --git a/appengine/flexible_python37_and_earlier/metadata/app.yaml b/appengine/flexible_python37_and_earlier/metadata/app.yaml deleted file mode 100644 index ca76f83fc3b..00000000000 --- a/appengine/flexible_python37_and_earlier/metadata/app.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2021 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -runtime: python -env: flex -entrypoint: gunicorn -b :$PORT main:app - -runtime_config: - python_version: 3 diff --git a/appengine/flexible_python37_and_earlier/metadata/main.py b/appengine/flexible_python37_and_earlier/metadata/main.py deleted file mode 100644 index 9d1e320865a..00000000000 --- a/appengine/flexible_python37_and_earlier/metadata/main.py +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright 2015 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import logging - -from flask import Flask -import requests - - -logging.basicConfig(level=logging.INFO) -app = Flask(__name__) - - -# [START gae_flex_metadata] -METADATA_NETWORK_INTERFACE_URL = ( - "http://metadata/computeMetadata/v1/instance/network-interfaces/0/" - "access-configs/0/external-ip" -) - - -def get_external_ip(): - """Gets the instance's external IP address from the Compute Engine metadata - server. - - If the metadata server is unavailable, it assumes that the application is running locally. - - Returns: - The instance's external IP address, or the string 'localhost' if the IP address - is not available. - """ - try: - r = requests.get( - METADATA_NETWORK_INTERFACE_URL, - headers={"Metadata-Flavor": "Google"}, - timeout=2, - ) - return r.text - except requests.RequestException: - logging.info("Metadata server could not be reached, assuming local.") - return "localhost" - - -# [END gae_flex_metadata] - - -@app.route("/") -def index(): - """Serves a string with the instance's external IP address. - - Websocket connections must be made directly to this instance. - - Returns: - A formatted string containing the instance's external IP address. - """ - external_ip = get_external_ip() - return f"External IP: {external_ip}" - - -@app.errorhandler(500) -def server_error(e): - """Serves a formatted message on-error. - - Returns: - The error message and a code 500 status. - """ - logging.exception("An error occurred during a request.") - return ( - f"An internal error occurred:
      {e}

      See logs for full stacktrace.", - 500, - ) - - -if __name__ == "__main__": - # This is used when running locally. Gunicorn is used to run the - # application on Google App Engine. See entrypoint in app.yaml. - app.run(host="127.0.0.1", port=8080, debug=True) diff --git a/appengine/flexible_python37_and_earlier/metadata/main_test.py b/appengine/flexible_python37_and_earlier/metadata/main_test.py deleted file mode 100644 index 55d345d170d..00000000000 --- a/appengine/flexible_python37_and_earlier/metadata/main_test.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2023 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import main - - -def test_index(): - main.app.testing = True - client = main.app.test_client() - - external_ip = main.get_external_ip() - - r = client.get("/") - assert r.status_code == 200 - assert f"External IP: {external_ip}" in r.data.decode("utf-8") diff --git a/appengine/flexible_python37_and_earlier/metadata/noxfile_config.py b/appengine/flexible_python37_and_earlier/metadata/noxfile_config.py deleted file mode 100644 index 1665dd736f8..00000000000 --- a/appengine/flexible_python37_and_earlier/metadata/noxfile_config.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Default TEST_CONFIG_OVERRIDE for python repos. - -# You can copy this file into your directory, then it will be imported from -# the noxfile.py. - -# The source of truth: -# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/noxfile_config.py - -TEST_CONFIG_OVERRIDE = { - # You can opt out from the test for specific Python versions. - # Skipping for Python 3.9 due to pyarrow compilation failure. - "ignored_versions": ["2.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"], - # Old samples are opted out of enforcing Python type hints - # All new samples should feature them - "enforce_type_hints": False, - # An envvar key for determining the project id to use. Change it - # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a - # build specific Cloud project. You can also use your own string - # to use your own Cloud project. - "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", - # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', - # A dictionary you want to inject into your test. Don't put any - # secrets here. These values will override predefined values. - "envs": {}, -} diff --git a/appengine/flexible_python37_and_earlier/metadata/requirements-test.txt b/appengine/flexible_python37_and_earlier/metadata/requirements-test.txt deleted file mode 100644 index 15d066af319..00000000000 --- a/appengine/flexible_python37_and_earlier/metadata/requirements-test.txt +++ /dev/null @@ -1 +0,0 @@ -pytest==8.2.0 diff --git a/appengine/flexible_python37_and_earlier/metadata/requirements.txt b/appengine/flexible_python37_and_earlier/metadata/requirements.txt deleted file mode 100644 index 9bfb6dcc546..00000000000 --- a/appengine/flexible_python37_and_earlier/metadata/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -Flask==3.0.3; python_version > '3.6' -Flask==2.3.3; python_version < '3.7' -gunicorn==23.0.0 -requests[security]==2.31.0 -Werkzeug==3.0.3 diff --git a/appengine/flexible_python37_and_earlier/multiple_services/README.md b/appengine/flexible_python37_and_earlier/multiple_services/README.md deleted file mode 100644 index 1e300dd8e00..00000000000 --- a/appengine/flexible_python37_and_earlier/multiple_services/README.md +++ /dev/null @@ -1,63 +0,0 @@ -# Python Google Cloud Microservices Example - API Gateway - -[![Open in Cloud Shell][shell_img]][shell_link] - -[shell_img]: http://gstatic.com/cloudssh/images/open-btn.png -[shell_link]: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=appengine/flexible_python37_and_earlier/multiple_services/README.md - -This example demonstrates how to deploy multiple python services to [App Engine flexible environment](https://cloud.google.com/appengine/docs/flexible/) - -## To Run Locally - -Open a terminal and start the first service: - -```Bash -$ cd gateway-service -$ # follow https://cloud.google.com/python/docs/setup to set up a Python -development environment -$ pip install -r requirements.txt -$ python main.py -``` - -In a separate terminal, start the second service: - -```Bash -$ cd static-service -$ # follow https://cloud.google.com/python/docs/setup to set up a Python -$ pip install -r requirements.txt -$ python main.py -``` - -## To Deploy to App Engine - -### YAML Files - -Each directory contains an `app.yaml` file. These files all describe a -separate App Engine service within the same project. - -For the gateway: - -[Gateway service ](gateway/app.yaml) - -This is the `default` service. There must be one (and not more). The deployed -url will be `https://.appspot.com` - -For the static file server: - -[Static file service ](static/app.yaml) - -The deployed url will be `https://-dot-.appspot.com` - -### Deployment - -To deploy a service cd into its directory and run: -```Bash -$ gcloud app deploy app.yaml -``` -and enter `Y` when prompted. Or to skip the check add `-q`. - -To deploy multiple services simultaneously just add the path to each `app.yaml` -file as an argument to `gcloud app deploy `: -```Bash -$ gcloud app deploy gateway-service/app.yaml static-service/app.yaml -``` diff --git a/appengine/flexible_python37_and_earlier/multiple_services/gateway-service/app.yaml b/appengine/flexible_python37_and_earlier/multiple_services/gateway-service/app.yaml deleted file mode 100644 index fde45fada1c..00000000000 --- a/appengine/flexible_python37_and_earlier/multiple_services/gateway-service/app.yaml +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright 2021 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -service: default -runtime: python -env: flex -entrypoint: gunicorn -b :$PORT main:app - -runtime_config: - python_version: 3 - -manual_scaling: - instances: 1 diff --git a/appengine/flexible_python37_and_earlier/multiple_services/gateway-service/main.py b/appengine/flexible_python37_and_earlier/multiple_services/gateway-service/main.py deleted file mode 100644 index f963995ae0b..00000000000 --- a/appengine/flexible_python37_and_earlier/multiple_services/gateway-service/main.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright 2016 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from flask import Flask -import requests - -import services_config - -app = Flask(__name__) -services_config.init_app(app) - - -@app.route("/") -def root(): - """Gets index.html from the static file server""" - res = requests.get(app.config["SERVICE_MAP"]["static"]) - return res.content - - -@app.route("/hello/") -def say_hello(service): - """Recieves requests from buttons on the front end and resopnds - or sends request to the static file server""" - # If 'gateway' is specified return immediate - if service == "gateway": - return "Gateway says hello" - - # Otherwise send request to service indicated by URL param - responses = [] - url = app.config["SERVICE_MAP"][service] - res = requests.get(url + "/hello") - responses.append(res.content) - return b"\n".join(responses) - - -@app.route("/") -def static_file(path): - """Gets static files required by index.html to static file server""" - url = app.config["SERVICE_MAP"]["static"] - res = requests.get(url + "/" + path) - return res.content, 200, {"Content-Type": res.headers["Content-Type"]} - - -if __name__ == "__main__": - # This is used when running locally. Gunicorn is used to run the - # application on Google App Engine. See entrypoint in app.yaml. - app.run(host="127.0.0.1", port=8000, debug=True) diff --git a/appengine/flexible_python37_and_earlier/multiple_services/gateway-service/requirements-test.txt b/appengine/flexible_python37_and_earlier/multiple_services/gateway-service/requirements-test.txt deleted file mode 100644 index 15d066af319..00000000000 --- a/appengine/flexible_python37_and_earlier/multiple_services/gateway-service/requirements-test.txt +++ /dev/null @@ -1 +0,0 @@ -pytest==8.2.0 diff --git a/appengine/flexible_python37_and_earlier/multiple_services/gateway-service/requirements.txt b/appengine/flexible_python37_and_earlier/multiple_services/gateway-service/requirements.txt deleted file mode 100644 index 052021ed812..00000000000 --- a/appengine/flexible_python37_and_earlier/multiple_services/gateway-service/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -Flask==3.0.3; python_version > '3.6' -Flask==2.3.3; python_version < '3.7' -gunicorn==23.0.0 -requests==2.31.0 -Werkzeug==3.0.3 diff --git a/appengine/flexible_python37_and_earlier/multiple_services/gateway-service/services_config.py b/appengine/flexible_python37_and_earlier/multiple_services/gateway-service/services_config.py deleted file mode 100644 index 429ed402e03..00000000000 --- a/appengine/flexible_python37_and_earlier/multiple_services/gateway-service/services_config.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright 2016 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os - -# To add services insert key value pair of the name of the service and -# the port you want it to run on when running locally -SERVICES = {"default": 8000, "static": 8001} - - -def init_app(app): - # The GAE_INSTANCE environment variable will be set when deployed to GAE. - gae_instance = os.environ.get("GAE_INSTANCE", os.environ.get("GAE_MODULE_INSTANCE")) - environment = "production" if gae_instance is not None else "development" - app.config["SERVICE_MAP"] = map_services(environment) - - -def map_services(environment): - """Generates a map of services to correct urls for running locally - or when deployed.""" - url_map = {} - for service, local_port in SERVICES.items(): - if environment == "production": - url_map[service] = production_url(service) - if environment == "development": - url_map[service] = local_url(local_port) - return url_map - - -def production_url(service_name): - """Generates url for a service when deployed to App Engine.""" - project_id = os.getenv("GOOGLE_CLOUD_PROJECT") - project_url = f"{project_id}.appspot.com" - if service_name == "default": - return f"https://{project_url}" - else: - return f"https://{service_name}-dot-{project_url}" - - -def local_url(port): - """Generates url for a service when running locally""" - return f"http://localhost:{str(port)}" diff --git a/appengine/flexible_python37_and_earlier/multiple_services/noxfile_config.py b/appengine/flexible_python37_and_earlier/multiple_services/noxfile_config.py deleted file mode 100644 index 1665dd736f8..00000000000 --- a/appengine/flexible_python37_and_earlier/multiple_services/noxfile_config.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Default TEST_CONFIG_OVERRIDE for python repos. - -# You can copy this file into your directory, then it will be imported from -# the noxfile.py. - -# The source of truth: -# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/noxfile_config.py - -TEST_CONFIG_OVERRIDE = { - # You can opt out from the test for specific Python versions. - # Skipping for Python 3.9 due to pyarrow compilation failure. - "ignored_versions": ["2.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"], - # Old samples are opted out of enforcing Python type hints - # All new samples should feature them - "enforce_type_hints": False, - # An envvar key for determining the project id to use. Change it - # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a - # build specific Cloud project. You can also use your own string - # to use your own Cloud project. - "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", - # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', - # A dictionary you want to inject into your test. Don't put any - # secrets here. These values will override predefined values. - "envs": {}, -} diff --git a/appengine/flexible_python37_and_earlier/multiple_services/static-service/app.yaml b/appengine/flexible_python37_and_earlier/multiple_services/static-service/app.yaml deleted file mode 100644 index 0583df96c7e..00000000000 --- a/appengine/flexible_python37_and_earlier/multiple_services/static-service/app.yaml +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright 2021 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -service: static -runtime: python -env: flex -entrypoint: gunicorn -b :$PORT main:app - -runtime_config: - python_version: 3 - -manual_scaling: - instances: 1 diff --git a/appengine/flexible_python37_and_earlier/multiple_services/static-service/main.py b/appengine/flexible_python37_and_earlier/multiple_services/static-service/main.py deleted file mode 100644 index c4b3a8d4799..00000000000 --- a/appengine/flexible_python37_and_earlier/multiple_services/static-service/main.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright 2016 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from flask import Flask - -app = Flask(__name__) - - -@app.route("/hello") -def say_hello(): - """responds to request from frontend via gateway""" - return "Static File Server says hello!" - - -@app.route("/") -def root(): - """serves index.html""" - return app.send_static_file("index.html") - - -@app.route("/") -def static_file(path): - """serves static files required by index.html""" - mimetype = "" - if "." in path and path.split(".")[1] == "css": - mimetype = "text/css" - if "." in path and path.split(".")[1] == "js": - mimetype = "application/javascript" - return app.send_static_file(path), 200, {"Content-Type": mimetype} - - -if __name__ == "__main__": - # This is used when running locally. Gunicorn is used to run the - # application on Google App Engine. See entrypoint in app.yaml. - app.run(host="127.0.0.1", port=8001, debug=True) diff --git a/appengine/flexible_python37_and_earlier/multiple_services/static-service/requirements-test.txt b/appengine/flexible_python37_and_earlier/multiple_services/static-service/requirements-test.txt deleted file mode 100644 index 15d066af319..00000000000 --- a/appengine/flexible_python37_and_earlier/multiple_services/static-service/requirements-test.txt +++ /dev/null @@ -1 +0,0 @@ -pytest==8.2.0 diff --git a/appengine/flexible_python37_and_earlier/multiple_services/static-service/requirements.txt b/appengine/flexible_python37_and_earlier/multiple_services/static-service/requirements.txt deleted file mode 100644 index 052021ed812..00000000000 --- a/appengine/flexible_python37_and_earlier/multiple_services/static-service/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -Flask==3.0.3; python_version > '3.6' -Flask==2.3.3; python_version < '3.7' -gunicorn==23.0.0 -requests==2.31.0 -Werkzeug==3.0.3 diff --git a/appengine/flexible_python37_and_earlier/multiple_services/static-service/static/index.html b/appengine/flexible_python37_and_earlier/multiple_services/static-service/static/index.html deleted file mode 100644 index 9310b700113..00000000000 --- a/appengine/flexible_python37_and_earlier/multiple_services/static-service/static/index.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - API Gateway on App Engine Flexible Environment - - -

      API GATEWAY DEMO

      -

      Say hi to:

      - - -
        - - diff --git a/appengine/flexible_python37_and_earlier/multiple_services/static-service/static/index.js b/appengine/flexible_python37_and_earlier/multiple_services/static-service/static/index.js deleted file mode 100644 index 021f835b9c1..00000000000 --- a/appengine/flexible_python37_and_earlier/multiple_services/static-service/static/index.js +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2016 Google LLC. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -function handleResponse(resp){ - const li = document.createElement('li'); - li.innerHTML = resp; - document.querySelector('.responses').appendChild(li) -} - -function handleClick(event){ - $.ajax({ - url: `hello/${event.target.id}`, - type: `GET`, - success(resp){ - handleResponse(resp); - } - }); -} - -document.addEventListener('DOMContentLoaded', () => { - const buttons = document.getElementsByTagName('button') - for (var i = 0; i < buttons.length; i++) { - buttons[i].addEventListener('click', handleClick); - } -}); diff --git a/appengine/flexible_python37_and_earlier/multiple_services/static-service/static/style.css b/appengine/flexible_python37_and_earlier/multiple_services/static-service/static/style.css deleted file mode 100644 index 65074a9ef4d..00000000000 --- a/appengine/flexible_python37_and_earlier/multiple_services/static-service/static/style.css +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -h1 { - color: red; -} diff --git a/appengine/flexible_python37_and_earlier/numpy/app.yaml b/appengine/flexible_python37_and_earlier/numpy/app.yaml deleted file mode 100644 index ca76f83fc3b..00000000000 --- a/appengine/flexible_python37_and_earlier/numpy/app.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2021 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -runtime: python -env: flex -entrypoint: gunicorn -b :$PORT main:app - -runtime_config: - python_version: 3 diff --git a/appengine/flexible_python37_and_earlier/numpy/main.py b/appengine/flexible_python37_and_earlier/numpy/main.py deleted file mode 100644 index cb14c931d62..00000000000 --- a/appengine/flexible_python37_and_earlier/numpy/main.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright 2016 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import logging - -from flask import Flask -import numpy as np - -app = Flask(__name__) - - -@app.route("/") -def calculate(): - """Performs a dot product on predefined arrays. - - Returns: - Returns a formatted message containing the dot product result of - two predefined arrays. - """ - return_str = "" - x = np.array([[1, 2], [3, 4]]) - y = np.array([[5, 6], [7, 8]]) - - return_str += f"x: {str(x)} , y: {str(y)}
        " - - # Multiply matrices - return_str += f"x dot y : {str(np.dot(x, y))}" - return return_str - - -@app.errorhandler(500) -def server_error(e): - """Serves a formatted message on-error. - - Returns: - The error message and a code 500 status. - """ - logging.exception("An error occurred during a request.") - return ( - f"An internal error occurred:
        {e}

        See logs for full stacktrace.", - 500, - ) - - -if __name__ == "__main__": - # This is used when running locally. Gunicorn is used to run the - # application on Google App Engine. See entrypoint in app.yaml. - app.run(host="127.0.0.1", port=8080, debug=True) diff --git a/appengine/flexible_python37_and_earlier/numpy/main_test.py b/appengine/flexible_python37_and_earlier/numpy/main_test.py deleted file mode 100644 index e25c4dfcac3..00000000000 --- a/appengine/flexible_python37_and_earlier/numpy/main_test.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright 2016 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import main - - -def test_index(): - main.app.testing = True - client = main.app.test_client() - - r = client.get("/") - assert r.status_code == 200 - assert "[[19 22]\n [43 50]]" in r.data.decode("utf-8") diff --git a/appengine/flexible_python37_and_earlier/numpy/noxfile_config.py b/appengine/flexible_python37_and_earlier/numpy/noxfile_config.py deleted file mode 100644 index 1665dd736f8..00000000000 --- a/appengine/flexible_python37_and_earlier/numpy/noxfile_config.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Default TEST_CONFIG_OVERRIDE for python repos. - -# You can copy this file into your directory, then it will be imported from -# the noxfile.py. - -# The source of truth: -# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/noxfile_config.py - -TEST_CONFIG_OVERRIDE = { - # You can opt out from the test for specific Python versions. - # Skipping for Python 3.9 due to pyarrow compilation failure. - "ignored_versions": ["2.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"], - # Old samples are opted out of enforcing Python type hints - # All new samples should feature them - "enforce_type_hints": False, - # An envvar key for determining the project id to use. Change it - # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a - # build specific Cloud project. You can also use your own string - # to use your own Cloud project. - "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", - # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', - # A dictionary you want to inject into your test. Don't put any - # secrets here. These values will override predefined values. - "envs": {}, -} diff --git a/appengine/flexible_python37_and_earlier/numpy/requirements-test.txt b/appengine/flexible_python37_and_earlier/numpy/requirements-test.txt deleted file mode 100644 index 15d066af319..00000000000 --- a/appengine/flexible_python37_and_earlier/numpy/requirements-test.txt +++ /dev/null @@ -1 +0,0 @@ -pytest==8.2.0 diff --git a/appengine/flexible_python37_and_earlier/numpy/requirements.txt b/appengine/flexible_python37_and_earlier/numpy/requirements.txt deleted file mode 100644 index ccd96a3d6d1..00000000000 --- a/appengine/flexible_python37_and_earlier/numpy/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -Flask==3.0.3; python_version > '3.6' -Flask==2.0.3; python_version < '3.7' -gunicorn==23.0.0 -numpy==2.2.4; python_version > '3.9' -numpy==2.2.4; python_version == '3.9' -numpy==2.2.4; python_version == '3.8' -numpy==2.2.4; python_version == '3.7' -Werkzeug==3.0.3 diff --git a/appengine/flexible_python37_and_earlier/pubsub/README.md b/appengine/flexible_python37_and_earlier/pubsub/README.md deleted file mode 100644 index 2e9b0d71918..00000000000 --- a/appengine/flexible_python37_and_earlier/pubsub/README.md +++ /dev/null @@ -1,75 +0,0 @@ -# Python Google Cloud Pub/Sub sample for Google App Engine Flexible Environment - -[![Open in Cloud Shell][shell_img]][shell_link] - -[shell_img]: http://gstatic.com/cloudssh/images/open-btn.png -[shell_link]: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=appengine/flexible_python37_and_earlier/pubsub/README.md - -This demonstrates how to send and receive messages using [Google Cloud Pub/Sub](https://cloud.google.com/pubsub) on [Google App Engine Flexible Environment](https://cloud.google.com/appengine). - -## Setup - -Before you can run or deploy the sample, you will need to do the following: - -1. Enable the Cloud Pub/Sub API in the [Google Developers Console](https://console.developers.google.com/project/_/apiui/apiview/pubsub/overview). - -2. Create a topic and subscription. - - $ gcloud beta pubsub topics create [your-topic-name] - $ gcloud beta pubsub subscriptions create [your-subscription-name] \ - --topic [your-topic-name] \ - --push-endpoint \ - https://[your-app-id].appspot.com/pubsub/push?token=[your-token] \ - --ack-deadline 30 - -3. Update the environment variables in ``app.yaml``. - -## Running locally - -Refer to the [top-level README](../README.md) for instructions on running and deploying. - -When running locally, you can use the [Google Cloud SDK](https://cloud.google.com/sdk) to provide authentication to use Google Cloud APIs: - - $ gcloud init - -Install dependencies, please follow https://cloud.google.com/python/docs/setup -to set up a Python development environment. Then run: - - $ pip install -r requirements.txt - -Then set environment variables before starting your application: - - $ export PUBSUB_VERIFICATION_TOKEN=[your-verification-token] - $ export PUBSUB_TOPIC=[your-topic] - $ python main.py - -### Simulating push notifications - -The application can send messages locally, but it is not able to receive push messages locally. You can, however, simulate a push message by making an HTTP request to the local push notification endpoint. There is an included ``sample_message.json``. You can use -``curl`` or [httpie](https://github.com/jkbrzt/httpie) to POST this: - - $ curl -i --data @sample_message.json ":8080/pubsub/push?token=[your-token]" - -Or - - $ http POST ":8080/pubsub/push?token=[your-token]" < sample_message.json - -Response: - - HTTP/1.0 200 OK - Content-Length: 2 - Content-Type: text/html; charset=utf-8 - Date: Mon, 10 Aug 2015 17:52:03 GMT - Server: Werkzeug/0.10.4 Python/2.7.10 - - OK - -After the request completes, you can refresh ``localhost:8080`` and see the message in the list of received messages. - -## Running on App Engine - -Deploy using `gcloud`: - - gcloud app deploy app.yaml - -You can now access the application at `https://your-app-id.appspot.com`. You can use the form to submit messages, but it's non-deterministic which instance of your application will receive the notification. You can send multiple messages and refresh the page to see the received message. diff --git a/appengine/flexible_python37_and_earlier/pubsub/app.yaml b/appengine/flexible_python37_and_earlier/pubsub/app.yaml deleted file mode 100644 index 5804ac2b266..00000000000 --- a/appengine/flexible_python37_and_earlier/pubsub/app.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright 2021 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -runtime: python -env: flex -entrypoint: gunicorn -b :$PORT main:app - -runtime_config: - python_version: 3 - -# [START gae_flex_pubsub_env] -env_variables: - PUBSUB_TOPIC: your-topic - # This token is used to verify that requests originate from your - # application. It can be any sufficiently random string. - PUBSUB_VERIFICATION_TOKEN: 1234abc -# [END gae_flex_pubsub_env] diff --git a/appengine/flexible_python37_and_earlier/pubsub/main.py b/appengine/flexible_python37_and_earlier/pubsub/main.py deleted file mode 100644 index 5ffc960841c..00000000000 --- a/appengine/flexible_python37_and_earlier/pubsub/main.py +++ /dev/null @@ -1,98 +0,0 @@ -# Copyright 2015 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import base64 -import json -import logging -import os - -from flask import current_app, Flask, render_template, request -from google.cloud import pubsub_v1 - - -app = Flask(__name__) - -# Configure the following environment variables via app.yaml -# This is used in the push request handler to verify that the request came from -# pubsub and originated from a trusted source. -app.config["PUBSUB_VERIFICATION_TOKEN"] = os.environ["PUBSUB_VERIFICATION_TOKEN"] -app.config["PUBSUB_TOPIC"] = os.environ["PUBSUB_TOPIC"] -app.config["PROJECT"] = os.environ["GOOGLE_CLOUD_PROJECT"] - - -# Global list to storage messages received by this instance. -MESSAGES = [] - -# Initialize the publisher client once to avoid memory leak -# and reduce publish latency. -publisher = pubsub_v1.PublisherClient() - - -# [START gae_flex_pubsub_index] -@app.route("/", methods=["GET", "POST"]) -def index(): - if request.method == "GET": - return render_template("index.html", messages=MESSAGES) - - data = request.form.get("payload", "Example payload").encode("utf-8") - - # publisher = pubsub_v1.PublisherClient() - topic_path = publisher.topic_path( - current_app.config["PROJECT"], current_app.config["PUBSUB_TOPIC"] - ) - - publisher.publish(topic_path, data=data) - - return "OK", 200 - - -# [END gae_flex_pubsub_index] - - -# [START gae_flex_pubsub_push] -@app.route("/pubsub/push", methods=["POST"]) -def pubsub_push(): - if request.args.get("token", "") != current_app.config["PUBSUB_VERIFICATION_TOKEN"]: - return "Invalid request", 400 - - envelope = json.loads(request.data.decode("utf-8")) - payload = base64.b64decode(envelope["message"]["data"]) - - MESSAGES.append(payload) - - # Returning any 2xx status indicates successful receipt of the message. - return "OK", 200 - - -# [END gae_flex_pubsub_push] - - -@app.errorhandler(500) -def server_error(e): - logging.exception("An error occurred during a request.") - return ( - """ - An internal error occurred:
        {}
        - See logs for full stacktrace. - """.format( - e - ), - 500, - ) - - -if __name__ == "__main__": - # This is used when running locally. Gunicorn is used to run the - # application on Google App Engine. See entrypoint in app.yaml. - app.run(host="127.0.0.1", port=8080, debug=True) diff --git a/appengine/flexible_python37_and_earlier/pubsub/main_test.py b/appengine/flexible_python37_and_earlier/pubsub/main_test.py deleted file mode 100644 index 37abb0d6240..00000000000 --- a/appengine/flexible_python37_and_earlier/pubsub/main_test.py +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright 2015 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import base64 -import json -import os - -import pytest - -import main - - -@pytest.fixture -def client(): - main.app.testing = True - return main.app.test_client() - - -def test_index(client): - r = client.get("/") - assert r.status_code == 200 - - -def test_post_index(client): - r = client.post("/", data={"payload": "Test payload"}) - assert r.status_code == 200 - - -def test_push_endpoint(client): - url = "/pubsub/push?token=" + os.environ["PUBSUB_VERIFICATION_TOKEN"] - - r = client.post( - url, - data=json.dumps( - {"message": {"data": base64.b64encode(b"Test message").decode("utf-8")}} - ), - ) - - assert r.status_code == 200 - - # Make sure the message is visible on the home page. - r = client.get("/") - assert r.status_code == 200 - assert "Test message" in r.data.decode("utf-8") - - -def test_push_endpoint_errors(client): - # no token - r = client.post("/pubsub/push") - assert r.status_code == 400 - - # invalid token - r = client.post("/pubsub/push?token=bad") - assert r.status_code == 400 diff --git a/appengine/flexible_python37_and_earlier/pubsub/noxfile_config.py b/appengine/flexible_python37_and_earlier/pubsub/noxfile_config.py deleted file mode 100644 index 1665dd736f8..00000000000 --- a/appengine/flexible_python37_and_earlier/pubsub/noxfile_config.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Default TEST_CONFIG_OVERRIDE for python repos. - -# You can copy this file into your directory, then it will be imported from -# the noxfile.py. - -# The source of truth: -# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/noxfile_config.py - -TEST_CONFIG_OVERRIDE = { - # You can opt out from the test for specific Python versions. - # Skipping for Python 3.9 due to pyarrow compilation failure. - "ignored_versions": ["2.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"], - # Old samples are opted out of enforcing Python type hints - # All new samples should feature them - "enforce_type_hints": False, - # An envvar key for determining the project id to use. Change it - # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a - # build specific Cloud project. You can also use your own string - # to use your own Cloud project. - "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", - # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', - # A dictionary you want to inject into your test. Don't put any - # secrets here. These values will override predefined values. - "envs": {}, -} diff --git a/appengine/flexible_python37_and_earlier/pubsub/requirements-test.txt b/appengine/flexible_python37_and_earlier/pubsub/requirements-test.txt deleted file mode 100644 index 15d066af319..00000000000 --- a/appengine/flexible_python37_and_earlier/pubsub/requirements-test.txt +++ /dev/null @@ -1 +0,0 @@ -pytest==8.2.0 diff --git a/appengine/flexible_python37_and_earlier/pubsub/requirements.txt b/appengine/flexible_python37_and_earlier/pubsub/requirements.txt deleted file mode 100644 index d5b7ce68695..00000000000 --- a/appengine/flexible_python37_and_earlier/pubsub/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -Flask==3.0.3; python_version > '3.6' -Flask==2.3.3; python_version < '3.7' -google-cloud-pubsub==2.28.0 -gunicorn==23.0.0 -Werkzeug==3.0.3 diff --git a/appengine/flexible_python37_and_earlier/pubsub/sample_message.json b/appengine/flexible_python37_and_earlier/pubsub/sample_message.json deleted file mode 100644 index 8fe62d23fb9..00000000000 --- a/appengine/flexible_python37_and_earlier/pubsub/sample_message.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "message": { - "data": "SGVsbG8sIFdvcmxkIQ==" - } -} diff --git a/appengine/flexible_python37_and_earlier/pubsub/templates/index.html b/appengine/flexible_python37_and_earlier/pubsub/templates/index.html deleted file mode 100644 index 28449216c37..00000000000 --- a/appengine/flexible_python37_and_earlier/pubsub/templates/index.html +++ /dev/null @@ -1,36 +0,0 @@ -{# -# Copyright 2015 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#} - - - - Pub/Sub Python on Google App Engine Flexible Environment - - -
        -

        Messages received by this instance:

        -
          - {% for message in messages: %} -
        • {{message}}
        • - {% endfor %} -
        -

        Note: because your application is likely running multiple instances, each instance will have a different list of messages.

        -
        -
        - - -
        - - diff --git a/appengine/flexible_python37_and_earlier/scipy/.gitignore b/appengine/flexible_python37_and_earlier/scipy/.gitignore deleted file mode 100644 index de724cf6213..00000000000 --- a/appengine/flexible_python37_and_earlier/scipy/.gitignore +++ /dev/null @@ -1 +0,0 @@ -assets/resized_google_logo.jpg diff --git a/appengine/flexible_python37_and_earlier/scipy/README.md b/appengine/flexible_python37_and_earlier/scipy/README.md deleted file mode 100644 index f1fe346a338..00000000000 --- a/appengine/flexible_python37_and_earlier/scipy/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# SciPy on App Engine Flexible - -[![Open in Cloud Shell][shell_img]][shell_link] - -[shell_img]: http://gstatic.com/cloudssh/images/open-btn.png -[shell_link]: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=appengine/flexible_python37_and_earlier/scipy/README.md - -This sample demonstrates how to use SciPy to resize an image on App Engine Flexible. - diff --git a/appengine/flexible_python37_and_earlier/scipy/app.yaml b/appengine/flexible_python37_and_earlier/scipy/app.yaml deleted file mode 100644 index ca76f83fc3b..00000000000 --- a/appengine/flexible_python37_and_earlier/scipy/app.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2021 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -runtime: python -env: flex -entrypoint: gunicorn -b :$PORT main:app - -runtime_config: - python_version: 3 diff --git a/appengine/flexible_python37_and_earlier/scipy/assets/google_logo.jpg b/appengine/flexible_python37_and_earlier/scipy/assets/google_logo.jpg deleted file mode 100644 index 5538eaed2bdfb5ccdb0f1082afb97a4d0cf1a465..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21568 zcmdqIWpo`o(?Y(+ET`HAIs*+SKbwABKtpI4Uk}{G22m}J8!9U<>4M|%@Ow3SORY_7t zUILr}07%mER`w1MXaHbm@9L~7B}$_GT89L77=Qra0T_S<0E|pr92L|g)d6slkq{$s z0W&@0|D?;G09XaeV0su%E%*|$M=3)ZI!C-9b;p+HIKL%q= zW1D9T0riZX!3u)$%V%uy2d4kc^9L4w#-?`mreL0DogGc>OrLQV7>ByMnSn8+JQ%-s zw=(kp<4G`nW$R{V1;*E4jA>_P#a zSF^WZJpn+>!SS85m4&4%3AG6g2@4MoH;J^Fhpm~bD}%C;iH(u7DT$baouiTcI{^5j z&CjKP7tei50`6o^79LJc23AIJ_y3*#rM1gN1T&yfe zp2=dW>Ley^&hF0`JSLtm&;SB}3Sa`b01-eAPy-AAE5HTt0m6U;APXn~>cDHj05Ab8 z0Xx7M@BqAl0N_0k0elAHffOJU$ODRj3ZMq~0(=F&13!QPU=)}F=7AMp1K0zOflJ^H zJdweIkUVlepT7^1>dW1%XCV-}c z=7pAl)`qr#_JDp5od8_`T@T$2Jq5i1eF*~tgAGFk!v!M^^BTq)#tSAACLN{{<{Qiy z%rBS=SXfwGSUOk%SS45^SXbEhu*tAxu{;YZ*%;O`L75vULZ5Y!Q@5dsjB5GoM55f%{6 z5D^i{5P1+)5UmjX5t9(B5PK0<5pR%QAkiX;A?YExA$>wBMEZs_i*$yJj7*6vjI4v~ ziu?(=7`YR93HcfY1BDSq2E`P`4ls<;$s;x40y@61XE1&OVQ4fHX+~)wXgO#tX;W!O=pg7g>8$9|>Bi`x>3Qhw>2v6( z8ITx67~C038CDsw8RZ!R8NVW1eSu!6M5N$kNPm%1X;> z!kWrD$%f1($>zt_$aca`$8N@+!9K%*!J)wMfun=tfs>2VnX`g(hwByB8?H32X>Lqz zW$sAsULI&3Q64{@uRJ%roV>2QHM~cB416|xC4AfbRQzWAdHlZw$OMc8vISNINd(^r zW(lqckqQ|JtrdZ}!mP^)0wo494PE9UFZcUz6 z-dVm`0YX7mAy#2QkwVd4@rx3mB%>6ow5Uv_?4;bH0;8g=lB%+)%A)G6+M|Y{W~f%8 zcBw9^9;H67L9OAY(V>Z|si#@2d8H+;6{EGH&7|$4J@6XuwbkoJ9atSrodTT;T?yTI z-3>hsz4v-Q^{MpV>h~Go8Q2=M8loB+8P>jmexvoKQu-d{z4QC&4}2doL!m;gLWjfH z!ji)t!%f5cBbXx+BJLwiBKtqGd`$ZI^vV3wa1>`$#%Gw%_Md-73q=>jpvQQ|{EAhG zZHOa@3y(XGH;nH~U{A6IDNneAEI*^@cR)M7+6>sI0Q5V z1bBD^OcYckG#t#AFL5xjvGE8gi1F~r39zwA=t;;asc2|vaETe18K{{lsA;I5g@C~8 z8Uh>w1_A;GH6At|_5bhm)B&KwfxbYJK!V5s2y_r6I_Rkjzz2sk5RlK$JAWr=7!VXJ zBm_7U4@?K={WB913K{|g^E3w_L4pgSL85_UqFw*L%l@AUki7ZN-#sLw3jcTNLs@+7 z>rwyZFjd{X&G_@~nqu|pm@99ux23i33>U$jX}d`lyEDW8U&O$?e**Z{T8Bo)G`3sf zBEEisZ2FVUdo7V&*KL85Ezkh9SdlkO$K7T?L9f2Q#GLYN8&uk6pU$AF>%tG2RjUu_ z{~{PN{an!livit`*ZxSlWp)yt;SVO3lq+vZQT*hfqc&fQjRF^Y8{0UB4;lU^m;5DK zp0@h8b-JE@-&7j@MI^wpEWYKWI)-&taCjcknl2nO`kM>(1Ao;|HG3V zinX#ww>|FZ%ZN0DJV#AR&6wbBqHUmGHeUHak+pyUU zzVJ~VVG74RI1$6?M)4FZ46&NpS1q-AxQXZdAurHI%3*%?$`YLUYbw(Ala7@C(Q-O{qSzJ9q__Wi<|xKuniL*WS^Hjd9kcu1!r z7}(rUm>q`CUE1cq%i1t}c8@deza2~hVsY4b`CD;a=0)@CzKI0}lQle>U&Boattqu) zJF4i1rn}3^7`H<7!KqUTM6QBC6!t{}S}CV2?B76<;eJDX<_Om7SN+!OG^L5jLyv-O z?raHo7ZqQBq8N@gjSTs`(fpaTjkSmT1f;=R`9m?lmO9_23X(O{kM~KpEZ;O*9(?qR zsTtmp#9<7C=9G9=d>=NEPEv;ln7OMH`slkO8BM!K2W?wzmh{ZO|U3W%DE$8T( zcW+Jjm4aJibUt&H`RI1SlVnLPSk1qSTs0;NX_5H4y0b3A3?2_~Rw)dj#_yMmuVl7w zuE;D2VKcp-fGH?E0TfLl6S<9`B7e{@uMm4>ejd%?;PSS|K)aC6>E+4w9r6S6PGcIe zNdf>!iOF?&SA<$9nfX_J^LZqccG%!GWZ%A-=7g~u1;hu$WC&k5Y&f_Q`HVU{G=T^G zyRVkvHxnM75=+B{d=ZJE`Iia@IIQ+NpkxTXxn%)CH_5)X>ZBK10BVNcXs)e@sE_x- z7JYr5zU%%ZL(-g~F%5V>1Z(N~uE+xab?#3CD!b;pmpUnFBc(2$_&N|3LlyG_%?b;I z0fLTz^%)(7b;7You4SXR+_rZYN?Mmt|Gei7O`p}EYt)<>)Pg_9^(CDg_w|t0@{cE= zO+kF%=AliB-Sgc8jOdKs?5=`oQ{Hsa|!!jSPL!d26|rQEtq zSgJGYyRHUl?x6ni-PhE9rMPZ0x@||U zMU8nbH*HPE^VWpX*bohuN#52g*SFE`nRn|s38`L|y*?MTXML<@81$gCB@yqvQ~EqUlwZ}W7rTYx&BbXB*DHK>1+H9HO+VM@^!|{0O#*uO-(A#d>K z(!-WbjF?>cP8-4BfXPHsojNf+Qa7zspR(EL!{s#&*xQ8 z;>F);{m>S$g~-1tt63;4Ws_;AzvN|*b|I(C8wsxU-xA~+HZ7uLTx7lt=gLm6RhmA@ zR(8;@ba~-Di;%gW5rUsCQHmr;{;lMHXn5)<$*|7#ENM1do6hjW9){Lrb<=Qg>(wEl zv3NLh?(UM3{{P|rR|G;=+J1B|J(FlAmdum0*|fZvpTBp5CO1zkG|{yjZY26^js4fs z|5p+Hz(FfL8UTWPPEdb^wU8k2{x~!mItlv=3}#F$QWjR>H)L$e44QQnQIFUFp-)dz42RGzg$bhaUCD zGQTOtKyV;gDYQShVLUN#uSLsIYfdw5Ox7d; zj1=hX%|jZ@;JKAkH=J>%O|v+Mm3+hKLcOGPuD05EWcZ=wHcvL6c8ezYYC(iKjCP?| zHatS}+NbUbFt?z?&M;ile|649Z{0eY%}%qHj9w_s+#>flIjqUR-`>fBCPqFP+(B6i zK?};v{iAlirZyMrsFKI%-|8JF?vOrAypcadlNYHy)li`s51hLg+o9~yv3hyL+FGo? zsrA-1*!m{zvLQkHMa&zT_xSg0Jlb`qSF}5dG1$|UQ3^@0Y6jD9sf2PE?I3r#)`^6_ z#V|&>q2-ZnV5tW2|0bHiFL(Sr7zr7RuG!*{ufQd;vG!~g0}9+M^zdI8G-uKf<=jg4KX|mS zRaJ#+J#RO0a6BJ~udd6M=hcvBAQvj#(K)mex^*y}kZFc&N+cS^i&>TlXKVY$TwbL+ z^ake@K$n*@ud+;P?=A*;h^4g3LOqEO5J!^=|S8vA;Mseo|iC3LTxi1}~wNFnfY_pYQDEoY*_# zV5JyeZrJ3;HsgJMj9!P>bEZtqi%|Hyd=j>K*xRY1x2^ISY2GPf(f-XJUCLOPSc}hD zl-@nFMXtI-LMcCoybu*p>lTs9cYzb|bAqn5YU<#9r7ee-QRzIg!Ho4~r&?_OOfA)e zK`T9Z^KyhX-#FJIXfZ04lml7bqpJHyMkWl+${%KKH$)Tonbo&MRd2V0CKr1%v3_F}V7Sua_ z48g&u+an=!sY{1u#}nAZu(UFrcW%y`#;-ymfA!HcL^_c5)C*XmixOTbUqo=;5C#^e ztofcCQ+mKn?$}Lngrus~e^^O6EY_|qsHJY74UKpL456m32LuQD{RT)plsy&`rIY?r zxS2AR3j+6}aQ(-+9~@yT>QfXU!nX4TRvS%n+wyaerMqgLzczafp<>(v+vy_kh0iQgyVSoH;CR~BCv^yaqE zr3Um?)Gmxmq=*tb8Z=uL%=|7QgWuEI)QY}StvMZ5YJs%$+6R7%V?ARJj5jF4MU3)p zEC-&p8si_i$&1;f?HaaQ3pS5Ysy8veNWPWtpJn*l|a9RZb?Z z3zj4$s&qH0%}#28lrl$Jn&RkP?;{#(u&w{R|L3^S=PBCkdl5W?zrfX*)Q3DvD@WcS zvq%z;vbL$*JI>{LE#O+5voE1JK}lGbh>_3GdtRHzz#rwi=gpX2l9G2lK+uFs#D%ac zUg=IKcp#NzVk@pi54V)|I*u`Krn9v!CZ<(YZN14#OFvqoWW7w3RahOmS5dg=Fv4J) zmtt%{g08>iuek_Ex^;B}OGV6?d5m!=zlHUq3)|SR7NhOS!sId|KFSBkjw6zpHC{|i z?6a@}g2s_gPXGd0lig?wUmoNS;9{IItVZ3v7dLpNz2{FSd)Fi~-D4kEnRnb;bRKOd zk>ej|2&uD0@8z;6f(Wq;dO6OiUq6^HVSN~dR;cYbOURW3x9dePbReOF=?CiQHQx~M%?i}vnqa({PWVvSvEK`s!W-+|G;?Y?diuckwf_9D|Jzag1!RJu}xEM~#2 zKxa|x3v}K0`ml9&C%iB#>xps;*~=UDdE1ceIen2Y{;pw#VQM&T7?vz&Z-h_ zoNTHolMa!i9Xb@{RzGjxWgsM2XC$&j(5gKR({vxrjgI}Vyu3Xxug_lwB3?F?d*aRD zOdLo^=(@(`eUT~9i7cVhP8GwK%*@%V4BS63U7K7q^DWsTSDr?%iDK?9mH&w2L=b6| zxj;!jc>SQcKh1tEG4^IqjIz5lvhA?ftuSi2fXIKtMmo8-K)7&l_q`Xy`9kp)pBFSun^%nAuc>l~s+f zME!pU9N@4-2=oN_v)0}@ITESuwuiGe-j|hS(*>Goor-*4?Af!3`@xFCy@B*za{Ynl z6%za@&J`yT<8cVvH2t~u=1nb;wijhu)K5)2?7Z3Yj019-BSs$1@XU=~Bv(15j00t{ zD3^BK*L6Cv7URXfim7VdQBDsrwU+giBRPsNDm*+YU(~8Ej0b`2l3;eDzuyMOt{ESw8^iFzIs1#-;(kNPk!ThHX`% zGtbY%T`c6wdZA$Yxo$v_5)M7Mux2XCB=+j{tg=2oCND-J zR~I{Mf_`L`kYX^`qX&h^k6d!5_z3>YS@X`4KF-b*oYpDe|EQUMn{Jr}*^+>fYxA|3 zeAG^^9^AV8nMPY4!R&f7C#x|$M#e*RXPyrX>ZiPtX1|)fAm{UnO;{T13M1`qI$6HK$5!@B;HS8P_IlfZXXMNJBO%*w_%>P`d0b7U$)eu%M- z)y6^ZXnbDv%ZB4fgPqWVhZNG?`vs{ZN;?F*4+W`aC7h|M-sduPbBWeva}09TYCZ*p z%=}g~Wi~K)80$7Ldg@Xw;L3~`_<^t!IwMSjkw(KAIF*V+!HqVO^t~9VRt;h&uI%f zzpIp~Ru)p{zSk*fmgKHNq7oS4aZ4Hh<)6{Ryt;_dndxE+PdH&6dfHS_VHP%J7`%n^ zt`6*J1guSCUn zJ%UNzsfglQC$k-37e&D;*J@@^tr5PPS;6k69~(zxR{57DO(w(&c`f z*rs&outOTYR-d#BYVS|LmSx^xgCx?K7<&bJm{@c1K5yXS>-YpLZRMmCj}q@l@KNAe zZ5NfB3Vd+Riym)LtHhGzo{!GxmIFCBjqMQfmjB@^v(~Z?f_)fz1gwnG z4$dW|XZulQT|Ms*6O1>Kg6W9~-y~eY*H3V5_{GEvR=noV$XZ*5Q*U))QuIpSDjwq) zNHDggw=Uu~D{jac^{$l334DHsqW z^q)KWf6Y@Y&=@47tRk3X%qrw;!pcVeqN=~=DTqJjsfanS33IbA&*$FGS7{@Hc;OZs zRh7ZBU)uOY(EW6a>}FhN^yO-BzZdnItqF2%o(+FEM|3cqiF|zJ31n zTz&5Rew{V90-hx1$Y_V}ULNuyUR1{oG(x}uEUevx5XO7P&Sno>kAi}e#5?N`;oVdg z?!rL}t{nM2Al8tcK5v*uwQcDWYVoxLqUnxkIgA``=R_N+`b9p;fptt;AyQ7^gGzZe z>UyVJmT~F~t?$v^xH!)j%(%*r%^&j<2}6!;r?K~QW3_A3@K`k*&|G`uIIFDq$U3;N z^D2CJp1`kN!Q&SG2jb6ZEa|HkzhHZ<5KwhyE-Wbjml}Z@Q}2g_XX_C zRygFeHH_-r*`f!iD9Ru0vCDlEOwPunA6RwT2Wi0eqfyjLVs#8dRxBcl&2#xuYvdA( zn`JyXZevzLw$XmSmmj58sXAB zXy5Tf^%oHxK%TOU@zpq4{=jPt_HODospICRrodq4*FQqC&nZkyCH2gYN~X4s+$PC# zF3H_w`yxT$nOS;b{RI5(rh=DlxgmLG0L)J+I5hX|{Njo8w(jgsXu*)eZaiGHoM={m zzwH8+vX^!xC2l^*wW&o$=t05L*lEGD-xpnUZ zNqo29?`2```pYWar0hZ(H5j9Z+=@0Q+3}U6DtGO-Cu9Yvw=<3y`y!EE{?&SOYwS;= zU6fXm<{_{0cPbfXnPXTzNlRRPTB&z&DHoZNqYu?D%oT^`bEG>bl62d}$QTtYEx)xo zAWdI%rGcl^j@A6dz}!^}-VI^nZwYB3(As_$4x`riWz2t?OGn#ffedcxy8jcg{hsY9 zmvM+~#Aa{08hPu*FY1o*CS+00PsOCBZAN?{!fJ2jK@v&?29U+Ey+h$QHB^(m5B+(# zaXO8UIUVD?h+<##-dBhzTiP#=iAYZ)D=HP*BffW9#?4#17uBfq;O6 zhJgO(yo8SSf`pWeMa7AkRm3QOEgOSe_)WB^vZ`@)Ojh8Z+cq#l&)YU5%&()q9Jvk$ zcsC5Vd5wg4g%zx6j3N9O)pS$xTQ=Pat5jxErY-XxncO{q7*-f{JY=&E@x!sYTTs|J zIX|N7iyh5PAg&?R2|8>`&Cds z3cU+!6%AevQv1Z!d2KaeoyH_un$uwUozZ! z&$IrZ@iuL_wQDJpSb8fXv}rcQ6mxNxxQ5I@DJl=`{btV(k0(IL^pAWSIUU}tvCp@q zQzv+>!mYwpjYJ$9E{tYLzl=$kz^$QYf(^p(^mb$lo!W(Vl`MNj1SPii-S6Z!xyw0@ zRNY#A>C{$Fi^C{e)fd)d)u`3Ne62df1Llm&5}N?B=4-48Z>MN}{{ezUCu_>qrQT!T zN0$mJSz`yK2#$;uh&Y%VI{dzB&c+bx#-8RhHmh7M?To@*TG7@gV6E(o?R&|+yvQIM z%w+$;_a={q*RYRqLrswrkhKmvMo04CPBnhC572zKqG=SZ$|XKN$izu{@Lh;))-z*Z z^3tHN=0YH`c)wN2Y}boP?GK_7Iw32~wc{{brj6dGd%Ew~ZbpM7n@h-H@oW|p@yQ`e zeIclQwT^oEhSo&q;4Z>UY{F^h;}Y{&H8#SAOfePB+}))YJu@a+z;WpGo#c7&B-=8# z`mB+=_@If6zyfF@p{Tw+Gg)aoFsZ7ZHRE+wvs>$$?B;SIDATY^R{QvF%*Ybc*pjvm z^+uIv=0htBS7A3R!N=xq%Ove8W);s4+LjuIw{4$~CZ^=o;Es&az}2`fOoE)!mVVeN>^r7j|MQ;y5dRIm>m|_7h6h7oml%o)PzWkP13=N zH=7z62uf^pt5)9Zx5%#Aw0!$&C^LQt^|UGbQHx!ufAhnQ-slrR$LXtSd!NIee@Eu3 z=(MMBU*7Jo$CK*wf$cg4sDcH;B~U$VU}$z6S8z@DcK%QtZFV!E%8i=cl;$Q1B#Y=B z&;5yVaBf0 zumg5+YCcsy1V)Z1vk$<2fkSP)%A&$`|K}Ij1ibh=O|WizJ&9WSMy*GR)u=%@p1SoY zOPK`dMkPd-_+LOvG=J!VPQz#k^||`%fw{aDty8*Il{T(#!(QaP@uQSQPfPq41f8+T zELib7eZC@1nR*hi(wnoCP7kM|*b{L) z<3zCkvXIm3yPPJM+D8^8Tih<;HuWuR=DJV8Jg8E~`&;Q(dY{QH(T=(qmCT?Kj%UJ3nL4F$ZY~_LmB5J@O<7 zU8+^7sIChpSd42213xzfcKu-tl6AEtPgR1!j=LdmV~uwDAJx(m`Q++G_G76%_>$gpl-QQ}>xw7{s$MYkyq zX%=s?{K2z%!wHchrcL&4s`G=FsT<-(`)Lhq|N52o!YJm3_EpZy0z!&myRGK+E4_@} zT72@>Vs3eQHY07KaUsMQUoEQUu1){ymqNq zdd~180#;u4KIN-Lwb)onsK#zt&@Lv zJprq^zYjvdM;m`^O#Bt`qXVR9BrGCE%qmU+(b?7C$4-Rb94lw7{&f%{L<(L}JCIdB zPYPHn2<&3G++O5PBItjJpUj1GuW55nbCp;v@^kSV--^&k>}rTecmf(nvx|{^iz~-C zu=U&5{Uh`O1qA5M`$OS3o`9utRHk1w!pL)0b@qoRvSnqhDw1;SIj>TE)&0rIp8)4` z{`Sb7Z_Ra6%Kd2+GexdmamYs!_)&X`J?*;s>&2Q3R7mT+{_r{^@%DZTOkxAqCD1#s zTNY{{s`QJXd?pNBht9rr4k6_6)t?s#2bl*bGftB9Pdx5;5*c=X#TI~($ZLj%_|QII zh^jMjTF&C0N_?#}ewm8p^ZYd>^#|d*RT$na1=zdPQ`cR>uF=OtRm!(Jb3tVs; zXrmd=yF)!6r#LUlf-uulyixRN-Y#u!+Kl*B$9Qr%tjkI?9!SdAErsTJG3G1IQV%S34_yn-` zL7r|~t0ZOV$M;|Q(k1TSYthYKS9sK(=p9+CH7$*FyR>G8Z+Qt}4TH1O_wQ9agXv!m z&*QkO4=p-FR2h62|1h+gux&U;BOO8>-L2RPNj*m^r4b)SO3i3BO}F^AEm=9(TWfCz z6YAYA*7iG-njatBli&?;A+jjeqT(htdwXx<$;Im;Oxti~V^?nR;?L4K9NIu8{)gk} zG<4wr?KUQL)&BI)0O<3gY!XFT7O5cFg%*+ASQCY<1r}>(?jj^iW(hCaIz={`eALC|F4O@*AoZBp4aSh$Byn&NIEuyjXJ4j$vsNkY%+t!=2)6GOkPmXEIGD7G zA>#XJP`bkq<7AdQVe75yyX>958Jo3`9dhaBOLrC3?R#=vxg>;j*R$6C_Fn5?IsBpJ zL@`n}!o=x7=E!v1I)wXMfqgSNq}i`mi3|MP3Uf(e#viy}pnUYEiyWDNlT+hObbZ?=a8SFJ=keaGwDfiBU z5eMZWIh7ADh-{25nqL7tcE>&xGM42>%nOk2fHVn_{_`x z7*i_|I%sJELJO(W67k~2UWD)Nxzm~2#XPRr5Xh0~3mRM&J{{IGUtX$zWAh&Ec~?BukHgHsBPVk^gXF~^w0`JucO_#OeV6Kd3Y(n zp7gfivajbQLWI~x(TB;o&|O5>cdA9gHKvV(7;Z4QT)ec{87xx)UgLbEX(9!CTU4=(7(q6s2WRr@!v{1pdiLeaFu~|@B?Kmjb9iJjIy|vO z2tT1+WzYGms@z<5qH&!DEr;7+m)POA=C8LEv6^vJ8;1{QQKPNZdH8vR!LwO1ih$`x z$AI;<7pv%pd9y&cNFP?}7c<3ZeB=(8B?BIqfenwC05TFZ;STy_cONNr$H;WTqhY9cc&`Qt+e>q*H{Bm}+$GUGeG?zsp-z&(0B#50KXX%ks)>9hFc#5~VyyEzcw`a8zXY3RjowmTt__|Cz-Uu`uRRQ#ZrQZ+Xo2*IqoW*@ne zrcUc!^2t}X6`iDogW=+}S};COudJtaBGg=4a5-%>4$oAu&L@EsfMi18XA56kKf&f~ zH^%ajN{kRnnB<29^=xlsc`1h4Ubb!_L=!8ONf4lDy}G5V^O&rn>8&pH1jG{;l)pV4 zYllktXm&cOO@vZ}KIovpgg{wO^(pGk&~xeu7@W)8m$atHbiW0EFC+~GPy36i8()8O zGNI>Z)NI}^R)t{*GL-9)U<|u%>i2NfB?0Vh{uGxnEh{7EUG#jfu&&$0orq{v-O~7@ z3az%hR4KLO-U;)3V)T5^m1(sEBACw$84AFOnU1U8C0$?_@t8ja{3t77neiptgAP5c zdm-o&AzA>r820DULAr{6ioRnZex;XCsT@h!Om`S5BRiIVzcB50$-FOU`hkVzF*lE8 z+3PLrP@-a?x#Y+Yu#^|DRO{KX9)W=^XSQ{;Ge?>zjQMPG7%=_9LizRC;mNvsFZ(EL_2!O_p} zx0!+G101xo5&r+9|I4k+|AqeSf6>`+mj!v2y#KoWXM1ts9sa!CysRI20v^Qt{+azJ zKFyr^v#@Rh=Q4d;%)-CPqIod9l=!0ci2Dhc;m=vS zKO5G%J8G*WZc&-xuig??#ekqkimI>N^Ed+CV|r&fyPiD(tu9LZ$0iCi55A`({Mq17 z?TEck*F=eRzn=ZbI415}t{(SDe{rTr{H$14o0EepxaL!|7vNjaYoXc^{`RAQ$9RD& zXdxg!+*g5GDLYGb$jXW?yNQGUII&VpahMg*>;kPGdLP8Ror z8uTBA>Cmc*Ao3O{>E9*%&34-1cm@#sefwX5)1LpH=f5iZzZD()U`2xtJ~{zGfIpM? z>kTsyfKH0WLL#DK3As?O*D!jda^6EvLhn2nef}->y|Jq~L z#H7!79y!;TUsSp=fxa+i%}GKh&ffbCU(`~ncwS+IZPV)LeOehFt8Hrg>dS^1ER|qu zF@^+Zyoumq^*-*m|Ifx0ckmLiD{w^72&N}Dfn!Cn^pO@&P2M4}3 zGA*UpzsqD(@&><_y{V!XWY31qKdr6m=;b=}jlwS1IV&gvtIU*ZR@18^|2waYR7VK8K5cB)PDGU{tDW zxQf%5yLygC`zHC5=M$j%BY3c z^d54(XBI=qKFNRKv^J{~W4dgw;9tj{TU|=E1?;*u z;yU-3SX@j_o`71cs0Q-%YnT2kE_;T047X+M*HkHH5Zc?;538<>?n0q<2vY;bAX!0a zGfY2~vXPlgXm#%S6=9MY3cc(*-E#4guzCMeN&%-_$eBBPn)l@O!}FYtx=kR%lL7Ep zr2Ts=g1=(={r&}bEV7`H{vM0j0l&v$wg30yliyn$&qtcz!3cnyXapA(?LHme<4I$~ zHOPE4^J;&umg1KZ*f$s9a=v%%VrF?WL4bi&*`=s{okThg-WLc}OduZoe!)V}`@$M= zJ=}!%F^5ibH~a@(+8&!-Q>$NU(5{c@V+zI658u9^on5Llbb>--<9Ub4p+X5hrx|s! zX@~$@(E;}e_pJ1ypTCYm?GhZ7Q;mM^dMMkp4DyESTST2fnVO>@i@DS3=Ju0ya{kQC z4Juy6+Xuh6NA>pVHvQM~u1Az#@shw}ZZMtV6A)frldnVLh>bf2IVy>4xGayiDKAJ$ zN9Q9{gj?oH$P3x7Pw)e74cmN^{elwN4Z1qrDBE8Gm!x35ba3ovsQuA5d0%dc9}ipD z^CqLlCga&!)uB6sE&(;aFw$Ed>&lN6tMJ4H1I10MV=uT~UbYx*i9z>MGej|fALkdt zbmI!-Q#@l~n*Cit%=l={=rd(es_^i0*vN_0KNPM-seKf;iWe@REq&z1&-il+PI5kO z9xH#4ooT&nN55@>THyf6^Q%6vNUnd5_?ca&vjEK&+mI3bz0!qdMPbf=W}%nLs+ZOr zPoqL12VUNtr}{_8=;FBlO_0S$0Td+9j*8tMx_cdn6~Wz{^Ygc(b@#{kg~f&0%#K!s zWT_S8qHG>&fTOwdAUgecEA-83-bDH*M@awhg|GRmSxlxYvkUpMr$VAfH({TVJY7o7 zvLdHBY4pu#`T>kI4OXtC;YthQc_q;|)61UQB3#hfS%^jrjjRVv%u>$k_~e|skZ|3b zp~#0PG!id%-J`fFg2VY}2BCNxy4DJ76&}9lsQFcWTsoa-G7VmLHM-QFV*-EW%F9P| z$nQ2<&c*Ue)OtG4IbTt$(fe{@G>PL1<5f|irMtBYT`DMav51~7$jbrRez_1Hhf%^2 z>Igsk+d&Hfz{uyoRvGp-s1$SlCCsPHMuHFEg{-hW>jytSwvpSS|U|vWZbXTVK~a7J3ROKk`^75 zyM!EYlO$=Yc7|KCe7yDNz9R$x1qJi`UE%LB`Mf-`h^U~E7&$S6_Z`0KF^1{&?4{$lAj>aLP^ zD9zI!ajnzR;^c0;7I{y9a-aH6u~Bww_NT?eK&-%}E~5|U=p#cX<UvvYX?R4_E7z@*OAxT5MfOSqFs>ywLb9q8`r@2%({s@9 z-muFt3rQnfURDy*+Zi8=8K@7cazfG4Zx_h=Oo-i5Q?*g_y~k9UJiL*we!rVCF67m# zNlQpriz&mL8jt=+DT)*k$$Du9=NyE7fQa-`RGWk>MBU zJzG};9tPsBhK20vSRXOs44f3~x8r@Tryt}7nd#J~Uf7K-w7g9Z;uDK)@s%3$OoE_>%w9zH@x+FgYZ>BwzN~r1w?c;zl z{y__Uu{YDVCZn{g7<04r?L%Sdn)PNf-Wq@9W}PfostL}Is570w3#wOA zUTT1RiMCH(ukogbp><01$9v4j*IkU15Qa9F{!rr1itaRM=G8rA$wE#rtFCZ`m^HJG zVkVKg5O#0HYY#6-d&;vjd3&@O2bDON9Q`ecW6vYi@$1YS&1db|zo`+hA#86-feJB4 zTC__ddN@nU?navLm1^!~uc@)(TOFT`xYO`~prD9MV+S5vU%SQfEYW(K9hFhsOp{(6 z#KR~yV%XRpYi>_CP7n_X4PXAYv9pF3Q_a0d z2R0QsBmX`Jy*Xvrz<17z;#|fOHp0cMQ#~DqenvKIrb_LZbZecjdxQxv(aETeW}a7_ zQ4`zWKK)^xm7a1#`0m|(3D^i&syD~1io87MW|Q^$vCf86GHdH@xeHp~q~9B%RFO=y zEfL!;ref1;SDW|Kh_b_0IVsEM7N23ptIvH64p9^qDTj5vdW)il4cMK#g+KwOY1;cM zruGLILf`P6(e53L;e$@&(jp)+U-d(hwre{Y|6t#gAIaTHIByLZMC?3DM^dhEC>&ea z5IBpPw?k^=O$cu%gkVb8$QtE!hF3L3d@XIhmY|L89^YqBfc%o6Vvz6Bs0aL60ZSzl za2a`re9zkuwH0{G-dY;>nu%OvEom;my36xAGj3c3^=HH$@Dge#H!RV-nqaG#*`Ake z?0Q{e+8X?EAORM3x4fH3OFKyyS7z7C?~A0aVsAoaMLq@cg})h~N-icK;@k@gYpSy; z+kgA<)4PNA7*-8@Ev;}m9jGbI4V1&Gu>xpv>N&c)m2BUdOVG#UOXF4@1Sxx^|FkAI zfapOpt)5tqhtSO=`yN9JTkfzCrpx*B{}bE^Blc1yjLmq$p?Z;WWE}?Q8xXgw-Q@s) zz?zd@3Y5Tyt7Gu;1vb`xOW=D(eTM!Y(CfnlBT2i@j&M0Uq`qMe4*_s|9af0w%R}OU z6a>cP$+*$8slv{xW1c)(>ly9(LSzDf#vMvWD#4*ElK zS+^Uusp`YY%DjlBD%$6Z#0Mx7tD*Dd0=dl1J1ZAa$c-Bhp}6p)+%8V98X=m|4Y`G{ zUT`czk~&!p_Ho2a7TuY6M9)CQtAY86O6Kt^n=v(J`51Z4d4QC|p$fAi^F4U|K8(^i zPkiI{+e0a7Rpwd{%Z6Nn`*nrrFdaCfMOI$dV8%N|%Qf;JEVK(ja61`t+wuc|IXB$n zzXI;l-U>sR#AqVE*Q6oNb1;>vTwMtH*)KPPDP0g0%&AYcN9TtjCSzYC2y=tB!8av0 ziRevn0MSZrg1;{dK8LY~8sb@r<^KR-mw+G`g>gYM5uHp7rP`~`jzd{aIleA-Q08aY ze#4K`=*=UP_s&0Uw9+;#95&(rgS5Iw0t{7ZTdCogUsou|kA-}Z#w3)rv)IP!t?e#A1wBlHdEqLbM`i#@M0K4B_IiQ#Mkz&G!8AAj-9!*pi>P zRFwG0@_>NQSH+Y|2`8*O|Q;Rfq9kb5Iw;abGWoi%6sP@rrK!|-he$0G#+N{V$ZBmV@uQIXXnZpzYOVOPyYa`k?#Q2)sFeO zc}yJ7V`(*fjc&>}X#&U=CL(yKF=1y>43@sHVB%8Qgg@wo&>{d`y#(X{9Lwttb-{>> zb_Zdj*yHymqvmNCPtoE;I^p^FhdY$3e4aU{N(^u^QS&&N!OPC}m(0LbEbm+RZoI7Yh3IHa&9tSc&q%qd}c4AIyjKt%X zP=$+j8#|C>y@_KllKC7egH{@DD0udFV_;RO#dxRzhe%8ni0;+kEF75C$lJQCcY9)j+jSjBtiDT%Yb+qBWbE?fRtiSb9CG#!3{w|w&maom=G2!jjmwXmiaW7LwQYFa zmJRvo!#2+R$QjdLlf@oBkl)Mq2GDF96PD&QpnCL=h9M1&4nI$$G>%i>IQ=%z$|Y!> z_h9^TE*PK!n)fpF=4F!%9jPD%SiT+2VBEQUjLL7l19PlIX(=>$Efq7wUQmQKKmAB` zR~sjjNSv=VP(vt=vFntmY-;D-h<3vnT6>Rn8pE|s>vC3z+@pHp%#8;b;nGZJ8M#0<8GfQXog-d$%pjt@=2U{lb>X1$Q;g>EUB?J;ms+0B?qPi%^JpD;=ziwSuyE`d9KTPa!ODB*AEw$FN|k~| zLtSD80TTpt;-E^zq-7w2hXs2x^CQmr2|el^-_bz)#SEFhl;CB)FVSPafpLN@f}Er(}T@;g^R%*2urDe;>AbZJQ?qDrBAln9B6Zk z67guo>({JDji{xr&p2frm>t<(V;U{G?*IT~K=57(lC&2PP;F1cC=HP69;y0^7R?Y} zP?`lge)x^O&*l^FpW@}av=8rUEkxs#dK00nBZ1(uiFwA9q34|KLig7OUeOs?FQ`E^ zkEsJy)Iq?$vrRbt_-+$^Z~#1s(&z@DoIcppDpiMlc@l+A)V{BHU}$G2)-P zzliX=wn;-zLxJERl$b;kx75~JVtn-u<^CoL&CLANr6u3suy3EfmIu68L)HhOlmGw& zAc49D^RL$$M#=8rifO;=2LkX0Pku|xzcj$g9I@nG93Qu`N z8-JRTj4uBG1(&0P>z2QXU&+^v%wPs%eC$T9x7}Cgjtgc^ zxqQt$I+enuO5tDN)Q9G@O}I*6z5f7G#Mh+rb9~@nYFz8D%h#O8B$j4+`wckJs1GX_ z^Zr$X0>u?W1o`vgw3Y)ghNLQ-4x_J5QDge6D#BC_ zD?%)n0pd-;h?$HAqETw~>jt(ov^?jhf-Fb2sY!Gt=nr>ZMm%k0Fwj0>m-%xR7CR=L z<6>$;k>t;t8&i&%-Ix*_R%=y<`HF)hDTjaVFVlp24}hrc#Bi>g!noah-PnJcUMDfK zBAUnLbNt7t{nhzr8Lu`$T0K-O4)vk$85{e}
        See logs for full stacktrace.", - 500, - ) - - -if __name__ == "__main__": - # This is used when running locally. Gunicorn is used to run the - # application on Google App Engine. See entrypoint in app.yaml. - app.run(host="127.0.0.1", port=8080, debug=True) diff --git a/appengine/flexible_python37_and_earlier/scipy/main_test.py b/appengine/flexible_python37_and_earlier/scipy/main_test.py deleted file mode 100644 index d3124ffbb0a..00000000000 --- a/appengine/flexible_python37_and_earlier/scipy/main_test.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright 2016 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import tempfile - -import main - - -def test_index(): - main.app.testing = True - client = main.app.test_client() - with tempfile.TemporaryDirectory() as test_dir: - output_image_path = os.path.join(test_dir, "resized_google_logo.jpg") - r = client.get("/", query_string={"output_image_path": output_image_path}) - - assert os.path.isfile(output_image_path) - assert r.status_code == 200 diff --git a/appengine/flexible_python37_and_earlier/scipy/noxfile_config.py b/appengine/flexible_python37_and_earlier/scipy/noxfile_config.py deleted file mode 100644 index 887244766fd..00000000000 --- a/appengine/flexible_python37_and_earlier/scipy/noxfile_config.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright 2022 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Default TEST_CONFIG_OVERRIDE for python repos. - -# You can copy this file into your directory, then it will be imported from -# the noxfile.py. - -# The source of truth: -# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/noxfile_config.py - -TEST_CONFIG_OVERRIDE = { - # You can opt out from the test for specific Python versions. - # Skipping for Python 3.9 due to pyarrow compilation failure. - "ignored_versions": ["2.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"], - # Old samples are opted out of enforcing Python type hints - # All new samples should feature them - "enforce_type_hints": False, - # An envvar key for determining the project id to use. Change it - # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a - # build specific Cloud project. You can also use your own string - # to use your own Cloud project. - "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", - # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', - # A dictionary you want to inject into your test. Don't put any - # secrets here. These values will override predefined values. - "envs": {}, -} diff --git a/appengine/flexible_python37_and_earlier/scipy/requirements-test.txt b/appengine/flexible_python37_and_earlier/scipy/requirements-test.txt deleted file mode 100644 index 15d066af319..00000000000 --- a/appengine/flexible_python37_and_earlier/scipy/requirements-test.txt +++ /dev/null @@ -1 +0,0 @@ -pytest==8.2.0 diff --git a/appengine/flexible_python37_and_earlier/scipy/requirements.txt b/appengine/flexible_python37_and_earlier/scipy/requirements.txt deleted file mode 100644 index a67d9f49c61..00000000000 --- a/appengine/flexible_python37_and_earlier/scipy/requirements.txt +++ /dev/null @@ -1,11 +0,0 @@ -Flask==3.0.3; python_version > '3.6' -Flask==2.0.3; python_version < '3.7' -gunicorn==23.0.0 -imageio==2.36.1 -numpy==2.2.4; python_version > '3.9' -numpy==2.2.4; python_version == '3.9' -numpy==2.2.4; python_version == '3.8' -numpy==2.2.4; python_version == '3.7' -pillow==10.4.0 -scipy==1.14.1 -Werkzeug==3.0.3 diff --git a/appengine/flexible_python37_and_earlier/static_files/README.md b/appengine/flexible_python37_and_earlier/static_files/README.md deleted file mode 100644 index 024a0abfbd9..00000000000 --- a/appengine/flexible_python37_and_earlier/static_files/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# Python / Flask static files sample for Google App Engine Flexible Environment - -[![Open in Cloud Shell][shell_img]][shell_link] - -[shell_img]: http://gstatic.com/cloudssh/images/open-btn.png -[shell_link]: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=appengine/flexible_python37_and_earlier/static_files/README.md - -This demonstrates how to use [Flask](http://flask.pocoo.org/) to serve static files in your application. - -Flask automatically makes anything in the ``static`` directory available via the ``/static`` URL. If you plan on using a different framework, it may have different conventions for serving static files. - -Refer to the [top-level README](../README.md) for instructions on running and deploying. diff --git a/appengine/flexible_python37_and_earlier/static_files/app.yaml b/appengine/flexible_python37_and_earlier/static_files/app.yaml deleted file mode 100644 index ca76f83fc3b..00000000000 --- a/appengine/flexible_python37_and_earlier/static_files/app.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2021 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -runtime: python -env: flex -entrypoint: gunicorn -b :$PORT main:app - -runtime_config: - python_version: 3 diff --git a/appengine/flexible_python37_and_earlier/static_files/main.py b/appengine/flexible_python37_and_earlier/static_files/main.py deleted file mode 100644 index d77eca69f44..00000000000 --- a/appengine/flexible_python37_and_earlier/static_files/main.py +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright 2015 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# [START gae_flex_python_static_files] -import logging - -from flask import Flask, render_template - - -app = Flask(__name__) - - -@app.route("/") -def hello(): - """Renders and serves a static HTML template page. - - Returns: - A string containing the rendered HTML page. - """ - return render_template("index.html") - - -@app.errorhandler(500) -def server_error(e): - """Serves a formatted message on-error. - - Returns: - The error message and a code 500 status. - """ - logging.exception("An error occurred during a request.") - return ( - f"An internal error occurred:
        {e}

        See logs for full stacktrace.", - 500, - ) - - -if __name__ == "__main__": - # This is used when running locally. Gunicorn is used to run the - # application on Google App Engine. See entrypoint in app.yaml. - app.run(host="127.0.0.1", port=8080, debug=True) -# [END gae_flex_python_static_files] diff --git a/appengine/flexible_python37_and_earlier/static_files/main_test.py b/appengine/flexible_python37_and_earlier/static_files/main_test.py deleted file mode 100644 index 2662db44201..00000000000 --- a/appengine/flexible_python37_and_earlier/static_files/main_test.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2015 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import main - - -def test_index(): - main.app.testing = True - client = main.app.test_client() - - r = client.get("/") - assert r.status_code == 200 - - r = client.get("/static/main.css") - assert r.status_code == 200 diff --git a/appengine/flexible_python37_and_earlier/static_files/noxfile_config.py b/appengine/flexible_python37_and_earlier/static_files/noxfile_config.py deleted file mode 100644 index 1665dd736f8..00000000000 --- a/appengine/flexible_python37_and_earlier/static_files/noxfile_config.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Default TEST_CONFIG_OVERRIDE for python repos. - -# You can copy this file into your directory, then it will be imported from -# the noxfile.py. - -# The source of truth: -# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/noxfile_config.py - -TEST_CONFIG_OVERRIDE = { - # You can opt out from the test for specific Python versions. - # Skipping for Python 3.9 due to pyarrow compilation failure. - "ignored_versions": ["2.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"], - # Old samples are opted out of enforcing Python type hints - # All new samples should feature them - "enforce_type_hints": False, - # An envvar key for determining the project id to use. Change it - # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a - # build specific Cloud project. You can also use your own string - # to use your own Cloud project. - "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", - # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', - # A dictionary you want to inject into your test. Don't put any - # secrets here. These values will override predefined values. - "envs": {}, -} diff --git a/appengine/flexible_python37_and_earlier/static_files/requirements-test.txt b/appengine/flexible_python37_and_earlier/static_files/requirements-test.txt deleted file mode 100644 index 15d066af319..00000000000 --- a/appengine/flexible_python37_and_earlier/static_files/requirements-test.txt +++ /dev/null @@ -1 +0,0 @@ -pytest==8.2.0 diff --git a/appengine/flexible_python37_and_earlier/static_files/requirements.txt b/appengine/flexible_python37_and_earlier/static_files/requirements.txt deleted file mode 100644 index 70ecce34b5b..00000000000 --- a/appengine/flexible_python37_and_earlier/static_files/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -Flask==3.0.3; python_version > '3.6' -Flask==2.0.3; python_version < '3.7' -gunicorn==23.0.0 -Werkzeug==3.0.3 diff --git a/appengine/flexible_python37_and_earlier/static_files/static/main.css b/appengine/flexible_python37_and_earlier/static_files/static/main.css deleted file mode 100644 index f906044f4e4..00000000000 --- a/appengine/flexible_python37_and_earlier/static_files/static/main.css +++ /dev/null @@ -1,21 +0,0 @@ -/* -Copyright 2015 Google LLC. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -/* [START gae_flex_python_css] */ -body { - font-family: Verdana, Helvetica, sans-serif; - background-color: #CCCCFF; -} -/* [END gae_flex_python_css] */ diff --git a/appengine/flexible_python37_and_earlier/static_files/templates/index.html b/appengine/flexible_python37_and_earlier/static_files/templates/index.html deleted file mode 100644 index 13b2ebe61af..00000000000 --- a/appengine/flexible_python37_and_earlier/static_files/templates/index.html +++ /dev/null @@ -1,29 +0,0 @@ -{# -# Copyright 2015 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#} - - - - Static Files - - - - -

        This is a static file serving example.

        - - diff --git a/appengine/flexible_python37_and_earlier/twilio/README.md b/appengine/flexible_python37_and_earlier/twilio/README.md deleted file mode 100644 index 9a62b8400b5..00000000000 --- a/appengine/flexible_python37_and_earlier/twilio/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# Python Twilio voice and SMS sample for Google App Engine Flexible Environment - -[![Open in Cloud Shell][shell_img]][shell_link] - -[shell_img]: http://gstatic.com/cloudssh/images/open-btn.png -[shell_link]: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=appengine/flexible_python37_and_earlier/twilio/README.md - -This sample demonstrates how to use [Twilio](https://www.twilio.com) on [Google App Engine Flexible Environment](https://cloud.google.com/appengine). - -For more information about Twilio, see their [Python quickstart tutorials](https://www.twilio.com/docs/quickstart/python). - -## Setup - -Before you can run or deploy the sample, you will need to do the following: - -1. [Create a Twilio Account](http://ahoy.twilio.com/googlecloudplatform). Google App Engine -customers receive a complimentary credit for SMS messages and inbound messages. - -2. Create a number on twilio, and configure the voice request URL to be ``https://your-app-id.appspot.com/call/receive`` -and the SMS request URL to be ``https://your-app-id.appspot.com/sms/receive``. - -3. Configure your Twilio settings in the environment variables section in ``app.yaml``. - -## Running locally - -Refer to the [top-level README](../README.md) for instructions on running and deploying. - -You can run the application locally to test the callbacks and SMS sending. You -will need to set environment variables before starting your application: - - $ export TWILIO_ACCOUNT_SID=[your-twilio-account-sid] - $ export TWILIO_AUTH_TOKEN=[your-twilio-auth-token] - $ export TWILIO_NUMBER=[your-twilio-number] - $ python main.py diff --git a/appengine/flexible_python37_and_earlier/twilio/app.yaml b/appengine/flexible_python37_and_earlier/twilio/app.yaml deleted file mode 100644 index 0e7de97eb19..00000000000 --- a/appengine/flexible_python37_and_earlier/twilio/app.yaml +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2021 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -runtime: python -env: flex -entrypoint: gunicorn -b :$PORT main:app - -runtime_config: - python_version: 3 - -# [START gae_flex_twilio_env] -env_variables: - TWILIO_ACCOUNT_SID: your-account-sid - TWILIO_AUTH_TOKEN: your-auth-token - TWILIO_NUMBER: your-twilio-number -# [END gae_flex_twilio_env] diff --git a/appengine/flexible_python37_and_earlier/twilio/main.py b/appengine/flexible_python37_and_earlier/twilio/main.py deleted file mode 100644 index 6f2a3a6830f..00000000000 --- a/appengine/flexible_python37_and_earlier/twilio/main.py +++ /dev/null @@ -1,96 +0,0 @@ -# Copyright 2015 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import logging -import os - -from flask import Flask, request -from twilio import rest -from twilio.twiml import messaging_response, voice_response - - -TWILIO_ACCOUNT_SID = os.environ["TWILIO_ACCOUNT_SID"] -TWILIO_AUTH_TOKEN = os.environ["TWILIO_AUTH_TOKEN"] -TWILIO_NUMBER = os.environ["TWILIO_NUMBER"] - - -app = Flask(__name__) - - -# [START gae_flex_twilio_receive_call] -@app.route("/call/receive", methods=["POST"]) -def receive_call(): - """Answers a call and replies with a simple greeting.""" - response = voice_response.VoiceResponse() - response.say("Hello from Twilio!") - return str(response), 200, {"Content-Type": "application/xml"} - - -# [END gae_flex_twilio_receive_call] - - -# [START gae_flex_twilio_send_sms] -@app.route("/sms/send") -def send_sms(): - """Sends a simple SMS message.""" - to = request.args.get("to") - if not to: - return ( - 'Please provide the number to message in the "to" query string' - " parameter." - ), 400 - - client = rest.Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN) - rv = client.messages.create(to=to, from_=TWILIO_NUMBER, body="Hello from Twilio!") - return str(rv) - - -# [END gae_flex_twilio_send_sms] - - -# [START gae_flex_twilio_receive_sms] -@app.route("/sms/receive", methods=["POST"]) -def receive_sms(): - """Receives an SMS message and replies with a simple greeting.""" - sender = request.values.get("From") - body = request.values.get("Body") - - message = f"Hello, {sender}, you said: {body}" - - response = messaging_response.MessagingResponse() - response.message(message) - return str(response), 200, {"Content-Type": "application/xml"} - - -# [END gae_flex_twilio_receive_sms] - - -@app.errorhandler(500) -def server_error(e): - logging.exception("An error occurred during a request.") - return ( - """ - An internal error occurred:
        {}
        - See logs for full stacktrace. - """.format( - e - ), - 500, - ) - - -if __name__ == "__main__": - # This is used when running locally. Gunicorn is used to run the - # application on Google App Engine. See entrypoint in app.yaml. - app.run(host="127.0.0.1", port=8080, debug=True) diff --git a/appengine/flexible_python37_and_earlier/twilio/main_test.py b/appengine/flexible_python37_and_earlier/twilio/main_test.py deleted file mode 100644 index 4878384f65a..00000000000 --- a/appengine/flexible_python37_and_earlier/twilio/main_test.py +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright 2016 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import re - -import pytest -import responses - - -@pytest.fixture -def app(monkeypatch): - monkeypatch.setenv("TWILIO_ACCOUNT_SID", "sid123") - monkeypatch.setenv("TWILIO_AUTH_TOKEN", "auth123") - monkeypatch.setenv("TWILIO_NUMBER", "0123456789") - - import main - - main.app.testing = True - return main.app.test_client() - - -def test_receive_call(app): - r = app.post("/call/receive") - assert "Hello from Twilio!" in r.data.decode("utf-8") - - -@responses.activate -def test_send_sms(app, monkeypatch): - sample_response = { - "sid": "sid", - "date_created": "Wed, 20 Dec 2017 19:32:14 +0000", - "date_updated": "Wed, 20 Dec 2017 19:32:14 +0000", - "date_sent": None, - "account_sid": "account_sid", - "to": "+1234567890", - "from": "+9876543210", - "messaging_service_sid": None, - "body": "Hello from Twilio!", - "status": "queued", - "num_segments": "1", - "num_media": "0", - "direction": "outbound-api", - "api_version": "2010-04-01", - "price": None, - "price_unit": "USD", - "error_code": None, - "error_message": None, - "uri": "/2010-04-01/Accounts/sample.json", - "subresource_uris": {"media": "/2010-04-01/Accounts/sample/Media.json"}, - } - responses.add(responses.POST, re.compile(".*"), json=sample_response, status=200) - - r = app.get("/sms/send") - assert r.status_code == 400 - - r = app.get("/sms/send?to=5558675309") - assert r.status_code == 200 - - -def test_receive_sms(app): - r = app.post( - "/sms/receive", data={"From": "5558675309", "Body": "Jenny, I got your number."} - ) - assert r.status_code == 200 - assert "Jenny, I got your number" in r.data.decode("utf-8") diff --git a/appengine/flexible_python37_and_earlier/twilio/noxfile_config.py b/appengine/flexible_python37_and_earlier/twilio/noxfile_config.py deleted file mode 100644 index 1665dd736f8..00000000000 --- a/appengine/flexible_python37_and_earlier/twilio/noxfile_config.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Default TEST_CONFIG_OVERRIDE for python repos. - -# You can copy this file into your directory, then it will be imported from -# the noxfile.py. - -# The source of truth: -# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/noxfile_config.py - -TEST_CONFIG_OVERRIDE = { - # You can opt out from the test for specific Python versions. - # Skipping for Python 3.9 due to pyarrow compilation failure. - "ignored_versions": ["2.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"], - # Old samples are opted out of enforcing Python type hints - # All new samples should feature them - "enforce_type_hints": False, - # An envvar key for determining the project id to use. Change it - # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a - # build specific Cloud project. You can also use your own string - # to use your own Cloud project. - "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", - # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', - # A dictionary you want to inject into your test. Don't put any - # secrets here. These values will override predefined values. - "envs": {}, -} diff --git a/appengine/flexible_python37_and_earlier/twilio/requirements-test.txt b/appengine/flexible_python37_and_earlier/twilio/requirements-test.txt deleted file mode 100644 index e89f6031ad7..00000000000 --- a/appengine/flexible_python37_and_earlier/twilio/requirements-test.txt +++ /dev/null @@ -1,3 +0,0 @@ -pytest==8.2.0 -responses==0.17.0; python_version < '3.7' -responses==0.23.1; python_version > '3.6' diff --git a/appengine/flexible_python37_and_earlier/twilio/requirements.txt b/appengine/flexible_python37_and_earlier/twilio/requirements.txt deleted file mode 100644 index cfa80d12edf..00000000000 --- a/appengine/flexible_python37_and_earlier/twilio/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -Flask==3.0.3; python_version > '3.6' -Flask==2.0.3; python_version < '3.7' -gunicorn==23.0.0 -twilio==9.0.3 -Werkzeug==3.0.3; python_version >= '3.7' -Werkzeug==2.3.8; python_version < '3.7' From eb0e055aacca566acb7dd81dcb2bf4ffe559487e Mon Sep 17 00:00:00 2001 From: alarconesparza Date: Fri, 20 Feb 2026 20:54:36 -0600 Subject: [PATCH 57/67] chore(bigquery-datatransfer): Remove bigquerydatatransfer_update_config sample and test (#13830) --- .../snippets/manage_transfer_configs.py | 35 ------------------- .../snippets/manage_transfer_configs_test.py | 13 ------- 2 files changed, 48 deletions(-) diff --git a/bigquery-datatransfer/snippets/manage_transfer_configs.py b/bigquery-datatransfer/snippets/manage_transfer_configs.py index 57da430afb5..726b4caf8f2 100644 --- a/bigquery-datatransfer/snippets/manage_transfer_configs.py +++ b/bigquery-datatransfer/snippets/manage_transfer_configs.py @@ -13,41 +13,6 @@ # limitations under the License. -def update_config(override_values={}): - # [START bigquerydatatransfer_update_config] - from google.cloud import bigquery_datatransfer - from google.protobuf import field_mask_pb2 - - transfer_client = bigquery_datatransfer.DataTransferServiceClient() - - transfer_config_name = "projects/1234/locations/us/transferConfigs/abcd" - new_display_name = "My Transfer Config" - # [END bigquerydatatransfer_update_config] - # To facilitate testing, we replace values with alternatives - # provided by the testing harness. - new_display_name = override_values.get("new_display_name", new_display_name) - transfer_config_name = override_values.get( - "transfer_config_name", transfer_config_name - ) - # [START bigquerydatatransfer_update_config] - - transfer_config = bigquery_datatransfer.TransferConfig(name=transfer_config_name) - transfer_config.display_name = new_display_name - - transfer_config = transfer_client.update_transfer_config( - { - "transfer_config": transfer_config, - "update_mask": field_mask_pb2.FieldMask(paths=["display_name"]), - } - ) - - print(f"Updated config: '{transfer_config.name}'") - print(f"New display name: '{transfer_config.display_name}'") - # [END bigquerydatatransfer_update_config] - # Return the config name for testing purposes, so that it can be deleted. - return transfer_config - - def update_credentials_with_service_account(override_values={}): # [START bigquerydatatransfer_update_credentials] from google.cloud import bigquery_datatransfer diff --git a/bigquery-datatransfer/snippets/manage_transfer_configs_test.py b/bigquery-datatransfer/snippets/manage_transfer_configs_test.py index 0110fe29fd5..505c61d269c 100644 --- a/bigquery-datatransfer/snippets/manage_transfer_configs_test.py +++ b/bigquery-datatransfer/snippets/manage_transfer_configs_test.py @@ -15,19 +15,6 @@ from . import manage_transfer_configs -def test_update_config(capsys, transfer_config_name): - manage_transfer_configs.update_config( - { - "new_display_name": "name from test_update_config", - "transfer_config_name": transfer_config_name, - } - ) - out, _ = capsys.readouterr() - assert "Updated config:" in out - assert transfer_config_name in out - assert "name from test_update_config" in out - - def test_update_credentials_with_service_account( capsys, project_id, service_account_name, transfer_config_name ): From ddfe339a1e76014f267fd63f9a780d26db315967 Mon Sep 17 00:00:00 2001 From: Jonathan Yasharel Date: Wed, 25 Feb 2026 15:44:35 -0500 Subject: [PATCH 58/67] feat(cloudkms): add samples for CryptoKey/CryptoKeyVersion deletion and get/lists RetiredResources (#13852) * feat: Add python delete and retired resource samples * Add and fix KMS delete samples * Add permanent deletion warning to delete_key and delete_key_version snippets --- kms/snippets/delete_key.py | 54 ++++++++++++++++++++++++ kms/snippets/delete_key_version.py | 57 ++++++++++++++++++++++++++ kms/snippets/get_retired_resource.py | 50 ++++++++++++++++++++++ kms/snippets/list_retired_resources.py | 50 ++++++++++++++++++++++ kms/snippets/requirements.txt | 2 +- kms/snippets/snippets_test.py | 41 ++++++++++++++++++ 6 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 kms/snippets/delete_key.py create mode 100644 kms/snippets/delete_key_version.py create mode 100644 kms/snippets/get_retired_resource.py create mode 100644 kms/snippets/list_retired_resources.py diff --git a/kms/snippets/delete_key.py b/kms/snippets/delete_key.py new file mode 100644 index 00000000000..512e3df6a42 --- /dev/null +++ b/kms/snippets/delete_key.py @@ -0,0 +1,54 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START kms_delete_key] +from google.cloud import kms + + +def delete_key( + project_id: str, location_id: str, key_ring_id: str, key_id: str +) -> None: + """ + Delete the given key. This action is permanent and cannot be undone. Once the + key is deleted, it will no longer exist. + + Args: + project_id (str): Google Cloud project ID (e.g. 'my-project'). + location_id (str): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (str): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (str): ID of the key to use (e.g. 'my-key'). + + Returns: + None + + """ + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the key name. + key_name = client.crypto_key_path(project_id, location_id, key_ring_id, key_id) + + # Call the API. + # Note: delete_crypto_key returns a long-running operation. + # Warning: This operation is permanent and cannot be undone. + operation = client.delete_crypto_key(request={"name": key_name}) + + # Wait for the operation to complete. + operation.result() + + print(f"Deleted key: {key_name}") + + +# [END kms_delete_key] diff --git a/kms/snippets/delete_key_version.py b/kms/snippets/delete_key_version.py new file mode 100644 index 00000000000..669de9afbd6 --- /dev/null +++ b/kms/snippets/delete_key_version.py @@ -0,0 +1,57 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START kms_delete_key_version] +from google.cloud import kms + + +def delete_key_version( + project_id: str, location_id: str, key_ring_id: str, key_id: str, version_id: str +) -> None: + """ + Delete the given key version. This action is permanent and cannot be undone. + Once the key version is deleted, it will no longer exist. + + Args: + project_id (str): Google Cloud project ID (e.g. 'my-project'). + location_id (str): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (str): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (str): ID of the key to use (e.g. 'my-key'). + version_id (str): ID of the key version to delete (e.g. '1'). + + Returns: + None + + """ + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the key version name. + key_version_name = client.crypto_key_version_path( + project_id, location_id, key_ring_id, key_id, version_id + ) + + # Call the API. + # Note: delete_crypto_key_version returns a long-running operation. + # Warning: This operation is permanent and cannot be undone. + operation = client.delete_crypto_key_version(request={"name": key_version_name}) + + # Wait for the operation to complete. + operation.result() + + print(f"Deleted key version: {key_version_name}") + + +# [END kms_delete_key_version] diff --git a/kms/snippets/get_retired_resource.py b/kms/snippets/get_retired_resource.py new file mode 100644 index 00000000000..48042d7fa9f --- /dev/null +++ b/kms/snippets/get_retired_resource.py @@ -0,0 +1,50 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START kms_get_retired_resource] +from google.cloud import kms + + +def get_retired_resource( + project_id: str, location_id: str, retired_resource_id: str +) -> kms.RetiredResource: + """ + Get the details of a retired resource. + + Args: + project_id (str): Google Cloud project ID (e.g. 'my-project'). + location_id (str): Cloud KMS location (e.g. 'us-east1'). + resource_id (str): ID of the retired resource to get. + + Returns: + kms.RetiredResource: The requested retired resource. + + """ + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the retired resource name. + # Note: Retired resources are tied to a Location, not a KeyRing. + # The name is like projects/{project}/locations/{location}/retiredResources/{id} + name = client.retired_resource_path(project_id, location_id, retired_resource_id) + + # Call the API. + response = client.get_retired_resource(request={"name": name}) + + print(f"Got retired resource: {response.name}") + return response + + +# [END kms_get_retired_resource] diff --git a/kms/snippets/list_retired_resources.py b/kms/snippets/list_retired_resources.py new file mode 100644 index 00000000000..9393b34de1c --- /dev/null +++ b/kms/snippets/list_retired_resources.py @@ -0,0 +1,50 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START kms_list_retired_resources] +from typing import List + +from google.cloud import kms + + +def list_retired_resources(project_id: str, location_id: str) -> List[kms.RetiredResource]: + """ + List the retired resources in a location. + + Args: + project_id (str): Google Cloud project ID (e.g. 'my-project'). + location_id (str): Cloud KMS location (e.g. 'us-east1'). + + Returns: + list[kms.RetiredResource]: The list of retired resources. + """ + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the parent location name. + parent = client.common_location_path(project_id, location_id) + + # Call the API. + # The API paginates, but the Python client library handles that for us. + resources_list = list(client.list_retired_resources(request={"parent": parent})) + + # Iterate over the resources and print them. + for resource in resources_list: + print(f"Retired resource: {resource.name}") + + return resources_list + + +# [END kms_list_retired_resources] diff --git a/kms/snippets/requirements.txt b/kms/snippets/requirements.txt index 6e15391cfd6..167c2a25011 100644 --- a/kms/snippets/requirements.txt +++ b/kms/snippets/requirements.txt @@ -1,4 +1,4 @@ -google-cloud-kms==3.2.1 +google-cloud-kms==3.11.0 cryptography==45.0.1 crcmod==1.7 jwcrypto==1.5.6 \ No newline at end of file diff --git a/kms/snippets/snippets_test.py b/kms/snippets/snippets_test.py index 970cf13dfe6..002ab499269 100644 --- a/kms/snippets/snippets_test.py +++ b/kms/snippets/snippets_test.py @@ -52,6 +52,7 @@ from create_key_version import create_key_version from decrypt_asymmetric import decrypt_asymmetric from decrypt_symmetric import decrypt_symmetric +from delete_key import delete_key from destroy_key_version import destroy_key_version from disable_key_version import disable_key_version from enable_key_version import enable_key_version @@ -62,10 +63,12 @@ from get_key_version_attestation import get_key_version_attestation from get_public_key import get_public_key from get_public_key_jwk import get_public_key_jwk +from get_retired_resource import get_retired_resource from iam_add_member import iam_add_member from iam_get_policy import iam_get_policy from iam_remove_member import iam_remove_member from import_manually_wrapped_key import import_manually_wrapped_key +from list_retired_resources import list_retired_resources from quickstart import quickstart from restore_key_version import restore_key_version from sign_asymmetric import sign_asymmetric @@ -886,3 +889,41 @@ def test_verify_mac( def test_quickstart(project_id: str, location_id: str) -> None: key_rings = quickstart(project_id, location_id) assert key_rings + + +def test_delete_key_and_retired_resources( + client: kms.KeyManagementServiceClient, + project_id: str, + location_id: str, + key_ring_id: str, +) -> None: + # We can test key deletion and retired resources by first creating a key. + key_id = f"delete-key-{uuid.uuid4()}" + key_ring_name = client.key_ring_path(project_id, location_id, key_ring_id) + key = client.create_crypto_key( + request={ + "parent": key_ring_name, + "crypto_key_id": key_id, + "crypto_key": { + "purpose": kms.CryptoKey.CryptoKeyPurpose.ENCRYPT_DECRYPT, + }, + "skip_initial_version_creation": True, + } + ) + + # Delete the key. + delete_key(project_id, location_id, key_ring_id, key_id) + + # List retired resources and filter to just our deleted key. + all_retired = list_retired_resources(project_id, location_id) + filtered_retired = [r for r in all_retired if r.original_resource == key.name] + + # Make sure the len is 1 + assert len(filtered_retired) == 1 + + # Get the retired resource + resource_id = filtered_retired[0].name.split("/")[-1] + retrieved = get_retired_resource(project_id, location_id, resource_id) + + # See if the result is the same as retired resource list[0] + assert retrieved.name == filtered_retired[0].name From 6dc735a833e89db4c28e8c8ac2647c2d8c1f9401 Mon Sep 17 00:00:00 2001 From: Chalmer Lowe Date: Thu, 26 Feb 2026 15:40:08 -0500 Subject: [PATCH 59/67] chore: migrate code from googleapis/python-datastore (#13837) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs(samples): add samples for IN, NOT_IN, and != operators. (#312) * docs: add samples for IN, NOT_IN, and != operators. * Update samples/snippets_test.py Co-authored-by: Mariatta Wijaya * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Add an index.yaml file with required indexes * Fix linting errors. Remove unused libraries. * Opt into build specific gcloud projects. * Typo in code comment * Add a test fixture to set up a required index. * Remove snippets that require an index. Will add them in another PR. * Fix linting error * Create a snippets subdirectory under samples * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Use build-specific gcloud projects for noxfile. * Restore noxfile.py to owlbot version. Add noxfile_config.py Co-authored-by: Mariatta Wijaya Co-authored-by: Owl Bot * fix: require python 3.7+ (#332) * chore(python): drop python 3.6 Source-Link: https://github.com/googleapis/synthtool/commit/4f89b13af10d086458f9b379e56a614f9d6dab7b Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:e7bb19d47c13839fe8c147e50e02e8b6cf5da8edd1af8b82208cd6f66cc2829c * add api_description to .repo-metadata.json * require python 3.7+ in setup.py * remove python 3.6 sample configs * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * remove python 3.6 from noxfile * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * remove python 3.6 from noxfile * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * ci: update replacement in owlbot.py * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou * chore(deps): update all dependencies (#340) * chore(deps): update all dependencies * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * revert * revert Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou * docs: Move the schedule_export samples from python-docs-samples (#344) Moved from https://github.com/GoogleCloudPlatform/python-docs-samples/tree/main/datastore/schedule-export * docs(samples): Add an example of using read_time in queries and get() (#342) * Add an example of using read_time in queries and get() * Fix test for query_with_readtime * chore(deps): update dependency google-cloud-datastore to v2.8.1 (#348) * chore(deps): update dependency pytest to v7.1.3 (#359) * chore: detect samples tests in nested directories (#360) Source-Link: https://github.com/googleapis/synthtool/commit/50db768f450a50d7c1fd62513c113c9bb96fd434 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:e09366bdf0fd9c8976592988390b24d53583dd9f002d476934da43725adbb978 * samples: Update the read_time snippet. (#363) Co-authored-by: Anthonios Partheniou * chore(deps): update dependency google-cloud-datastore to v2.8.2 (#369) * chore(deps): update dependency backoff to v2.2.1 (#371) * chore(deps): update dependency google-cloud-datastore to v2.8.3 (#375) * chore(deps): update dependency google-cloud-datastore to v2.9.0 (#376) * chore(deps): update dependency pytest to v7.2.0 (#377) * chore(deps): update dependency google-cloud-datastore to v2.10.0 (#381) * chore(python): drop flake8-import-order in samples noxfile (#387) Source-Link: https://github.com/googleapis/synthtool/commit/6ed3a831cb9ff69ef8a504c353e098ec0192ad93 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:3abfa0f1886adaf0b83f07cb117b24a639ea1cb9cffe56d43280b977033563eb Co-authored-by: Owl Bot * chore(deps): update dependency google-cloud-datastore to v2.11.0 (#389) * samples: Add snippets and samples for Count query (#383) * Add samples for Count query * Remove unused variable. * Add count query samples with limit * Fix the stale read test. * Raise ValueError instead of general Exception * chore(deps): update dependency google-cloud-datastore to v2.11.1 (#394) * chore(python): add support for python 3.11 (#395) Source-Link: https://github.com/googleapis/synthtool/commit/7197a001ffb6d8ce7b0b9b11c280f0c536c1033a Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:c43f1d918bcf817d337aa29ff833439494a158a0831508fda4ec75dc4c0d0320 Co-authored-by: Owl Bot * chore(deps): update dependency google-cloud-datastore to v2.12.0 (#399) * chore(deps): update dependency pytest to v7.2.1 (#403) * chore(deps): update dependency google-cloud-datastore to v2.13.0 (#405) * chore(deps): update dependency google-cloud-datastore to v2.13.1 (#409) * chore(deps): update dependency google-cloud-datastore to v2.13.2 (#411) * chore(deps): update dependency google-cloud-datastore to v2.14.0 (#423) * chore(deps): update dependency pytest to v7.2.2 (#424) * chore(deps): update dependency google-cloud-datastore to v2.15.0 (#426) Co-authored-by: Mariatta Wijaya * chore(deps): update dependency google-cloud-datastore to v2.15.1 (#431) * chore(deps): update dependency pytest to v7.3.1 (#433) * chore(deps): update dependency google-cloud-datastore to v2.15.2 (#438) Co-authored-by: meredithslota * chore(deps): update dependency pytest to v7.3.2 (#445) * chore(deps): update all dependencies (#449) Co-authored-by: Anthonios Partheniou * chore(deps): update dependency google-cloud-datastore to v2.16.1 (#454) * chore(deps): update dependency google-cloud-datastore to v2.17.0 (#469) * chore(deps): update all dependencies (#473) * chore(deps): update all dependencies (#475) * chore(deps): update all dependencies (#483) * samples: Add snippets for sum and avg (#480) * chore(deps): update all dependencies (#493) * chore(deps): update all dependencies * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot * feat: Add support for Python 3.12 (#498) * chore(python): Add Python 3.12 Source-Link: https://github.com/googleapis/synthtool/commit/af16e6d4672cc7b400f144de2fc3068b54ff47d2 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:bacc3af03bff793a03add584537b36b5644342931ad989e3ba1171d3bd5399f5 * Add python 3.12 to setup.py, constraints and required checks --------- Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou Co-authored-by: Daniel Sanche * chore(deps): update dependency google-cloud-datastore to v2.19.0 (#508) * chore(deps): update dependency pytest to v7.4.4 (#511) * feat: implement query profiling (#542) * chore(deps): update all dependencies (#519) * chore(deps): update all dependencies * pin pytest for python 3.7 --------- Co-authored-by: Daniel Sanche Co-authored-by: Anthonios Partheniou * chore(deps): update all dependencies (#563) * chore(deps): update all dependencies * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou * chore(python): update dependencies in .kokoro/docker/docs (#574) * chore(python): update dependencies in .kokoro/docker/docs Source-Link: https://github.com/googleapis/synthtool/commit/59171c8f83f3522ce186e4d110d27e772da4ba7a Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:2ed982f884312e4883e01b5ab8af8b6935f0216a5a2d82928d273081fc3be562 * Add constraints file for python 3.13 --------- Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou * chore(python): Add support for Python 3.14 (#644) This PR adds support for Python 3.14 to the library. Key changes include: - Update to `.github/workflows` files to account for Python runtimes both 3.14 and 3.8. - Adding Python 3.14 to the test matrix in `.github/workflows/unittest.yml`, etc. - Updating `.github/sync-repo-settings.yaml` to include 3.14 unit tests in required checks. - Update to `.kokoro/presubmit` files to update the system test and `presubmit.cfg` - Adding `testing/constraints-3.14.txt`. - Updates `CONTRIBUTING.rst` to list Python 3.14 as a supported version. - Updates `mypy.ini` to suppress several known type hinting errors in two files. - Updating `noxfile.py` to include 3.14 sessions. - Updates to `owlbot.py` to include Python 3.14. - Updating `setup.py` to include the Python 3.14 classifier and add conditional dependencies for `grpcio`. --------- Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou * chore(deps): update all dependencies (#660) > **Note:** This PR body was truncated due to platform limits. This PR contains the following updates: | Package | Type | Update | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---|---|---| | [actions/checkout](https://redirect.github.com/actions/checkout) | action | major | `v4` → `v6` | ![age](https://developer.mend.io/api/mc/badges/age/github-tags/actions%2fcheckout/v6?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/github-tags/actions%2fcheckout/v4/v6?slim=true) | | [actions/setup-python](https://redirect.github.com/actions/setup-python) | action | major | `v5` → `v6` | ![age](https://developer.mend.io/api/mc/badges/age/github-tags/actions%2fsetup-python/v6?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/github-tags/actions%2fsetup-python/v5/v6?slim=true) | | [google-cloud-datastore](https://redirect.github.com/googleapis/python-datastore) | | minor | `==2.20.0` → `==2.23.0` | ![age](https://developer.mend.io/api/mc/badges/age/pypi/google-cloud-datastore/2.23.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/google-cloud-datastore/2.20.0/2.23.0?slim=true) | | [pytest](https://redirect.github.com/pytest-dev/pytest) ([changelog](https://docs.pytest.org/en/stable/changelog.html)) | | major | `==8.3.2` → `==9.0.2` | ![age](https://developer.mend.io/api/mc/badges/age/pypi/pytest/9.0.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/pytest/8.3.2/9.0.2?slim=true) | | [python](https://redirect.github.com/actions/python-versions) | uses-with | minor | `3.10` → `3.14` | ![age](https://developer.mend.io/api/mc/badges/age/github-releases/actions%2fpython-versions/3.14.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/github-releases/actions%2fpython-versions/3.10.19/3.14.2?slim=true) | --- ### Release Notes
        actions/checkout (actions/checkout) ### [`v6`](https://redirect.github.com/actions/checkout/compare/v5...v6) [Compare Source](https://redirect.github.com/actions/checkout/compare/v5...v6) ### [`v5`](https://redirect.github.com/actions/checkout/compare/v4...v5) [Compare Source](https://redirect.github.com/actions/checkout/compare/v4...v5)
        actions/setup-python (actions/setup-python) ### [`v6`](https://redirect.github.com/actions/setup-python/compare/v5...v6) [Compare Source](https://redirect.github.com/actions/setup-python/compare/v5...v6)
        googleapis/python-datastore (google-cloud-datastore) ### [`v2.23.0`](https://redirect.github.com/googleapis/python-datastore/blob/HEAD/CHANGELOG.md#2230-2025-12-16) [Compare Source](https://redirect.github.com/googleapis/python-datastore/compare/v2.22.0...v2.23.0) ##### Features - support mTLS certificates when available ([#​658](https://redirect.github.com/googleapis/python-datastore/issues/658)) ([85c023287daebb0d5c1a009e2beaccf0c6ea75eb](https://redirect.github.com/googleapis/python-datastore/commit/85c023287daebb0d5c1a009e2beaccf0c6ea75eb)) ### [`v2.22.0`](https://redirect.github.com/googleapis/python-datastore/blob/HEAD/CHANGELOG.md#2220-2025-12-12) [Compare Source](https://redirect.github.com/googleapis/python-datastore/compare/v2.21.0...v2.22.0) ##### Bug Fixes - remove setup.cfg configuration for creating universal wheels ([#​601](https://redirect.github.com/googleapis/python-datastore/issues/601)) ([df729015149bd69e9d6dbced260d97c8eed77d4f](https://redirect.github.com/googleapis/google-cloud-python/commit/df729015149bd69e9d6dbced260d97c8eed77d4f)) ### [`v2.21.0`](https://redirect.github.com/googleapis/python-datastore/blob/HEAD/CHANGELOG.md#2210-2025-04-10) [Compare Source](https://redirect.github.com/googleapis/python-datastore/compare/v2.20.2...v2.21.0) ##### Features - Add REST Interceptors which support reading metadata ([7be9c4c](https://redirect.github.com/googleapis/python-datastore/commit/7be9c4c594af2c2414e394b8bfe62574b58ef337)) - Add support for opt-in debug logging ([7be9c4c](https://redirect.github.com/googleapis/python-datastore/commit/7be9c4c594af2c2414e394b8bfe62574b58ef337)) ##### Bug Fixes - Allow protobuf 6.x ([#​598](https://redirect.github.com/googleapis/python-datastore/issues/598)) ([7c1171b](https://redirect.github.com/googleapis/python-datastore/commit/7c1171bf657f7cf4d1404e19611f6c874a8998ca)) - Backwards-compatibility for previous meaning format ([#​603](https://redirect.github.com/googleapis/python-datastore/issues/603)) ([ed92e8e](https://redirect.github.com/googleapis/python-datastore/commit/ed92e8e54a9e0f44302efee89a30a322d0a73636)) - Fix typing issue with gRPC metadata when key ends in -bin ([7be9c4c](https://redirect.github.com/googleapis/python-datastore/commit/7be9c4c594af2c2414e394b8bfe62574b58ef337)) ### [`v2.20.2`](https://redirect.github.com/googleapis/python-datastore/blob/HEAD/CHANGELOG.md#2202-2024-12-12) [Compare Source](https://redirect.github.com/googleapis/python-datastore/compare/v2.20.1...v2.20.2) ##### Bug Fixes - Preserve list meanings ([#​575](https://redirect.github.com/googleapis/python-datastore/issues/575)) ([266243b](https://redirect.github.com/googleapis/python-datastore/commit/266243ba360a9d41ab4b51c323eac44d2cfc35cb)) ### [`v2.20.1`](https://redirect.github.com/googleapis/python-datastore/blob/HEAD/CHANGELOG.md#2201-2024-08-14) [Compare Source](https://redirect.github.com/googleapis/python-datastore/compare/v2.20.0...v2.20.1) ##### Bug Fixes - Allow protobuf 5.x; require protobuf >=3.20.2 ([#​560](https://redirect.github.com/googleapis/python-datastore/issues/560)) ([ad50e36](https://redirect.github.com/googleapis/python-datastore/commit/ad50e3648954edf27575001be833bb5e1e598f46))
        pytest-dev/pytest (pytest) ### [`v9.0.2`](https://redirect.github.com/pytest-dev/pytest/releases/tag/9.0.2) [Compare Source](https://redirect.github.com/pytest-dev/pytest/compare/9.0.1...9.0.2) ### pytest 9.0.2 (2025-12-06) #### Bug fixes - [#​13896](https://redirect.github.com/pytest-dev/pytest/issues/13896): The terminal progress feature added in pytest 9.0.0 has been disabled by default, except on Windows, due to compatibility issues with some terminal emulators. You may enable it again by passing `-p terminalprogress`. We may enable it by default again once compatibility improves in the future. Additionally, when the environment variable `TERM` is `dumb`, the escape codes are no longer emitted, even if the plugin is enabled. - [#​13904](https://redirect.github.com/pytest-dev/pytest/issues/13904): Fixed the TOML type of the `tmp_path_retention_count` settings in the API reference from number to string. - [#​13946](https://redirect.github.com/pytest-dev/pytest/issues/13946): The private `config.inicfg` attribute was changed in a breaking manner in pytest 9.0.0. Due to its usage in the ecosystem, it is now restored to working order using a compatibility shim. It will be deprecated in pytest 9.1 and removed in pytest 10. - [#​13965](https://redirect.github.com/pytest-dev/pytest/issues/13965): Fixed quadratic-time behavior when handling `unittest` subtests in Python 3.10. #### Improved documentation - [#​4492](https://redirect.github.com/pytest-dev/pytest/issues/4492): The API Reference now contains cross-reference-able documentation of `pytest's command-line flags `. ### [`v9.0.1`](https://redirect.github.com/pytest-dev/pytest/releases/tag/9.0.1) [Compare Source](https://redirect.github.com/pytest-dev/pytest/compare/9.0.0...9.0.1) ### pytest 9.0.1 (2025-11-12) #### Bug fixes - [#​13895](https://redirect.github.com/pytest-dev/pytest/issues/13895): Restore support for skipping tests via `raise unittest.SkipTest`. - [#​13896](https://redirect.github.com/pytest-dev/pytest/issues/13896): The terminal progress plugin added in pytest 9.0 is now automatically disabled when iTerm2 is detected, it generated desktop notifications instead of the desired functionality. - [#​13904](https://redirect.github.com/pytest-dev/pytest/issues/13904): Fixed the TOML type of the verbosity settings in the API reference from number to string. - [#​13910](https://redirect.github.com/pytest-dev/pytest/issues/13910): Fixed UserWarning: Do not expect file\_or\_dir on some earlier Python 3.12 and 3.13 point versions. #### Packaging updates and notes for downstreams - [#​13933](https://redirect.github.com/pytest-dev/pytest/issues/13933): The tox configuration has been adjusted to make sure the desired version string can be passed into its `package_env` through the `SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST` environment variable as a part of the release process -- by `webknjaz`. #### Contributor-facing changes - [#​13891](https://redirect.github.com/pytest-dev/pytest/issues/13891), [#​13942](https://redirect.github.com/pytest-dev/pytest/issues/13942): The CI/CD part of the release automation is now capable of creating GitHub Releases without having a Git checkout on disk -- by `bluetech` and `webknjaz`. - [#​13933](https://redirect.github.com/pytest-dev/pytest/issues/13933): The tox configuration has been adjusted to make sure the desired version string can be passed into its `package_env` through the `SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST` environment variable as a part of the release process -- by `webknjaz`. ### [`v9.0.0`](https://redirect.github.com/pytest-dev/pytest/releases/tag/9.0.0) [Compare Source](https://redirect.github.com/pytest-dev/pytest/compare/8.4.2...9.0.0) ### pytest 9.0.0 (2025-11-05) #### New features - [#​1367](https://redirect.github.com/pytest-dev/pytest/issues/1367): **Support for subtests** has been added. `subtests ` are an alternative to parametrization, useful in situations where the parametrization values are not all known at collection time. Example: ```python def contains_docstring(p: Path) -> bool: """Return True if the given Python file contains a top-level docstring.""" ... def test_py_files_contain_docstring(subtests: pytest.Subtests) -> None: for path in Path.cwd().glob("*.py"): with subtests.test(path=str(path)): assert contains_docstring(path) ``` Each assert failure or error is caught by the context manager and reported individually, giving a clear picture of all files that are missing a docstring. In addition, `unittest.TestCase.subTest` is now also supported. This feature was originally implemented as a separate plugin in [pytest-subtests](https://redirect.github.com/pytest-dev/pytest-subtests), but since then has been merged into the core. > \[!NOTE] > This feature is experimental and will likely evolve in future releases. By that we mean that we might change how subtests are reported on failure, but the functionality and how to use it are stable. - [#​13743](https://redirect.github.com/pytest-dev/pytest/issues/13743): Added support for **native TOML configuration files**. While pytest, since version 6, supports configuration in `pyproject.toml` files under `[tool.pytest.ini_options]`, it does so in an "INI compatibility mode", where all configuration values are treated as strings or list of strings. Now, pytest supports the native TOML data model. In `pyproject.toml`, the native TOML configuration is under the `[tool.pytest]` table. ```toml # pyproject.toml [tool.pytest] minversion = "9.0" addopts = ["-ra", "-q"] testpaths = [ "tests", "integration", ] ``` The `[tool.pytest.ini_options]` table remains supported, but both tables cannot be used at the same time. If you prefer to use a separate configuration file, or don't use `pyproject.toml`, you can use `pytest.toml` or `.pytest.toml`: ```toml # pytest.toml or .pytest.toml [pytest] minversion = "9.0" addopts = ["-ra", "-q"] testpaths = [ "tests", "integration", ] ``` The documentation now (sometimes) shows configuration snippets in both TOML and INI formats, in a tabbed interface. See `config file formats` for full details. - [#​13823](https://redirect.github.com/pytest-dev/pytest/issues/13823): Added a **"strict mode"** enabled by the `strict` configuration option. When set to `true`, the `strict` option currently enables - `strict_config` - `strict_markers` - `strict_parametrization_ids` - `strict_xfail` The individual strictness options can be explicitly set to override the global `strict` setting. The previously-deprecated `--strict` command-line flag now enables strict mode. If pytest adds new strictness options in the future, they will also be enabled in strict mode. Therefore, you should only enable strict mode if you use a pinned/locked version of pytest, or if you want to proactively adopt new strictness options as they are added. See `strict mode` for more details. - [#​13737](https://redirect.github.com/pytest-dev/pytest/issues/13737): Added the `strict_parametrization_ids` configuration option. When set, pytest emits an error if it detects non-unique parameter set IDs, rather than automatically making the IDs unique by adding 0, 1, ... to them. This can be particularly useful for catching unintended duplicates. - [#​13072](https://redirect.github.com/pytest-dev/pytest/issues/13072): Added support for displaying test session **progress in the terminal tab** using the [OSC 9;4;](https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC) ANSI sequence. When pytest runs in a supported terminal emulator like ConEmu, Gnome Terminal, Ptyxis, Windows Terminal, Kitty or Ghostty, you'll see the progress in the terminal tab or window, allowing you to monitor pytest's progress at a glance. This feature is automatically enabled when running in a TTY. It is implemented as an internal plugin. If needed, it can be disabled as follows: - On a user level, using `-p no:terminalprogress` on the command line or via an environment variable `PYTEST_ADDOPTS='-p no:terminalprogress'`. - On a project configuration level, using `addopts = "-p no:terminalprogress"`. - [#​478](https://redirect.github.com/pytest-dev/pytest/issues/478): Support PEP420 (implicit namespace packages) as --pyargs target when `consider_namespace_packages` is true in the config. Previously, this option only impacted package imports, now it also impacts tests discovery. - [#​13678](https://redirect.github.com/pytest-dev/pytest/issues/13678): Added a new `faulthandler_exit_on_timeout` configuration option set to "false" by default to let faulthandler interrupt the pytest process after a timeout in case of deadlock. Previously, a faulthandler timeout would only dump the traceback of all threads to stderr, but would not interrupt the pytest process. \-- by `ogrisel`. - [#​13829](https://redirect.github.com/pytest-dev/pytest/issues/13829): Added support for configuration option aliases via the `aliases` parameter in `Parser.addini() `. Plugins can now register alternative names for configuration options, allowing for more flexibility in configuration naming and supporting backward compatibility when renaming options. The canonical name always takes precedence if both the canonical name and an alias are specified in the configuration file. #### Improvements in existing functionality - [#​13330](https://redirect.github.com/pytest-dev/pytest/issues/13330): Having pytest configuration spread over more than one file (for example having both a `pytest.ini` file and `pyproject.toml` with a `[tool.pytest.ini_options]` table) will now print a warning to make it clearer to the user that only one of them is actually used. \-- by `sgaist` - [#​13574](https://redirect.github.com/pytest-dev/pytest/issues/13574): The single argument `--version` no longer loads the entire plugin infrastructure, making it faster and more reliable when displaying only the pytest version. Passing `--version` twice (e.g., `pytest --version --version`) retains the original behavior, showing both the pytest version and plugin information. > \[!NOTE] > Since `--version` is now processed early, it only takes effect when passed directly via the command line. It will not work if set through other mechanisms, such as `PYTEST_ADDOPTS` or `addopts`. - [#​13823](https://redirect.github.com/pytest-dev/pytest/issues/13823): Added `strict_xfail` as an alias to the `xfail_strict` option, `strict_config` as an alias to the `--strict-config` flag, and `strict_markers` as an alias to the `--strict-markers` flag. This makes all strictness options consistently have configuration options with the prefix `strict_`. - [#​13700](https://redirect.github.com/pytest-dev/pytest/issues/13700): --junitxml no longer prints the generated xml file summary at the end of the pytest session when --quiet is given. - [#​13732](https://redirect.github.com/pytest-dev/pytest/issues/13732): Previously, when filtering warnings, pytest would fail if the filter referenced a class that could not be imported. Now, this only outputs a message indicating the problem. - [#​13859](https://redirect.github.com/pytest-dev/pytest/issues/13859): Clarify the error message for pytest.raises() when a regex match fails. - [#​13861](https://redirect.github.com/pytest-dev/pytest/issues/13861): Better sentence structure in a test's expected error message. Previously, the error message would be "expected exception must be \, but got \". Now, it is "Expected \, but got \". #### Removals and backward incompatible breaking changes - [#​12083](https://redirect.github.com/pytest-dev/pytest/issues/12083): Fixed a bug where an invocation such as pytest a/ a/b would cause only tests from a/b to run, and not other tests under a/. The fix entails a few breaking changes to how such overlapping arguments and duplicates are handled: 1. pytest a/b a/ or pytest a/ a/b are equivalent to pytest a; if an argument overlaps another arguments, only the prefix remains. 2. pytest x.py x.py is equivalent to pytest x.py; previously such an invocation was taken as an explicit request to run the tests from the file twice. If you rely on these behaviors, consider using `--keep-duplicates `, which retains its existing behavior (including the bug). - [#​13719](https://redirect.github.com/pytest-dev/pytest/issues/13719): Support for Python 3.9 is dropped following its end of life. - [#​13766](https://redirect.github.com/pytest-dev/pytest/issues/13766): Previously, pytest would assume it was running in a CI/CD environment if either of the environment variables $CI or $BUILD\_NUMBER was defined; now, CI mode is only activated if at least one of those variables is defined and set to a *non-empty* value. - [#​13779](https://redirect.github.com/pytest-dev/pytest/issues/13779): **PytestRemovedIn9Warning deprecation warnings are now errors by default.** Following our plan to remove deprecated features with as little disruption as possible, all warnings of type `PytestRemovedIn9Warning` now generate errors instead of warning messages by default. **The affected features will be effectively removed in pytest 9.1**, so please consult the `deprecations` section in the docs for directions on how to update existing code. In the pytest `9.0.X` series, it is possible to change the errors back into warnings as a stopgap measure by adding this to your `pytest.ini` file: ```ini [pytest] filterwarnings = ignore::pytest.PytestRemovedIn9Warning ``` But this will stop working when pytest `9.1` is released. **If you have concerns** about the removal of a specific feature, please add a comment to `13779`. #### Deprecations (removal in next major release) - [#​13807](https://redirect.github.com/pytest-dev/pytest/issues/13807): `monkeypatch.syspath_prepend() ` now issues a deprecation warning when the prepended path contains legacy namespace packages (those using `pkg_resources.declare_namespace()`). Users should migrate to native namespace packages (`420`). See `monkeypatch-fixup-namespace-packages` for details. #### Bug fixes - [#​13445](https://redirect.github.com/pytest-dev/pytest/issues/13445): Made the type annotations of `pytest.skip` and friends more spec-complaint to have them work across more type checkers. - [#​13537](https://redirect.github.com/pytest-dev/pytest/issues/13537): Fixed a bug in which `ExceptionGroup` with only `Skipped` exceptions in teardown was not handled correctly and showed as error. - [#​13598](https://redirect.github.com/pytest-dev/pytest/issues/13598): Fixed possible collection confusion on Windows when short paths and symlinks are involved. - [#​13716](https://redirect.github.com/pytest-dev/pytest/issues/13716): Fixed a bug where a nonsensical invocation like `pytest x.py[a]` (a file cannot be parametrized) was silently treated as `pytest x.py`. This is now a usage error. - [#​13722](https://redirect.github.com/pytest-dev/pytest/issues/13722): Fixed a misleading assertion failure message when using `pytest.approx` on mappings with differing lengths. - [#​13773](https://redirect.github.com/pytest-dev/pytest/issues/13773): Fixed the static fixture closure calculation to properly consider transitive dependencies requested by overridden fixtures. - [#​13816](https://redirect.github.com/pytest-dev/pytest/issues/13816): Fixed `pytest.approx` which now returns a clearer error message when comparing mappings with different keys. - [#​13849](https://redirect.github.com/pytest-dev/pytest/issues/13849): Hidden `.pytest.ini` files are now picked up as the config file even if empty. This was an inconsistency with non-hidden `pytest.ini`. - [#​13865](https://redirect.github.com/pytest-dev/pytest/issues/13865): Fixed --show-capture with --tb=line. - [#​13522](https://redirect.github.com/pytest-dev/pytest/issues/13522): Fixed `pytester` in subprocess mode ignored all :attr\`pytester.plugins \\` except the first. Fixed `pytester` in subprocess mode silently ignored non-str `pytester.plugins `. Now it errors instead. If you are affected by this, specify the plugin by name, or switch the affected tests to use `pytester.runpytest_inprocess ` explicitly instead. #### Packaging updates and notes for downstreams - [#​13791](https://redirect.github.com/pytest-dev/pytest/issues/13791): Minimum requirements on `iniconfig` and `packaging` were bumped to `1.0.1` and `22.0.0`, respectively. #### Contributor-facing changes - [#​12244](https://redirect.github.com/pytest-dev/pytest/issues/12244): Fixed self-test failures when TERM=dumb. - [#​12474](https://redirect.github.com/pytest-dev/pytest/issues/12474): Added scheduled GitHub Action Workflow to run Sphinx linkchecks in repo documentation. - [#​13621](https://redirect.github.com/pytest-dev/pytest/issues/13621): pytest's own testsuite now handles the `lsof` command hanging (e.g. due to unreachable network filesystems), with the affected selftests being skipped after 10 seconds. - [#​13638](https://redirect.github.com/pytest-dev/pytest/issues/13638): Fixed deprecated `gh pr new` command in `scripts/prepare-release-pr.py`. The script now uses `gh pr create` which is compatible with GitHub CLI v2.0+. - [#​13695](https://redirect.github.com/pytest-dev/pytest/issues/13695): Flush stdout and stderr in Pytester.run to avoid truncated outputs in test\_faulthandler.py::test\_timeout on CI -- by `ogrisel`. - [#​13771](https://redirect.github.com/pytest-dev/pytest/issues/13771): Skip test\_do\_not\_collect\_symlink\_siblings on Windows environments without symlink support to avoid false negatives. - [#​13841](https://redirect.github.com/pytest-dev/pytest/issues/13841): `tox>=4` is now required when contributing to pytest. - [#​13625](https://redirect.github.com/pytest-dev/pytest/issues/13625): Added missing docstrings to `pytest_addoption()`, `pytest_configure()`, and `cacheshow()` functions in `cacheprovider.py`. #### Miscellaneous internal changes - [#​13830](https://redirect.github.com/pytest-dev/pytest/issues/13830): Configuration overrides (`-o`/`--override-ini`) are now processed during startup rather than during `config.getini() `. ### [`v8.4.2`](https://redirect.github.com/pytest-dev/pytest/releases/tag/8.4.2) [Compare Source](https://redirect.github.com/pytest-dev/pytest/compare/8.4.1...8.4.2) ### pytest 8.4.2 (2025-09-03) #### Bug fixes - [#​13478](https://redirect.github.com/pytest-dev/pytest/issues/13478): Fixed a crash when using `console_output_style`{.interpreted-text role="confval"} with `times` and a module is skipped. - [#​13530](https://redirect.github.com/pytest-dev/pytest/issues/13530): Fixed a crash when using `pytest.approx`{.interpreted-text role="func"} and `decimal.Decimal`{.interpreted-text role="class"} instances with the `decimal.FloatOperation`{.interpreted-text role="class"} trap set. - [#​13549](https://redirect.github.com/pytest-dev/pytest/issues/13549): No longer evaluate type annotations in Python `3.14` when inspecting function signatures. This prevents crashes during module collection when modules do not explicitly use `from __future__ import annotations` and import types for annotations within a `if TYPE_CHECKING:` block. - [#​13559](https://redirect.github.com/pytest-dev/pytest/issues/13559): Added missing \[int]{.title-ref} and \[float]{.title-ref} variants to the \[Literal]{.title-ref} type annotation of the \[type]{.title-ref} parameter in `pytest.Parser.addini`{.interpreted-text role="meth"}. - [#​13563](https://redirect.github.com/pytest-dev/pytest/issues/13563): `pytest.approx`{.interpreted-text role="func"} now only imports `numpy` if NumPy is already in `sys.modules`. This fixes unconditional import behavior introduced in \[8.4.0]{.title-ref}. #### Improved documentation - [#​13577](https://redirect.github.com/pytest-dev/pytest/issues/13577): Clarify that `pytest_generate_tests` is discovered in test modules/classes; other hooks must be in `conftest.py` or plugins. #### Contributor-facing changes - [#​13480](https://redirect.github.com/pytest-dev/pytest/issues/13480): Self-testing: fixed a few test failures when run with `-Wdefault` or a similar override. - [#​13547](https://redirect.github.com/pytest-dev/pytest/issues/13547): Self-testing: corrected expected message for `test_doctest_unexpected_exception` in Python `3.14`. - [#​13684](https://redirect.github.com/pytest-dev/pytest/issues/13684): Make pytest's own testsuite insensitive to the presence of the `CI` environment variable -- by `ogrisel`{.interpreted-text role="user"}. ### [`v8.4.1`](https://redirect.github.com/pytest-dev/pytest/releases/tag/8.4.1) [Compare Source](https://redirect.github.com/pytest-dev/pytest/compare/8.4.0...8.4.1) ### pytest 8.4.1 (2025-06-17) #### Bug fixes - [#​13461](https://redirect.github.com/pytest-dev/pytest/issues/13461): Corrected `_pytest.terminal.TerminalReporter.isatty` to support being called as a method. Before it was just a boolean which could break correct code when using `-o log_cli=true`). - [#​13477](https://redirect.github.com/pytest-dev/pytest/issues/13477): Reintroduced `pytest.PytestReturnNotNoneWarning`{.interpreted-text role="class"} which was removed by accident in pytest \[8.4]{.title-ref}. This warning is raised when a test functions returns a value other than `None`, which is often a mistake made by beginners. See `return-not-none`{.interpreted-text role="ref"} for more information. - [#​13497](https://redirect.github.com/pytest-dev/pytest/issues/13497): Fixed compatibility with `Twisted 25+`. #### Improved documentation - [#​13492](https://redirect.github.com/pytest-dev/pytest/issues/13492): Fixed outdated warning about `faulthandler` not working on Windows. ### [`v8.4.0`](https://redirect.github.com/pytest-dev/pytest/releases/tag/8.4.0) [Compare Source](https://redirect.github.com/pytest-dev/pytest/compare/8.3.5...8.4.0) ### pytest 8.4.0 (2025-06-02) #### Removals and backward incompatible breaking changes - [#​11372](https://redirect.github.com/pytest-dev/pytest/issues/11372): Async tests will now fail, instead of warning+skipping, if you don't have any suitable plugin installed. - [#​12346](https://redirect.github.com/pytest-dev/pytest/issues/12346): Tests will now fail, instead of raising a warning, if they return any value other than None. - [#​12874](https://redirect.github.com/pytest-dev/pytest/issues/12874): We dropped support for Python 3.8 following its end of life (2024-10-07). - [#​12960](https://redirect.github.com/pytest-dev/pytest/issues/12960): Test functions containing a yield now cause an explicit error. They have not been run since pytest 4.0, and were previously marked as an expected failure and deprecation warning. See `the docs `{.interpreted-text role="ref"} for more information. #### Deprecations (removal in next major release) - [#​10839](https://redirect.github.com/pytest-dev/pytest/issues/10839): Requesting an asynchronous fixture without a \[pytest\_fixture\_setup]{.title-ref} hook that resolves it will now give a DeprecationWarning. This most commonly happens if a sync test requests an async fixture. This should have no effect on a majority of users with async tests or fixtures using async pytest plugins, but may affect non-standard hook setups or `autouse=True`. For guidance on how to work around this warning see `sync-test-async-fixture`{.interpreted-text role="ref"}. #### New features - [#​11538](https://redirect.github.com/pytest-dev/pytest/issues/11538): Added `pytest.RaisesGroup`{.interpreted-text role="class"} as an equivalent to `pytest.raises`{.interpreted-text role="func"} for expecting `ExceptionGroup`{.interpreted-text role="exc"}. Also adds `pytest.RaisesExc`{.interpreted-text role="class"} which is now the logic behind `pytest.raises`{.interpreted-text role="func"} and used as parameter to `pytest.RaisesGroup`{.interpreted-text role="class"}. `RaisesGroup` includes the ability to specify multiple different expected exceptions, the structure of nested exception groups, and flags for emulating `except* `{.interpreted-text role="ref"}. See `assert-matching-exception-groups`{.interpreted-text role="ref"} and docstrings for more information. - [#​12081](https://redirect.github.com/pytest-dev/pytest/issues/12081): Added `capteesys`{.interpreted-text role="fixture"} to capture AND pass output to next handler set by `--capture=`. - [#​12504](https://redirect.github.com/pytest-dev/pytest/issues/12504): `pytest.mark.xfail`{.interpreted-text role="func"} now accepts `pytest.RaisesGroup`{.interpreted-text role="class"} for the `raises` parameter when you expect an exception group. You can also pass a `pytest.RaisesExc`{.interpreted-text role="class"} if you e.g. want to make use of the `check` parameter. - [#​12713](https://redirect.github.com/pytest-dev/pytest/issues/12713): New \[--force-short-summary]{.title-ref} option to force condensed summary output regardless of verbosity level. This lets users still see condensed summary output of failures for quick reference in log files from job outputs, being especially useful if non-condensed output is very verbose. - [#​12749](https://redirect.github.com/pytest-dev/pytest/issues/12749): pytest traditionally collects classes/functions in the test module namespace even if they are imported from another file. For example: ```python ``` ### contents of src/domain.py ``` class Testament: ... ``` ### contents of tests/test\_testament.py ```` from domain import Testament def test_testament(): ... ``` In this scenario with the default options, pytest will collect the class [Testament]{.title-ref} from [tests/test_testament.py]{.title-ref} because it starts with [Test]{.title-ref}, even though in this case it is a production class being imported in the test module namespace. This behavior can now be prevented by setting the new `collect_imported_tests`{.interpreted-text role="confval"} configuration option to `false`, which will make pytest collect classes/functions from test files **only** if they are defined in that file. \-- by `FreerGit`{.interpreted-text role="user"} ```` - [#​12765](https://redirect.github.com/pytest-dev/pytest/issues/12765): Thresholds to trigger snippet truncation can now be set with `truncation_limit_lines`{.interpreted-text role="confval"} and `truncation_limit_chars`{.interpreted-text role="confval"}. See `truncation-params`{.interpreted-text role="ref"} for more information. - [#​13125](https://redirect.github.com/pytest-dev/pytest/issues/13125): `console_output_style`{.interpreted-text role="confval"} now supports `times` to show execution time of each test. - [#​13192](https://redirect.github.com/pytest-dev/pytest/issues/13192): `pytest.raises`{.interpreted-text role="func"} will now raise a warning when passing an empty string to `match`, as this will match against any value. Use `match="^$"` if you want to check that an exception has no message. - [#​13192](https://redirect.github.com/pytest-dev/pytest/issues/13192): `pytest.raises`{.interpreted-text role="func"} will now print a helpful string diff if matching fails and the match parameter has `^` and `$` and is otherwise escaped. - [#​13192](https://redirect.github.com/pytest-dev/pytest/issues/13192): You can now pass `with pytest.raises(check=fn): `{.interpreted-text role="func"}, where `fn` is a function which takes a raised exception and returns a boolean. The `raises` fails if no exception was raised (as usual), passes if an exception is raised and `fn` returns `True` (as well as `match` and the type matching, if specified, which are checked before), and propagates the exception if `fn` returns `False` (which likely also fails the test). - [#​13228](https://redirect.github.com/pytest-dev/pytest/issues/13228): `hidden-param`{.interpreted-text role="ref"} can now be used in `id` of `pytest.param`{.interpreted-text role="func"} or in `ids` of `Metafunc.parametrize `{.interpreted-text role="py:func"}. It hides the parameter set from the test name. - [#​13253](https://redirect.github.com/pytest-dev/pytest/issues/13253): New flag: `--disable-plugin-autoload `{.interpreted-text role="ref"} which works as an alternative to `PYTEST_DISABLE_PLUGIN_AUTOLOAD`{.interpreted-text role="envvar"} when setting environment variables is inconvenient; and allows setting it in config files with `addopts`{.interpreted-text role="confval"}. #### Improvements in existing functionality - [#​10224](https://redirect.github.com/pytest-dev/pytest/issues/10224): pytest's `short` and `long` traceback styles (`how-to-modifying-python-tb-printing`{.interpreted-text role="ref"}) now have partial `657`{.interpreted-text role="pep"} support and will show specific code segments in the traceback. ```pytest ================================= FAILURES ================================= _______________________ test_gets_correct_tracebacks _______________________ test_tracebacks.py:12: in test_gets_correct_tracebacks assert manhattan_distance(p1, p2) == 1 ^^^^^^^^^^^^^^^^^^^^^^^^^^ test_tracebacks.py:6: in manhattan_distance return abs(point_1.x - point_2.x) + abs(point_1.y - point_2.y) ^^^^^^^^^ E AttributeError: 'NoneType' object has no attribute 'x' ``` \-- by `ammaraskar`{.interpreted-text role="user"} - [#​11118](https://redirect.github.com/pytest-dev/pytest/issues/11118): Now `pythonpath`{.interpreted-text role="confval"} configures \[$PYTHONPATH]{.title-ref} earlier than before during the initialization process, which now also affects plugins loaded via the \[-p]{.title-ref} command-line option. \-- by `millerdev`{.interpreted-text role="user"} - [#​11381](https://redirect.github.com/pytest-dev/pytest/issues/11381): The `type` parameter of the `parser.addini` method now accepts \["int"]{.title-ref} and `"float"` parameters, facilitating the parsing of configuration values in the configuration file. Example: ```python def pytest_addoption(parser): parser.addini("int_value", type="int", default=2, help="my int value") parser.addini("float_value", type="float", default=4.2, help="my float value") ``` The \[pytest.ini]{.title-ref} file: ```ini [pytest] int_value = 3 float_value = 5.4 ``` - [#​11525](https://redirect.github.com/pytest-dev/pytest/issues/11525): Fixtures are now clearly represented in the output as a "fixture object", not as a normal function as before, making it easy for beginners to catch mistakes such as referencing a fixture declared in the same module but not requested in the test function. \-- by `the-compiler`{.interpreted-text role="user"} and `glyphack`{.interpreted-text role="user"} - [#​12426](https://redirect.github.com/pytest-dev/pytest/issues/12426): A warning is now issued when `pytest.mark.usefixtures ref`{.interpreted-text role="ref"} is used without specifying any fixtures. Previously, empty usefixtures markers were silently ignored. - [#​12707](https://redirect.github.com/pytest-dev/pytest/issues/12707): Exception chains can be navigated when dropped into Pdb in Python 3.13+. - [#​12736](https://redirect.github.com/pytest-dev/pytest/issues/12736): Added a new attribute \[name]{.title-ref} with the fixed value \["pytest tests"]{.title-ref} to the root tag \[testsuites]{.title-ref} of the junit-xml generated by pytest. This attribute is part of many junit-xml specifications and is even part of the \[junit-10.xsd]{.title-ref} specification that pytest's implementation is based on. - [#​12943](https://redirect.github.com/pytest-dev/pytest/issues/12943): If a test fails with an exceptiongroup with a single exception, the contained exception will now be displayed in the short test summary info. - [#​12958](https://redirect.github.com/pytest-dev/pytest/issues/12958): A number of `unraisable `{.interpreted-text role="ref"} enhancements: - Set the unraisable hook as early as possible and unset it as late as possible, to collect the most possible number of unraisable exceptions. - Call the garbage collector just before unsetting the unraisable hook, to collect any straggling exceptions. - Collect multiple unraisable exceptions per test phase. - Report the `tracemalloc`{.interpreted-text role="mod"} allocation traceback (if available). - Avoid using a generator based hook to allow handling `StopIteration`{.interpreted-text role="class"} in test failures. - Report the unraisable exception as the cause of the `pytest.PytestUnraisableExceptionWarning`{.interpreted-text role="class"} exception if raised. - Compute the `repr` of the unraisable object in the unraisable hook so you get the latest information if available, and should help with resurrection of the object. - [#​13010](https://redirect.github.com/pytest-dev/pytest/issues/13010): `pytest.approx`{.interpreted-text role="func"} now can compare collections that contain numbers and non-numbers mixed. - [#​13016](https://redirect.github.com/pytest-dev/pytest/issues/13016): A number of `threadexception `{.interpreted-text role="ref"} enhancements: - Set the excepthook as early as possible and unset it as late as possible, to collect the most possible number of unhandled exceptions from threads. - Collect multiple thread exceptions per test phase. - Report the `tracemalloc`{.interpreted-text role="mod"} allocation traceback (if available). - Avoid using a generator based hook to allow handling `StopIteration`{.interpreted-text role="class"} in test failures. - Report the thread exception as the cause of the `pytest.PytestUnhandledThreadExceptionWarning`{.interpreted-text role="class"} exception if raised. - Extract the `name` of the thread object in the excepthook which should help with resurrection of the thread. - [#​13031](https://redirect.github.com/pytest-dev/pytest/issues/13031): An empty parameter set as in `pytest.mark.parametrize([], ids=idfunc)` will no longer trigger a call to `idfunc` with internal objects. - [#​13115](https://redirect.github.com/pytest-dev/pytest/issues/13115): Allows supplying `ExceptionGroup[Exception]` and `BaseExceptionGroup[BaseException]` to `pytest.raises` to keep full typing on `ExceptionInfo `{.interpreted-text role="class"}: ```python with pytest.raises(ExceptionGroup[Exception]) as exc_info: some_function() ``` Parametrizing with other exception types remains an error - we do not check the types of child exceptions and thus do not permit code that might look like we do. - [#​13122](https://redirect.github.com/pytest-dev/pytest/issues/13122): The `--stepwise` mode received a number of improvements: - It no longer forgets the last failed test in case pytest is executed later without the flag. This enables the following workflow: 1. Execute pytest with `--stepwise`, pytest then stops at the first failing test; 2. Iteratively update the code and run the test in isolation, without the `--stepwise` flag (for example in an IDE), until it is fixed. 3. Execute pytest with `--stepwise` again and pytest will continue from the previously failed test, and if it passes, continue on to the next tests. Previously, at step 3, pytest would start from the beginning, forgetting the previously failed test. This change however might cause issues if the `--stepwise` mode is used far apart in time, as the state might get stale, so the internal state will be reset automatically in case the test suite changes (for now only the number of tests are considered for this, we might change/improve this on the future). - New `--stepwise-reset`/`--sw-reset` flag, allowing the user to explicitly reset the stepwise state and restart the workflow from the beginning. - [#​13308](https://redirect.github.com/pytest-dev/pytest/issues/13308): Added official support for Python 3.14. - [#​13380](https://redirect.github.com/pytest-dev/pytest/issues/13380): Fix `ExceptionGroup`{.interpreted-text role="class"} traceback filtering to exclude pytest internals. - [#​13415](https://redirect.github.com/pytest-dev/pytest/issues/13415): The author metadata of the BibTex example is now correctly formatted with last names following first names. An example of BibLaTex has been added. BibTex and BibLaTex examples now clearly indicate that what is cited is software. \-- by `willynilly`{.interpreted-text role="user"} - [#​13420](https://redirect.github.com/pytest-dev/pytest/issues/13420): Improved test collection performance by optimizing path resolution used in `FSCollector`. - [#​13457](https://redirect.github.com/pytest-dev/pytest/issues/13457): The error message about duplicate parametrization no longer displays an internal stack trace. - [#​4112](https://redirect.github.com/pytest-dev/pytest/issues/4112): Using `pytest.mark.usefixtures `{.interpreted-text role="ref"} on `pytest.param`{.interpreted-text role="func"} now produces an error instead of silently doing nothing. - [#​5473](https://redirect.github.com/pytest-dev/pytest/issues/5473): Replace \[:]{.title-ref} with \[;]{.title-ref} in the assertion rewrite warning message so it can be filtered using standard Python warning filters before calling `pytest.main`{.interpreted-text role="func"}. - ``` [#​6985](https://redirect.github.com/pytest-dev/pytest/issues/6985): Improved `pytest.approx`{.interpreted-text role="func"} to enhance the readability of value ranges and tolerances between 0.001 and 1000. ``` ```` : - The [repr]{.title-ref} method now provides clearer output for values within those ranges, making it easier to interpret the results. - Previously, the output for those ranges of values and tolerances was displayed in scientific notation (e.g., [42 ± 1.0e+00]{.title-ref}). The updated method now presents the tolerance as a decimal for better readability (e.g., [42 ± 1]{.title-ref}). Example: **Previous Output:** ``` console >>> pytest.approx(42, abs=1) 42 ± 1.0e+00 ``` **Current Output:** ``` console >>> pytest.approx(42, abs=1) 42 ± 1 ``` \-- by `fazeelghafoor`{.interpreted-text role="user"} ```` - [#​7683](https://redirect.github.com/pytest-dev/pytest/issues/7683): The formerly optional `pygments` dependency is now required, causing output always to be source-highlighted (unless disabled via the `--code-highlight=no` CLI option). #### Bug fixes - [#​10404](https://redirect.github.com/pytest-dev/pytest/issues/10404): Apply filterwarnings from config/cli as soon as possible, and revert them as late as possible so that warnings as errors are collected throughout the pytest run and before the unraisable and threadexcept hooks are removed. This allows very late warnings and unraisable/threadexcept exceptions to fail the test suite. This also changes the warning that the lsof plugin issues from PytestWarning to the new warning PytestFDWarning so it can be more easily filtered. - [#​11067](https://redirect.github.com/pytest-dev/pytest/issues/11067): The test report is now consistent regardless if the test xfailed via `pytest.mark.xfail `{.interpreted-text role="ref"} or `pytest.fail`{.interpreted-text role="func"}. Previously, *xfailed* tests via the marker would have the string `"reason: "` prefixed to the message, while those *xfailed* via the function did not. The prefix has been removed. - [#​12008](https://redirect.github.com/pytest-dev/pytest/issues/12008): In `11220`{.interpreted-text role="pr"}, an unintended change in reordering was introduced by changing the way indices were assigned to direct params. More specifically, before that change, the indices of direct params to metafunc's callspecs were assigned after all parametrizations took place. Now, that change is reverted. - [#​12863](https://redirect.github.com/pytest-dev/pytest/issues/12863): Fix applying markers, including `pytest.mark.parametrize `{.interpreted-text role="ref"} when placed above \[[@​staticmethod](https://redirect.github.com/staticmethod)]{.title-ref} or \[[@​classmethod](https://redirect.github.com/classmethod)]{.title-ref}. - [#​12929](https://redirect.github.com/pytest-dev/pytest/issues/12929): Handle StopIteration from test cases, setup and teardown correctly. - [#​12938](https://redirect.github.com/pytest-dev/pytest/issues/12938): Fixed `--durations-min` argument not respected if `-vv` is used. - [#​12946](https://redirect.github.com/pytest-dev/pytest/issues/12946): Fixed missing help for `pdb`{.interpreted-text role="mod"} commands wrapped by pytest -- by `adamchainz`{.interpreted-text role="user"}. - [#​12981](https://redirect.github.com/pytest-dev/pytest/issues/12981): Prevent exceptions in `pytest.Config.add_cleanup`{.interpreted-text role="func"} callbacks preventing further cleanups. - [#​13047](https://redirect.github.com/pytest-dev/pytest/issues/13047): Restore `pytest.approx`{.interpreted-text role="func"} handling of equality checks between \[bool]{.title-ref} and \[numpy.bool\_]{.title-ref} types. Comparing \[bool]{.title-ref} and \[numpy.bool\_]{.title-ref} using `pytest.approx`{.interpreted-text role="func"} accidentally changed in version \[8.3.4]{.title-ref} and \[8.3.5]{.title-ref} to no longer match: ```pycon >>> import numpy as np >>> from pytest import approx >>> [np.True_, np.True_] == pytest.approx([True, True]) False ``` This has now been fixed: ```pycon >>> [np.True_, np.True_] == pytest.approx([True, True]) True ``` - [#​13119](https://redirect.github.com/pytest-dev/pytest/issues/13119): Improved handling of invalid regex patterns for filter warnings by providing a clear error message. - [#​13175](https://redirect.github.com/pytest-dev/pytest/issues/13175): The diff is now also highlighted correctly when comparing two strings. - [#​13248](https://redirect.github.com/pytest-dev/pytest/issues/13248): Fixed an issue where passing a `scope` in `Metafunc.parametrize `{.interpreted-text role="py:func"} with `indirect=True` could result in other fixtures being unable to depend on the parametrized fixture. - [#​13291](https://redirect.github.com/pytest-dev/pytest/issues/13291): Fixed `repr` of `attrs` objects in assertion failure messages when using `attrs>=25.2`. - [#​13312](https://redirect.github.com/pytest-dev/pytest/issues/13312): Fixed a possible `KeyError` crash on PyPy during collection of tests involving higher-scoped parameters. - [#​13345](https://redirect.github.com/pytest-dev/pytest/issues/13345): Fix type hints for `pytest.TestReport.when`{.interpreted-text role="attr"} and `pytest.TestReport.location`{.interpreted-text role="attr"}. - [#​13377](https://redirect.github.com/pytest-dev/pytest/issues/13377): Fixed handling of test methods with positional-only parameter syntax. Now, methods are supported that formally define `self` as positional-only and/or fixture parameters as keyword-only, e.g.: ```python class TestClass: def test_method(self, /, *, fixture): ... ``` Before, this caused an internal error in pytest. - [#​13384](https://redirect.github.com/pytest-dev/pytest/issues/13384): Fixed an issue where pytest could report negative durations. - [#​13420](https://redirect.github.com/pytest-dev/pytest/issues/13420): Added `lru_cache` to `nodes._check_initialpaths_for_relpath`. - [#​9037](https://redirect.github.com/pytest-dev/pytest/issues/9037): Honor `disable_test_id_escaping_and_forfeit_all_rights_to_community_support`{.interpreted-text role="confval"} when escaping ids in parametrized tests. #### Improved documentation - [#​12535](https://redirect.github.com/pytest-dev/pytest/issues/12535): \[This example]{.title-ref}<> showed `print` statements that do not exactly reflect what the different branches actually do. The fix makes the example more precise. - [#​13218](https://redirect.github.com/pytest-dev/pytest/issues/13218): Pointed out in the `pytest.approx`{.interpreted-text role="func"} documentation that it considers booleans unequal to numeric zero or one. - [#​13221](https://redirect.github.com/pytest-dev/pytest/issues/13221): Improved grouping of CLI options in the `--help` output. - [#​6649](https://redirect.github.com/pytest-dev/pytest/issues/6649): Added `~pytest.TerminalReporter`{.interpreted-text role="class"} to the `api-reference`{.interpreted-text role="ref"} documentation page. - [#​8612](https://redirect.github.com/pytest-dev/pytest/issues/8612): Add a recipe for handling abstract test classes in the documentation. A new example has been added to the documentation to demonstrate how to use a mixin class to handle abstract test classes without manually setting the `__test__` attribute for subclasses. This ensures that subclasses of abstract test classes are automatically collected by pytest. #### Packaging updates and notes for downstreams - [#​13317](https://redirect.github.com/pytest-dev/pytest/issues/13317): Specified minimum allowed versions of `colorama`, `iniconfig`, and `packaging`; and bumped the minimum allowed version of `exceptiongroup` for `python_version<'3.11'` from a release candidate to a full release. #### Contributor-facing changes - [#​12017](https://redirect.github.com/pytest-dev/pytest/issues/12017): Mixed internal improvements: - Migrate formatting to f-strings in some tests. - Use type-safe constructs in JUnitXML tests. - Moved`MockTiming` into `_pytest.timing`. \-- by `RonnyPfannschmidt`{.interpreted-text role="user"} - [#​12647](https://redirect.github.com/pytest-dev/pytest/issues/12647): Fixed running the test suite with the `hypothesis` pytest plugin. #### Miscellaneous internal changes - [#​6649](https://redirect.github.com/pytest-dev/pytest/issues/6649): Added `~pytest.TerminalReporter`{.interpreted-text role="class"} to the public pytest API, as it is part of the signature of the `pytest_terminal_summary`{.interpreted-text role="hook"} hook. ### [`v8.3.5`](https://redirect.github.com/pytest-dev/pytest/releases/tag/8.3.5) [Compare Source](https://redirect.github.com/pytest-dev/pytest/compare/8.3.4...8.3.5) ### pytest 8.3.5 (2025-03-02) #### Bug fixes - [#​11777](https://redirect.github.com/pytest-dev/pytest/issues/11777): Fixed issue where sequences were still being shortened even with `-vv` verbosity. - [#​12888](https://redirect.github.com/pytest-dev/pytest/issues/12888): Fixed broken input when using Python 3.13+ and a `libedit` build of Python, such as on macOS or with uv-managed Python binaries from the `python-build-standalone` project. This could manifest e.g. by a broken prompt when using `Pdb`, or seeing empty inputs with manual usage of `input()` and suspended capturing. - [#​13026](https://redirect.github.com/pytest-dev/pytest/issues/13026): Fixed `AttributeError`{.interpreted-text role="class"} crash when using `--import-mode=importlib` when top-level directory same name as another module of the standard library. - [#​13053](https://redirect.github.com/pytest-dev/pytest/issues/13053): Fixed a regression in pytest 8.3.4 where, when using `--import-mode=importlib`, a directory containing py file with the same name would cause an `ImportError` - [#​13083](https://redirect.github.com/pytest-dev/pytest/issues/13083): Fixed issue where pytest could crash if one of the collected directories got removed during collection. #### Improved documentation - [#​12842](https://redirect.github.com/pytest-dev/pytest/issues/12842): Added dedicated page about using types with pytest. See `types`{.interpreted-text role="ref"} for detailed usage. #### Contributor-facing changes - [#​13112](https://redirect.github.com/pytest-dev/pytest/issues/13112): Fixed selftest failures in `test_terminal.py` with Pygments >= 2.19.0 - [#​13256](https://redirect.github.com/pytest-dev/pytest/issues/13256): Support for Towncrier versions released in 2024 has been re-enabled when building Sphinx docs -- by `webknjaz`{.interpreted-text role="user"}. ### [`v8.3.4`](https://redirect.github.com/pytest-dev/pytest/releases/tag/8.3.4) [Compare Source](https://redirect.github.com/pytest-dev/pytest/compare/8.3.3...8.3.4) ### pytest 8.3.4 (2024-12-01) #### Bug fixes - [#​12592](https://redirect.github.com/pytest-dev/pytest/issues/12592): Fixed `KeyError`{.interpreted-text role="class"} crash when using `--import-mode=importlib` in a directory l
        --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/googleapis/python-datastore). --------- Co-authored-by: Anthonios Partheniou * chore(deps): update all dependencies (#670) This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | Type | Update | |---|---|---|---|---|---| | [pytest](https://redirect.github.com/pytest-dev/pytest) ([changelog](https://docs.pytest.org/en/stable/changelog.html)) | `==8.4.2` → `==9.0.2` | ![age](https://developer.mend.io/api/mc/badges/age/pypi/pytest/9.0.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/pytest/8.4.2/9.0.2?slim=true) | | major | | [python](https://redirect.github.com/actions/python-versions) | `3.10` → `3.14` | ![age](https://developer.mend.io/api/mc/badges/age/github-releases/actions%2fpython-versions/3.14.3?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/github-releases/actions%2fpython-versions/3.10.19/3.14.3?slim=true) | uses-with | minor | --- ### Release Notes
        pytest-dev/pytest (pytest) ### [`v9.0.2`](https://redirect.github.com/pytest-dev/pytest/releases/tag/9.0.2) [Compare Source](https://redirect.github.com/pytest-dev/pytest/compare/9.0.1...9.0.2) ### pytest 9.0.2 (2025-12-06) #### Bug fixes - [#​13896](https://redirect.github.com/pytest-dev/pytest/issues/13896): The terminal progress feature added in pytest 9.0.0 has been disabled by default, except on Windows, due to compatibility issues with some terminal emulators. You may enable it again by passing `-p terminalprogress`. We may enable it by default again once compatibility improves in the future. Additionally, when the environment variable `TERM` is `dumb`, the escape codes are no longer emitted, even if the plugin is enabled. - [#​13904](https://redirect.github.com/pytest-dev/pytest/issues/13904): Fixed the TOML type of the `tmp_path_retention_count` settings in the API reference from number to string. - [#​13946](https://redirect.github.com/pytest-dev/pytest/issues/13946): The private `config.inicfg` attribute was changed in a breaking manner in pytest 9.0.0. Due to its usage in the ecosystem, it is now restored to working order using a compatibility shim. It will be deprecated in pytest 9.1 and removed in pytest 10. - [#​13965](https://redirect.github.com/pytest-dev/pytest/issues/13965): Fixed quadratic-time behavior when handling `unittest` subtests in Python 3.10. #### Improved documentation - [#​4492](https://redirect.github.com/pytest-dev/pytest/issues/4492): The API Reference now contains cross-reference-able documentation of `pytest's command-line flags `. ### [`v9.0.1`](https://redirect.github.com/pytest-dev/pytest/releases/tag/9.0.1) [Compare Source](https://redirect.github.com/pytest-dev/pytest/compare/9.0.0...9.0.1) ### pytest 9.0.1 (2025-11-12) #### Bug fixes - [#​13895](https://redirect.github.com/pytest-dev/pytest/issues/13895): Restore support for skipping tests via `raise unittest.SkipTest`. - [#​13896](https://redirect.github.com/pytest-dev/pytest/issues/13896): The terminal progress plugin added in pytest 9.0 is now automatically disabled when iTerm2 is detected, it generated desktop notifications instead of the desired functionality. - [#​13904](https://redirect.github.com/pytest-dev/pytest/issues/13904): Fixed the TOML type of the verbosity settings in the API reference from number to string. - [#​13910](https://redirect.github.com/pytest-dev/pytest/issues/13910): Fixed UserWarning: Do not expect file\_or\_dir on some earlier Python 3.12 and 3.13 point versions. #### Packaging updates and notes for downstreams - [#​13933](https://redirect.github.com/pytest-dev/pytest/issues/13933): The tox configuration has been adjusted to make sure the desired version string can be passed into its `package_env` through the `SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST` environment variable as a part of the release process -- by `webknjaz`. #### Contributor-facing changes - [#​13891](https://redirect.github.com/pytest-dev/pytest/issues/13891), [#​13942](https://redirect.github.com/pytest-dev/pytest/issues/13942): The CI/CD part of the release automation is now capable of creating GitHub Releases without having a Git checkout on disk -- by `bluetech` and `webknjaz`. - [#​13933](https://redirect.github.com/pytest-dev/pytest/issues/13933): The tox configuration has been adjusted to make sure the desired version string can be passed into its `package_env` through the `SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST` environment variable as a part of the release process -- by `webknjaz`. ### [`v9.0.0`](https://redirect.github.com/pytest-dev/pytest/releases/tag/9.0.0) [Compare Source](https://redirect.github.com/pytest-dev/pytest/compare/8.4.2...9.0.0) ### pytest 9.0.0 (2025-11-05) #### New features - [#​1367](https://redirect.github.com/pytest-dev/pytest/issues/1367): **Support for subtests** has been added. `subtests ` are an alternative to parametrization, useful in situations where the parametrization values are not all known at collection time. Example: ```python def contains_docstring(p: Path) -> bool: """Return True if the given Python file contains a top-level docstring.""" ... def test_py_files_contain_docstring(subtests: pytest.Subtests) -> None: for path in Path.cwd().glob("*.py"): with subtests.test(path=str(path)): assert contains_docstring(path) ``` Each assert failure or error is caught by the context manager and reported individually, giving a clear picture of all files that are missing a docstring. In addition, `unittest.TestCase.subTest` is now also supported. This feature was originally implemented as a separate plugin in [pytest-subtests](https://redirect.github.com/pytest-dev/pytest-subtests), but since then has been merged into the core. > \[!NOTE] > This feature is experimental and will likely evolve in future releases. By that we mean that we might change how subtests are reported on failure, but the functionality and how to use it are stable. - [#​13743](https://redirect.github.com/pytest-dev/pytest/issues/13743): Added support for **native TOML configuration files**. While pytest, since version 6, supports configuration in `pyproject.toml` files under `[tool.pytest.ini_options]`, it does so in an "INI compatibility mode", where all configuration values are treated as strings or list of strings. Now, pytest supports the native TOML data model. In `pyproject.toml`, the native TOML configuration is under the `[tool.pytest]` table. ```toml # pyproject.toml [tool.pytest] minversion = "9.0" addopts = ["-ra", "-q"] testpaths = [ "tests", "integration", ] ``` The `[tool.pytest.ini_options]` table remains supported, but both tables cannot be used at the same time. If you prefer to use a separate configuration file, or don't use `pyproject.toml`, you can use `pytest.toml` or `.pytest.toml`: ```toml # pytest.toml or .pytest.toml [pytest] minversion = "9.0" addopts = ["-ra", "-q"] testpaths = [ "tests", "integration", ] ``` The documentation now (sometimes) shows configuration snippets in both TOML and INI formats, in a tabbed interface. See `config file formats` for full details. - [#​13823](https://redirect.github.com/pytest-dev/pytest/issues/13823): Added a **"strict mode"** enabled by the `strict` configuration option. When set to `true`, the `strict` option currently enables - `strict_config` - `strict_markers` - `strict_parametrization_ids` - `strict_xfail` The individual strictness options can be explicitly set to override the global `strict` setting. The previously-deprecated `--strict` command-line flag now enables strict mode. If pytest adds new strictness options in the future, they will also be enabled in strict mode. Therefore, you should only enable strict mode if you use a pinned/locked version of pytest, or if you want to proactively adopt new strictness options as they are added. See `strict mode` for more details. - [#​13737](https://redirect.github.com/pytest-dev/pytest/issues/13737): Added the `strict_parametrization_ids` configuration option. When set, pytest emits an error if it detects non-unique parameter set IDs, rather than automatically making the IDs unique by adding 0, 1, ... to them. This can be particularly useful for catching unintended duplicates. - [#​13072](https://redirect.github.com/pytest-dev/pytest/issues/13072): Added support for displaying test session **progress in the terminal tab** using the [OSC 9;4;](https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC) ANSI sequence. When pytest runs in a supported terminal emulator like ConEmu, Gnome Terminal, Ptyxis, Windows Terminal, Kitty or Ghostty, you'll see the progress in the terminal tab or window, allowing you to monitor pytest's progress at a glance. This feature is automatically enabled when running in a TTY. It is implemented as an internal plugin. If needed, it can be disabled as follows: - On a user level, using `-p no:terminalprogress` on the command line or via an environment variable `PYTEST_ADDOPTS='-p no:terminalprogress'`. - On a project configuration level, using `addopts = "-p no:terminalprogress"`. - [#​478](https://redirect.github.com/pytest-dev/pytest/issues/478): Support PEP420 (implicit namespace packages) as --pyargs target when `consider_namespace_packages` is true in the config. Previously, this option only impacted package imports, now it also impacts tests discovery. - [#​13678](https://redirect.github.com/pytest-dev/pytest/issues/13678): Added a new `faulthandler_exit_on_timeout` configuration option set to "false" by default to let faulthandler interrupt the pytest process after a timeout in case of deadlock. Previously, a faulthandler timeout would only dump the traceback of all threads to stderr, but would not interrupt the pytest process. \-- by `ogrisel`. - [#​13829](https://redirect.github.com/pytest-dev/pytest/issues/13829): Added support for configuration option aliases via the `aliases` parameter in `Parser.addini() `. Plugins can now register alternative names for configuration options, allowing for more flexibility in configuration naming and supporting backward compatibility when renaming options. The canonical name always takes precedence if both the canonical name and an alias are specified in the configuration file. #### Improvements in existing functionality - [#​13330](https://redirect.github.com/pytest-dev/pytest/issues/13330): Having pytest configuration spread over more than one file (for example having both a `pytest.ini` file and `pyproject.toml` with a `[tool.pytest.ini_options]` table) will now print a warning to make it clearer to the user that only one of them is actually used. \-- by `sgaist` - [#​13574](https://redirect.github.com/pytest-dev/pytest/issues/13574): The single argument `--version` no longer loads the entire plugin infrastructure, making it faster and more reliable when displaying only the pytest version. Passing `--version` twice (e.g., `pytest --version --version`) retains the original behavior, showing both the pytest version and plugin information. > \[!NOTE] > Since `--version` is now processed early, it only takes effect when passed directly via the command line. It will not work if set through other mechanisms, such as `PYTEST_ADDOPTS` or `addopts`. - [#​13823](https://redirect.github.com/pytest-dev/pytest/issues/13823): Added `strict_xfail` as an alias to the `xfail_strict` option, `strict_config` as an alias to the `--strict-config` flag, and `strict_markers` as an alias to the `--strict-markers` flag. This makes all strictness options consistently have configuration options with the prefix `strict_`. - [#​13700](https://redirect.github.com/pytest-dev/pytest/issues/13700): --junitxml no longer prints the generated xml file summary at the end of the pytest session when --quiet is given. - [#​13732](https://redirect.github.com/pytest-dev/pytest/issues/13732): Previously, when filtering warnings, pytest would fail if the filter referenced a class that could not be imported. Now, this only outputs a message indicating the problem. - [#​13859](https://redirect.github.com/pytest-dev/pytest/issues/13859): Clarify the error message for pytest.raises() when a regex match fails. - [#​13861](https://redirect.github.com/pytest-dev/pytest/issues/13861): Better sentence structure in a test's expected error message. Previously, the error message would be "expected exception must be \, but got \". Now, it is "Expected \, but got \". #### Removals and backward incompatible breaking changes - [#​12083](https://redirect.github.com/pytest-dev/pytest/issues/12083): Fixed a bug where an invocation such as pytest a/ a/b would cause only tests from a/b to run, and not other tests under a/. The fix entails a few breaking changes to how such overlapping arguments and duplicates are handled: 1. pytest a/b a/ or pytest a/ a/b are equivalent to pytest a; if an argument overlaps another arguments, only the prefix remains. 2. pytest x.py x.py is equivalent to pytest x.py; previously such an invocation was taken as an explicit request to run the tests from the file twice. If you rely on these behaviors, consider using `--keep-duplicates `, which retains its existing behavior (including the bug). - [#​13719](https://redirect.github.com/pytest-dev/pytest/issues/13719): Support for Python 3.9 is dropped following its end of life. - [#​13766](https://redirect.github.com/pytest-dev/pytest/issues/13766): Previously, pytest would assume it was running in a CI/CD environment if either of the environment variables $CI or $BUILD\_NUMBER was defined; now, CI mode is only activated if at least one of those variables is defined and set to a *non-empty* value. - [#​13779](https://redirect.github.com/pytest-dev/pytest/issues/13779): **PytestRemovedIn9Warning deprecation warnings are now errors by default.** Following our plan to remove deprecated features with as little disruption as possible, all warnings of type `PytestRemovedIn9Warning` now generate errors instead of warning messages by default. **The affected features will be effectively removed in pytest 9.1**, so please consult the `deprecations` section in the docs for directions on how to update existing code. In the pytest `9.0.X` series, it is possible to change the errors back into warnings as a stopgap measure by adding this to your `pytest.ini` file: ```ini [pytest] filterwarnings = ignore::pytest.PytestRemovedIn9Warning ``` But this will stop working when pytest `9.1` is released. **If you have concerns** about the removal of a specific feature, please add a comment to `13779`. #### Deprecations (removal in next major release) - [#​13807](https://redirect.github.com/pytest-dev/pytest/issues/13807): `monkeypatch.syspath_prepend() ` now issues a deprecation warning when the prepended path contains legacy namespace packages (those using `pkg_resources.declare_namespace()`). Users should migrate to native namespace packages (`420`). See `monkeypatch-fixup-namespace-packages` for details. #### Bug fixes - [#​13445](https://redirect.github.com/pytest-dev/pytest/issues/13445): Made the type annotations of `pytest.skip` and friends more spec-complaint to have them work across more type checkers. - [#​13537](https://redirect.github.com/pytest-dev/pytest/issues/13537): Fixed a bug in which `ExceptionGroup` with only `Skipped` exceptions in teardown was not handled correctly and showed as error. - [#​13598](https://redirect.github.com/pytest-dev/pytest/issues/13598): Fixed possible collection confusion on Windows when short paths and symlinks are involved. - [#​13716](https://redirect.github.com/pytest-dev/pytest/issues/13716): Fixed a bug where a nonsensical invocation like `pytest x.py[a]` (a file cannot be parametrized) was silently treated as `pytest x.py`. This is now a usage error. - [#​13722](https://redirect.github.com/pytest-dev/pytest/issues/13722): Fixed a misleading assertion failure message when using `pytest.approx` on mappings with differing lengths. - [#​13773](https://redirect.github.com/pytest-dev/pytest/issues/13773): Fixed the static fixture closure calculation to properly consider transitive dependencies requested by overridden fixtures. - [#​13816](https://redirect.github.com/pytest-dev/pytest/issues/13816): Fixed `pytest.approx` which now returns a clearer error message when comparing mappings with different keys. - [#​13849](https://redirect.github.com/pytest-dev/pytest/issues/13849): Hidden `.pytest.ini` files are now picked up as the config file even if empty. This was an inconsistency with non-hidden `pytest.ini`. - [#​13865](https://redirect.github.com/pytest-dev/pytest/issues/13865): Fixed --show-capture with --tb=line. - [#​13522](https://redirect.github.com/pytest-dev/pytest/issues/13522): Fixed `pytester` in subprocess mode ignored all :attr\`pytester.plugins \\` except the first. Fixed `pytester` in subprocess mode silently ignored non-str `pytester.plugins `. Now it errors instead. If you are affected by this, specify the plugin by name, or switch the affected tests to use `pytester.runpytest_inprocess ` explicitly instead. #### Packaging updates and notes for downstreams - [#​13791](https://redirect.github.com/pytest-dev/pytest/issues/13791): Minimum requirements on `iniconfig` and `packaging` were bumped to `1.0.1` and `22.0.0`, respectively. #### Contributor-facing changes - [#​12244](https://redirect.github.com/pytest-dev/pytest/issues/12244): Fixed self-test failures when TERM=dumb. - [#​12474](https://redirect.github.com/pytest-dev/pytest/issues/12474): Added scheduled GitHub Action Workflow to run Sphinx linkchecks in repo documentation. - [#​13621](https://redirect.github.com/pytest-dev/pytest/issues/13621): pytest's own testsuite now handles the `lsof` command hanging (e.g. due to unreachable network filesystems), with the affected selftests being skipped after 10 seconds. - [#​13638](https://redirect.github.com/pytest-dev/pytest/issues/13638): Fixed deprecated `gh pr new` command in `scripts/prepare-release-pr.py`. The script now uses `gh pr create` which is compatible with GitHub CLI v2.0+. - [#​13695](https://redirect.github.com/pytest-dev/pytest/issues/13695): Flush stdout and stderr in Pytester.run to avoid truncated outputs in test\_faulthandler.py::test\_timeout on CI -- by `ogrisel`. - [#​13771](https://redirect.github.com/pytest-dev/pytest/issues/13771): Skip test\_do\_not\_collect\_symlink\_siblings on Windows environments without symlink support to avoid false negatives. - [#​13841](https://redirect.github.com/pytest-dev/pytest/issues/13841): `tox>=4` is now required when contributing to pytest. - [#​13625](https://redirect.github.com/pytest-dev/pytest/issues/13625): Added missing docstrings to `pytest_addoption()`, `pytest_configure()`, and `cacheshow()` functions in `cacheprovider.py`. #### Miscellaneous internal changes - [#​13830](https://redirect.github.com/pytest-dev/pytest/issues/13830): Configuration overrides (`-o`/`--override-ini`) are now processed during startup rather than during `config.getini() `.
        actions/python-versions (python) ### [`v3.14.3`](https://redirect.github.com/actions/python-versions/releases/tag/3.14.3-21673711214): 3.14.3 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.14.2-20014991423...3.14.3-21673711214) Python 3.14.3 ### [`v3.14.2`](https://redirect.github.com/actions/python-versions/releases/tag/3.14.2-20014991423): 3.14.2 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.14.1-19879739908...3.14.2-20014991423) Python 3.14.2 ### [`v3.14.1`](https://redirect.github.com/actions/python-versions/releases/tag/3.14.1-19879739908): 3.14.1 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.14.0-18313368925...3.14.1-19879739908) Python 3.14.1 ### [`v3.14.0`](https://redirect.github.com/actions/python-versions/releases/tag/3.14.0-18313368925): 3.14.0 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.13.12-21673645133...3.14.0-18313368925) Python 3.14.0 ### [`v3.13.12`](https://redirect.github.com/actions/python-versions/releases/tag/3.13.12-21673645133): 3.13.12 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.13.11-20014977833...3.13.12-21673645133) Python 3.13.12 ### [`v3.13.11`](https://redirect.github.com/actions/python-versions/releases/tag/3.13.11-20014977833): 3.13.11 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.13.10-19879712315...3.13.11-20014977833) Python 3.13.11 ### [`v3.13.10`](https://redirect.github.com/actions/python-versions/releases/tag/3.13.10-19879712315): 3.13.10 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.13.9-18515951191...3.13.10-19879712315) Python 3.13.10 ### [`v3.13.9`](https://redirect.github.com/actions/python-versions/releases/tag/3.13.9-18515951191): 3.13.9 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.13.8-18331000654...3.13.9-18515951191) Python 3.13.9 ### [`v3.13.8`](https://redirect.github.com/actions/python-versions/releases/tag/3.13.8-18331000654): 3.13.8 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.13.7-16980743123...3.13.8-18331000654) Python 3.13.8 ### [`v3.13.7`](https://redirect.github.com/actions/python-versions/releases/tag/3.13.7-16980743123): 3.13.7 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.13.6-16792117939...3.13.7-16980743123) Python 3.13.7 ### [`v3.13.6`](https://redirect.github.com/actions/python-versions/releases/tag/3.13.6-16792117939): 3.13.6 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.13.5-15601068749...3.13.6-16792117939) Python 3.13.6 ### [`v3.13.5`](https://redirect.github.com/actions/python-versions/releases/tag/3.13.5-15601068749): 3.13.5 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.13.4-15433317575...3.13.5-15601068749) Python 3.13.5 ### [`v3.13.4`](https://redirect.github.com/actions/python-versions/releases/tag/3.13.4-15433317575): 3.13.4 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.13.3-14344076652...3.13.4-15433317575) Python 3.13.4 ### [`v3.13.3`](https://redirect.github.com/actions/python-versions/releases/tag/3.13.3-14344076652): 3.13.3 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.13.2-13708744326...3.13.3-14344076652) Python 3.13.3 ### [`v3.13.2`](https://redirect.github.com/actions/python-versions/releases/tag/3.13.2-13708744326): 3.13.2 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.13.1-13437882550...3.13.2-13708744326) Python 3.13.2 ### [`v3.13.1`](https://redirect.github.com/actions/python-versions/releases/tag/3.13.1-13437882550): 3.13.1 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.13.0-13707372259...3.13.1-13437882550) Python 3.13.1 ### [`v3.13.0`](https://redirect.github.com/actions/python-versions/releases/tag/3.13.0-13707372259): 3.13.0 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.12.12-18393146713...3.13.0-13707372259) Python 3.13.0 ### [`v3.12.12`](https://redirect.github.com/actions/python-versions/releases/tag/3.12.12-18393146713): 3.12.12 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.12.11-15433310049...3.12.12-18393146713) Python 3.12.12 ### [`v3.12.11`](https://redirect.github.com/actions/python-versions/releases/tag/3.12.11-15433310049): 3.12.11 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.12.10-14343898437...3.12.11-15433310049) Python 3.12.11 ### [`v3.12.10`](https://redirect.github.com/actions/python-versions/releases/tag/3.12.10-14343898437): 3.12.10 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.12.9-13149478207...3.12.10-14343898437) Python 3.12.10 ### [`v3.12.9`](https://redirect.github.com/actions/python-versions/releases/tag/3.12.9-13149478207): 3.12.9 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.12.8-12154062663...3.12.9-13149478207) Python 3.12.9 ### [`v3.12.8`](https://redirect.github.com/actions/python-versions/releases/tag/3.12.8-12154062663): 3.12.8 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.12.7-11128208086...3.12.8-12154062663) Python 3.12.8 ### [`v3.12.7`](https://redirect.github.com/actions/python-versions/releases/tag/3.12.7-11128208086): 3.12.7 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.12.6-10765725458...3.12.7-11128208086) Python 3.12.7 ### [`v3.12.6`](https://redirect.github.com/actions/python-versions/releases/tag/3.12.6-10765725458): 3.12.6 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.12.5-10375840348...3.12.6-10765725458) Python 3.12.6 ### [`v3.12.5`](https://redirect.github.com/actions/python-versions/releases/tag/3.12.5-10375840348): 3.12.5 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.12.4-9947065640...3.12.5-10375840348) Python 3.12.5 ### [`v3.12.4`](https://redirect.github.com/actions/python-versions/releases/tag/3.12.4-9947065640): 3.12.4 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.12.3-11057844995...3.12.4-9947065640) Python 3.12.4 ### [`v3.12.3`](https://redirect.github.com/actions/python-versions/releases/tag/3.12.3-11057844995): 3.12.3 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.12.2-11057786931...3.12.3-11057844995) Python 3.12.3 ### [`v3.12.2`](https://redirect.github.com/actions/python-versions/releases/tag/3.12.2-11057786931): 3.12.2 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.12.1-11057762749...3.12.2-11057786931) Python 3.12.2 ### [`v3.12.1`](https://redirect.github.com/actions/python-versions/releases/tag/3.12.1-11057762749): 3.12.1 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.12.0-11057302691...3.12.1-11057762749) Python 3.12.1 ### [`v3.12.0`](https://redirect.github.com/actions/python-versions/releases/tag/3.12.0-11057302691): 3.12.0 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.11.14-18393181605...3.12.0-11057302691) Python 3.12.0 ### [`v3.11.14`](https://redirect.github.com/actions/python-versions/releases/tag/3.11.14-18393181605): 3.11.14 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.11.13-15433298024...3.11.14-18393181605) Python 3.11.14 ### [`v3.11.13`](https://redirect.github.com/actions/python-versions/releases/tag/3.11.13-15433298024): 3.11.13 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.11.12-14343939122...3.11.13-15433298024) Python 3.11.13 ### [`v3.11.12`](https://redirect.github.com/actions/python-versions/releases/tag/3.11.12-14343939122): 3.11.12 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.11.11-12160100664...3.11.12-14343939122) Python 3.11.12 ### [`v3.11.11`](https://redirect.github.com/actions/python-versions/releases/tag/3.11.11-12160100664): 3.11.11 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.11.10-10765870205...3.11.11-12160100664) Python 3.11.11 ### [`v3.11.10`](https://redirect.github.com/actions/python-versions/releases/tag/3.11.10-10765870205): 3.11.10 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.11.9-9947079978...3.11.10-10765870205) Python 3.11.10 ### [`v3.11.9`](https://redirect.github.com/actions/python-versions/releases/tag/3.11.9-9947079978): 3.11.9 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.11.8-11113201752...3.11.9-9947079978) Python 3.11.9 ### [`v3.11.8`](https://redirect.github.com/actions/python-versions/releases/tag/3.11.8-11113201752): 3.11.8 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.11.7-11113197120...3.11.8-11113201752) Python 3.11.8 ### [`v3.11.7`](https://redirect.github.com/actions/python-versions/releases/tag/3.11.7-11113197120): 3.11.7 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.11.6-11113179737...3.11.7-11113197120) Python 3.11.7 ### [`v3.11.6`](https://redirect.github.com/actions/python-versions/releases/tag/3.11.6-11113179737): 3.11.6 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.11.5-11113174019...3.11.6-11113179737) Python 3.11.6 ### [`v3.11.5`](https://redirect.github.com/actions/python-versions/releases/tag/3.11.5-11113174019): 3.11.5 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.11.4-11113170699...3.11.5-11113174019) Python 3.11.5 ### [`v3.11.4`](https://redirect.github.com/actions/python-versions/releases/tag/3.11.4-11113170699): 3.11.4 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.11.3-11059198104...3.11.4-11113170699) Python 3.11.4 ### [`v3.11.3`](https://redirect.github.com/actions/python-versions/releases/tag/3.11.3-11059198104): 3.11.3 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.11.2-11059137522...3.11.3-11059198104) Python 3.11.3 ### [`v3.11.2`](https://redirect.github.com/actions/python-versions/releases/tag/3.11.2-11059137522): 3.11.2 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.11.1-11058799881...3.11.2-11059137522) Python 3.11.2 ### [`v3.11.1`](https://redirect.github.com/actions/python-versions/releases/tag/3.11.1-11058799881): 3.11.1 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.11.0-11058707212...3.11.1-11058799881) Python 3.11.1 ### [`v3.11.0`](https://redirect.github.com/actions/python-versions/releases/tag/3.11.0-11058707212): 3.11.0 [Compare Source](https://redirect.github.com/actions/python-versions/compare/3.10.19-18393196481...3.11.0-11058707212) Python 3.11.0
        --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/googleapis/python-datastore). --------- Co-authored-by: Anthonios Partheniou * fix: can't claim all rights reserved and be Apache 2. * fix: Update requirements-test.txt * Delete datastore/samples/snippets/schedule-export/noxfile.py * Delete datastore/samples/snippets/schedule-export/noxfile_config.py * Delete datastore/samples/snippets/noxfile.py * Delete datastore/samples/snippets/noxfile_config.py --------- Co-authored-by: Juan Lara Co-authored-by: Mariatta Wijaya Co-authored-by: Owl Bot Co-authored-by: gcf-owl-bot[bot] <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Co-authored-by: Anthonios Partheniou Co-authored-by: WhiteSource Renovate Co-authored-by: meredithslota Co-authored-by: Daniel Sanche Co-authored-by: Jennifer Davis --- .../samples/snippets/requirements-test.txt | 7 + datastore/samples/snippets/requirements.txt | 1 + .../snippets/schedule-export/README.md | 5 + .../samples/snippets/schedule-export/main.py | 57 ++ .../schedule-export/requirements-test.txt | 2 + .../snippets/schedule-export/requirements.txt | 1 + .../schedule-export/schedule_export_test.py | 73 +++ datastore/samples/snippets/snippets.py | 513 ++++++++++++++++++ datastore/samples/snippets/snippets_test.py | 249 +++++++++ 9 files changed, 908 insertions(+) create mode 100644 datastore/samples/snippets/requirements-test.txt create mode 100644 datastore/samples/snippets/requirements.txt create mode 100644 datastore/samples/snippets/schedule-export/README.md create mode 100644 datastore/samples/snippets/schedule-export/main.py create mode 100644 datastore/samples/snippets/schedule-export/requirements-test.txt create mode 100644 datastore/samples/snippets/schedule-export/requirements.txt create mode 100644 datastore/samples/snippets/schedule-export/schedule_export_test.py create mode 100644 datastore/samples/snippets/snippets.py create mode 100644 datastore/samples/snippets/snippets_test.py diff --git a/datastore/samples/snippets/requirements-test.txt b/datastore/samples/snippets/requirements-test.txt new file mode 100644 index 00000000000..2a21e952015 --- /dev/null +++ b/datastore/samples/snippets/requirements-test.txt @@ -0,0 +1,7 @@ +backoff===1.11.1; python_version < "3.7" +backoff==2.2.1; python_version >= "3.7" +pytest===7.4.3; python_version == '3.7' +pytest===8.3.5; python_version == '3.8' +pytest===8.4.2; python_version == '3.9' +pytest==9.0.2; python_version >= '3.10' +flaky==3.8.1 diff --git a/datastore/samples/snippets/requirements.txt b/datastore/samples/snippets/requirements.txt new file mode 100644 index 00000000000..7852f23b24e --- /dev/null +++ b/datastore/samples/snippets/requirements.txt @@ -0,0 +1 @@ +google-cloud-datastore==2.23.0 \ No newline at end of file diff --git a/datastore/samples/snippets/schedule-export/README.md b/datastore/samples/snippets/schedule-export/README.md new file mode 100644 index 00000000000..a8501cddc34 --- /dev/null +++ b/datastore/samples/snippets/schedule-export/README.md @@ -0,0 +1,5 @@ +# Scheduling Datastore exports with Cloud Functions and Cloud Scheduler + +This sample application demonstrates how to schedule exports of your Datastore entities. To deploy this sample, see: + +[Scheduling exports](https://cloud.google.com/datastore/docs/schedule-export) diff --git a/datastore/samples/snippets/schedule-export/main.py b/datastore/samples/snippets/schedule-export/main.py new file mode 100644 index 00000000000..f91b1466913 --- /dev/null +++ b/datastore/samples/snippets/schedule-export/main.py @@ -0,0 +1,57 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import base64 +import json +import os + +from google.cloud import datastore_admin_v1 + +project_id = os.environ.get("GCP_PROJECT") +client = datastore_admin_v1.DatastoreAdminClient() + + +def datastore_export(event, context): + """Triggers a Datastore export from a Cloud Scheduler job. + + Args: + event (dict): event[data] must contain a json object encoded in + base-64. Cloud Scheduler encodes payloads in base-64 by default. + Object must include a 'bucket' value and can include 'kinds' + and 'namespaceIds' values. + context (google.cloud.functions.Context): The Cloud Functions event + metadata. + """ + if "data" in event: + # Triggered via Cloud Scheduler, decode the inner data field of the json payload. + json_data = json.loads(base64.b64decode(event["data"]).decode("utf-8")) + else: + # Otherwise, for instance if triggered via the Cloud Console on a Cloud Function, the event is the data. + json_data = event + + bucket = json_data["bucket"] + entity_filter = datastore_admin_v1.EntityFilter() + + if "kinds" in json_data: + entity_filter.kinds = json_data["kinds"] + + if "namespaceIds" in json_data: + entity_filter.namespace_ids = json_data["namespaceIds"] + + export_request = datastore_admin_v1.ExportEntitiesRequest( + project_id=project_id, output_url_prefix=bucket, entity_filter=entity_filter + ) + operation = client.export_entities(request=export_request) + response = operation.result() + print(response) diff --git a/datastore/samples/snippets/schedule-export/requirements-test.txt b/datastore/samples/snippets/schedule-export/requirements-test.txt new file mode 100644 index 00000000000..cb982446b31 --- /dev/null +++ b/datastore/samples/snippets/schedule-export/requirements-test.txt @@ -0,0 +1,2 @@ +pytest===8.4.2; python_version == '3.9' +pytest==9.0.2; python_version >= '3.10' diff --git a/datastore/samples/snippets/schedule-export/requirements.txt b/datastore/samples/snippets/schedule-export/requirements.txt new file mode 100644 index 00000000000..fa16c1e95ab --- /dev/null +++ b/datastore/samples/snippets/schedule-export/requirements.txt @@ -0,0 +1 @@ +google-cloud-datastore==2.23.0 diff --git a/datastore/samples/snippets/schedule-export/schedule_export_test.py b/datastore/samples/snippets/schedule-export/schedule_export_test.py new file mode 100644 index 00000000000..48d9147c923 --- /dev/null +++ b/datastore/samples/snippets/schedule-export/schedule_export_test.py @@ -0,0 +1,73 @@ +# Copyright 2019 Google LLC All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import base64 +from unittest.mock import Mock + +import main + +mock_context = Mock() +mock_context.event_id = "617187464135194" +mock_context.timestamp = "2020-04-15T22:09:03.761Z" + + +def test_datastore_export(capsys): + # Test an export without an entity filter + bucket = "gs://my-bucket" + json_string = '{{ "bucket": "{bucket}" }}'.format(bucket=bucket) + + # Encode data like Cloud Scheduler + data = bytes(json_string, "utf-8") + data_encoded = base64.b64encode(data) + event = {"data": data_encoded} + + # Mock the Datastore service + mockDatastore = Mock() + main.client = mockDatastore + + # Call tested function + main.datastore_export(event, mock_context) + out, err = capsys.readouterr() + export_args = mockDatastore.export_entities.call_args[1] + # Assert request includes test values + assert export_args["request"].output_url_prefix == bucket + + +def test_datastore_export_entity_filter(capsys): + # Test an export with an entity filter + bucket = "gs://my-bucket" + kinds = "Users,Tasks" + namespaceIds = "Customer831,Customer157" + json_string = '{{ "bucket": "{bucket}", "kinds": "{kinds}", "namespaceIds": "{namespaceIds}" }}'.format( + bucket=bucket, kinds=kinds, namespaceIds=namespaceIds + ) + + # Encode data like Cloud Scheduler + data = bytes(json_string, "utf-8") + data_encoded = base64.b64encode(data) + event = {"data": data_encoded} + + # Mock the Datastore service + mockDatastore = Mock() + main.client = mockDatastore + + # Call tested function + main.datastore_export(event, mock_context) + out, err = capsys.readouterr() + export_args = mockDatastore.export_entities.call_args[1] + # Assert request includes test values + + assert export_args["request"].output_url_prefix == bucket + assert export_args["request"].entity_filter.kinds == kinds + assert export_args["request"].entity_filter.namespace_ids == namespaceIds diff --git a/datastore/samples/snippets/snippets.py b/datastore/samples/snippets/snippets.py new file mode 100644 index 00000000000..1b86ba8b0cd --- /dev/null +++ b/datastore/samples/snippets/snippets.py @@ -0,0 +1,513 @@ +# Copyright 2022 Google, Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +from datetime import datetime, timedelta, timezone +from pprint import pprint +import time + +from google.cloud import datastore # noqa: I100 + + +def _preamble(): + # [START datastore_size_coloration_query] + from google.cloud import datastore + + # For help authenticating your client, visit + # https://cloud.google.com/docs/authentication/getting-started + client = datastore.Client() + + # [END datastore_size_coloration_query] + assert client is not None + + +def in_query(client): + # [START datastore_in_query] + query = client.query(kind="Task") + query.add_filter("tag", "IN", ["learn", "study"]) + # [END datastore_in_query] + + return list(query.fetch()) + + +def not_equals_query(client): + # [START datastore_not_equals_query] + query = client.query(kind="Task") + query.add_filter("category", "!=", "work") + # [END datastore_not_equals_query] + + return list(query.fetch()) + + +def not_in_query(client): + # [START datastore_not_in_query] + query = client.query(kind="Task") + query.add_filter("category", "NOT_IN", ["work", "chores", "school"]) + # [END datastore_not_in_query] + + return list(query.fetch()) + + +def query_with_readtime(client): + # [START datastore_stale_read] + # Create a read time of 15 seconds in the past + read_time = datetime.now(timezone.utc) - timedelta(seconds=15) + + # Fetch an entity with read_time + task_key = client.key("Task", "sampletask") + entity = client.get(task_key, read_time=read_time) + + # Query Task entities with read_time + query = client.query(kind="Task") + tasks = query.fetch(read_time=read_time, limit=10) + # [END datastore_stale_read] + + results = list(tasks) + results.append(entity) + + return results + + +def count_query_in_transaction(client): + # [START datastore_count_in_transaction] + task1 = datastore.Entity(client.key("Task", "task1")) + task2 = datastore.Entity(client.key("Task", "task2")) + + task1["owner"] = "john" + task2["owner"] = "john" + + tasks = [task1, task2] + client.put_multi(tasks) + + with client.transaction() as transaction: + + tasks_of_john = client.query(kind="Task") + tasks_of_john.add_filter("owner", "=", "john") + total_tasks_query = client.aggregation_query(tasks_of_john) + + query_result = total_tasks_query.count(alias="tasks_count").fetch() + for task_result in query_result: + tasks_count = task_result[0] + if tasks_count.value < 2: + task3 = datastore.Entity(client.key("Task", "task3")) + task3["owner"] = "john" + transaction.put(task3) + tasks.append(task3) + else: + print(f"Found existing {tasks_count.value} tasks, rolling back") + client.entities_to_delete.extend(tasks) + raise ValueError("User 'John' cannot have more than 2 tasks") + # [END datastore_count_in_transaction] + + +def count_query_on_kind(client): + # [START datastore_count_on_kind] + task1 = datastore.Entity(client.key("Task", "task1")) + task2 = datastore.Entity(client.key("Task", "task2")) + + tasks = [task1, task2] + client.put_multi(tasks) + all_tasks_query = client.query(kind="Task") + all_tasks_count_query = client.aggregation_query(all_tasks_query).count() + query_result = all_tasks_count_query.fetch() + for aggregation_results in query_result: + for aggregation in aggregation_results: + print(f"Total tasks (accessible from default alias) is {aggregation.value}") + # [END datastore_count_on_kind] + return tasks + + +def count_query_with_limit(client): + # [START datastore_count_with_limit] + task1 = datastore.Entity(client.key("Task", "task1")) + task2 = datastore.Entity(client.key("Task", "task2")) + task3 = datastore.Entity(client.key("Task", "task3")) + + tasks = [task1, task2, task3] + client.put_multi(tasks) + all_tasks_query = client.query(kind="Task") + all_tasks_count_query = client.aggregation_query(all_tasks_query).count() + query_result = all_tasks_count_query.fetch(limit=2) + for aggregation_results in query_result: + for aggregation in aggregation_results: + print(f"We have at least {aggregation.value} tasks") + # [END datastore_count_with_limit] + return tasks + + +def count_query_property_filter(client): + # [START datastore_count_with_property_filter] + task1 = datastore.Entity(client.key("Task", "task1")) + task2 = datastore.Entity(client.key("Task", "task2")) + task3 = datastore.Entity(client.key("Task", "task3")) + + task1["done"] = True + task2["done"] = False + task3["done"] = True + + tasks = [task1, task2, task3] + client.put_multi(tasks) + completed_tasks = client.query(kind="Task").add_filter("done", "=", True) + remaining_tasks = client.query(kind="Task").add_filter("done", "=", False) + + completed_tasks_query = client.aggregation_query(query=completed_tasks).count( + alias="total_completed_count" + ) + remaining_tasks_query = client.aggregation_query(query=remaining_tasks).count( + alias="total_remaining_count" + ) + + completed_query_result = completed_tasks_query.fetch() + for aggregation_results in completed_query_result: + for aggregation_result in aggregation_results: + if aggregation_result.alias == "total_completed_count": + print(f"Total completed tasks count is {aggregation_result.value}") + + remaining_query_result = remaining_tasks_query.fetch() + for aggregation_results in remaining_query_result: + for aggregation_result in aggregation_results: + if aggregation_result.alias == "total_remaining_count": + print(f"Total remaining tasks count is {aggregation_result.value}") + # [END datastore_count_with_property_filter] + return tasks + + +def count_query_with_stale_read(client): + + tasks = [task for task in client.query(kind="Task").fetch()] + client.delete_multi(tasks) # ensure the database is empty before starting + + # [START datastore_count_query_with_stale_read] + task1 = datastore.Entity(client.key("Task", "task1")) + task2 = datastore.Entity(client.key("Task", "task2")) + + # Saving two tasks + task1["done"] = True + task2["done"] = False + client.put_multi([task1, task2]) + time.sleep(10) + + past_timestamp = datetime.now( + timezone.utc + ) # we have two tasks in database at this time. + time.sleep(10) + + # Saving third task + task3 = datastore.Entity(client.key("Task", "task3")) + task3["done"] = False + client.put(task3) + + all_tasks = client.query(kind="Task") + all_tasks_count = client.aggregation_query( + query=all_tasks, + ).count(alias="all_tasks_count") + + # Executing aggregation query + query_result = all_tasks_count.fetch() + for aggregation_results in query_result: + for aggregation_result in aggregation_results: + print(f"Latest tasks count is {aggregation_result.value}") + + # Executing aggregation query with past timestamp + tasks_in_past = client.aggregation_query(query=all_tasks).count( + alias="tasks_in_past" + ) + tasks_in_the_past_query_result = tasks_in_past.fetch(read_time=past_timestamp) + for aggregation_results in tasks_in_the_past_query_result: + for aggregation_result in aggregation_results: + print(f"Stale tasks count is {aggregation_result.value}") + # [END datastore_count_query_with_stale_read] + return [task1, task2, task3] + + +def sum_query_on_kind(client): + # [START datastore_sum_aggregation_query_on_kind] + # Set up sample entities + # Use incomplete key to auto-generate ID + task1 = datastore.Entity(client.key("Task")) + task2 = datastore.Entity(client.key("Task")) + task3 = datastore.Entity(client.key("Task")) + + task1["hours"] = 5 + task2["hours"] = 3 + task3["hours"] = 1 + + tasks = [task1, task2, task3] + client.put_multi(tasks) + + # Execute sum aggregation query + all_tasks_query = client.query(kind="Task") + all_tasks_sum_query = client.aggregation_query(all_tasks_query).sum("hours") + query_result = all_tasks_sum_query.fetch() + for aggregation_results in query_result: + for aggregation in aggregation_results: + print(f"Total sum of hours in tasks is {aggregation.value}") + # [END datastore_sum_aggregation_query_on_kind] + return tasks + + +def sum_query_property_filter(client): + # [START datastore_sum_aggregation_query_with_filters] + # Set up sample entities + # Use incomplete key to auto-generate ID + task1 = datastore.Entity(client.key("Task")) + task2 = datastore.Entity(client.key("Task")) + task3 = datastore.Entity(client.key("Task")) + + task1["hours"] = 5 + task2["hours"] = 3 + task3["hours"] = 1 + + task1["done"] = True + task2["done"] = True + task3["done"] = False + + tasks = [task1, task2, task3] + client.put_multi(tasks) + + # Execute sum aggregation query with filters + completed_tasks = client.query(kind="Task").add_filter("done", "=", True) + completed_tasks_query = client.aggregation_query(query=completed_tasks).sum( + property_ref="hours", alias="total_completed_sum_hours" + ) + + completed_query_result = completed_tasks_query.fetch() + for aggregation_results in completed_query_result: + for aggregation_result in aggregation_results: + if aggregation_result.alias == "total_completed_sum_hours": + print( + f"Total sum of hours in completed tasks is {aggregation_result.value}" + ) + # [END datastore_sum_aggregation_query_with_filters] + return tasks + + +def avg_query_on_kind(client): + # [START datastore_avg_aggregation_query_on_kind] + # Set up sample entities + # Use incomplete key to auto-generate ID + task1 = datastore.Entity(client.key("Task")) + task2 = datastore.Entity(client.key("Task")) + task3 = datastore.Entity(client.key("Task")) + + task1["hours"] = 5 + task2["hours"] = 3 + task3["hours"] = 1 + + tasks = [task1, task2, task3] + client.put_multi(tasks) + + # Execute average aggregation query + all_tasks_query = client.query(kind="Task") + all_tasks_avg_query = client.aggregation_query(all_tasks_query).avg("hours") + query_result = all_tasks_avg_query.fetch() + for aggregation_results in query_result: + for aggregation in aggregation_results: + print(f"Total average of hours in tasks is {aggregation.value}") + # [END datastore_avg_aggregation_query_on_kind] + return tasks + + +def avg_query_property_filter(client): + # [START datastore_avg_aggregation_query_with_filters] + # Set up sample entities + # Use incomplete key to auto-generate ID + task1 = datastore.Entity(client.key("Task")) + task2 = datastore.Entity(client.key("Task")) + task3 = datastore.Entity(client.key("Task")) + + task1["hours"] = 5 + task2["hours"] = 3 + task3["hours"] = 1 + + task1["done"] = True + task2["done"] = True + task3["done"] = False + + tasks = [task1, task2, task3] + client.put_multi(tasks) + + # Execute average aggregation query with filters + completed_tasks = client.query(kind="Task").add_filter("done", "=", True) + completed_tasks_query = client.aggregation_query(query=completed_tasks).avg( + property_ref="hours", alias="total_completed_avg_hours" + ) + + completed_query_result = completed_tasks_query.fetch() + for aggregation_results in completed_query_result: + for aggregation_result in aggregation_results: + if aggregation_result.alias == "total_completed_avg_hours": + print( + f"Total average of hours in completed tasks is {aggregation_result.value}" + ) + # [END datastore_avg_aggregation_query_with_filters] + return tasks + + +def multiple_aggregations_query(client): + # [START datastore_multiple_aggregation_in_structured_query] + # Set up sample entities + # Use incomplete key to auto-generate ID + task1 = datastore.Entity(client.key("Task")) + task2 = datastore.Entity(client.key("Task")) + task3 = datastore.Entity(client.key("Task")) + + task1["hours"] = 5 + task2["hours"] = 3 + task3["hours"] = 1 + + tasks = [task1, task2, task3] + client.put_multi(tasks) + + # Execute query with multiple aggregations + all_tasks_query = client.query(kind="Task") + aggregation_query = client.aggregation_query(all_tasks_query) + # Add aggregations + aggregation_query.add_aggregations( + [ + datastore.aggregation.CountAggregation(alias="count_aggregation"), + datastore.aggregation.SumAggregation( + property_ref="hours", alias="sum_aggregation" + ), + datastore.aggregation.AvgAggregation( + property_ref="hours", alias="avg_aggregation" + ), + ] + ) + + query_result = aggregation_query.fetch() + for aggregation_results in query_result: + for aggregation in aggregation_results: + print(f"{aggregation.alias} value is {aggregation.value}") + # [END datastore_multiple_aggregation_in_structured_query] + return tasks + + +def explain_analyze_entity(client): + # [START datastore_query_explain_analyze_entity] + # Build the query with explain_options + # analzye = true to get back the query stats, plan info, and query results + query = client.query( + kind="Task", explain_options=datastore.ExplainOptions(analyze=True) + ) + + # initiate the query + iterator = query.fetch() + + # explain_metrics is only available after query is completed + for task_result in iterator: + print(task_result) + + # get the plan summary + plan_summary = iterator.explain_metrics.plan_summary + print(f"Indexes used: {plan_summary.indexes_used}") + + # get the execution stats + execution_stats = iterator.explain_metrics.execution_stats + print(f"Results returned: {execution_stats.results_returned}") + print(f"Execution duration: {execution_stats.execution_duration}") + print(f"Read operations: {execution_stats.read_operations}") + print(f"Debug stats: {execution_stats.debug_stats}") + # [END datastore_query_explain_analyze_entity] + + +def explain_entity(client): + # [START datastore_query_explain_entity] + # Build the query with explain_options + # by default (analyze = false), only plan_summary property is available + query = client.query(kind="Task", explain_options=datastore.ExplainOptions()) + + # initiate the query + iterator = query.fetch() + + # get the plan summary + plan_summary = iterator.explain_metrics.plan_summary + print(f"Indexes used: {plan_summary.indexes_used}") + # [END datastore_query_explain_entity] + + +def explain_analyze_aggregation(client): + # [START datastore_query_explain_analyze_aggregation] + # Build the aggregation query with explain_options + # analzye = true to get back the query stats, plan info, and query results + all_tasks_query = client.query(kind="Task") + count_query = client.aggregation_query( + all_tasks_query, explain_options=datastore.ExplainOptions(analyze=True) + ).count() + + # initiate the query + iterator = count_query.fetch() + + # explain_metrics is only available after query is completed + for task_result in iterator: + print(task_result) + + # get the plan summary + plan_summary = iterator.explain_metrics.plan_summary + print(f"Indexes used: {plan_summary.indexes_used}") + + # get the execution stats + execution_stats = iterator.explain_metrics.execution_stats + print(f"Results returned: {execution_stats.results_returned}") + print(f"Execution duration: {execution_stats.execution_duration}") + print(f"Read operations: {execution_stats.read_operations}") + print(f"Debug stats: {execution_stats.debug_stats}") + # [END datastore_query_explain_analyze_aggregation] + + +def explain_aggregation(client): + # [START datastore_query_explain_aggregation] + # Build the aggregation query with explain_options + # by default (analyze = false), only plan_summary property is available + all_tasks_query = client.query(kind="Task") + count_query = client.aggregation_query( + all_tasks_query, explain_options=datastore.ExplainOptions() + ).count() + + # initiate the query + iterator = count_query.fetch() + + # get the plan summary + plan_summary = iterator.explain_metrics.plan_summary + print(f"Indexes used: {plan_summary.indexes_used}") + # [END datastore_query_explain_aggregation] + + +def main(project_id): + client = datastore.Client(project_id) + + for name, function in globals().items(): + if name in ( + "main", + "_preamble", + "defaultdict", + "datetime", + "timezone", + "timedelta", + ) or not callable(function): + continue + + print(name) + pprint(function(client)) + print("\n-----------------\n") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Demonstrates datastore API operations." + ) + parser.add_argument("project_id", help="Your cloud project ID.") + + args = parser.parse_args() + + main(args.project_id) diff --git a/datastore/samples/snippets/snippets_test.py b/datastore/samples/snippets/snippets_test.py new file mode 100644 index 00000000000..ae3b2948b34 --- /dev/null +++ b/datastore/samples/snippets/snippets_test.py @@ -0,0 +1,249 @@ +# Copyright 2022 Google, Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +import backoff +import google.api_core.exceptions +from google.cloud import datastore +from google.cloud import datastore_admin_v1 +import pytest + +import snippets + +PROJECT = os.environ["GOOGLE_CLOUD_PROJECT"] + + +class CleanupClient(datastore.Client): + def __init__(self, *args, **kwargs): + super(CleanupClient, self).__init__(*args, **kwargs) + self.entities_to_delete = [] + self.keys_to_delete = [] + + def cleanup(self): + with self.batch(): + self.delete_multi( + list(set([x.key for x in self.entities_to_delete if x])) + + list(set(self.keys_to_delete)) + ) + + +@pytest.fixture +def client(): + client = CleanupClient(PROJECT) + yield client + client.cleanup() + + +@pytest.fixture(scope="session", autouse=True) +def setup_indexes(request): + # Set up required indexes + admin_client = datastore_admin_v1.DatastoreAdminClient() + + indexes = [] + done_property_index = datastore_admin_v1.Index.IndexedProperty( + name="done", direction=datastore_admin_v1.Index.Direction.ASCENDING + ) + hour_property_index = datastore_admin_v1.Index.IndexedProperty( + name="hours", direction=datastore_admin_v1.Index.Direction.ASCENDING + ) + done_hour_index = datastore_admin_v1.Index( + kind="Task", + ancestor=datastore_admin_v1.Index.AncestorMode.NONE, + properties=[done_property_index, hour_property_index], + ) + indexes.append(done_hour_index) + + for index in indexes: + request = datastore_admin_v1.CreateIndexRequest(project_id=PROJECT, index=index) + # Create the required index + # Dependant tests will fail until the index is ready + try: + admin_client.create_index(request) + # Pass if the index already exists + except (google.api_core.exceptions.AlreadyExists): + pass + + +@pytest.mark.flaky +class TestDatastoreSnippets: + # These tests mostly just test the absence of exceptions. + + @backoff.on_exception(backoff.expo, AssertionError, max_time=240) + def test_in_query(self, client): + tasks = snippets.in_query(client) + client.entities_to_delete.extend(tasks) + assert tasks is not None + + @backoff.on_exception(backoff.expo, AssertionError, max_time=240) + def test_not_equals_query(self, client): + tasks = snippets.not_equals_query(client) + client.entities_to_delete.extend(tasks) + assert tasks is not None + + @backoff.on_exception(backoff.expo, AssertionError, max_time=240) + def test_not_in_query(self, client): + tasks = snippets.not_in_query(client) + client.entities_to_delete.extend(tasks) + assert tasks is not None + + @backoff.on_exception(backoff.expo, AssertionError, max_time=240) + def test_query_with_readtime(self, client): + tasks = snippets.query_with_readtime(client) + client.entities_to_delete.extend(tasks) + assert tasks is not None + + @backoff.on_exception(backoff.expo, AssertionError, max_time=240) + def test_count_query_in_transaction(self, client): + with pytest.raises(ValueError) as excinfo: + snippets.count_query_in_transaction(client) + assert "User 'John' cannot have more than 2 tasks" in str(excinfo.value) + + @backoff.on_exception(backoff.expo, AssertionError, max_time=240) + def test_count_query_on_kind(self, capsys, client): + tasks = snippets.count_query_on_kind(client) + captured = capsys.readouterr() + assert ( + captured.out.strip() == "Total tasks (accessible from default alias) is 2" + ) + assert captured.err == "" + + client.entities_to_delete.extend(tasks) + + @backoff.on_exception(backoff.expo, AssertionError, max_time=240) + def test_count_query_with_limit(self, capsys, client): + tasks = snippets.count_query_with_limit(client) + captured = capsys.readouterr() + assert captured.out.strip() == "We have at least 2 tasks" + assert captured.err == "" + + client.entities_to_delete.extend(tasks) + + @backoff.on_exception(backoff.expo, AssertionError, max_time=240) + def test_count_query_property_filter(self, capsys, client): + tasks = snippets.count_query_property_filter(client) + captured = capsys.readouterr() + + assert "Total completed tasks count is 2" in captured.out + assert "Total remaining tasks count is 1" in captured.out + assert captured.err == "" + + client.entities_to_delete.extend(tasks) + + @backoff.on_exception(backoff.expo, AssertionError, max_time=240) + def test_count_query_with_stale_read(self, capsys, client): + tasks = snippets.count_query_with_stale_read(client) + captured = capsys.readouterr() + + assert "Latest tasks count is 3" in captured.out + assert "Stale tasks count is 2" in captured.out + assert captured.err == "" + + client.entities_to_delete.extend(tasks) + + @backoff.on_exception(backoff.expo, AssertionError, max_time=240) + def test_sum_query_on_kind(self, capsys, client): + tasks = snippets.sum_query_on_kind(client) + captured = capsys.readouterr() + assert captured.out.strip() == "Total sum of hours in tasks is 9" + assert captured.err == "" + + client.entities_to_delete.extend(tasks) + + @backoff.on_exception(backoff.expo, AssertionError, max_time=240) + def test_sum_query_property_filter(self, capsys, client): + tasks = snippets.sum_query_property_filter(client) + captured = capsys.readouterr() + assert captured.out.strip() == "Total sum of hours in completed tasks is 8" + assert captured.err == "" + + client.entities_to_delete.extend(tasks) + + @backoff.on_exception(backoff.expo, AssertionError, max_time=240) + def test_avg_query_on_kind(self, capsys, client): + tasks = snippets.avg_query_on_kind(client) + captured = capsys.readouterr() + assert captured.out.strip() == "Total average of hours in tasks is 3.0" + assert captured.err == "" + + client.entities_to_delete.extend(tasks) + + @backoff.on_exception(backoff.expo, AssertionError, max_time=240) + def test_avg_query_property_filter(self, capsys, client): + tasks = snippets.avg_query_property_filter(client) + captured = capsys.readouterr() + assert ( + captured.out.strip() == "Total average of hours in completed tasks is 4.0" + ) + assert captured.err == "" + + client.entities_to_delete.extend(tasks) + + @backoff.on_exception(backoff.expo, AssertionError, max_time=240) + def test_multiple_aggregations_query(self, capsys, client): + tasks = snippets.multiple_aggregations_query(client) + captured = capsys.readouterr() + assert "avg_aggregation value is 3.0" in captured.out + assert "count_aggregation value is 3" in captured.out + assert "sum_aggregation value is 9" in captured.out + assert captured.err == "" + + client.entities_to_delete.extend(tasks) + + @backoff.on_exception(backoff.expo, AssertionError, max_time=240) + def test_explain_analyze_entity(self, capsys, client): + snippets.explain_analyze_entity(client) + captured = capsys.readouterr() + assert ( + "Indexes used: [{'properties': '(__name__ ASC)', 'query_scope': 'Collection group'}]" + in captured.out + ) + assert "Results returned: 0" in captured.out + assert "Execution duration: 0:00" in captured.out + assert "Read operations: 0" in captured.out + assert "Debug stats: {" in captured.out + assert captured.err == "" + + @backoff.on_exception(backoff.expo, AssertionError, max_time=240) + def test_explain_entity(self, capsys, client): + snippets.explain_entity(client) + captured = capsys.readouterr() + assert ( + "Indexes used: [{'properties': '(__name__ ASC)', 'query_scope': 'Collection group'}]" + in captured.out + ) + assert captured.err == "" + + @backoff.on_exception(backoff.expo, AssertionError, max_time=240) + def test_explain_analyze_aggregation(self, capsys, client): + snippets.explain_analyze_aggregation(client) + captured = capsys.readouterr() + assert ( + "Indexes used: [{'properties': '(__name__ ASC)', 'query_scope': 'Collection group'}]" + in captured.out + ) + assert "Results returned: 1" in captured.out + assert "Execution duration: 0:00" in captured.out + assert "Read operations: 1" in captured.out + assert "Debug stats: {" in captured.out + assert captured.err == "" + + @backoff.on_exception(backoff.expo, AssertionError, max_time=240) + def test_explain_aggregation(self, capsys, client): + snippets.explain_aggregation(client) + captured = capsys.readouterr() + assert ( + "Indexes used: [{'properties': '(__name__ ASC)', 'query_scope': 'Collection group'}]" + in captured.out + ) + assert captured.err == "" From 7d099b48020d1d4ed32e9dea0b65df3abb51f4cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:48:14 -0500 Subject: [PATCH 60/67] chore(deps): bump werkzeug from 3.0.3 to 3.1.6 in /run/helloworld (#13864) Bumps [werkzeug](https://github.com/pallets/werkzeug) from 3.0.3 to 3.1.6. - [Release notes](https://github.com/pallets/werkzeug/releases) - [Changelog](https://github.com/pallets/werkzeug/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/werkzeug/compare/3.0.3...3.1.6) --- updated-dependencies: - dependency-name: werkzeug dependency-version: 3.1.6 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- run/helloworld/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run/helloworld/requirements.txt b/run/helloworld/requirements.txt index 664e38630a1..64cd68f0f5f 100644 --- a/run/helloworld/requirements.txt +++ b/run/helloworld/requirements.txt @@ -1,3 +1,3 @@ Flask==3.0.3 gunicorn==23.0.0 -Werkzeug==3.0.3 +Werkzeug==3.1.6 From d9c6112294c3b806930dd5ead0e67896052c7f03 Mon Sep 17 00:00:00 2001 From: David del Real Date: Thu, 5 Mar 2026 14:47:25 -0600 Subject: [PATCH 61/67] fix(logging): fixed test to actually test (#13886) * Remove deprecated samples Removal of appengine/flexible_python37_and_earlier path for deprecation. As per my search on Code Search, there are no samples using these deprecated code snippets anymore. b/475332253 * fix(logging): fixed test to actually test * - Removed unnecessary comment. - Updated requirements. - Changed fixture in snippets test. * Fixing linting issues. --- logging/samples/snippets/export_test.py | 2 +- logging/samples/snippets/requirements.txt | 8 ++++---- logging/samples/snippets/snippets.py | 1 + logging/samples/snippets/snippets_test.py | 14 +++++++++++++- logging/samples/snippets/usage_guide.py | 1 + 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/logging/samples/snippets/export_test.py b/logging/samples/snippets/export_test.py index c9eb18efb85..e7dacd49ee4 100644 --- a/logging/samples/snippets/export_test.py +++ b/logging/samples/snippets/export_test.py @@ -13,8 +13,8 @@ # limitations under the License. import os -import re import random +import re import string import time diff --git a/logging/samples/snippets/requirements.txt b/logging/samples/snippets/requirements.txt index 8a52ee5c680..65b84840d38 100644 --- a/logging/samples/snippets/requirements.txt +++ b/logging/samples/snippets/requirements.txt @@ -1,4 +1,4 @@ -google-cloud-logging==3.10.0 -google-cloud-bigquery==3.25.0 -google-cloud-storage==2.17.0 -google-cloud-pubsub==2.22.0 +google-cloud-logging==3.13.0 +google-cloud-bigquery==3.40.1 +google-cloud-storage==3.7.0 +google-cloud-pubsub==2.35.0 diff --git a/logging/samples/snippets/snippets.py b/logging/samples/snippets/snippets.py index 736311e0f44..f6c16d17e38 100644 --- a/logging/samples/snippets/snippets.py +++ b/logging/samples/snippets/snippets.py @@ -90,6 +90,7 @@ def delete_logger(logger_name): if __name__ == "__main__": + parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter ) diff --git a/logging/samples/snippets/snippets_test.py b/logging/samples/snippets/snippets_test.py index 479f742ae5c..5cddc92d313 100644 --- a/logging/samples/snippets/snippets_test.py +++ b/logging/samples/snippets/snippets_test.py @@ -23,6 +23,7 @@ TEST_LOGGER_NAME = "example_log_{}".format(uuid.uuid4().hex) +TEST_TEXT = "Hello, world." @pytest.fixture @@ -44,9 +45,18 @@ def eventually_consistent_test(): eventually_consistent_test() -def test_write(): +def test_write(capsys): + snippets.write_entry(TEST_LOGGER_NAME) + @backoff.on_exception(backoff.expo, AssertionError, max_time=120) + def eventually_consistent_test(): + snippets.list_entries(TEST_LOGGER_NAME) + out, _ = capsys.readouterr() + assert TEST_TEXT in out + + eventually_consistent_test() + def test_delete(example_log, capsys): @backoff.on_exception(backoff.expo, NotFound, max_time=120) @@ -54,3 +64,5 @@ def eventually_consistent_test(): snippets.delete_logger(TEST_LOGGER_NAME) out, _ = capsys.readouterr() assert TEST_LOGGER_NAME in out + + eventually_consistent_test() diff --git a/logging/samples/snippets/usage_guide.py b/logging/samples/snippets/usage_guide.py index 6dee33798b1..c15fb72afe1 100644 --- a/logging/samples/snippets/usage_guide.py +++ b/logging/samples/snippets/usage_guide.py @@ -557,6 +557,7 @@ def main(): except Exception as error: # pylint: disable=broad-except print(" ERROR: %r" % (error,)) for item in to_delete: + pass _backoff_not_found(item.delete) From de18a99a0e29c2e147640bc4c6da42e47086c4b3 Mon Sep 17 00:00:00 2001 From: David del Real Date: Fri, 6 Mar 2026 16:23:51 -0600 Subject: [PATCH 62/67] Chore(logging) Removal of invalid samples and region tags (#13895) * Removing file with invalid samples and invalid region tags that are not in use. * Removed file. --- logging/samples/snippets/usage_guide.py | 565 ------------------- logging/samples/snippets/usage_guide_test.py | 96 ---- 2 files changed, 661 deletions(-) delete mode 100644 logging/samples/snippets/usage_guide.py delete mode 100644 logging/samples/snippets/usage_guide_test.py diff --git a/logging/samples/snippets/usage_guide.py b/logging/samples/snippets/usage_guide.py deleted file mode 100644 index c15fb72afe1..00000000000 --- a/logging/samples/snippets/usage_guide.py +++ /dev/null @@ -1,565 +0,0 @@ -# Copyright 2016 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Samples embedded in the Usage Guide (docs/usage.rst) - -Each example function takes a ``client`` argument (which must be an instance -of :class:`google.cloud.logging.client.Client`) and uses it to perform a task -with the API. - -To facilitate running the examples as system tests, each example is also passed -a ``to_delete`` list; the function adds to the list any objects created which -need to be deleted during teardown. -""" - -import os -import time - -from google.cloud.logging import Client - - -def snippet(func): - """Mark ``func`` as a snippet example function.""" - func._snippet = True - return func - - -def _millis(): - return time.time() * 1000 - - -def do_something_with(item): # pylint: disable=unused-argument - pass - - -# pylint: enable=reimported,unused-variable,unused-argument - - -@snippet -def client_list_entries(client, to_delete): # pylint: disable=unused-argument - """List entries via client.""" - - # [START client_list_entries_default] - for entry in client.list_entries(): # API call(s) - do_something_with(entry) - # [END client_list_entries_default] - break - - # [START client_list_entries_filter] - filter_str = "logName:log_name AND textPayload:simple" - for entry in client.list_entries(filter_=filter_str): # API call(s) - do_something_with(entry) - # [END client_list_entries_filter] - break - - # [START client_list_entries_order_by] - from google.cloud.logging import DESCENDING - - for entry in client.list_entries(order_by=DESCENDING): # API call(s) - do_something_with(entry) - # [END client_list_entries_order_by] - break - - # [START logging_list_gke_audit_logs] - import google.cloud.logging - from datetime import datetime, timedelta, timezone - import os - - # pull your project id from an environment variable - project_id = os.environ["GOOGLE_CLOUD_PROJECT"] - # construct a date object representing yesterday - yesterday = datetime.now(timezone.utc) - timedelta(days=1) - # Cloud Logging expects a timestamp in RFC3339 UTC "Zulu" format - # https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry - time_format = "%Y-%m-%dT%H:%M:%S.%f%z" - # build a filter that returns GKE Admin Activity audit Logs from - # the past 24 hours - # https://cloud.google.com/kubernetes-engine/docs/how-to/audit-logging - filter_str = ( - f'logName="projects/{project_id}/logs/cloudaudit.googleapis.com%2Factivity"' - f' AND resource.type="k8s_cluster"' - f' AND timestamp>="{yesterday.strftime(time_format)}"' - ) - # query and print all matching logs - client = google.cloud.logging.Client() - for entry in client.list_entries(filter_=filter_str): - print(entry) - # [END logging_list_gke_audit_logs] - break # we don't really need to print them all - - -@snippet -def client_setup(client2, to_delete): - """Client setup.""" - - # [START usage_client_setup] - import google.cloud.logging - - # if project not given, it will be inferred from the environment - client = google.cloud.logging.Client(project="my-project") - # [END usage_client_setup] - to_delete.append(client) - - # [START usage_http_client_setup] - http_client = google.cloud.logging.Client(_use_grpc=False) - # [END usage_http_client_setup] - to_delete.append(http_client) - - -@snippet -def logger_usage(client_true, to_delete): - """Logger usage.""" - import google.cloud.logging - - # [START logger_create] - client = google.cloud.logging.Client(project="my-project") - logger = client.logger(name="log_id") - # logger will bind to logName "projects/my_project/logs/log_id" - # [END logger_create] - client = client_true - - log_id = "logger_usage_%d" % (_millis()) - # [START logger_custom_labels] - custom_labels = {"my-key": "my-value"} - label_logger = client.logger(log_id, labels=custom_labels) - # [END logger_custom_labels] - to_delete.append(label_logger) - # [START logger_custom_resource] - from google.cloud.logging_v2.resource import Resource - - resource = Resource(type="global", labels={}) - global_logger = client.logger(log_id, resource=resource) - # [END logger_custom_resource] - to_delete.append(global_logger) - - logger = client_true.logger(log_id) - to_delete.append(logger) - - # [START logger_log_basic] - logger.log("A simple entry") # API call - # [END logger_log_basic] - - # [START logger_log_fields] - logger.log( - "an entry with fields set", - severity="ERROR", - insert_id="0123", - labels={"my-label": "my-value"}, - ) # API call - # [END logger_log_fields] - - # [START logger_log_text] - logger.log_text("A simple entry") # API call - # [END logger_log_text] - - # [START logger_log_struct] - logger.log_struct( - {"message": "My second entry", "weather": "partly cloudy"} - ) # API call - # [END logger_log_struct] - - # [START logger_log_resource_text] - from google.cloud.logging import Resource - - res = Resource( - type="generic_node", - labels={ - "location": "us-central1-a", - "namespace": "default", - "node_id": "10.10.10.1", - }, - ) - logger.log_struct( - {"message": "My first entry", "weather": "partly cloudy"}, resource=res - ) - # [END logger_log_resource_text] - - # [START logger_log_batch] - batch = logger.batch() - batch.log("first log") - batch.log("second log") - batch.commit() - # [END logger_log_batch] - - # [START logger_log_batch_context] - with logger.batch() as batch: - batch.log("first log") - # do work - batch.log("last log") - # [END logger_log_batch_context] - - # [START logger_list_entries] - from google.cloud.logging import DESCENDING - - for entry in logger.list_entries(order_by=DESCENDING): # API call(s) - do_something_with(entry) - # [END logger_list_entries] - - def _logger_delete(): - # [START logger_delete] - logger.delete() # API call - # [END logger_delete] - - _backoff_not_found(_logger_delete) - to_delete.remove(logger) - - -@snippet -def metric_crud(client, to_delete): - """Metric CRUD.""" - metric_name = "robots-%d" % (_millis(),) - description = "Robots all up in your server" - filter = "logName:apache-access AND textPayload:robot" - updated_filter = "textPayload:robot" - updated_description = "Danger, Will Robinson!" - - # [START client_list_metrics] - for metric in client.list_metrics(): # API call(s) - do_something_with(metric) - # [END client_list_metrics] - - # [START metric_create] - metric = client.metric(metric_name, filter_=filter, description=description) - assert not metric.exists() # API call - metric.create() # API call - assert metric.exists() # API call - # [END metric_create] - to_delete.append(metric) - - # [START metric_reload] - existing_metric = client.metric(metric_name) - existing_metric.reload() # API call - # [END metric_reload] - assert existing_metric.filter_ == filter - assert existing_metric.description == description - - # [START metric_update] - existing_metric.filter_ = updated_filter - existing_metric.description = updated_description - existing_metric.update() # API call - # [END metric_update] - existing_metric.reload() - assert existing_metric.filter_ == updated_filter - assert existing_metric.description == updated_description - - def _metric_delete(): - # [START metric_delete] - metric.delete() - # [END metric_delete] - - _backoff_not_found(_metric_delete) - to_delete.remove(metric) - - -def _sink_storage_setup(client): - from google.cloud import storage - - bucket_name = "sink-storage-%d" % (_millis(),) - client = storage.Client() - bucket = client.bucket(bucket_name) - bucket.create() - - # [START sink_bucket_permissions] - bucket.acl.reload() # API call - logs_group = bucket.acl.group("cloud-logs@google.com") - logs_group.grant_owner() - bucket.acl.add_entity(logs_group) - bucket.acl.save() # API call - # [END sink_bucket_permissions] - - return bucket - - -@snippet -def sink_storage(client, to_delete): - """Sink log entries to storage.""" - bucket = _sink_storage_setup(client) - to_delete.append(bucket) - sink_name = "robots-storage-%d" % (_millis(),) - filter = "textPayload:robot" - - # [START sink_storage_create] - destination = "storage.googleapis.com/%s" % (bucket.name,) - sink = client.sink(sink_name, filter_=filter, destination=destination) - assert not sink.exists() # API call - sink.create() # API call - assert sink.exists() # API call - # [END sink_storage_create] - to_delete.insert(0, sink) # delete sink before bucket - - -def _sink_bigquery_setup(client): - from google.cloud import bigquery - - dataset_name = "sink_bigquery_%d" % (_millis(),) - client = bigquery.Client() - dataset = client.create_dataset(dataset_name) - - # [START sink_dataset_permissions] - from google.cloud.bigquery.dataset import AccessEntry - - entry_list = dataset.access_entries - entry_list.append(AccessEntry("WRITER", "groupByEmail", "cloud-logs@google.com")) - dataset.access_entries = entry_list - client.update_dataset(dataset, ["access_entries"]) # API call - # [END sink_dataset_permissions] - - # create callback wrapper to delete dataset when done - class DatasetDeleter: - def delete(self): - client.delete_dataset(dataset, delete_contents=True) - - return dataset, DatasetDeleter() - - -@snippet -def sink_bigquery(client, to_delete): - """Sink log entries to bigquery.""" - dataset, dataset_deleter = _sink_bigquery_setup(client) - to_delete.append(dataset_deleter) - sink_name = "robots-bigquery-%d" % (_millis(),) - filter_str = "textPayload:robot" - - # [START sink_bigquery_create] - destination = "bigquery.googleapis.com%s" % (dataset.path,) - sink = client.sink(sink_name, filter_=filter_str, destination=destination) - assert not sink.exists() # API call - sink.create() # API call - assert sink.exists() # API call - # [END sink_bigquery_create] - to_delete.insert(0, sink) # delete sink before dataset - - -def _sink_pubsub_setup(client): - from google.cloud import pubsub - - client = pubsub.PublisherClient() - - project_id = os.environ["GOOGLE_CLOUD_PROJECT"] - topic_id = "sink-pubsub-%d" % (_millis(),) - - # [START sink_topic_permissions] - topic_path = client.topic_path(project_id, topic_id) - topic = client.create_topic(request={"name": topic_path}) - - policy = client.get_iam_policy(request={"resource": topic_path}) # API call - policy.bindings.add(role="roles/owner", members=["group:cloud-logs@google.com"]) - - client.set_iam_policy( - request={"resource": topic_path, "policy": policy} - ) # API call - # [END sink_topic_permissions] - - # create callback wrapper to delete topic when done - class TopicDeleter: - def delete(self): - client.delete_topic(request={"topic": topic_path}) - - return topic, TopicDeleter() - - -@snippet -def sink_pubsub(client, to_delete): - """Sink log entries to pubsub.""" - topic, topic_deleter = _sink_pubsub_setup(client) - to_delete.append(topic_deleter) - sink_name = "robots-pubsub-%d" % (_millis(),) - filter_str = "logName:apache-access AND textPayload:robot" - updated_filter = "textPayload:robot" - - # [START sink_pubsub_create] - destination = "pubsub.googleapis.com/%s" % (topic.name,) - sink = client.sink(sink_name, filter_=filter_str, destination=destination) - assert not sink.exists() # API call - sink.create() # API call - assert sink.exists() # API call - # [END sink_pubsub_create] - to_delete.append(sink) - created_sink = sink - - # [START client_list_sinks] - for sink in client.list_sinks(): # API call(s) - do_something_with(sink) - # [END client_list_sinks] - - # [START sink_reload] - existing_sink = client.sink(sink_name) - existing_sink.reload() - # [END sink_reload] - assert existing_sink.filter_ == filter_str - assert existing_sink.destination == destination - - # [START sink_update] - existing_sink.filter_ = updated_filter - existing_sink.update() - # [END sink_update] - existing_sink.reload() - assert existing_sink.filter_ == updated_filter - - sink = created_sink - # [START sink_delete] - sink.delete() - # [END sink_delete] - - -@snippet -def logging_handler(client): - # [START create_default_handler] - import logging - - handler = client.get_default_handler() - cloud_logger = logging.getLogger("cloudLogger") - cloud_logger.setLevel(logging.INFO) - cloud_logger.addHandler(handler) - cloud_logger.error("bad news") - # [END create_default_handler] - - # [START create_cloud_handler] - from google.cloud.logging.handlers import CloudLoggingHandler - from google.cloud.logging_v2.handlers import setup_logging - - handler = CloudLoggingHandler(client) - setup_logging(handler) - # [END create_cloud_handler] - - # [START create_named_handler] - handler = CloudLoggingHandler(client, name="mycustomlog") - # [END create_named_handler] - - -@snippet -def logging_json(client): - # [START logging_json_dumps] - import logging - import json - - data_dict = {"hello": "world"} - logging.info(json.dumps(data_dict)) - # [END logging_json_dumps] - - # [START logging_extra_json_fields] - import logging - - data_dict = {"hello": "world"} - logging.info("message field", extra={"json_fields": data_dict}) - # [END logging_extra_json_fields] - - -@snippet -def using_extras(client): - import logging - - # [START logging_extras] - my_labels = {"foo": "bar"} - my_http = {"requestUrl": "localhost"} - my_trace = "01234" - - logging.info( - "hello", extra={"labels": my_labels, "http_request": my_http, "trace": my_trace} - ) - # [END logging_extras] - - -@snippet -def setup_logging(client): - import logging - - # [START logging_setup_logging] - client.setup_logging(log_level=logging.INFO) - # [END logging_setup_logging] - - # [START logging_setup_logging_excludes] - client.setup_logging(log_level=logging.INFO, excluded_loggers=("werkzeug",)) - # [END logging_setup_logging_excludes] - - -@snippet -def logging_dict_config(client): - # [START logging_dict_config] - import logging.config - - import google.cloud.logging - - client = google.cloud.logging.Client() - - LOGGING = { - "version": 1, - "handlers": { - "cloud_logging_handler": { - "class": "google.cloud.logging.handlers.CloudLoggingHandler", - "client": client, - }, - "structured_log_handler": { - "class": "google.cloud.logging.handlers.StructuredLogHandler" - }, - }, - "root": {"handlers": [], "level": "WARNING"}, - "loggers": { - "cloud_logger": {"handlers": ["cloud_logging_handler"], "level": "INFO"}, - "structured_logger": { - "handlers": ["structured_log_handler"], - "level": "INFO", - }, - }, - } - - logging.config.dictConfig(LOGGING) - # [END logging_dict_config] - - -def _line_no(func): - return func.__code__.co_firstlineno - - -def _find_examples(): - funcs = [obj for obj in globals().values() if getattr(obj, "_snippet", False)] - for func in sorted(funcs, key=_line_no): - yield func - - -def _name_and_doc(func): - return func.__name__, func.__doc__ - - -def _backoff_not_found(deleter): - from google.cloud.exceptions import NotFound - - timeouts = [1, 2, 4, 8, 16] - while timeouts: - try: - deleter() - except NotFound: - time.sleep(timeouts.pop(0)) - else: - break - - -def main(): - client = Client() - for example in _find_examples(): - to_delete = [] - print("%-25s: %s" % _name_and_doc(example)) - try: - example(client, to_delete) - except AssertionError as failure: - print(" FAIL: %s" % (failure,)) - except Exception as error: # pylint: disable=broad-except - print(" ERROR: %r" % (error,)) - for item in to_delete: - pass - _backoff_not_found(item.delete) - - -if __name__ == "__main__": - main() diff --git a/logging/samples/snippets/usage_guide_test.py b/logging/samples/snippets/usage_guide_test.py deleted file mode 100644 index 3f606dd656b..00000000000 --- a/logging/samples/snippets/usage_guide_test.py +++ /dev/null @@ -1,96 +0,0 @@ -# Copyright 2016 Google Inc. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -from google.cloud.logging import Client - -import usage_guide - - -def test_logger_usage(): - client = Client() - - to_delete = [] - usage_guide.logger_usage(client, to_delete) - - for item in to_delete: - usage_guide._backoff_not_found(item.delete) - - -def test_metric_crud(): - client = Client() - - to_delete = [] - usage_guide.metric_crud(client, to_delete) - - for item in to_delete: - usage_guide._backoff_not_found(item.delete) - - -def test_sink_storage(): - client = Client() - - to_delete = [] - usage_guide.sink_storage(client, to_delete) - - for item in to_delete: - usage_guide._backoff_not_found(item.delete) - - -def test_sink_bigquery(): - client = Client() - - to_delete = [] - usage_guide.sink_bigquery(client, to_delete) - - for item in to_delete: - usage_guide._backoff_not_found(item.delete) - - -def test_sink_pubsub(): - client = Client() - - to_delete = [] - usage_guide.sink_pubsub(client, to_delete) - - for item in to_delete: - usage_guide._backoff_not_found(item.delete) - - -def test_logging_handler(): - client = Client() - - usage_guide.logging_handler(client) - - -def test_setup_logging(): - client = Client() - - usage_guide.setup_logging(client) - - -def test_client_list_entries(): - client = Client() - - to_delete = [] - usage_guide.client_list_entries(client, to_delete) - - for item in to_delete: - usage_guide._backoff_not_found(item.delete) - - -def test_dict_config(): - client = Client() - - usage_guide.logging_dict_config(client) From a18e19543245728eed33f86b36262aae88032d3a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 18:50:08 -0800 Subject: [PATCH 63/67] chore(deps): bump flask from 3.0.3 to 3.1.3 in /iap (#13881) Bumps [flask](https://github.com/pallets/flask) from 3.0.3 to 3.1.3. - [Release notes](https://github.com/pallets/flask/releases) - [Changelog](https://github.com/pallets/flask/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/flask/compare/3.0.3...3.1.3) --- updated-dependencies: - dependency-name: flask dependency-version: 3.1.3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- iap/app_engine_app/requirements.txt | 2 +- iap/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/iap/app_engine_app/requirements.txt b/iap/app_engine_app/requirements.txt index 6857fdf3175..3954d17e732 100644 --- a/iap/app_engine_app/requirements.txt +++ b/iap/app_engine_app/requirements.txt @@ -1,2 +1,2 @@ -Flask==3.0.3 +Flask==3.1.3 Werkzeug==3.1.4 diff --git a/iap/requirements.txt b/iap/requirements.txt index 850043f7bd1..c0d103f39e4 100644 --- a/iap/requirements.txt +++ b/iap/requirements.txt @@ -1,5 +1,5 @@ cryptography==45.0.1 -Flask==3.0.3 +Flask==3.1.3 google-auth==2.38.0 gunicorn==23.0.0 requests==2.32.4 From b419c4e8caa4dbeabee8a63f694858e921d99c48 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Sat, 7 Mar 2026 02:50:49 +0000 Subject: [PATCH 64/67] chore(deps): update dependency cloud-sql-python-connector to v1.20.0 (#13883) --- cloud-sql/mysql/sqlalchemy/requirements.txt | 2 +- cloud-sql/postgres/sqlalchemy/requirements.txt | 2 +- cloud-sql/sql-server/sqlalchemy/requirements.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cloud-sql/mysql/sqlalchemy/requirements.txt b/cloud-sql/mysql/sqlalchemy/requirements.txt index 397f59c2759..a5e6f819085 100644 --- a/cloud-sql/mysql/sqlalchemy/requirements.txt +++ b/cloud-sql/mysql/sqlalchemy/requirements.txt @@ -2,6 +2,6 @@ Flask==2.2.2 SQLAlchemy==2.0.40 PyMySQL==1.1.1 gunicorn==23.0.0 -cloud-sql-python-connector==1.18.4 +cloud-sql-python-connector==1.20.0 functions-framework==3.9.2 Werkzeug==2.3.8 diff --git a/cloud-sql/postgres/sqlalchemy/requirements.txt b/cloud-sql/postgres/sqlalchemy/requirements.txt index d3a74b1c5ef..ba738cc1669 100644 --- a/cloud-sql/postgres/sqlalchemy/requirements.txt +++ b/cloud-sql/postgres/sqlalchemy/requirements.txt @@ -1,7 +1,7 @@ Flask==2.2.2 pg8000==1.31.5 SQLAlchemy==2.0.40 -cloud-sql-python-connector==1.18.4 +cloud-sql-python-connector==1.20.0 gunicorn==23.0.0 functions-framework==3.9.2 Werkzeug==2.3.8 diff --git a/cloud-sql/sql-server/sqlalchemy/requirements.txt b/cloud-sql/sql-server/sqlalchemy/requirements.txt index 3302326ab42..a2aae8784d1 100644 --- a/cloud-sql/sql-server/sqlalchemy/requirements.txt +++ b/cloud-sql/sql-server/sqlalchemy/requirements.txt @@ -3,7 +3,7 @@ gunicorn==23.0.0 python-tds==1.16.0 pyopenssl==25.0.0 SQLAlchemy==2.0.40 -cloud-sql-python-connector==1.18.4 +cloud-sql-python-connector==1.20.0 sqlalchemy-pytds==1.0.2 functions-framework==3.9.2 Werkzeug==2.3.8 From 94cf1605a9ffb54385cb7d8572a1020941c2a597 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 18:51:18 -0800 Subject: [PATCH 65/67] chore(deps): bump google-cloud-aiplatform in /generative_ai/labels (#13873) Bumps [google-cloud-aiplatform](https://github.com/googleapis/python-aiplatform) from 1.74.0 to 1.133.0. - [Release notes](https://github.com/googleapis/python-aiplatform/releases) - [Changelog](https://github.com/googleapis/python-aiplatform/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/python-aiplatform/compare/v1.74.0...v1.133.0) --- updated-dependencies: - dependency-name: google-cloud-aiplatform dependency-version: 1.133.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- generative_ai/labels/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generative_ai/labels/requirements.txt b/generative_ai/labels/requirements.txt index 913473b5ef0..44964bbf7b1 100644 --- a/generative_ai/labels/requirements.txt +++ b/generative_ai/labels/requirements.txt @@ -1 +1 @@ -google-cloud-aiplatform==1.74.0 +google-cloud-aiplatform==1.133.0 From bbc0b8274174321ee62c686da13a98e38a1390b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 13:34:21 -0400 Subject: [PATCH 66/67] chore(deps): bump werkzeug from 3.1.4 to 3.1.5 in /dialogflow-cx (#13717) Bumps [werkzeug](https://github.com/pallets/werkzeug) from 3.1.4 to 3.1.5. - [Release notes](https://github.com/pallets/werkzeug/releases) - [Changelog](https://github.com/pallets/werkzeug/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/werkzeug/compare/3.1.4...3.1.5) --- updated-dependencies: - dependency-name: werkzeug dependency-version: 3.1.5 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dialogflow-cx/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dialogflow-cx/requirements.txt b/dialogflow-cx/requirements.txt index fe7011b74ee..5c29bf4a7bf 100644 --- a/dialogflow-cx/requirements.txt +++ b/dialogflow-cx/requirements.txt @@ -2,7 +2,7 @@ google-cloud-dialogflow-cx==2.0.0 Flask==3.0.3 python-dateutil==2.9.0.post0 functions-framework==3.9.2 -Werkzeug==3.1.4 +Werkzeug==3.1.5 termcolor==3.0.0; python_version >= "3.9" termcolor==2.4.0; python_version == "3.8" pyaudio==0.2.14 \ No newline at end of file From e9685806da09da4b30f41834a5850b63adf33580 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 13:34:45 -0400 Subject: [PATCH 67/67] chore(deps): bump werkzeug in /cloud_scheduler/snippets (#13715) Bumps [werkzeug](https://github.com/pallets/werkzeug) from 3.0.6 to 3.1.5. - [Release notes](https://github.com/pallets/werkzeug/releases) - [Changelog](https://github.com/pallets/werkzeug/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/werkzeug/compare/3.0.6...3.1.5) --- updated-dependencies: - dependency-name: werkzeug dependency-version: 3.1.5 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cloud_scheduler/snippets/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud_scheduler/snippets/requirements.txt b/cloud_scheduler/snippets/requirements.txt index e95a2ef8c50..af65635c6c9 100644 --- a/cloud_scheduler/snippets/requirements.txt +++ b/cloud_scheduler/snippets/requirements.txt @@ -1,4 +1,4 @@ Flask==3.0.3 gunicorn==23.0.0 google-cloud-scheduler==2.14.1 -Werkzeug==3.0.6 +Werkzeug==3.1.5