Skip to content

Commit 200aa9d

Browse files
Merge pull request #1126 from guardrails-ai/feat/reusable-validator-ci
Publish to Guardrails Hub PyPi Action
2 parents b114ef6 + 91354ff commit 200aa9d

File tree

4 files changed

+180
-0
lines changed

4 files changed

+180
-0
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
name: Publish to Guardrails Hub
2+
description: Re-Usable action to publish a Validator to Guardrails PyPi
3+
inputs:
4+
validator_id:
5+
description: 'Validator ID ex. guardrails/detect_pii'
6+
required: true
7+
guardrails_token:
8+
description: 'Guardrails Token'
9+
required: true
10+
pypi_repository_url:
11+
description: 'PyPi Repository URL'
12+
required: false
13+
default: 'https://pypi.guardrailsai.com'
14+
15+
runs:
16+
using: "composite"
17+
steps:
18+
- name: Checkout "Validator" Repository
19+
uses: actions/checkout@v3
20+
with:
21+
path: 'validator'
22+
23+
- name: Checkout "Action" repository
24+
uses: actions/checkout@v3
25+
with:
26+
repository: guardrails-ai/guardrails
27+
ref: main
28+
path: shared-ci-scripts
29+
sparse-checkout: |
30+
.github
31+
32+
- name: Set up Python
33+
uses: actions/setup-python@v4
34+
with:
35+
python-version: '3.10'
36+
37+
- name: Install Twine & Build
38+
shell: bash
39+
run: |
40+
python -m pip install --upgrade pip
41+
pip install twine build toml
42+
43+
- name: Create .pypirc
44+
shell: bash
45+
run: |
46+
touch ~/.pypirc
47+
echo "[distutils]" >> ~/.pypirc
48+
echo "index-servers =" >> ~/.pypirc
49+
echo " private-repository" >> ~/.pypirc
50+
echo "" >> ~/.pypirc
51+
echo "[private-repository]" >> ~/.pypirc
52+
echo "repository = ${{ inputs.pypi_repository_url }}" >> ~/.pypirc
53+
echo "username = __token__" >> ~/.pypirc
54+
echo "password = ${{ inputs.guardrails_token }}" >> ~/.pypirc
55+
56+
- name: Move CI Scripts to Validator
57+
shell: bash
58+
run: |
59+
mv shared-ci-scripts/.github/actions/validator_pypi_publish/*.py ./validator
60+
61+
- name: Rename Package
62+
shell: bash
63+
run: |
64+
cd validator
65+
CONCATANATED_NAME=$(python concat_name.py ${{ inputs.validator_id }})
66+
NEW_PEP_PACKAGE_NAME=$(python package_name_normalization.py $CONCATANATED_NAME)
67+
VALIDATOR_FOLDER_NAME=$(echo $NEW_PEP_PACKAGE_NAME | tr - _)
68+
mv ./validator ./$VALIDATOR_FOLDER_NAME
69+
python add_build_prefix.py ./pyproject.toml $NEW_PEP_PACKAGE_NAME $VALIDATOR_FOLDER_NAME
70+
71+
- name: Build & Upload
72+
shell: bash
73+
run: |
74+
cd validator
75+
python -m build
76+
twine upload dist/* -u __token__ -p ${{ inputs.guardrails_token }} -r private-repository
77+
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import re
2+
import sys
3+
import toml
4+
5+
6+
def add_package_name_prefix(
7+
pyproject_path, pep_503_new_package_name, validator_folder_name
8+
):
9+
# Read the existing pyproject.toml file
10+
with open(pyproject_path, "r") as f:
11+
content = f.read()
12+
parsed_toml = toml.loads(content)
13+
14+
# get the existing package name
15+
existing_name = parsed_toml.get("project", {}).get("name")
16+
17+
# Update the project name to the new PEP 503-compliant name
18+
# The package name would've been converted to PEP 503-compliant anyways
19+
# But we use this name since it's been concatenated with the seperator
20+
updated_content = re.sub(
21+
rf'(^name\s*=\s*")({re.escape(existing_name)})(")',
22+
rf"\1{pep_503_new_package_name}\3",
23+
content,
24+
flags=re.MULTILINE,
25+
)
26+
27+
# Now we manually add the [tool.setuptools] section with the new folder name
28+
# If the section already exists, we append the correct package name
29+
setuptools_section = f"""
30+
31+
[tool.setuptools]
32+
packages = ["{validator_folder_name}"]
33+
34+
"""
35+
36+
# Check if the [tool.setuptools] section already exists
37+
if "[tool.setuptools]" in updated_content:
38+
# If it exists, update the packages value
39+
updated_content = re.sub(
40+
r"(^\[tool\.setuptools\].*?^packages\s*=\s*\[.*?\])",
41+
f'[tool.setuptools]\npackages = ["{validator_folder_name}"]',
42+
updated_content,
43+
flags=re.DOTALL | re.MULTILINE,
44+
)
45+
else:
46+
# If the section doesn't exist, append it at the end of the file
47+
updated_content += setuptools_section
48+
49+
# Write the modified content back to the pyproject.toml file
50+
with open(pyproject_path, "w") as f:
51+
f.write(updated_content)
52+
53+
print(f"Updated project name to '{pep_503_new_package_name}'.")
54+
print(f"Added package folder '{validator_folder_name}' in {pyproject_path}")
55+
56+
57+
if __name__ == "__main__":
58+
if len(sys.argv) < 3:
59+
print(
60+
"Usage: python script.py <pyproject_path>"
61+
" <pep_503_new_package_name> <validator-folder-name>"
62+
)
63+
sys.exit(1)
64+
65+
pyproject_path = sys.argv[1]
66+
pep_503_new_package_name = sys.argv[2]
67+
validator_folder_name = sys.argv[3]
68+
69+
add_package_name_prefix(
70+
pyproject_path, pep_503_new_package_name, validator_folder_name
71+
)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
def concat_name(validator_id):
2+
validator_id_parts = validator_id.split("/")
3+
namespace = validator_id_parts[0]
4+
package_name = validator_id_parts[1]
5+
return f"{namespace}-grhub-{package_name}"
6+
7+
8+
if __name__ == "__main__":
9+
import sys
10+
11+
if len(sys.argv) < 2:
12+
print("Usage: python concat_name.py <validator-id>")
13+
sys.exit(1)
14+
15+
package_name = sys.argv[1]
16+
print(concat_name(package_name))
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from packaging.utils import canonicalize_name # PEP 503
2+
3+
4+
def normalize_package_name(concatanated_name: str) -> str:
5+
return canonicalize_name(concatanated_name)
6+
7+
8+
if __name__ == "__main__":
9+
import sys
10+
11+
if len(sys.argv) < 2:
12+
print("Usage: python package_name_normalization.py <concat-name>")
13+
sys.exit(1)
14+
15+
concatenated_name = sys.argv[1]
16+
print(normalize_package_name(concatenated_name))

0 commit comments

Comments
 (0)