rules_python
rules_python copied to clipboard
Add a library that defines a `Label` class in python
🚀 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__.
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()
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.
@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.
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
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