rules_python icon indicating copy to clipboard operation
rules_python copied to clipboard

Add a library that defines a `Label` class in python

Open UebelAndre opened this issue 1 year ago • 5 comments
trafficstars

🚀 feature request

Description

I would love rules_python to expose a library similar to @rules_rust//util/label which defines a validated representation of a Label for users to use in their code.

It'd be fantastic if the interface was a class (Label) that did validation on __init__.

UebelAndre avatar Dec 09 '23 01:12 UebelAndre

Maybe something like the following would suffice


"""Python representations of Bazel labels."""

import re


class Label:
    """A python representation of a Bazel Label.

    For more details see https://bazel.build/rules/lib/builtins/Label
    """

    def __init__(self, text: str) -> None:
        pattern = r"^(@@?[\w\d\-_\.]*)?/{0,2}([\w\d\-_\./+]+)?(:([\+\w\d\-_\./]+))?$"
        match = re.match(pattern, text)
        if not match:
            raise ValueError("Failed to parse Label from: %s", text)

        self._workspace_name = match.group(1) if match.group(1) is not None else ""
        self._package = match.group(2) if match.group(2) is not None else ""
        if match.group(3) is None:
            _, _, name = self._package.rpartition("/")
            self._name = name
        else:
            self._name = match.group(3).lstrip(":")
        self._str = text

    def __str__(self) -> str:
        return self._str

    @property
    def workspace_name(self) -> str:
        """The repository part of this label.

        For instance:

        ```python
        Label("@foo//bar:baz").workspace_name == "foo"
        ```

        Returns:
            The workspace name.
        """
        return self._workspace_name

    @property
    def package(self) -> str:
        """The package part of this label.

        For instance:

        ```python
        Label("//pkg/foo:abc").package == "pkg/foo"
        ```

        Returns:
            The package name.
        """
        return self._package

    @property
    def name(self) -> str:
        """The name of this label within the package.

        For instance:

        ```python
        Label("//pkg/foo:abc").name == "abc"
        ```

        Returns:
            The name.
        """
        return self._name

with something like the following tests:

"""Unit tests for Label."""

import unittest


from label import Label

class LabelTest(unittest.TestCase):
    """Test cases for Label"""

    def test_valid_labels(self) -> None:
        """Test a variety of valid labels."""
        for text, components in [
            ("@//package", ("@", "package", "package")),
            ("@//package/subpackage:name", ("@", "package/subpackage", "name")),
            ("@//package/subpackage", ("@", "package/subpackage", "subpackage")),
            ("@repository//package:name", ("@repository", "package", "name")),
            ("@repository//package", ("@repository", "package", "package")),
            (
                "@repository//package/subpackage:name",
                (
                    "@repository",
                    "package/subpackage",
                    "name",
                ),
            ),
            (
                "@repository//package/subpackage",
                (
                    "@repository",
                    "package/subpackage",
                    "subpackage",
                ),
            ),
            ("//package:name", ("", "package", "name")),
            ("//package", ("", "package", "package")),
            ("//package/subpackage:name", ("", "package/subpackage", "name")),
            ("//package/subpackage", ("", "package/subpackage", "subpackage")),
        ]:
            label = Label(text)
            self.assertEqual(str(label), text)
            self.assertEqual(
                (label.workspace_name, label.package, label.name), components
            )

    def assert_invalid_label(self, text: str) -> None:
        """Assert that the appropriate exception is raised for invalid labels."""
        with self.assertRaises(ValueError) as exc:
            Label(text)

    def test_invalid_labels(self) -> None:
        """Test a variety of invalid labels."""
        self.assert_invalid_label("@@@")

if __name__ == “__main__”:
    unittest.main()

UebelAndre avatar Dec 09 '23 03:12 UebelAndre

The regex here is more of a shot from the hip. Close enough but should be improved if it's gonna be a utility in rules_python.

UebelAndre avatar Dec 09 '23 03:12 UebelAndre

@aiuto @rickeylev any suggestions/thoughts on if something like this could live in rules_python and what it would look like? I'm finding variants of this class popup in projects I work in and it'd be nice to centralize.

UebelAndre avatar Jan 09 '24 17:01 UebelAndre

Somewhat relatedly it'd be nice if there was an official Python-side equivalent to https://github.com/bazelbuild/rules_go/blob/dcca142eac849d24c9e1939b83393d5ed00b9681/go/runfiles/global.go#L58

arrdem avatar Jan 09 '24 19:01 arrdem

Somewhat relatedly it'd be nice if there was an official Python-side equivalent to https://github.com/bazelbuild/rules_go/blob/dcca142eac849d24c9e1939b83393d5ed00b9681/go/runfiles/global.go#L58

This? https://github.com/bazelbuild/rules_python/blob/0.28.0/python/runfiles/runfiles.py#L213-L242

UebelAndre avatar Jan 09 '24 20:01 UebelAndre