Files
microsoft--promptflow/scripts/readme/workflow_generator.py
T
wehub-resource-sync e768098d0e
tools_continuous_delivery / Private PyPI non-main branch release (push) Has been skipped
tools_continuous_delivery / Private PyPI main branch release (push) Failing after 2m42s
Publish Promptflow Doc / Build (push) Has been cancelled
Publish Promptflow Doc / Deploy (push) Has been cancelled
Flake8 Lint / flake8 (push) Has been cancelled
Spell check CI / Spell_Check (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:39:52 +08:00

193 lines
6.3 KiB
Python

import os
import glob
import argparse
from pathlib import Path
import ntpath
import re
import hashlib
import json
from jinja2 import Environment, FileSystemLoader
from ghactions_driver.readme_step import ReadmeStepsManage
from ghactions_driver.resource_resolver import resolve_tutorial_resource
from ghactions_driver.telemetry_obj import Telemetry
def format_ipynb(notebooks):
# run code formatter on .ipynb files
for notebook in notebooks:
os.system(f"black-nb --clear-output {notebook}")
def _get_paths(paths_list):
"""
Convert the path list to unix format.
:param paths_list: The input path list.
:returns: The same list with unix-like paths.
"""
paths_list.sort()
if ntpath.sep == os.path.sep:
return [pth.replace(ntpath.sep, "/") for pth in paths_list]
return paths_list
def write_notebook_workflow(notebook, name, output_telemetry=Telemetry()):
temp_name_list = re.split(r"/|\.", notebook)
temp_name_list = [
x
for x in temp_name_list
if x != "tutorials" and x != "examples" and x != "ipynb"
]
temp_name_list = [x.replace("-", "") for x in temp_name_list]
workflow_name = "_".join(["samples"] + temp_name_list)
place_to_write = (
Path(ReadmeStepsManage.git_base_dir())
/ ".github"
/ "workflows"
/ f"{workflow_name}.yml"
)
gh_working_dir = "/".join(notebook.split("/")[:-1])
env = Environment(
loader=FileSystemLoader("./scripts/readme/ghactions_driver/workflow_templates")
)
template = env.get_template("basic_workflow.yml.jinja2")
# Schedule notebooks at different times to reduce maximum quota usage.
name_hash = int(hashlib.sha512(workflow_name.encode()).hexdigest(), 16)
schedule_minute = name_hash % 60
schedule_hour = (name_hash // 60) % 4 + 19 # 19-22 UTC
notebook_path = Path(ReadmeStepsManage.git_base_dir()) / str(notebook)
try:
# resolve tutorial resources
path_filter = resolve_tutorial_resource(workflow_name, notebook_path.resolve(), output_telemetry)
except Exception:
if "examples/tutorials" in gh_working_dir:
raise
else:
pass
if "samples_configuration" in workflow_name:
# exception, samples configuration is very simple and not related to other prompt flow examples
path_filter = (
"[ examples/configuration.ipynb, .github/workflows/samples_configuration.yml ]"
)
else:
path_filter = f"[ {gh_working_dir}/**, examples/*requirements.txt, .github/workflows/{workflow_name}.yml ]"
# these workflows require config.json to init PF/ML client
workflows_require_config_json = [
"configuration",
"runflowwithpipeline",
"quickstartazure",
"cloudrunmanagement",
"chatwithclassbasedflowazure",
]
if any(keyword in workflow_name for keyword in workflows_require_config_json):
template = env.get_template("workflow_config_json.yml.jinja2")
elif "chatwithpdf" in workflow_name:
template = env.get_template("pdf_workflow.yml.jinja2")
elif "flowasfunction" in workflow_name:
template = env.get_template("flow_as_function.yml.jinja2")
elif "traceautogengroupchat" in workflow_name:
template = env.get_template("autogen_workflow.yml.jinja2")
content = template.render(
{
"workflow_name": workflow_name,
"ci_name": "samples_notebook_ci",
"name": name,
"gh_working_dir": gh_working_dir,
"path_filter": path_filter,
"crontab": f"{schedule_minute} {schedule_hour} * * *",
"crontab_comment": f"Every day starting at {schedule_hour - 16}:{schedule_minute} BJT",
}
)
# To customize workflow, add new steps in steps.py
# make another function for special cases.
with open(place_to_write.resolve(), "w") as f:
f.write(content)
print(f"Write workflow: {place_to_write.resolve()}")
output_telemetry.workflow_name = workflow_name
output_telemetry.name = name
output_telemetry.gh_working_dir = gh_working_dir
output_telemetry.path_filter = path_filter
def write_workflows(notebooks, output_telemetries=[]):
# process notebooks
for notebook in notebooks:
# get notebook name
output_telemetry = Telemetry()
nb_path = Path(notebook)
name, _ = os.path.splitext(nb_path.parts[-1])
# write workflow file
write_notebook_workflow(notebook, name, output_telemetry)
output_telemetry.notebook = nb_path
output_telemetries.append(output_telemetry)
def local_filter(callback, array):
results = []
for index, item in enumerate(array):
result = callback(item, index, array)
# if returned true, append item to results
if result:
results.append(item)
return results
def no_readme_generation_filter(item, index, array) -> bool:
"""
Set each ipynb metadata no_readme_generation to "true" to skip readme generation
"""
try:
if item.endswith("test.ipynb"):
return False
if "examples/flows/integrations/" in item:
return False
# read in notebook
with open(item, "r", encoding="utf-8") as f:
data = json.load(f)
try:
if data["metadata"]["no_readme_generation"] is not None:
# no_readme_generate == "true", then no generation
return data["metadata"]["no_readme_generation"] != "true"
except Exception:
return True # generate readme
except Exception:
return False # not generate readme
def main(input_glob, output_files=[], check=False):
# get list of workflows
notebooks = _get_paths(
[j for i in [glob.glob(p, recursive=True) for p in input_glob] for j in i]
)
# check each workflow, get metadata.
notebooks = local_filter(no_readme_generation_filter, notebooks)
# format code
if not check:
format_ipynb(notebooks)
# write workflows
write_workflows(notebooks, output_files)
# run functions
if __name__ == "__main__":
# setup argparse
parser = argparse.ArgumentParser()
parser.add_argument(
"-g", "--input-glob", nargs="+", help="Input glob example 'examples/**/*.ipynb'"
)
args = parser.parse_args()
# call main
main(input_glob=args.input_glob)