Files
microsoft--promptflow/scripts/distributing/publish_package.py
T
wehub-resource-sync e768098d0e
Flake8 Lint / flake8 (push) Waiting to run
Spell check CI / Spell_Check (push) Waiting to run
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
chore: import upstream snapshot with attribution
2026-07-13 13:39:52 +08:00

189 lines
9.2 KiB
Python

import argparse
import json
import os
import re
from datetime import datetime, timedelta
from azure.storage.blob import (
AccountSasPermissions,
BlobServiceClient,
ContentSettings,
ResourceTypes,
generate_account_sas,
)
def get_connection_string(storage_account, storage_key):
return f"DefaultEndpointsProtocol=https;AccountName={storage_account};AccountKey={storage_key};EndpointSuffix=core.windows.net" # noqa: E501
def get_object_sas_token(storage_account, storage_key):
sas_token = generate_account_sas(
account_name=storage_account,
account_key=storage_key,
resource_types=ResourceTypes(object=True),
permission=AccountSasPermissions(read=True),
expiry=datetime.utcnow() + timedelta(days=365),
)
return sas_token
def get_wheel_distribution_name(package_name):
"""The wheel filename is {distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl.
The distribution name is normalized from the package name."""
return package_name.replace(".", "_").replace("-", "_").replace(" ", "_")
def package_name_based_blob_prefix(package_name):
"""Convert package name to a valid blob prefix."""
prefix = package_name.replace(".", "-")
prefix = prefix.replace("_", "-")
prefix = prefix.lower()
return prefix
def override_version_with_latest(distribution_name):
return re.sub("-([0-9.]*)-", "-latest-", distribution_name, count=1)
def publish_package_internal(package_dir_path, storage_key, release_config):
index = release_config["index"]
index_config = config_json["targets"][index]
storage_account = index_config["storage_account"]
packages_container = index_config["packages_container"]
index_container = index_config["index_container"]
blob_prefix = index_config["blob_prefix"]
pypi_endpoint = index_config["endpoint"]
account_url = f"https://{storage_account}.blob.core.windows.net"
wheel_pattern = re.compile(r".+\.whl$")
whl_distributions = [d for d in os.listdir(package_dir_path) if wheel_pattern.match(d)]
if len(whl_distributions) != 1:
print(
f"[Error] Found {len(whl_distributions)} wheel distributions in {package_dir_path}. "
"There should be exactly one."
)
exit(1)
whl_distribution = whl_distributions[0]
# Create the BlobServiceClient with connection string
blob_service_client = BlobServiceClient.from_connection_string(get_connection_string(storage_account, storage_key))
container_client = blob_service_client.get_container_client(packages_container)
# Upload the wheel package to blob storage
package_blob = os.path.join(blob_prefix, whl_distribution)
package_blob_client = blob_service_client.get_blob_client(container=packages_container, blob=package_blob)
upload_file_path = os.path.join(package_dir_path, whl_distribution)
with open(file=upload_file_path, mode="rb") as package_file:
print(f"[Debug] Uploading {whl_distribution} to container: {packages_container}, blob: {package_blob}...")
package_blob_client.upload_blob(package_file, overwrite=True)
if upload_as_latest:
latest_distribution = override_version_with_latest(whl_distribution)
latest_package_blob = os.path.join(blob_prefix, latest_distribution)
latest_package_blob_client = blob_service_client.get_blob_client(
container=packages_container, blob=latest_package_blob
)
upload_file_path = os.path.join(package_dir_path, whl_distribution)
with open(file=upload_file_path, mode="rb") as package_file:
print(
f"[Debug] Uploading {whl_distribution} as latest distribution to "
f"container: {packages_container}, blob: {latest_package_blob}..."
)
latest_package_blob_client.upload_blob(package_file, overwrite=True)
# List the blobs and generate download sas urls
sas_token = get_object_sas_token(storage_account, storage_key)
print(f"Listing wheel packages with prefix {blob_prefix} in container...")
blob_list = container_client.list_blobs(name_starts_with=f"{blob_prefix}/")
distribution_blobs = [d for d in blob_list if wheel_pattern.match(d.name)]
# Reverse the list so that the latest distribution is at the top
distribution_blobs.reverse()
packages_indexes = {} # {package_name: [distributions]}
for blob in distribution_blobs:
distribution_name = blob.name.split("/")[-1]
package_name = package_name_based_blob_prefix(distribution_name.split("-")[0])
print(f"[Debug] Blob: {blob.name}. Package distribution: {distribution_name}. Package name: {package_name}")
download_link = f"{account_url}/{blob.container}/{blob.name}?{sas_token}"
index_item = f"<a href='{download_link}' rel='external'>{distribution_name}</a><br/>"
if package_name in packages_indexes:
packages_indexes[package_name].append(index_item)
else:
packages_indexes[package_name] = [index_item]
# Update index.html in the top level blob prefix for the project
project_index_file = "project_index.html"
with open(project_index_file, "w", encoding="utf8") as index_file:
index_file.write("<!DOCTYPE html>\n")
index_file.write(
"<html lang='en'><head><meta charset='utf-8'>"
"<meta name='api-version' value='2'/>"
"<title>Simple Index</title></head><body>\n"
)
for package_name in packages_indexes:
package_index_url = f"https://{pypi_endpoint}/{blob_prefix}/{package_name}"
print(f"[Debug] Updated package_index_url: {package_index_url}")
index_file.write(f"<a href='{package_index_url}'>{package_name}</a><br/>\n")
index_file.write("</body></html>\n")
project_index_blob = os.path.join(blob_prefix, "index.html")
project_index_blob_client = blob_service_client.get_blob_client(container=index_container, blob=project_index_blob)
content_settings = ContentSettings(content_type="text/html")
with open(file=project_index_file, mode="rb") as index:
print(f"Uploading {project_index_file} to container: {index_container}, blob: {project_index_blob}...")
project_index_blob_client.upload_blob(index, overwrite=True, content_settings=content_settings)
# Update index.html for the package distributions
for package_name, distribution_indexes in packages_indexes.items():
package_index_file = f"{package_name}_index.html"
if len(distribution_indexes) > 0:
print(f"{len(distribution_indexes)} distributions found for package {package_name}. Updating index.html...")
with open(package_index_file, "w", encoding="utf8") as index_file:
index_file.write("<!DOCTYPE html>\n")
index_file.write(
f"<html lang='en'><head><meta charset='utf-8'><title>{package_name}</title></head><body>\n"
)
for item in distribution_indexes:
index_file.write(f"{item}\n")
index_file.write("</body></html>\n")
# Update the index.html to the blob with prefix: <blob_prefix>/<normalized package_name>
index_blob = os.path.join(blob_prefix, package_name, "index.html")
index_blob_client = blob_service_client.get_blob_client(container=index_container, blob=index_blob)
content_settings = ContentSettings(content_type="text/html")
with open(file=package_index_file, mode="rb") as index:
print(f"Uploading {package_index_file} to container: {index_container}, blob: {index_blob}...")
index_blob_client.upload_blob(index, overwrite=True, content_settings=content_settings)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--config", type=str)
parser.add_argument("--src_folder_name", type=str)
parser.add_argument("--package_dir_path", type=str)
parser.add_argument("--storage_key", type=str)
parser.add_argument("--upload_as_latest", type=str, default="False")
parser.add_argument("--pypi_type", type=str, default="internal") # internal or public pypi
parser.add_argument("--release_type", type=str, default="release") # release or test
args = parser.parse_args()
print("[Debug] Arguments:")
print(f"[Debug] config: {args.config}")
print(f"[Debug] src_folder_name: {args.src_folder_name}")
print(f"[Debug] package_dir_path: {args.package_dir_path}")
upload_as_latest = args.upload_as_latest.lower() == "true"
print(f"[Debug] upload_as_latest: {args.upload_as_latest}. Boolean upload_as_latest: {upload_as_latest}")
print(f"[Debug] pypi_type: {args.pypi_type}")
print(f"[Debug] release_type: {args.release_type}")
cwd = os.getcwd()
print(f"Current working directory: {cwd}")
with open(os.path.join(os.getcwd(), args.config), "r") as config_file:
config_json = json.load(config_file)
package_dir_path = os.path.join(cwd, args.package_dir_path)
release_config = config_json["releases"][args.pypi_type][f"{args.src_folder_name}-{args.release_type}"]
if args.pypi_type == "internal":
publish_package_internal(package_dir_path, args.storage_key, release_config)