diff --git a/.bazelignore b/.bazelignore index 9bcb523a38..bedb73b7f0 100644 --- a/.bazelignore +++ b/.bazelignore @@ -19,6 +19,10 @@ examples/bzlmod/other_module/bazel-other_module examples/bzlmod/other_module/bazel-out examples/bzlmod/other_module/bazel-testlogs examples/bzlmod_build_file_generation/bazel-bzlmod_build_file_generation +examples/bzlmod_python_src_dir_with_separate_tests_dir/bazel-bin +examples/bzlmod_python_src_dir_with_separate_tests_dir/bazel-bzlmod_python_src_dir_with_separate_tests_dir +examples/bzlmod_python_src_dir_with_separate_tests_dir/bazel-out +examples/bzlmod_python_src_dir_with_separate_tests_dir/bazel-testlogs examples/multi_python_versions/bazel-multi_python_versions examples/pip_parse/bazel-pip_parse examples/pip_parse_vendored/bazel-pip_parse_vendored diff --git a/.bazelrc b/.bazelrc index 61fd0e7601..a3cf74e34f 100644 --- a/.bazelrc +++ b/.bazelrc @@ -4,8 +4,8 @@ # (Note, we cannot use `common --deleted_packages` because the bazel version command doesn't support it) # To update these lines, execute # `bazel run @rules_bazel_integration_test//tools:update_deleted_packages` -build --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod/entry_points,examples/bzlmod/entry_points/tests,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/patches,examples/bzlmod/py_proto_library,examples/bzlmod/py_proto_library/example.com/another_proto,examples/bzlmod/py_proto_library/example.com/proto,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/dupe_requirements,examples/bzlmod/tests/other_module,examples/bzlmod/whl_mods,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_proto_library,examples/py_proto_library/example.com/another_proto,examples/py_proto_library/example.com/proto,gazelle,gazelle/manifest,gazelle/manifest/generate,gazelle/manifest/hasher,gazelle/manifest/test,gazelle/modules_mapping,gazelle/python,gazelle/pythonconfig,tests/integration/compile_pip_requirements,tests/integration/compile_pip_requirements_test_from_external_repo,tests/integration/ignore_root_user_error,tests/integration/ignore_root_user_error/submodule,tests/integration/pip_parse,tests/integration/pip_parse/empty,tests/integration/pip_repository_entry_points,tests/integration/py_cc_toolchain_registered -query --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod/entry_points,examples/bzlmod/entry_points/tests,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/patches,examples/bzlmod/py_proto_library,examples/bzlmod/py_proto_library/example.com/another_proto,examples/bzlmod/py_proto_library/example.com/proto,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/dupe_requirements,examples/bzlmod/tests/other_module,examples/bzlmod/whl_mods,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_proto_library,examples/py_proto_library/example.com/another_proto,examples/py_proto_library/example.com/proto,gazelle,gazelle/manifest,gazelle/manifest/generate,gazelle/manifest/hasher,gazelle/manifest/test,gazelle/modules_mapping,gazelle/python,gazelle/pythonconfig,tests/integration/compile_pip_requirements,tests/integration/compile_pip_requirements_test_from_external_repo,tests/integration/ignore_root_user_error,tests/integration/ignore_root_user_error/submodule,tests/integration/pip_parse,tests/integration/pip_parse/empty,tests/integration/pip_repository_entry_points,tests/integration/py_cc_toolchain_registered +build --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod/entry_points,examples/bzlmod/entry_points/tests,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/patches,examples/bzlmod/py_proto_library,examples/bzlmod/py_proto_library/example.com/another_proto,examples/bzlmod/py_proto_library/example.com/proto,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/dupe_requirements,examples/bzlmod/tests/other_module,examples/bzlmod/whl_mods,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/bzlmod_python_src_dir_with_separate_tests_dir,examples/bzlmod_python_src_dir_with_separate_tests_dir/src,examples/bzlmod_python_src_dir_with_separate_tests_dir/src/my_package,examples/bzlmod_python_src_dir_with_separate_tests_dir/tests,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_proto_library,examples/py_proto_library/example.com/another_proto,examples/py_proto_library/example.com/proto,tests/integration/compile_pip_requirements,tests/integration/compile_pip_requirements_test_from_external_repo,tests/integration/ignore_root_user_error,tests/integration/ignore_root_user_error/submodule,tests/integration/pip_parse,tests/integration/pip_parse/empty,tests/integration/pip_repository_entry_points,tests/integration/py_cc_toolchain_registered +query --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod/entry_points,examples/bzlmod/entry_points/tests,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/patches,examples/bzlmod/py_proto_library,examples/bzlmod/py_proto_library/example.com/another_proto,examples/bzlmod/py_proto_library/example.com/proto,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/dupe_requirements,examples/bzlmod/tests/other_module,examples/bzlmod/whl_mods,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/bzlmod_python_src_dir_with_separate_tests_dir,examples/bzlmod_python_src_dir_with_separate_tests_dir/src,examples/bzlmod_python_src_dir_with_separate_tests_dir/src/my_package,examples/bzlmod_python_src_dir_with_separate_tests_dir/tests,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_proto_library,examples/py_proto_library/example.com/another_proto,examples/py_proto_library/example.com/proto,tests/integration/compile_pip_requirements,tests/integration/compile_pip_requirements_test_from_external_repo,tests/integration/ignore_root_user_error,tests/integration/ignore_root_user_error/submodule,tests/integration/pip_parse,tests/integration/pip_parse/empty,tests/integration/pip_repository_entry_points,tests/integration/py_cc_toolchain_registered test --test_output=errors diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f0c2e0e2a..882b06af2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,8 @@ A brief description of the categories of changes: downloader. If you see any issues, report in [#1357](https://github.com/bazelbuild/rules_python/issues/1357). The URLs for the whl and sdist files will be written to the lock file. +* (docs) Added example of using bzlmod and Gazelle with a python `src` directory + and separate `tests` directory. [0.XX.0]: https://github.com/bazelbuild/rules_python/releases/tag/0.XX.0 [python_default_visibility]: gazelle/README.md#directive-python_default_visibility diff --git a/examples/bzlmod_python_src_dir_with_separate_tests_dir/BUILD.bazel b/examples/bzlmod_python_src_dir_with_separate_tests_dir/BUILD.bazel new file mode 100644 index 0000000000..87fbad68e4 --- /dev/null +++ b/examples/bzlmod_python_src_dir_with_separate_tests_dir/BUILD.bazel @@ -0,0 +1,102 @@ +# Load all of the various rules and functions that we end up using. +load("@bazel_gazelle//:def.bzl", "gazelle") +load("@pypi//:requirements.bzl", "all_whl_requirements") +load("@rules_python//python:pip.bzl", "compile_pip_requirements") +load("@rules_python_gazelle_plugin//manifest:defs.bzl", "gazelle_python_manifest") +load("@rules_python_gazelle_plugin//modules_mapping:def.bzl", "modules_mapping") + +# This filegroup is only needed if +# (a) you're using pyproject.toml to store your dependencies and +# (b) you have [tool.setuptools.package-dir] and [tool.setuptools.packages.find] +# configured for `src` dirs per https://setuptools.pypa.io/en/stable/userguide/package_discovery.html +# (which you probably do if you're looking at this example). +# This target gets used later by compile_pip_requirements. +filegroup( + name = "src_dir", + srcs = ["src"], +) + +###### +# Gazelle +# +# Gazelle is an automatic BUILD(.bazel) file generator. Run via: +# bazel run //:gazelle +###### + +# Comments that start with "# gazelle:XYZ" are called *directives*. Some directives +# can and should be set here, in the same bazel package (BUILD file) that defines +# gazelle, while other directives (such as "# gazelle:python_root") should be +# defined in a BUILD file specific to that part of the folder tree. See +# src/BUILD for such an example - it's how we define that the "src" dir should +# be the root of python files and thus get added to sys.path. + +# This directive tells gazelle that our tests are named "test_foo.py" instead +# of "foo_test.py". +# gazelle:python_test_naming_convention test_$package_name$ + +# This directive tells gazelle to make a single bazel target per python file. +# The default is to make a single bazel target per python _package_). +# gazelle:python_generation_mode file + +###### End Gazelle Directives ###### + +# This rule will compile the project requirements into a lock file that +# contains versions and hashes. The lock file ends up getting used when +# installing dependencies via pip. +# bazel run //:requirements.update +compile_pip_requirements( + # Name this target. This will be how you run with `bazel run //:.update` + name = "requirements", + # See comment about filegroup above. If both (a) and (b) are true, you need this otherwise + # the compiling will fail with "error in 'egg_base' option: 'src' does not exist or + # is not a directory". + data = [":src_dir"], + # Optional. Tell pip_tools to be more verbose. + # extra_args = ["-v"], + # If you store requirements in a separate file, name that file in `src`. + # Otherwise, gazelle will pull from pyproject.toml's [project.dependencies] section. + # src = "requirements.in", + requirements_txt = "requirements.lock", +) + +# This rule fetches the metadata for python packages we depend on. That data is +# required for the gazelle_python_manifest rule to update our manifest file. +modules_mapping( + # Name this target. This name is used in `gazelle_python_manifest.modules_mapping` below. + name = "modules_map", + wheels = all_whl_requirements, +) + +# Gazelle python extension needs a manifest file mapping from +# an import to the installed package that provides it. This target updates the +# "gazelle_python.yaml" file when run. The file must already exist. +# This target produces two targets: +# bazel run //:gazelle_python_manifest.update +# bazel run //:gazelle_python_manifest.test +gazelle_python_manifest( + # Name this target. This will be how you run with `bazel run //:.update` + name = "gazelle_python_manifest", + # Same as `modules_mapping.name` (with ":"), above. + modules_mapping = ":modules_map", + # This is what we called our `pip_parse` rule, where third-party + # python libraries are loaded in BUILD files. + pip_repository_name = "pypi", + # This should point to wherever we declare our python dependencies. + # It's the same as what we passed to the pip.parse rule in MODULE.bazel and + # is the same filename that we used in the `requirements_txt` attribute of + # `compile_pip_requirements`, above.) + # This argument is optional. If provided, the `.test` target is very + # fast because it just has to check an integrity field. If not provided, + # the integrity field is not added to the manifest which can help avoid + # merge conflicts in large repos. + requirements = "//:requirements.lock", +) + +# Make a target for running gazelle. +# bazel run //:gazelle +# or: +# bazel run //:gazelle update # Note: "update" is the arg, not part of the target +gazelle( + name = "gazelle", + gazelle = "@rules_python_gazelle_plugin//python:gazelle_binary", +) diff --git a/examples/bzlmod_python_src_dir_with_separate_tests_dir/MODULE.bazel b/examples/bzlmod_python_src_dir_with_separate_tests_dir/MODULE.bazel new file mode 100644 index 0000000000..06520ec1e7 --- /dev/null +++ b/examples/bzlmod_python_src_dir_with_separate_tests_dir/MODULE.bazel @@ -0,0 +1,73 @@ +# Define metadata about this repository/project. +module( + name = "example_bzlmod_python_src_dir_with_separate_tests_dir", + version = "0.0.0", + compatibility_level = 1, +) + +# Install rules_python, which allows us to define how Bazel should work with python files. +# See ../bzlmod/MODULE.bazel for another example. +# In the old WORKSPACE file, this would be 4 items: +# 1. `load` the http_archive rule +# 2. Run the http_archive rule, grabbing rules_python from GitHub +# 3. Load the py_repositories target from rules_python +# 4. Execute py_respositories() +# See ../pip_parse/WORKSPACE and ../pip_parse_vendored for more complete examples. +bazel_dep(name = "rules_python", version = "0.31.0") + +### THIS BLOCK IS ONLY REQUIRED FOR EXAMPLES ### +# This `local_path_override` allows us to use this repo's version of rules_python. +# For usual setups you should remove this local_path_override block. +local_path_override( + module_name = "rules_python", + path = "../..", +) +### END EXAMPLE-ONLY BLOCK ### + +# Gazelle for auto BUILD generation. See +# ../../gazelle/README.md for another example. +# First install the gazelle config that's specific to python. +bazel_dep(name = "rules_python_gazelle_plugin", version = "0.31.0") # same version as rules_python + +### THIS BLOCK IS ONLY REQUIRED FOR EXAMPLES ### +# This `local_path_override` allows us to use this repo's version of rules_python. +# For usual setups you should remove this local_path_override block. +local_path_override( + module_name = "rules_python_gazelle_plugin", + path = "../../gazelle", +) +### END EXAMPLE-ONLY BLOCK ### + +# Then install gazelle itself. +bazel_dep(name = "gazelle", version = "0.35.0", repo_name = "bazel_gazelle") + +# Initialize the python toolchain using the rules_python extension. +# This is similar to the "python_register_toolchains" function in WORKSPACE. +# It creates a hermetic python rather than relying on a system-installed interpreter. +python = use_extension("@rules_python//python/extensions:python.bzl", "python") +python.toolchain( + configure_coverage_tool = True, + python_version = "3.9", +) + +# Enable pip +pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip") + +# Configure how we fetch python dependencies via pip +pip.parse( + # Use the Bazel downloader for pulling PyPI packages. + experimental_index_url = "https://pypi.org/simple", + # This hub name is what gets used in other BUILD files with `load()`. + hub_name = "pypi", + python_version = "3.9", + # The file that contains the python dependencies, versions, and hashes. + # This target needs to be the same as what's passed to `gazelle_python_manifest.requirements` + # in ./BUILD.bazel. + requirements_lock = "//:requirements.lock", +) + +# Similar to WORKSPACE install_deps(), though the Python package installation doesn't +# happen immediately. +# This exposes the repo to be used by the module, and Bazel lazily installs the Python +# packages that the repo defines. +use_repo(pip, "pypi") diff --git a/examples/bzlmod_python_src_dir_with_separate_tests_dir/README.md b/examples/bzlmod_python_src_dir_with_separate_tests_dir/README.md new file mode 100644 index 0000000000..584737fe7e --- /dev/null +++ b/examples/bzlmod_python_src_dir_with_separate_tests_dir/README.md @@ -0,0 +1,90 @@ +# Using a `src` dir and separate `tests` dir, with bzlmod + +This example highlights how to set up `MODULE.bazel`, `BUILD.bazel`, and `gazelle` to work with +a python `src` directory and a separate `tests` directory[^1]. + + +Run tests by first `cd`ing into this directory and then running `bazel test`: + +```shell +$ cd examples/bzlmod_python_src_dir_with_separate_tests_dir +$ bazel test --test_output=errors //... +``` + +Everything should pass. + +Try changing `tests/test_my_python_module.py`'s assert to a different value and run +`bazel test` again. You'll see a test failure, yay! + + +[^1]: This is how the [Python Packaging User Guide][pypa-tutorial] recommends new python libraries +be set up. + +[pypa-tutorial]: https://github.com/pypa/packaging.python.org/blob/091e45c8f78614307ccfdc061a6e562d669b178b/source/tutorials/packaging-projects.rst + + +## Details + +The folder structure, prior to adding Bazel, is: + +``` +./ +├── pyproject.toml +├── README.md +├── src/ +│ └── my_package/ +│ ├── __init__.py +│ └── my_python_module.py +└── tests/ + ├── __init__.py + └── test_my_python_module.py +``` + +After adding files and configuration for Bazel and gazelle: + +``` +packaging_tutorial/ +├── BUILD.bazel # New +├── gazelle_python.yaml # New, empty +├── MODULE.bazel # New +├── pyproject.toml +├── README.md +├── requirements.lock # New, empty +├── src/ +│ ├── BUILD.bazel # New +│ └── mypackage/ +│ ├── __init__.py +│ └── my_python_module.py +└── tests/ + ├── __init__.py + └── test_my_python_module.py +``` + +After running Gazelle: + +```shell +$ bazel run //:requirements.update +$ bazel run //:gazelle_python_manifest.update +$ bazel run //:gazelle +``` + +``` +packaging_tutorial/ +├── BUILD.bazel +├── gazelle_python.yaml # Updated by 'bazel run //:gazelle_python_manifest.update' +├── MODULE.bazel +├── MODULE.bazel.lock # New, not included in git repo +├── pyproject.toml +├── README.md +├── requirements.lock # Updated by 'bazel run //:requirements.update' +├── src/ +│ ├── BUILD.bazel +│ └── mypackage/ +│ ├── __init__.py +│ ├── BUILD.bazel # New, added by 'bazel run //:gazelle' +│ └── my_python_module.py +└── tests/ + ├── __init__.py + ├── BUILD.bazel # New, added by 'bazel run //:gazelle' + └── test_my_python_module.py +``` diff --git a/examples/bzlmod_python_src_dir_with_separate_tests_dir/gazelle_python.yaml b/examples/bzlmod_python_src_dir_with_separate_tests_dir/gazelle_python.yaml new file mode 100644 index 0000000000..1ee385d77d --- /dev/null +++ b/examples/bzlmod_python_src_dir_with_separate_tests_dir/gazelle_python.yaml @@ -0,0 +1,17 @@ +# GENERATED FILE - DO NOT EDIT! +# +# To update this file, run: +# bazel run //:gazelle_python_manifest.update + +manifest: + modules_mapping: + pathspec: pathspec + pathspec.gitignore: pathspec + pathspec.pathspec: pathspec + pathspec.pattern: pathspec + pathspec.patterns: pathspec + pathspec.patterns.gitwildmatch: pathspec + pathspec.util: pathspec + pip_repository: + name: pypi +integrity: 05245d78ed551ea7a050bc567024326e6d9256b8b8d356f855c3f29af654685a diff --git a/examples/bzlmod_python_src_dir_with_separate_tests_dir/pyproject.toml b/examples/bzlmod_python_src_dir_with_separate_tests_dir/pyproject.toml new file mode 100644 index 0000000000..7d94cf9a2b --- /dev/null +++ b/examples/bzlmod_python_src_dir_with_separate_tests_dir/pyproject.toml @@ -0,0 +1,17 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "my_package" +version = "0.0.1" +description = "Example of using Bazel with python `src` and `tests` dir" +dependencies = [ + "pathspec==0.12.1", +] + +[tool.setuptools] +package-dir = {"" = "src"} + +[tool.setuptools.packages.find] +where = ["src"] diff --git a/examples/bzlmod_python_src_dir_with_separate_tests_dir/requirements.lock b/examples/bzlmod_python_src_dir_with_separate_tests_dir/requirements.lock new file mode 100644 index 0000000000..57d69f022e --- /dev/null +++ b/examples/bzlmod_python_src_dir_with_separate_tests_dir/requirements.lock @@ -0,0 +1,10 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# bazel run //:requirements.update +# +pathspec==0.12.1 \ + --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \ + --hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712 + # via my-package (pyproject.toml) diff --git a/examples/bzlmod_python_src_dir_with_separate_tests_dir/src/BUILD.bazel b/examples/bzlmod_python_src_dir_with_separate_tests_dir/src/BUILD.bazel new file mode 100644 index 0000000000..44ec818cac --- /dev/null +++ b/examples/bzlmod_python_src_dir_with_separate_tests_dir/src/BUILD.bazel @@ -0,0 +1,6 @@ +# This directive tells Gazelle to use this dir (`src`) as the root python path. +# gazelle:python_root + +# This directive tells Gazelle to append "//tests:__subpackages__" to the +# visibility of all python targets. +# gazelle:python_visibility //tests:__subpackages__ diff --git a/examples/bzlmod_python_src_dir_with_separate_tests_dir/src/my_package/BUILD.bazel b/examples/bzlmod_python_src_dir_with_separate_tests_dir/src/my_package/BUILD.bazel new file mode 100644 index 0000000000..146fa7864e --- /dev/null +++ b/examples/bzlmod_python_src_dir_with_separate_tests_dir/src/my_package/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_python//python:defs.bzl", "py_library") + +py_library( + name = "my_python_module", + srcs = ["my_python_module.py"], + imports = [".."], + visibility = [ + "//src:__subpackages__", + "//tests:__subpackages__", + ], + deps = ["@pypi//pathspec"], +) diff --git a/examples/bzlmod_python_src_dir_with_separate_tests_dir/src/my_package/__init__.py b/examples/bzlmod_python_src_dir_with_separate_tests_dir/src/my_package/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/bzlmod_python_src_dir_with_separate_tests_dir/src/my_package/my_python_module.py b/examples/bzlmod_python_src_dir_with_separate_tests_dir/src/my_package/my_python_module.py new file mode 100644 index 0000000000..54070ca67e --- /dev/null +++ b/examples/bzlmod_python_src_dir_with_separate_tests_dir/src/my_package/my_python_module.py @@ -0,0 +1,9 @@ +# A import something small and benign so that we can showcase installing packages from pip +import pathspec + +# Satisfy linters by ignoring the import. +del pathspec + + +def my_func(a: int) -> int: + return a + 5 diff --git a/examples/bzlmod_python_src_dir_with_separate_tests_dir/tests/BUILD.bazel b/examples/bzlmod_python_src_dir_with_separate_tests_dir/tests/BUILD.bazel new file mode 100644 index 0000000000..3110909460 --- /dev/null +++ b/examples/bzlmod_python_src_dir_with_separate_tests_dir/tests/BUILD.bazel @@ -0,0 +1,7 @@ +load("@rules_python//python:defs.bzl", "py_test") + +py_test( + name = "test_my_python_module", + srcs = ["test_my_python_module.py"], + deps = ["//src/my_package:my_python_module"], +) diff --git a/examples/bzlmod_python_src_dir_with_separate_tests_dir/tests/__init__.py b/examples/bzlmod_python_src_dir_with_separate_tests_dir/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/bzlmod_python_src_dir_with_separate_tests_dir/tests/test_my_python_module.py b/examples/bzlmod_python_src_dir_with_separate_tests_dir/tests/test_my_python_module.py new file mode 100644 index 0000000000..c5a9c2b7e3 --- /dev/null +++ b/examples/bzlmod_python_src_dir_with_separate_tests_dir/tests/test_my_python_module.py @@ -0,0 +1,17 @@ +import unittest + +from my_package import my_python_module + + +class TestMyFunc(unittest.TestCase): + def test_good_values(self) -> None: + got = my_python_module.my_func(0) + self.assertEqual(got, 5) + + def test_bad_values(self) -> None: + with self.assertRaises(TypeError): + my_python_module.my_func(int) + + +if __name__ == "__main__": + unittest.main()