From c8a46cffa0a0980a8539d7722c15d3553aceed41 Mon Sep 17 00:00:00 2001 From: njlr Date: Thu, 19 Dec 2024 20:59:09 +0000 Subject: [PATCH 1/4] - Make resolutions consistent between `py_binary` and `py_test` - Allow a list to be passed to `from_requirements` - Add an example of `virtual_deps` feature --- examples/virtual_deps/BUILD.bazel | 65 ++++++++++++++++++++++++ examples/virtual_deps/README.md | 9 ++++ examples/virtual_deps/cowsnake/cowsay.py | 4 ++ examples/virtual_deps/greet.py | 4 ++ examples/virtual_deps/greet_test.py | 6 +++ examples/virtual_deps/main.py | 3 ++ py/defs.bzl | 27 ++++++++-- py/private/virtual.bzl | 7 ++- requirements.in | 1 + requirements.txt | 4 ++ 10 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 examples/virtual_deps/BUILD.bazel create mode 100644 examples/virtual_deps/README.md create mode 100644 examples/virtual_deps/cowsnake/cowsay.py create mode 100644 examples/virtual_deps/greet.py create mode 100644 examples/virtual_deps/greet_test.py create mode 100644 examples/virtual_deps/main.py diff --git a/examples/virtual_deps/BUILD.bazel b/examples/virtual_deps/BUILD.bazel new file mode 100644 index 00000000..64918361 --- /dev/null +++ b/examples/virtual_deps/BUILD.bazel @@ -0,0 +1,65 @@ +load("@aspect_rules_py//py:defs.bzl", "py_binary", "py_library", "py_pytest_main", "py_test", "resolutions") +load("@pypi//:requirements.bzl", "requirement") + +py_library( + name = "greet", + imports = ["."], + srcs = ["greet.py"], + virtual_deps = ["cowsay"], +) + +# A library that looks like cowsay... but isn't! +py_library( + name = "cowsnake", + imports = ["cowsnake"], + srcs = ["cowsnake/cowsay.py"], + virtual_deps = ["snakesay"], +) + +py_binary( + name = "app", + srcs = ["main.py"], + resolutions = resolutions.from_requirements([ + "cowsay", + ], requirement), + deps = [ + ":greet", + ], + python_version = "3.8.12", +) + +# Here we swap out the cowsay module for our own implementation +py_binary( + name = "app_snake", + srcs = ["main.py"], + deps = [ + ":greet", + ], + resolutions = resolutions.from_requirements([ + "snakesay", + ], requirement).override({ + "cowsay": ":cowsnake", + }), + python_version = "3.8.12", +) + +py_pytest_main( + name = "__test__", +) + +py_test( + name = "pytest_test", + srcs = [ + "greet_test.py", + ":__test__", + ], + main = ":__test__.py", + package_collisions = "error", + resolutions = resolutions.from_requirements([ + "cowsay", + ], requirement), + deps = [ + requirement("pytest"), + ":greet", + ], +) diff --git a/examples/virtual_deps/README.md b/examples/virtual_deps/README.md new file mode 100644 index 00000000..70ed542e --- /dev/null +++ b/examples/virtual_deps/README.md @@ -0,0 +1,9 @@ +# virtual_deps + +The example shows how to use `virtual_deps` feature. + + - `greet` is a library that has a virtual dependency on `cowsay` + - `cowsnake` is a library that implements some of the `cowsay` API + - `app` is a binary that uses `greet` and resolves the `cowsay` virtual dependency + - `app_snake` is like `app`, but swaps out `cowsay` for `cowsnake`! + - `pytest_test` tests `greet` using a resolved `cowsay` diff --git a/examples/virtual_deps/cowsnake/cowsay.py b/examples/virtual_deps/cowsnake/cowsay.py new file mode 100644 index 00000000..16842cb9 --- /dev/null +++ b/examples/virtual_deps/cowsnake/cowsay.py @@ -0,0 +1,4 @@ +import snakesay + +def get_output_string(_, x): + return snakesay.snakesay(x) diff --git a/examples/virtual_deps/greet.py b/examples/virtual_deps/greet.py new file mode 100644 index 00000000..075ddda6 --- /dev/null +++ b/examples/virtual_deps/greet.py @@ -0,0 +1,4 @@ +import cowsay + +def greet(x): + return cowsay.get_output_string("cow", x) diff --git a/examples/virtual_deps/greet_test.py b/examples/virtual_deps/greet_test.py new file mode 100644 index 00000000..b53a9900 --- /dev/null +++ b/examples/virtual_deps/greet_test.py @@ -0,0 +1,6 @@ +import pytest +from greet import greet + +def test_greet_contains_input(): + input = "Hello Alice!" + assert input in greet(input), "" diff --git a/examples/virtual_deps/main.py b/examples/virtual_deps/main.py new file mode 100644 index 00000000..351d6d0d --- /dev/null +++ b/examples/virtual_deps/main.py @@ -0,0 +1,3 @@ +from examples.virtual_deps.greet import greet + +print(greet("Hello Alice!")) diff --git a/py/defs.bzl b/py/defs.bzl index fa250767..334f673c 100644 --- a/py/defs.bzl +++ b/py/defs.bzl @@ -14,12 +14,12 @@ load("@rules_python//python:repositories.bzl", "py_repositories", "python_regist python_register_toolchains( name = "python_toolchain_3_8", python_version = "3.8.12", - # setting set_python_version_constraint makes it so that only matches py_* rule + # setting set_python_version_constraint makes it so that only matches py_* rule # which has this exact version set in the `python_version` attribute. set_python_version_constraint = True, ) -# It's important to register the default toolchain last it will match any py_* target. +# It's important to register the default toolchain last it will match any py_* target. python_register_toolchains( name = "python_toolchain", python_version = "3.9", @@ -116,9 +116,26 @@ def py_binary(name, srcs = [], main = None, **kwargs): _py_binary_or_test(name = name, rule = _py_binary, srcs = srcs, main = main, resolutions = resolutions, **kwargs) -def py_test(name, main = None, srcs = [], **kwargs): - "Identical to [py_binary](./py_binary.md), but produces a target that can be used with `bazel test`." +def py_test(name, srcs = [], main = None, **kwargs): + """Identical to [py_binary](./py_binary.md), but produces a target that can be used with `bazel test`. + + Args: + name: Name of the rule. + srcs: Python source files. + main: Entry point. + Like rules_python, this is treated as a suffix of a file that should appear among the srcs. + If absent, then `[name].py` is tried. As a final fallback, if the srcs has a single file, + that is used as the main. + **kwargs: additional named parameters to `py_binary_rule`. + """ # Ensure that any other targets we write will be testonly like the py_test target kwargs["testonly"] = True - _py_binary_or_test(name = name, rule = _py_test, srcs = srcs, main = main, **kwargs) + + # For a clearer DX when updating resolutions, the resolutions dict is "string" -> "label", + # where the rule attribute is a label-keyed-dict, so reverse them here. + resolutions = kwargs.pop("resolutions", None) + if resolutions: + resolutions = resolutions.to_label_keyed_dict() + + _py_binary_or_test(name = name, rule = _py_test, srcs = srcs, main = main, resolutions = resolutions, **kwargs) diff --git a/py/private/virtual.bzl b/py/private/virtual.bzl index 48ff813d..a92926ed 100644 --- a/py/private/virtual.bzl +++ b/py/private/virtual.bzl @@ -60,7 +60,12 @@ def _make_resolution(name, requirement): requirement = requirement, ) +def _from_requirements(base, requirement_fn = lambda r: r): + if type(base) == "list": + base = { k: None for k in base } + return _make_resolutions(base, requirement_fn) + resolutions = struct( - from_requirements = _make_resolutions, + from_requirements = _from_requirements, empty = lambda: _make_resolutions({}), ) diff --git a/requirements.in b/requirements.in index 29cbd745..a9b735dc 100644 --- a/requirements.in +++ b/requirements.in @@ -3,6 +3,7 @@ colorama~=0.4.0 click pytest cowsay +snakesay ftfy==6.2.0 neptune==1.10.2 six diff --git a/requirements.txt b/requirements.txt index e6f9c903..f830c054 100644 --- a/requirements.txt +++ b/requirements.txt @@ -780,6 +780,10 @@ smmap==5.0.1 \ --hash=sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62 \ --hash=sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da # via gitdb +snakesay==0.10.3 \ + --hash=sha256:0a601a0c408deba05a20b11ba2f0db336b1915274601053ef8de3a6b354c60fc \ + --hash=sha256:6346aa7231b1970efc6fa8b3ea78bd015b3d5a7e33ba709c17e00bcc3328f93f + # via -r requirements.in sqlparse==0.5.1 \ --hash=sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4 \ --hash=sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e From 10fc628fccbaab8b18264456b7df16b5f01f237d Mon Sep 17 00:00:00 2001 From: njlr Date: Thu, 19 Dec 2024 21:03:18 +0000 Subject: [PATCH 2/4] Update app text --- examples/virtual_deps/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/virtual_deps/main.py b/examples/virtual_deps/main.py index 351d6d0d..8305451b 100644 --- a/examples/virtual_deps/main.py +++ b/examples/virtual_deps/main.py @@ -1,3 +1,3 @@ from examples.virtual_deps.greet import greet -print(greet("Hello Alice!")) +print(greet("Hello virtual_deps!")) From f54ad78a87dfe5aeeb60a813011e7bcfa0b9ff2f Mon Sep 17 00:00:00 2001 From: njlr Date: Tue, 21 Jan 2025 18:59:21 +0000 Subject: [PATCH 3/4] Update BUILD.bazel --- examples/virtual_deps/BUILD.bazel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/virtual_deps/BUILD.bazel b/examples/virtual_deps/BUILD.bazel index 64918361..27b826be 100644 --- a/examples/virtual_deps/BUILD.bazel +++ b/examples/virtual_deps/BUILD.bazel @@ -8,7 +8,7 @@ py_library( virtual_deps = ["cowsay"], ) -# A library that looks like cowsay... but isn't! +# A library that *looks* like cowsay... but isn't! py_library( name = "cowsnake", imports = ["cowsnake"], From 166463e294dd20adc47ed729e01c58c85e72683d Mon Sep 17 00:00:00 2001 From: njlr Date: Tue, 21 Jan 2025 21:00:39 +0000 Subject: [PATCH 4/4] Update docs --- docs/py_binary.md | 4 ++-- docs/py_test.md | 14 +++++++------- gazelle_python.yaml | 4 +++- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/py_binary.md b/docs/py_binary.md index 7a0efcc8..f1938310 100644 --- a/docs/py_binary.md +++ b/docs/py_binary.md @@ -16,12 +16,12 @@ load("@rules_python//python:repositories.bzl", "py_repositories", "python_regist python_register_toolchains( name = "python_toolchain_3_8", python_version = "3.8.12", - # setting set_python_version_constraint makes it so that only matches py_* rule + # setting set_python_version_constraint makes it so that only matches py_* rule # which has this exact version set in the `python_version` attribute. set_python_version_constraint = True, ) -# It's important to register the default toolchain last it will match any py_* target. +# It's important to register the default toolchain last it will match any py_* target. python_register_toolchains( name = "python_toolchain", python_version = "3.9", diff --git a/docs/py_test.md b/docs/py_test.md index 247964d5..11a8cc03 100644 --- a/docs/py_test.md +++ b/docs/py_test.md @@ -16,12 +16,12 @@ load("@rules_python//python:repositories.bzl", "py_repositories", "python_regist python_register_toolchains( name = "python_toolchain_3_8", python_version = "3.8.12", - # setting set_python_version_constraint makes it so that only matches py_* rule + # setting set_python_version_constraint makes it so that only matches py_* rule # which has this exact version set in the `python_version` attribute. set_python_version_constraint = True, ) -# It's important to register the default toolchain last it will match any py_* target. +# It's important to register the default toolchain last it will match any py_* target. python_register_toolchains( name = "python_toolchain", python_version = "3.9", @@ -95,7 +95,7 @@ py_pytest_main wraps the template rendering target and the final py_library. ## py_test
-py_test(name, main, srcs, kwargs)
+py_test(name, srcs, main, kwargs)
 
