diff --git a/.github/actions/validator_pypi_publish/action.yml b/.github/actions/validator_pypi_publish/action.yml new file mode 100644 index 000000000..81c0ab5fe --- /dev/null +++ b/.github/actions/validator_pypi_publish/action.yml @@ -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 + diff --git a/.github/actions/validator_pypi_publish/add_build_prefix.py b/.github/actions/validator_pypi_publish/add_build_prefix.py new file mode 100644 index 000000000..d5069e551 --- /dev/null +++ b/.github/actions/validator_pypi_publish/add_build_prefix.py @@ -0,0 +1,71 @@ +import re +import sys +import toml + + +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() + parsed_toml = toml.loads(content) + + # get the existing package name + existing_name = parsed_toml.get("project", {}).get("name") + + # 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 " + " " + ) + 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 + ) diff --git a/.github/actions/validator_pypi_publish/concat_name.py b/.github/actions/validator_pypi_publish/concat_name.py new file mode 100644 index 000000000..5c3de5fbc --- /dev/null +++ b/.github/actions/validator_pypi_publish/concat_name.py @@ -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 ") + sys.exit(1) + + package_name = sys.argv[1] + print(concat_name(package_name)) diff --git a/.github/actions/validator_pypi_publish/package_name_normalization.py b/.github/actions/validator_pypi_publish/package_name_normalization.py new file mode 100644 index 000000000..43710e8ac --- /dev/null +++ b/.github/actions/validator_pypi_publish/package_name_normalization.py @@ -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 ") + sys.exit(1) + + concatenated_name = sys.argv[1] + print(normalize_package_name(concatenated_name))