Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

fix(workflows): refactor sample 'workflows_api_quickstart' to comply with the Samples Style Guide and fix tests #13261

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions 102 workflows/cloud-client/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# 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 os
import time
import uuid

from google.cloud import workflows_v1

import pytest


PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT")
LOCATION = "us-central1"
WORKFLOW_ID = "myFirstWorkflow_" + str(uuid.uuid4())


def workflow_exists(client: workflows_v1.WorkflowsClient) -> bool:
"""Returns True if the workflow exists in this project."""
try:
workflow_name = client.workflow_path(
PROJECT_ID, LOCATION, WORKFLOW_ID
)
client.get_workflow(request={"name": workflow_name})
return True
except Exception as e:
print(f"Workflow doesn't exist: {e}")
return False


@pytest.fixture(scope="module")
def client() -> str:
assert PROJECT_ID, "'GOOGLE_CLOUD_PROJECT' environment variable not set."
workflows_client = workflows_v1.WorkflowsClient()
return workflows_client


@pytest.fixture(scope="module")
def project_id() -> str:
return PROJECT_ID


@pytest.fixture(scope="module")
def location() -> str:
return LOCATION


@pytest.fixture(scope="module")
def workflow_id(client: workflows_v1.WorkflowsClient) -> str:
creating_workflow = False
backoff_delay = 1 # Start wait with delay of 1 second.

# Create the workflow if it doesn't exist.
while not workflow_exists(client):
if not creating_workflow:
# Create the workflow.
workflow_file = open("myFirstWorkflow.workflows.yaml").read()

parent = client.common_location_path(PROJECT_ID, LOCATION)

client.create_workflow(
request={
"parent": parent,
"workflow_id": WORKFLOW_ID,
"workflow": {
"name": WORKFLOW_ID,
"source_contents": workflow_file
},
}
)

creating_workflow = True

# Wait until the workflow is created.
print("- Waiting for the Workflow to be created...")
time.sleep(backoff_delay)
# Double the delay to provide exponential backoff.
backoff_delay *= 2

yield WORKFLOW_ID

# Delete the workflow.
workflow_full_name = client.workflow_path(
PROJECT_ID, LOCATION, WORKFLOW_ID
)

client.delete_workflow(
request={
"name": workflow_full_name,
}
)
52 changes: 32 additions & 20 deletions 52 workflows/cloud-client/main.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2021 Google LLC
# 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.
Expand All @@ -12,60 +12,70 @@
# See the License for the specific language governing permissions and
# limitations under the License.

# [START workflows_api_quickstart]
import os
import time

from google.cloud import workflows_v1
from google.cloud.workflows import executions_v1
from google.cloud.workflows.executions_v1 import Execution
from google.cloud.workflows.executions_v1.types import executions

PROJECT = os.getenv("GOOGLE_CLOUD_PROJECT")
LOCATION = os.getenv("LOCATION", "us-central1")
WORKFLOW_ID = os.getenv("WORKFLOW", "myFirstWorkflow")


def execute_workflow(project: str, location: str, workflow: str) -> Execution:
def execute_workflow(
project_id: str,
location: str,
workflow_id: str
) -> Execution:
"""Execute a workflow and print the execution results.

A workflow consists of a series of steps described
using the Workflows syntax, and can be written in either YAML or JSON.

Args:
project: The Google Cloud project id
project: The ID of the Google Cloud project
which contains the workflow to execute.
location: The location for the workflow
location: The location for the workflow.
workflow: The ID of the workflow to execute.

Returns:
The execution response.
"""

# [START workflows_api_quickstart]
import time

from google.cloud import workflows_v1
from google.cloud.workflows import executions_v1

from google.cloud.workflows.executions_v1.types import executions

# TODO(developer): Update and uncomment the following lines.
# project_id = "MY_PROJECT_ID"
# location = "MY_LOCATION" # For example: us-central1
# workflow_id = "MY_WORKFLOW_ID" # For example: myFirstWorkflow

# [START workflows_api_quickstart_client_libraries]
# Set up API clients.
# Initialize API clients.
execution_client = executions_v1.ExecutionsClient()
workflows_client = workflows_v1.WorkflowsClient()
# [END workflows_api_quickstart_client_libraries]

# [START workflows_api_quickstart_execution]
# Construct the fully qualified location path.
parent = workflows_client.workflow_path(project, location, workflow)
parent = workflows_client.workflow_path(project_id, location, workflow_id)

# Execute the workflow.
response = execution_client.create_execution(request={"parent": parent})
print(f"Created execution: {response.name}")

# Wait for execution to finish, then print results.
execution_finished = False
backoff_delay = 1 # Start wait with delay of 1 second
backoff_delay = 1 # Start wait with delay of 1 second.
print("Poll for result...")

while not execution_finished:
execution = execution_client.get_execution(
request={"name": response.name}
)
execution_finished = execution.state != executions.Execution.State.ACTIVE

