Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bugfix/issue-466-py-test-resolutions-dx #487

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions examples/virtual_deps/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -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",
],
)
9 changes: 9 additions & 0 deletions examples/virtual_deps/README.md
Original file line number Diff line number Diff line change
@@ -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`
4 changes: 4 additions & 0 deletions examples/virtual_deps/cowsnake/cowsay.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import snakesay

def get_output_string(_, x):
return snakesay.snakesay(x)
4 changes: 4 additions & 0 deletions examples/virtual_deps/greet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import cowsay

def greet(x):
return cowsay.get_output_string("cow", x)
6 changes: 6 additions & 0 deletions examples/virtual_deps/greet_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import pytest
from greet import greet

def test_greet_contains_input():
input = "Hello Alice!"
assert input in greet(input), ""
3 changes: 3 additions & 0 deletions examples/virtual_deps/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from examples.virtual_deps.greet import greet

print(greet("Hello virtual_deps!"))
27 changes: 22 additions & 5 deletions py/defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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)
7 changes: 6 additions & 1 deletion py/private/virtual.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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({}),
)
1 change: 1 addition & 0 deletions requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ colorama~=0.4.0
click
pytest
cowsay
snakesay
ftfy==6.2.0
neptune==1.10.2
six
4 changes: 4 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down