Identical to [py_binary](./py_binary.md), but produces a target that can be used with `bazel test`. @@ -105,9 +105,9 @@ Identical to [py_binary](./py_binary.md), but produces a target that can be used | Name | Description | Default Value | | :------------- | :------------- | :------------- | -| name |

-

| none | -| main |

-

| None | -| srcs |

-

| [] | -| kwargs |

-

| none | +| name | Name of the rule. | none | +| srcs | Python source files. | [] | +| main | Entry point. Like rules_python, this is treated as a suffix of a file that should appear among the srcs. If absent, then [name].py is tried. As a final fallback, if the srcs has a single file, that is used as the main. | None | +| kwargs | additional named parameters to py_binary_rule. | none | diff --git a/gazelle_python.yaml b/gazelle_python.yaml index cc68df4c..0749cbbc 100644 --- a/gazelle_python.yaml +++ b/gazelle_python.yaml @@ -3860,6 +3860,8 @@ manifest: smmap.test.test_tutorial: smmap smmap.test.test_util: smmap smmap.util: smmap + snakesay: snakesay + snakesay.snakesay: snakesay sqlparse: sqlparse sqlparse.cli: sqlparse sqlparse.engine: sqlparse @@ -3988,4 +3990,4 @@ manifest: yaml.tokens: PyYAML pip_repository: name: pypi -integrity: 2e0cbc780621ce75951013e6968f66d1aadd8d1ecb8b7c470883c998b169e733 +integrity: de76d829a37e0a5afc12859854a0f8fa096534eb4c32e6a346b6378b55634f44