Skip to content
77 changes: 77 additions & 0 deletions .github/actions/validator_pypi_publish/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: Publish to Guardrails Hub
description: Re-Usable action to publish a Validator to Guardrails PyPi
inputs:
validator_id:
description: 'Validator ID ex. guardrails/detect_pii'
required: true
guardrails_token:
description: 'Guardrails Token'
required: true
pypi_repository_url:
description: 'PyPi Repository URL'
required: false
default: 'https://pypi.guardrailsai.com'

runs:
using: "composite"
steps:
- name: Checkout "Validator" Repository
uses: actions/checkout@v3
with:
path: 'validator'

- name: Checkout "Action" repository
uses: actions/checkout@v3
with:
repository: guardrails-ai/guardrails
ref: main
path: shared-ci-scripts
sparse-checkout: |
.github

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'

- name: Install Twine & Build
shell: bash
run: |
python -m pip install --upgrade pip
pip install twine build toml

- name: Create .pypirc
shell: bash
run: |
touch ~/.pypirc
echo "[distutils]" >> ~/.pypirc
echo "index-servers =" >> ~/.pypirc
echo " private-repository" >> ~/.pypirc
echo "" >> ~/.pypirc
echo "[private-repository]" >> ~/.pypirc
echo "repository = ${{ inputs.pypi_repository_url }}" >> ~/.pypirc
echo "username = __token__" >> ~/.pypirc
echo "password = ${{ inputs.guardrails_token }}" >> ~/.pypirc

- name: Move CI Scripts to Validator
shell: bash
run: |
mv shared-ci-scripts/.github/actions/validator_pypi_publish/*.py ./validator

- name: Rename Package
shell: bash
run: |
cd validator
CONCATANATED_NAME=$(python concat_name.py ${{ inputs.validator_id }})
NEW_PEP_PACKAGE_NAME=$(python package_name_normalization.py $CONCATANATED_NAME)
VALIDATOR_FOLDER_NAME=$(echo $NEW_PEP_PACKAGE_NAME | tr - _)
mv ./validator ./$VALIDATOR_FOLDER_NAME
python add_build_prefix.py ./pyproject.toml $NEW_PEP_PACKAGE_NAME $VALIDATOR_FOLDER_NAME

- name: Build & Upload
shell: bash
run: |
cd validator
python -m build
twine upload dist/* -u __token__ -p ${{ inputs.guardrails_token }} -r private-repository

79 changes: 79 additions & 0 deletions .github/actions/validator_pypi_publish/add_build_prefix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import re
import sys


def add_package_name_prefix(
pyproject_path, pep_503_new_package_name, validator_folder_name
):
# Read the existing pyproject.toml file
with open(pyproject_path, "r") as f:
content = f.read()

try:
# Check for the presence of the project.name key using a regular expression
project_name_match = re.search(
r'^name\s*=\s*"(.*?)"', content, flags=re.MULTILINE
)
if not project_name_match:
print(f"Could not find the 'project.name' in {pyproject_path}.")
sys.exit(1)
existing_name = project_name_match.group(1)
except Exception as e:
print(f"Failed to parse project name in {pyproject_path}: {e}")
sys.exit(1)

# Update the project name to the new PEP 503-compliant name
# The package name would've been converted to PEP 503-compliant anyways
# But we use this name since it's been concatenated with the seperator
updated_content = re.sub(
rf'(^name\s*=\s*")({re.escape(existing_name)})(")',
rf"\1{pep_503_new_package_name}\3",
content,
flags=re.MULTILINE,
)

# Now we manually add the [tool.setuptools] section with the new folder name
# If the section already exists, we append the correct package name
setuptools_section = f"""

[tool.setuptools]
packages = ["{validator_folder_name}"]

"""

# Check if the [tool.setuptools] section already exists
if "[tool.setuptools]" in updated_content:
# If it exists, update the packages value
updated_content = re.sub(
r"(^\[tool\.setuptools\].*?^packages\s*=\s*\[.*?\])",
f'[tool.setuptools]\npackages = ["{validator_folder_name}"]',
updated_content,
flags=re.DOTALL | re.MULTILINE,
)
else:
# If the section doesn't exist, append it at the end of the file
updated_content += setuptools_section

# Write the modified content back to the pyproject.toml file
with open(pyproject_path, "w") as f:
f.write(updated_content)

print(f"Updated project name to '{pep_503_new_package_name}'.")
print(f"Added package folder '{validator_folder_name}' in {pyproject_path}")


if __name__ == "__main__":
if len(sys.argv) < 3:
print(
"Usage: python script.py <pyproject_path> "
"<pep_503_new_package_name> <validator-folder-name>"
)
sys.exit(1)

pyproject_path = sys.argv[1]
pep_503_new_package_name = sys.argv[2]
validator_folder_name = sys.argv[3]

add_package_name_prefix(
pyproject_path, pep_503_new_package_name, validator_folder_name
)
16 changes: 16 additions & 0 deletions .github/actions/validator_pypi_publish/concat_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
def concat_name(validator_id):
validator_id_parts = validator_id.split("/")
namespace = validator_id_parts[0]
package_name = validator_id_parts[1]
return f"{namespace}-grhub-{package_name}"


if __name__ == "__main__":
import sys

if len(sys.argv) < 2:
print("Usage: python concat_name.py <validator-id>")
sys.exit(1)

package_name = sys.argv[1]
print(concat_name(package_name))
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from packaging.utils import canonicalize_name # PEP 503


def normalize_package_name(concatanated_name: str) -> str:
return canonicalize_name(concatanated_name)


if __name__ == "__main__":
import sys

if len(sys.argv) < 2:
print("Usage: python package_name_normalization.py <concat-name>")
sys.exit(1)

concatenated_name = sys.argv[1]
print(normalize_package_name(concatenated_name))
Loading