# If we haven't seen the result yet, wait a second.
# If we haven't seen the result yet, keep waiting.
if not execution_finished:
print("- Waiting for results...")
time.sleep(backoff_delay)
Expand All @@ -74,11 +84,13 @@ def execute_workflow(project: str, location: str, workflow: str) -> Execution:
else:
print(f"Execution finished with state: {execution.state.name}")
print(f"Execution results: {execution.result}")
return execution
# [END workflows_api_quickstart_execution]
# [END workflows_api_quickstart]
return execution


if __name__ == "__main__":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the main entry points have been requested by 'glasnt' to stay in the scripts, but the Style guide suggests not to include them in the sample, it was adjusted accordingly

Can you link to where you're reading this in the style guide?

As you can see in this example, the main method is included within the region tags, so we have an example of how to invoke the function. If the styleguide needs addressing, please raise this out of band.

Copy link
Contributor Author

@eapl-gemugami eapl-gemugami Mar 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't fit a full explanation in a short sentence, @iennae we could discuss it internally.


  1. "Methods should avoid a return type whenever possible."
    samples-style-guide/#result

Python examples in the Style Guide don't show any return. For example: samples-style-guide/#pattern

Removing returns creates a conflict for samples developers, as returned values help with testing. See a previous review by 'davidcavazos' here.

I've seen a pattern in the repo, which I followed, where the returned object is outside of the sample region tags (so it won't be shown in the documentation).

But if I do that, the function definition shows a type hint of a returned object, when the sample doesn't have any (it should be a None then). What I find that complies with all above is not including the function definition part, as it's in this PR. That is the reason for my decision on the region tags positions.
For instance, my samples in View the access policy of a dataset and view_dataset_access_policy.py#L19

I see 'grayside' has addressed a related matter in Issue #169


  1. The style guide for Python doesn't show the use of if __name__ == "__main__":
    Only Java and PHP show something similar in Language-specific practices

Please check a previous review by 'davidcavazos' here and samples-style-guide/#no-cli
Review: "Supporting a CLI entry point means additional maintenance."
Guide: "Do not include CLIs for running your sample. [...] Previous practice shows that CLIs are expensive to maintain and detract from the purpose of the snippet."

In addition to external tools and CLIs such as glogin, I understand this part as running the sample from the Terminal by using python3 sample.py as well.

I see this has not been addressed as an Issue in the samples-style-guide repo.

PROJECT = os.getenv("GOOGLE_CLOUD_PROJECT")
assert PROJECT, "'GOOGLE_CLOUD_PROJECT' environment variable not set."
execute_workflow(PROJECT, LOCATION, WORKFLOW_ID)
# [END workflows_api_quickstart]

execute_workflow(PROJECT, "us-central1", "myFirstWorkflow")
eapl-gemugami marked this conversation as resolved.
Show resolved Hide resolved
47 changes: 5 additions & 42 deletions 47 workflows/cloud-client/main_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,52 +12,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import os
import backoff

from google.cloud import workflows_v1
from google.cloud.workflows.executions_v1.types import executions

import main

PROJECT = os.getenv("GOOGLE_CLOUD_PROJECT")
LOCATION = "us-central1"
WORKFLOW_ID = "myFirstWorkflow"


def test_workflow_execution() -> None:
assert PROJECT, "'GOOGLE_CLOUD_PROJECT' environment variable not set."

if not workflow_exists():
workflow_file = open("myFirstWorkflow.workflows.yaml").read()

workflows_client = workflows_v1.WorkflowsClient()
workflows_client.create_workflow(
request={
# Manually construct the location
# https://github.com/googleapis/python-workflows/issues/21
"parent": f"projects/{PROJECT}/locations/{LOCATION}",
"workflow_id": WORKFLOW_ID,
"workflow": {
"name": WORKFLOW_ID,
"source_contents": workflow_file
},
}
)

result = main.execute_workflow(PROJECT, LOCATION, WORKFLOW_ID)
@backoff.on_exception(backoff.expo, AssertionError, max_time=60)
def test_workflow_execution(project_id: str, location: str, workflow_id: str) -> None:
result = main.execute_workflow(project_id, location, workflow_id)
assert result.state == executions.Execution.State.SUCCEEDED
assert result.result # Result not empty


def workflow_exists() -> bool:
"""Returns True if the workflow exists in this project."""
try:
workflows_client = workflows_v1.WorkflowsClient()
workflow_name = workflows_client.workflow_path(
PROJECT, LOCATION, WORKFLOW_ID
)
workflows_client.get_workflow(request={"name": workflow_name})
return True
except Exception as e:
print(f"Workflow doesn't exist: {e}")
return False
assert result.result
2 changes: 1 addition & 1 deletion 2 workflows/cloud-client/noxfile_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.11"],
"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,
Expand Down
3 changes: 2 additions & 1 deletion 3 workflows/cloud-client/requirements-test.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pytest==8.2.0
pytest==8.3.5
backoff==2.2.1
2 changes: 1 addition & 1 deletion 2 workflows/cloud-client/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
google-cloud-workflows==1.15.1
google-cloud-workflows==1.18.0
Morty Proxy This is a proxified and sanitized view of the page, visit original site.