Some transitive "extras" dependencies are not automatically included to the target #3352

Description

@hartikainen

🐞 bug report

Affected Rule

py_{test,binary,library}

Is this a regression?

I'm not sure. I think things work differently between venvs_site_packages=yes and venvs_site_packages=no. I started seeing this issue when switching over to venvs_site_packages=yes.

Description

The "Using PyPI" section in rules_python-documentation says:

‘Extras’ dependencies
Any “extras” specified in the requirements lock file will be automatically added as transitive dependencies of the package. In the example above, you’d just put requirement("useful_dep") or @pypi//useful_dep.

After switching to using venvs_site_packages=yes, I see that the above is not the case for all packages. For example, consider the following simple requirements.in:

etils[eapp,epath]

and its compiled requirements.txt as follows:

Details
# This file was autogenerated by uv via the following command:
# uv pip compile --no-strip-extras --generate-hashes --emit-index-url ./requirements.in -o ./requirements.txt
--index-url https://pypi.org/simple
absl-py==2.3.1 \
--hash=sha256:a97820526f7fbfd2ec1bce83f3f25e3a14840dac0d8e02a0b71cd75db3f77fc9 \
--hash=sha256:eeecf07f0c2a93ace0772c92e596ace6d3d3996c042b2128459aaae2a76de11d
# via etils
docstring-parser==0.17.0 \
--hash=sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912 \
--hash=sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708
# via simple-parsing
etils[eapp, epath]==1.13.0 \
--hash=sha256:a5b60c71f95bcd2d43d4e9fb3dc3879120c1f60472bb5ce19f7a860b1d44f607 \
--hash=sha256:d9cd4f40fbe77ad6613b7348a18132cc511237b6c076dbb89105c0b520a4c6bb
# via -r ./requirements.in
fsspec==2025.9.0 \
--hash=sha256:19fd429483d25d28b65ec68f9f4adc16c17ea2c7c7bf54ec61360d478fb19c19 \
--hash=sha256:530dc2a2af60a414a832059574df4a6e10cce927f6f4a78209390fe38955cfb7
# via etils
importlib-resources==6.5.2 \
--hash=sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c \
--hash=sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec
# via etils
simple-parsing==0.1.7 \
--hash=sha256:225e6b35252d68f7894716101fe3bd7e6dd3d30ab7b1c3c023f77a42dbe1336f \
--hash=sha256:5276e6c90c157362dd0173d1eecebe58361a66b457129cc9bba13b78a4e85092
# via etils
typing-extensions==4.15.0 \
--hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \
--hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548
# via
# etils
# simple-parsing
zipp==3.23.0 \
--hash=sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e \
--hash=sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166
# via etils

Based on the documentation referred to above, I'd expect the following py_test target to run without problems:

Details
load("@rules_python//python:defs.bzl", "py_test")
py_test(
name="etils_test",
srcs= ["etils_test.py"],
deps= [
"@pypi//etils",
],
)

where etils_test.py is defined as:

frometilsimporteappfrometilsimportepatheapp.better_logging()

However, I get the following error:

Details
bazel run \ --verbose_failures \ //:etils_testINFO: Analyzed target //:etils_test (0 packages loaded, 0 targets configured).INFO: Found 1 target...Target //:etils_test up-to-date: bazel-bin/etils_testINFO: Elapsed time: 0.133s, Critical Path: 0.00sINFO: 1 process: 1 internal.INFO: Build completed successfully, 1 total actionINFO: Running command line: external/bazel_tools/tools/test/test-setup.sh ./etils_testexec ${PAGER:-/usr/bin/less} "$0" || exit 1Executing tests from //:etils_test-----------------------------------------------------------------------------Traceback (most recent call last): File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test_stage2_bootstrap.py", line 499, in <module> main() File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test_stage2_bootstrap.py", line 493, in main _run_py_path(main_filename, args=sys.argv[1:]) File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test_stage2_bootstrap.py", line 287, in _run_py_path runpy.run_path(main_filename, run_name="__main__") File "<frozen runpy>", line 287, in run_path File "<frozen runpy>", line 98, in _run_module_code File "<frozen runpy>", line 88, in _run_code File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/etils_test.py", line 1, in <module> from etils import eapp File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/eapp/__init__.py", line 17, in <module> from etils.eapp.dataclass_flags import make_flags_parser File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/eapp/dataclass_flags.py", line 25, in <module> with _internal.check_missing_deps(): ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/external/rules_python++python+python_3_12_x86_64-unknown-linux-gnu/lib/python3.12/contextlib.py", line 158, in __exit__ self.gen.throw(value) File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/epy/_internal.py", line 48, in check_missing_deps reraise_utils.reraise( File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/epy/reraise_utils.py", line 112, in reraise raise new_exception.with_traceback(e.__traceback__) from e.__cause__ File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/epy/_internal.py", line 46, in check_missing_deps yield File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/eapp/dataclass_flags.py", line 27, in <module> from absl import flagsModuleNotFoundError: No module named 'absl'Each etils sub-modules require deps to be installed separately (e.g. `from etils import ecolab` -> `pip install etils[ecolab]`)

To make this target work, I need to add several transitive dependency packages to the rule manually. The following target works:

load("@rules_python//python:defs.bzl", "py_test")
py_test(
name="etils_test",
srcs= ["etils_test.py"],
deps= [
"@pypi//absl_py",
"@pypi//etils",
"@pypi//simple_parsing",
"@pypi//importlib_resources",
],
)

Note how absl_py is clearly a transitive dependency of etils, as can be seen from the compiled requirements.txt. I wonder if I just misunderstand what the documentation means about extras. It's unclear what the useful_dep in the documentation example is because I don't see any reference to useful_dep "in the example above" in the documentation.

🔬 Minimal Reproduction

See full reproduction here: main...hartikainen:rules_python:etils

🔥 Exception or Error

See Above

🌍 Your Environment

Operating System:

$lsb_release -aNo LSB modules are available.Distributor ID: UbuntuDescription: Ubuntu 24.04.1 LTSRelease: 24.04Codename: noble

Output of bazel version:

$ bazel --versionbazel 8.2.1

Rules_python version:

43a5acf

Anything else relevant?

N/A

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
       blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
      }
      } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
      })();
      (function(){
      try {
      var __m = "github.com";
      var __re = new RegExp('^' + "github\\.com" + '
      
      Skip to content

      Some transitive "extras" dependencies are not automatically included to the target #3352

      Description

      @hartikainen

      🐞 bug report

      Affected Rule

      py_{test,binary,library}

      Is this a regression?

      I'm not sure. I think things work differently between venvs_site_packages=yes and venvs_site_packages=no. I started seeing this issue when switching over to venvs_site_packages=yes.

      Description

      The "Using PyPI" section in rules_python-documentation says:

      ‘Extras’ dependencies
      Any “extras” specified in the requirements lock file will be automatically added as transitive dependencies of the package. In the example above, you’d just put requirement("useful_dep") or @pypi//useful_dep.

      After switching to using venvs_site_packages=yes, I see that the above is not the case for all packages. For example, consider the following simple requirements.in:

      etils[eapp,epath]
      

      and its compiled requirements.txt as follows:

      Details
      # This file was autogenerated by uv via the following command:
      # uv pip compile --no-strip-extras --generate-hashes --emit-index-url ./requirements.in -o ./requirements.txt
      --index-url https://pypi.org/simple
      absl-py==2.3.1 \
      --hash=sha256:a97820526f7fbfd2ec1bce83f3f25e3a14840dac0d8e02a0b71cd75db3f77fc9 \
      --hash=sha256:eeecf07f0c2a93ace0772c92e596ace6d3d3996c042b2128459aaae2a76de11d
      # via etils
      docstring-parser==0.17.0 \
      --hash=sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912 \
      --hash=sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708
      # via simple-parsing
      etils[eapp, epath]==1.13.0 \
      --hash=sha256:a5b60c71f95bcd2d43d4e9fb3dc3879120c1f60472bb5ce19f7a860b1d44f607 \
      --hash=sha256:d9cd4f40fbe77ad6613b7348a18132cc511237b6c076dbb89105c0b520a4c6bb
      # via -r ./requirements.in
      fsspec==2025.9.0 \
      --hash=sha256:19fd429483d25d28b65ec68f9f4adc16c17ea2c7c7bf54ec61360d478fb19c19 \
      --hash=sha256:530dc2a2af60a414a832059574df4a6e10cce927f6f4a78209390fe38955cfb7
      # via etils
      importlib-resources==6.5.2 \
      --hash=sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c \
      --hash=sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec
      # via etils
      simple-parsing==0.1.7 \
      --hash=sha256:225e6b35252d68f7894716101fe3bd7e6dd3d30ab7b1c3c023f77a42dbe1336f \
      --hash=sha256:5276e6c90c157362dd0173d1eecebe58361a66b457129cc9bba13b78a4e85092
      # via etils
      typing-extensions==4.15.0 \
      --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \
      --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548
      # via
      # etils
      # simple-parsing
      zipp==3.23.0 \
      --hash=sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e \
      --hash=sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166
      # via etils
      

      Based on the documentation referred to above, I'd expect the following py_test target to run without problems:

      Details
      load("@rules_python//python:defs.bzl", "py_test")
      py_test(
      name="etils_test",
      srcs= ["etils_test.py"],
      deps= [
      "@pypi//etils",
      ],
      )

      where etils_test.py is defined as:

      frometilsimporteappfrometilsimportepatheapp.better_logging()

      However, I get the following error:

      Details
      bazel run \ --verbose_failures \ //:etils_testINFO: Analyzed target //:etils_test (0 packages loaded, 0 targets configured).INFO: Found 1 target...Target //:etils_test up-to-date: bazel-bin/etils_testINFO: Elapsed time: 0.133s, Critical Path: 0.00sINFO: 1 process: 1 internal.INFO: Build completed successfully, 1 total actionINFO: Running command line: external/bazel_tools/tools/test/test-setup.sh ./etils_testexec ${PAGER:-/usr/bin/less} "$0" || exit 1Executing tests from //:etils_test-----------------------------------------------------------------------------Traceback (most recent call last): File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test_stage2_bootstrap.py", line 499, in <module> main() File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test_stage2_bootstrap.py", line 493, in main _run_py_path(main_filename, args=sys.argv[1:]) File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test_stage2_bootstrap.py", line 287, in _run_py_path runpy.run_path(main_filename, run_name="__main__") File "<frozen runpy>", line 287, in run_path File "<frozen runpy>", line 98, in _run_module_code File "<frozen runpy>", line 88, in _run_code File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/etils_test.py", line 1, in <module> from etils import eapp File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/eapp/__init__.py", line 17, in <module> from etils.eapp.dataclass_flags import make_flags_parser File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/eapp/dataclass_flags.py", line 25, in <module> with _internal.check_missing_deps(): ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/external/rules_python++python+python_3_12_x86_64-unknown-linux-gnu/lib/python3.12/contextlib.py", line 158, in __exit__ self.gen.throw(value) File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/epy/_internal.py", line 48, in check_missing_deps reraise_utils.reraise( File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/epy/reraise_utils.py", line 112, in reraise raise new_exception.with_traceback(e.__traceback__) from e.__cause__ File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/epy/_internal.py", line 46, in check_missing_deps yield File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/eapp/dataclass_flags.py", line 27, in <module> from absl import flagsModuleNotFoundError: No module named 'absl'Each etils sub-modules require deps to be installed separately (e.g. `from etils import ecolab` -> `pip install etils[ecolab]`)

      To make this target work, I need to add several transitive dependency packages to the rule manually. The following target works:

      load("@rules_python//python:defs.bzl", "py_test")
      py_test(
      name="etils_test",
      srcs= ["etils_test.py"],
      deps= [
      "@pypi//absl_py",
      "@pypi//etils",
      "@pypi//simple_parsing",
      "@pypi//importlib_resources",
      ],
      )

      Note how absl_py is clearly a transitive dependency of etils, as can be seen from the compiled requirements.txt. I wonder if I just misunderstand what the documentation means about extras. It's unclear what the useful_dep in the documentation example is because I don't see any reference to useful_dep "in the example above" in the documentation.

      🔬 Minimal Reproduction

      See full reproduction here: main...hartikainen:rules_python:etils

      🔥 Exception or Error

      See Above

      🌍 Your Environment

      Operating System:

      $lsb_release -aNo LSB modules are available.Distributor ID: UbuntuDescription: Ubuntu 24.04.1 LTSRelease: 24.04Codename: noble

      Output of bazel version:

      $ bazel --versionbazel 8.2.1

      Rules_python version:

      43a5acf

      Anything else relevant?

      N/A

      Metadata

      Metadata

      Assignees

      No one assigned

        Labels

        No labels
        No labels

        Type

        No type

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
          Skip to content

          Some transitive "extras" dependencies are not automatically included to the target #3352

          Description

          @hartikainen

          🐞 bug report

          Affected Rule

          py_{test,binary,library}

          Is this a regression?

          I'm not sure. I think things work differently between venvs_site_packages=yes and venvs_site_packages=no. I started seeing this issue when switching over to venvs_site_packages=yes.

          Description

          The "Using PyPI" section in rules_python-documentation says:

          ‘Extras’ dependencies
          Any “extras” specified in the requirements lock file will be automatically added as transitive dependencies of the package. In the example above, you’d just put requirement("useful_dep") or @pypi//useful_dep.

          After switching to using venvs_site_packages=yes, I see that the above is not the case for all packages. For example, consider the following simple requirements.in:

          etils[eapp,epath]
          

          and its compiled requirements.txt as follows:

          Details
          # This file was autogenerated by uv via the following command:
          # uv pip compile --no-strip-extras --generate-hashes --emit-index-url ./requirements.in -o ./requirements.txt
          --index-url https://pypi.org/simple
          absl-py==2.3.1 \
          --hash=sha256:a97820526f7fbfd2ec1bce83f3f25e3a14840dac0d8e02a0b71cd75db3f77fc9 \
          --hash=sha256:eeecf07f0c2a93ace0772c92e596ace6d3d3996c042b2128459aaae2a76de11d
          # via etils
          docstring-parser==0.17.0 \
          --hash=sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912 \
          --hash=sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708
          # via simple-parsing
          etils[eapp, epath]==1.13.0 \
          --hash=sha256:a5b60c71f95bcd2d43d4e9fb3dc3879120c1f60472bb5ce19f7a860b1d44f607 \
          --hash=sha256:d9cd4f40fbe77ad6613b7348a18132cc511237b6c076dbb89105c0b520a4c6bb
          # via -r ./requirements.in
          fsspec==2025.9.0 \
          --hash=sha256:19fd429483d25d28b65ec68f9f4adc16c17ea2c7c7bf54ec61360d478fb19c19 \
          --hash=sha256:530dc2a2af60a414a832059574df4a6e10cce927f6f4a78209390fe38955cfb7
          # via etils
          importlib-resources==6.5.2 \
          --hash=sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c \
          --hash=sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec
          # via etils
          simple-parsing==0.1.7 \
          --hash=sha256:225e6b35252d68f7894716101fe3bd7e6dd3d30ab7b1c3c023f77a42dbe1336f \
          --hash=sha256:5276e6c90c157362dd0173d1eecebe58361a66b457129cc9bba13b78a4e85092
          # via etils
          typing-extensions==4.15.0 \
          --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \
          --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548
          # via
          # etils
          # simple-parsing
          zipp==3.23.0 \
          --hash=sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e \
          --hash=sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166
          # via etils
          

          Based on the documentation referred to above, I'd expect the following py_test target to run without problems:

          Details
          load("@rules_python//python:defs.bzl", "py_test")
          py_test(
          name="etils_test",
          srcs= ["etils_test.py"],
          deps= [
          "@pypi//etils",
          ],
          )

          where etils_test.py is defined as:

          frometilsimporteappfrometilsimportepatheapp.better_logging()

          However, I get the following error:

          Details
          bazel run \ --verbose_failures \ //:etils_testINFO: Analyzed target //:etils_test (0 packages loaded, 0 targets configured).INFO: Found 1 target...Target //:etils_test up-to-date: bazel-bin/etils_testINFO: Elapsed time: 0.133s, Critical Path: 0.00sINFO: 1 process: 1 internal.INFO: Build completed successfully, 1 total actionINFO: Running command line: external/bazel_tools/tools/test/test-setup.sh ./etils_testexec ${PAGER:-/usr/bin/less} "$0" || exit 1Executing tests from //:etils_test-----------------------------------------------------------------------------Traceback (most recent call last): File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test_stage2_bootstrap.py", line 499, in <module> main() File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test_stage2_bootstrap.py", line 493, in main _run_py_path(main_filename, args=sys.argv[1:]) File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test_stage2_bootstrap.py", line 287, in _run_py_path runpy.run_path(main_filename, run_name="__main__") File "<frozen runpy>", line 287, in run_path File "<frozen runpy>", line 98, in _run_module_code File "<frozen runpy>", line 88, in _run_code File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/etils_test.py", line 1, in <module> from etils import eapp File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/eapp/__init__.py", line 17, in <module> from etils.eapp.dataclass_flags import make_flags_parser File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/eapp/dataclass_flags.py", line 25, in <module> with _internal.check_missing_deps(): ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/external/rules_python++python+python_3_12_x86_64-unknown-linux-gnu/lib/python3.12/contextlib.py", line 158, in __exit__ self.gen.throw(value) File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/epy/_internal.py", line 48, in check_missing_deps reraise_utils.reraise( File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/epy/reraise_utils.py", line 112, in reraise raise new_exception.with_traceback(e.__traceback__) from e.__cause__ File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/epy/_internal.py", line 46, in check_missing_deps yield File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/eapp/dataclass_flags.py", line 27, in <module> from absl import flagsModuleNotFoundError: No module named 'absl'Each etils sub-modules require deps to be installed separately (e.g. `from etils import ecolab` -> `pip install etils[ecolab]`)

          To make this target work, I need to add several transitive dependency packages to the rule manually. The following target works:

          load("@rules_python//python:defs.bzl", "py_test")
          py_test(
          name="etils_test",
          srcs= ["etils_test.py"],
          deps= [
          "@pypi//absl_py",
          "@pypi//etils",
          "@pypi//simple_parsing",
          "@pypi//importlib_resources",
          ],
          )

          Note how absl_py is clearly a transitive dependency of etils, as can be seen from the compiled requirements.txt. I wonder if I just misunderstand what the documentation means about extras. It's unclear what the useful_dep in the documentation example is because I don't see any reference to useful_dep "in the example above" in the documentation.

          🔬 Minimal Reproduction

          See full reproduction here: main...hartikainen:rules_python:etils

          🔥 Exception or Error

          See Above

          🌍 Your Environment

          Operating System:

          $lsb_release -aNo LSB modules are available.Distributor ID: UbuntuDescription: Ubuntu 24.04.1 LTSRelease: 24.04Codename: noble

          Output of bazel version:

          $ bazel --versionbazel 8.2.1

          Rules_python version:

          43a5acf

          Anything else relevant?

          N/A

          Metadata

          Metadata

          Assignees

          No one assigned

            Labels

            No labels
            No labels

            Type

            No type

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              Some transitive "extras" dependencies are not automatically included to the target #3352

              Description

              @hartikainen

              🐞 bug report

              Affected Rule

              py_{test,binary,library}

              Is this a regression?

              I'm not sure. I think things work differently between venvs_site_packages=yes and venvs_site_packages=no. I started seeing this issue when switching over to venvs_site_packages=yes.

              Description

              The "Using PyPI" section in rules_python-documentation says:

              ‘Extras’ dependencies
              Any “extras” specified in the requirements lock file will be automatically added as transitive dependencies of the package. In the example above, you’d just put requirement("useful_dep") or @pypi//useful_dep.

              After switching to using venvs_site_packages=yes, I see that the above is not the case for all packages. For example, consider the following simple requirements.in:

              etils[eapp,epath]
              

              and its compiled requirements.txt as follows:

              Details
              # This file was autogenerated by uv via the following command:
              # uv pip compile --no-strip-extras --generate-hashes --emit-index-url ./requirements.in -o ./requirements.txt
              --index-url https://pypi.org/simple
              absl-py==2.3.1 \
              --hash=sha256:a97820526f7fbfd2ec1bce83f3f25e3a14840dac0d8e02a0b71cd75db3f77fc9 \
              --hash=sha256:eeecf07f0c2a93ace0772c92e596ace6d3d3996c042b2128459aaae2a76de11d
              # via etils
              docstring-parser==0.17.0 \
              --hash=sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912 \
              --hash=sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708
              # via simple-parsing
              etils[eapp, epath]==1.13.0 \
              --hash=sha256:a5b60c71f95bcd2d43d4e9fb3dc3879120c1f60472bb5ce19f7a860b1d44f607 \
              --hash=sha256:d9cd4f40fbe77ad6613b7348a18132cc511237b6c076dbb89105c0b520a4c6bb
              # via -r ./requirements.in
              fsspec==2025.9.0 \
              --hash=sha256:19fd429483d25d28b65ec68f9f4adc16c17ea2c7c7bf54ec61360d478fb19c19 \
              --hash=sha256:530dc2a2af60a414a832059574df4a6e10cce927f6f4a78209390fe38955cfb7
              # via etils
              importlib-resources==6.5.2 \
              --hash=sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c \
              --hash=sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec
              # via etils
              simple-parsing==0.1.7 \
              --hash=sha256:225e6b35252d68f7894716101fe3bd7e6dd3d30ab7b1c3c023f77a42dbe1336f \
              --hash=sha256:5276e6c90c157362dd0173d1eecebe58361a66b457129cc9bba13b78a4e85092
              # via etils
              typing-extensions==4.15.0 \
              --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \
              --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548
              # via
              # etils
              # simple-parsing
              zipp==3.23.0 \
              --hash=sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e \
              --hash=sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166
              # via etils
              

              Based on the documentation referred to above, I'd expect the following py_test target to run without problems:

              Details
              load("@rules_python//python:defs.bzl", "py_test")
              py_test(
              name="etils_test",
              srcs= ["etils_test.py"],
              deps= [
              "@pypi//etils",
              ],
              )

              where etils_test.py is defined as:

              frometilsimporteappfrometilsimportepatheapp.better_logging()

              However, I get the following error:

              Details
              bazel run \ --verbose_failures \ //:etils_testINFO: Analyzed target //:etils_test (0 packages loaded, 0 targets configured).INFO: Found 1 target...Target //:etils_test up-to-date: bazel-bin/etils_testINFO: Elapsed time: 0.133s, Critical Path: 0.00sINFO: 1 process: 1 internal.INFO: Build completed successfully, 1 total actionINFO: Running command line: external/bazel_tools/tools/test/test-setup.sh ./etils_testexec ${PAGER:-/usr/bin/less} "$0" || exit 1Executing tests from //:etils_test-----------------------------------------------------------------------------Traceback (most recent call last): File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test_stage2_bootstrap.py", line 499, in <module> main() File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test_stage2_bootstrap.py", line 493, in main _run_py_path(main_filename, args=sys.argv[1:]) File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test_stage2_bootstrap.py", line 287, in _run_py_path runpy.run_path(main_filename, run_name="__main__") File "<frozen runpy>", line 287, in run_path File "<frozen runpy>", line 98, in _run_module_code File "<frozen runpy>", line 88, in _run_code File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/etils_test.py", line 1, in <module> from etils import eapp File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/eapp/__init__.py", line 17, in <module> from etils.eapp.dataclass_flags import make_flags_parser File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/eapp/dataclass_flags.py", line 25, in <module> with _internal.check_missing_deps(): ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/external/rules_python++python+python_3_12_x86_64-unknown-linux-gnu/lib/python3.12/contextlib.py", line 158, in __exit__ self.gen.throw(value) File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/epy/_internal.py", line 48, in check_missing_deps reraise_utils.reraise( File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/epy/reraise_utils.py", line 112, in reraise raise new_exception.with_traceback(e.__traceback__) from e.__cause__ File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/epy/_internal.py", line 46, in check_missing_deps yield File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/eapp/dataclass_flags.py", line 27, in <module> from absl import flagsModuleNotFoundError: No module named 'absl'Each etils sub-modules require deps to be installed separately (e.g. `from etils import ecolab` -> `pip install etils[ecolab]`)

              To make this target work, I need to add several transitive dependency packages to the rule manually. The following target works:

              load("@rules_python//python:defs.bzl", "py_test")
              py_test(
              name="etils_test",
              srcs= ["etils_test.py"],
              deps= [
              "@pypi//absl_py",
              "@pypi//etils",
              "@pypi//simple_parsing",
              "@pypi//importlib_resources",
              ],
              )

              Note how absl_py is clearly a transitive dependency of etils, as can be seen from the compiled requirements.txt. I wonder if I just misunderstand what the documentation means about extras. It's unclear what the useful_dep in the documentation example is because I don't see any reference to useful_dep "in the example above" in the documentation.

              🔬 Minimal Reproduction

              See full reproduction here: main...hartikainen:rules_python:etils

              🔥 Exception or Error

              See Above

              🌍 Your Environment

              Operating System:

              $lsb_release -aNo LSB modules are available.Distributor ID: UbuntuDescription: Ubuntu 24.04.1 LTSRelease: 24.04Codename: noble

              Output of bazel version:

              $ bazel --versionbazel 8.2.1

              Rules_python version:

              43a5acf

              Anything else relevant?

              N/A

              Metadata

              Metadata

              Assignees

              No one assigned

                Labels

                No labels
                No labels

                Type

                No type

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions

                  , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
                  Skip to content

                  Some transitive "extras" dependencies are not automatically included to the target #3352

                  Description

                  @hartikainen

                  🐞 bug report

                  Affected Rule

                  py_{test,binary,library}

                  Is this a regression?

                  I'm not sure. I think things work differently between venvs_site_packages=yes and venvs_site_packages=no. I started seeing this issue when switching over to venvs_site_packages=yes.

                  Description

                  The "Using PyPI" section in rules_python-documentation says:

                  ‘Extras’ dependencies
                  Any “extras” specified in the requirements lock file will be automatically added as transitive dependencies of the package. In the example above, you’d just put requirement("useful_dep") or @pypi//useful_dep.

                  After switching to using venvs_site_packages=yes, I see that the above is not the case for all packages. For example, consider the following simple requirements.in:

                  etils[eapp,epath]
                  

                  and its compiled requirements.txt as follows:

                  Details
                  # This file was autogenerated by uv via the following command:
                  # uv pip compile --no-strip-extras --generate-hashes --emit-index-url ./requirements.in -o ./requirements.txt
                  --index-url https://pypi.org/simple
                  absl-py==2.3.1 \
                  --hash=sha256:a97820526f7fbfd2ec1bce83f3f25e3a14840dac0d8e02a0b71cd75db3f77fc9 \
                  --hash=sha256:eeecf07f0c2a93ace0772c92e596ace6d3d3996c042b2128459aaae2a76de11d
                  # via etils
                  docstring-parser==0.17.0 \
                  --hash=sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912 \
                  --hash=sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708
                  # via simple-parsing
                  etils[eapp, epath]==1.13.0 \
                  --hash=sha256:a5b60c71f95bcd2d43d4e9fb3dc3879120c1f60472bb5ce19f7a860b1d44f607 \
                  --hash=sha256:d9cd4f40fbe77ad6613b7348a18132cc511237b6c076dbb89105c0b520a4c6bb
                  # via -r ./requirements.in
                  fsspec==2025.9.0 \
                  --hash=sha256:19fd429483d25d28b65ec68f9f4adc16c17ea2c7c7bf54ec61360d478fb19c19 \
                  --hash=sha256:530dc2a2af60a414a832059574df4a6e10cce927f6f4a78209390fe38955cfb7
                  # via etils
                  importlib-resources==6.5.2 \
                  --hash=sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c \
                  --hash=sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec
                  # via etils
                  simple-parsing==0.1.7 \
                  --hash=sha256:225e6b35252d68f7894716101fe3bd7e6dd3d30ab7b1c3c023f77a42dbe1336f \
                  --hash=sha256:5276e6c90c157362dd0173d1eecebe58361a66b457129cc9bba13b78a4e85092
                  # via etils
                  typing-extensions==4.15.0 \
                  --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \
                  --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548
                  # via
                  # etils
                  # simple-parsing
                  zipp==3.23.0 \
                  --hash=sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e \
                  --hash=sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166
                  # via etils
                  

                  Based on the documentation referred to above, I'd expect the following py_test target to run without problems:

                  Details
                  load("@rules_python//python:defs.bzl", "py_test")
                  py_test(
                  name="etils_test",
                  srcs= ["etils_test.py"],
                  deps= [
                  "@pypi//etils",
                  ],
                  )

                  where etils_test.py is defined as:

                  frometilsimporteappfrometilsimportepatheapp.better_logging()

                  However, I get the following error:

                  Details
                  bazel run \ --verbose_failures \ //:etils_testINFO: Analyzed target //:etils_test (0 packages loaded, 0 targets configured).INFO: Found 1 target...Target //:etils_test up-to-date: bazel-bin/etils_testINFO: Elapsed time: 0.133s, Critical Path: 0.00sINFO: 1 process: 1 internal.INFO: Build completed successfully, 1 total actionINFO: Running command line: external/bazel_tools/tools/test/test-setup.sh ./etils_testexec ${PAGER:-/usr/bin/less} "$0" || exit 1Executing tests from //:etils_test-----------------------------------------------------------------------------Traceback (most recent call last): File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test_stage2_bootstrap.py", line 499, in <module> main() File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test_stage2_bootstrap.py", line 493, in main _run_py_path(main_filename, args=sys.argv[1:]) File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test_stage2_bootstrap.py", line 287, in _run_py_path runpy.run_path(main_filename, run_name="__main__") File "<frozen runpy>", line 287, in run_path File "<frozen runpy>", line 98, in _run_module_code File "<frozen runpy>", line 88, in _run_code File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/etils_test.py", line 1, in <module> from etils import eapp File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/eapp/__init__.py", line 17, in <module> from etils.eapp.dataclass_flags import make_flags_parser File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/eapp/dataclass_flags.py", line 25, in <module> with _internal.check_missing_deps(): ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/external/rules_python++python+python_3_12_x86_64-unknown-linux-gnu/lib/python3.12/contextlib.py", line 158, in __exit__ self.gen.throw(value) File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/epy/_internal.py", line 48, in check_missing_deps reraise_utils.reraise( File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/epy/reraise_utils.py", line 112, in reraise raise new_exception.with_traceback(e.__traceback__) from e.__cause__ File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/epy/_internal.py", line 46, in check_missing_deps yield File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/eapp/dataclass_flags.py", line 27, in <module> from absl import flagsModuleNotFoundError: No module named 'absl'Each etils sub-modules require deps to be installed separately (e.g. `from etils import ecolab` -> `pip install etils[ecolab]`)

                  To make this target work, I need to add several transitive dependency packages to the rule manually. The following target works:

                  load("@rules_python//python:defs.bzl", "py_test")
                  py_test(
                  name="etils_test",
                  srcs= ["etils_test.py"],
                  deps= [
                  "@pypi//absl_py",
                  "@pypi//etils",
                  "@pypi//simple_parsing",
                  "@pypi//importlib_resources",
                  ],
                  )

                  Note how absl_py is clearly a transitive dependency of etils, as can be seen from the compiled requirements.txt. I wonder if I just misunderstand what the documentation means about extras. It's unclear what the useful_dep in the documentation example is because I don't see any reference to useful_dep "in the example above" in the documentation.

                  🔬 Minimal Reproduction

                  See full reproduction here: main...hartikainen:rules_python:etils

                  🔥 Exception or Error

                  See Above

                  🌍 Your Environment

                  Operating System:

                  $lsb_release -aNo LSB modules are available.Distributor ID: UbuntuDescription: Ubuntu 24.04.1 LTSRelease: 24.04Codename: noble

                  Output of bazel version:

                  $ bazel --versionbazel 8.2.1

                  Rules_python version:

                  43a5acf

                  Anything else relevant?

                  N/A

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Labels

                    No labels
                    No labels

                    Type

                    No type

                    Projects

                    No projects

                      Milestone

                      No milestone

                      Relationships

                      None yet

                      Development

                      No branches or pull requests

                      Issue actions

                      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                      Skip to content

                      Some transitive "extras" dependencies are not automatically included to the target #3352

                      Description

                      @hartikainen

                      🐞 bug report

                      Affected Rule

                      py_{test,binary,library}

                      Is this a regression?

                      I'm not sure. I think things work differently between venvs_site_packages=yes and venvs_site_packages=no. I started seeing this issue when switching over to venvs_site_packages=yes.

                      Description

                      The "Using PyPI" section in rules_python-documentation says:

                      ‘Extras’ dependencies
                      Any “extras” specified in the requirements lock file will be automatically added as transitive dependencies of the package. In the example above, you’d just put requirement("useful_dep") or @pypi//useful_dep.

                      After switching to using venvs_site_packages=yes, I see that the above is not the case for all packages. For example, consider the following simple requirements.in:

                      etils[eapp,epath]
                      

                      and its compiled requirements.txt as follows:

                      Details
                      # This file was autogenerated by uv via the following command:
                      # uv pip compile --no-strip-extras --generate-hashes --emit-index-url ./requirements.in -o ./requirements.txt
                      --index-url https://pypi.org/simple
                      absl-py==2.3.1 \
                      --hash=sha256:a97820526f7fbfd2ec1bce83f3f25e3a14840dac0d8e02a0b71cd75db3f77fc9 \
                      --hash=sha256:eeecf07f0c2a93ace0772c92e596ace6d3d3996c042b2128459aaae2a76de11d
                      # via etils
                      docstring-parser==0.17.0 \
                      --hash=sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912 \
                      --hash=sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708
                      # via simple-parsing
                      etils[eapp, epath]==1.13.0 \
                      --hash=sha256:a5b60c71f95bcd2d43d4e9fb3dc3879120c1f60472bb5ce19f7a860b1d44f607 \
                      --hash=sha256:d9cd4f40fbe77ad6613b7348a18132cc511237b6c076dbb89105c0b520a4c6bb
                      # via -r ./requirements.in
                      fsspec==2025.9.0 \
                      --hash=sha256:19fd429483d25d28b65ec68f9f4adc16c17ea2c7c7bf54ec61360d478fb19c19 \
                      --hash=sha256:530dc2a2af60a414a832059574df4a6e10cce927f6f4a78209390fe38955cfb7
                      # via etils
                      importlib-resources==6.5.2 \
                      --hash=sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c \
                      --hash=sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec
                      # via etils
                      simple-parsing==0.1.7 \
                      --hash=sha256:225e6b35252d68f7894716101fe3bd7e6dd3d30ab7b1c3c023f77a42dbe1336f \
                      --hash=sha256:5276e6c90c157362dd0173d1eecebe58361a66b457129cc9bba13b78a4e85092
                      # via etils
                      typing-extensions==4.15.0 \
                      --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \
                      --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548
                      # via
                      # etils
                      # simple-parsing
                      zipp==3.23.0 \
                      --hash=sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e \
                      --hash=sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166
                      # via etils
                      

                      Based on the documentation referred to above, I'd expect the following py_test target to run without problems:

                      Details
                      load("@rules_python//python:defs.bzl", "py_test")
                      py_test(
                      name="etils_test",
                      srcs= ["etils_test.py"],
                      deps= [
                      "@pypi//etils",
                      ],
                      )

                      where etils_test.py is defined as:

                      frometilsimporteappfrometilsimportepatheapp.better_logging()

                      However, I get the following error:

                      Details
                      bazel run \ --verbose_failures \ //:etils_testINFO: Analyzed target //:etils_test (0 packages loaded, 0 targets configured).INFO: Found 1 target...Target //:etils_test up-to-date: bazel-bin/etils_testINFO: Elapsed time: 0.133s, Critical Path: 0.00sINFO: 1 process: 1 internal.INFO: Build completed successfully, 1 total actionINFO: Running command line: external/bazel_tools/tools/test/test-setup.sh ./etils_testexec ${PAGER:-/usr/bin/less} "$0" || exit 1Executing tests from //:etils_test-----------------------------------------------------------------------------Traceback (most recent call last): File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test_stage2_bootstrap.py", line 499, in <module> main() File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test_stage2_bootstrap.py", line 493, in main _run_py_path(main_filename, args=sys.argv[1:]) File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test_stage2_bootstrap.py", line 287, in _run_py_path runpy.run_path(main_filename, run_name="__main__") File "<frozen runpy>", line 287, in run_path File "<frozen runpy>", line 98, in _run_module_code File "<frozen runpy>", line 88, in _run_code File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/etils_test.py", line 1, in <module> from etils import eapp File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/eapp/__init__.py", line 17, in <module> from etils.eapp.dataclass_flags import make_flags_parser File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/eapp/dataclass_flags.py", line 25, in <module> with _internal.check_missing_deps(): ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/external/rules_python++python+python_3_12_x86_64-unknown-linux-gnu/lib/python3.12/contextlib.py", line 158, in __exit__ self.gen.throw(value) File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/epy/_internal.py", line 48, in check_missing_deps reraise_utils.reraise( File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/epy/reraise_utils.py", line 112, in reraise raise new_exception.with_traceback(e.__traceback__) from e.__cause__ File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/epy/_internal.py", line 46, in check_missing_deps yield File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/eapp/dataclass_flags.py", line 27, in <module> from absl import flagsModuleNotFoundError: No module named 'absl'Each etils sub-modules require deps to be installed separately (e.g. `from etils import ecolab` -> `pip install etils[ecolab]`)

                      To make this target work, I need to add several transitive dependency packages to the rule manually. The following target works:

                      load("@rules_python//python:defs.bzl", "py_test")
                      py_test(
                      name="etils_test",
                      srcs= ["etils_test.py"],
                      deps= [
                      "@pypi//absl_py",
                      "@pypi//etils",
                      "@pypi//simple_parsing",
                      "@pypi//importlib_resources",
                      ],
                      )

                      Note how absl_py is clearly a transitive dependency of etils, as can be seen from the compiled requirements.txt. I wonder if I just misunderstand what the documentation means about extras. It's unclear what the useful_dep in the documentation example is because I don't see any reference to useful_dep "in the example above" in the documentation.

                      🔬 Minimal Reproduction

                      See full reproduction here: main...hartikainen:rules_python:etils

                      🔥 Exception or Error

                      See Above

                      🌍 Your Environment

                      Operating System:

                      $lsb_release -aNo LSB modules are available.Distributor ID: UbuntuDescription: Ubuntu 24.04.1 LTSRelease: 24.04Codename: noble

                      Output of bazel version:

                      $ bazel --versionbazel 8.2.1

                      Rules_python version:

                      43a5acf

                      Anything else relevant?

                      N/A

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Labels

                        No labels
                        No labels

                        Type

                        No type

                        Projects

                        No projects

                          Milestone

                          No milestone

                          Relationships

                          None yet

                          Development

                          No branches or pull requests

                          Issue actions

                          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                          Skip to content

                          Some transitive "extras" dependencies are not automatically included to the target #3352

                          Description

                          @hartikainen

                          🐞 bug report

                          Affected Rule

                          py_{test,binary,library}

                          Is this a regression?

                          I'm not sure. I think things work differently between venvs_site_packages=yes and venvs_site_packages=no. I started seeing this issue when switching over to venvs_site_packages=yes.

                          Description

                          The "Using PyPI" section in rules_python-documentation says:

                          ‘Extras’ dependencies
                          Any “extras” specified in the requirements lock file will be automatically added as transitive dependencies of the package. In the example above, you’d just put requirement("useful_dep") or @pypi//useful_dep.

                          After switching to using venvs_site_packages=yes, I see that the above is not the case for all packages. For example, consider the following simple requirements.in:

                          etils[eapp,epath]
                          

                          and its compiled requirements.txt as follows:

                          Details
                          # This file was autogenerated by uv via the following command:
                          # uv pip compile --no-strip-extras --generate-hashes --emit-index-url ./requirements.in -o ./requirements.txt
                          --index-url https://pypi.org/simple
                          absl-py==2.3.1 \
                          --hash=sha256:a97820526f7fbfd2ec1bce83f3f25e3a14840dac0d8e02a0b71cd75db3f77fc9 \
                          --hash=sha256:eeecf07f0c2a93ace0772c92e596ace6d3d3996c042b2128459aaae2a76de11d
                          # via etils
                          docstring-parser==0.17.0 \
                          --hash=sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912 \
                          --hash=sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708
                          # via simple-parsing
                          etils[eapp, epath]==1.13.0 \
                          --hash=sha256:a5b60c71f95bcd2d43d4e9fb3dc3879120c1f60472bb5ce19f7a860b1d44f607 \
                          --hash=sha256:d9cd4f40fbe77ad6613b7348a18132cc511237b6c076dbb89105c0b520a4c6bb
                          # via -r ./requirements.in
                          fsspec==2025.9.0 \
                          --hash=sha256:19fd429483d25d28b65ec68f9f4adc16c17ea2c7c7bf54ec61360d478fb19c19 \
                          --hash=sha256:530dc2a2af60a414a832059574df4a6e10cce927f6f4a78209390fe38955cfb7
                          # via etils
                          importlib-resources==6.5.2 \
                          --hash=sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c \
                          --hash=sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec
                          # via etils
                          simple-parsing==0.1.7 \
                          --hash=sha256:225e6b35252d68f7894716101fe3bd7e6dd3d30ab7b1c3c023f77a42dbe1336f \
                          --hash=sha256:5276e6c90c157362dd0173d1eecebe58361a66b457129cc9bba13b78a4e85092
                          # via etils
                          typing-extensions==4.15.0 \
                          --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \
                          --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548
                          # via
                          # etils
                          # simple-parsing
                          zipp==3.23.0 \
                          --hash=sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e \
                          --hash=sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166
                          # via etils
                          

                          Based on the documentation referred to above, I'd expect the following py_test target to run without problems:

                          Details
                          load("@rules_python//python:defs.bzl", "py_test")
                          py_test(
                          name="etils_test",
                          srcs= ["etils_test.py"],
                          deps= [
                          "@pypi//etils",
                          ],
                          )

                          where etils_test.py is defined as:

                          frometilsimporteappfrometilsimportepatheapp.better_logging()

                          However, I get the following error:

                          Details
                          bazel run \ --verbose_failures \ //:etils_testINFO: Analyzed target //:etils_test (0 packages loaded, 0 targets configured).INFO: Found 1 target...Target //:etils_test up-to-date: bazel-bin/etils_testINFO: Elapsed time: 0.133s, Critical Path: 0.00sINFO: 1 process: 1 internal.INFO: Build completed successfully, 1 total actionINFO: Running command line: external/bazel_tools/tools/test/test-setup.sh ./etils_testexec ${PAGER:-/usr/bin/less} "$0" || exit 1Executing tests from //:etils_test-----------------------------------------------------------------------------Traceback (most recent call last): File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test_stage2_bootstrap.py", line 499, in <module> main() File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test_stage2_bootstrap.py", line 493, in main _run_py_path(main_filename, args=sys.argv[1:]) File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test_stage2_bootstrap.py", line 287, in _run_py_path runpy.run_path(main_filename, run_name="__main__") File "<frozen runpy>", line 287, in run_path File "<frozen runpy>", line 98, in _run_module_code File "<frozen runpy>", line 88, in _run_code File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/etils_test.py", line 1, in <module> from etils import eapp File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/eapp/__init__.py", line 17, in <module> from etils.eapp.dataclass_flags import make_flags_parser File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/eapp/dataclass_flags.py", line 25, in <module> with _internal.check_missing_deps(): ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/external/rules_python++python+python_3_12_x86_64-unknown-linux-gnu/lib/python3.12/contextlib.py", line 158, in __exit__ self.gen.throw(value) File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/epy/_internal.py", line 48, in check_missing_deps reraise_utils.reraise( File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/epy/reraise_utils.py", line 112, in reraise raise new_exception.with_traceback(e.__traceback__) from e.__cause__ File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/epy/_internal.py", line 46, in check_missing_deps yield File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/eapp/dataclass_flags.py", line 27, in <module> from absl import flagsModuleNotFoundError: No module named 'absl'Each etils sub-modules require deps to be installed separately (e.g. `from etils import ecolab` -> `pip install etils[ecolab]`)

                          To make this target work, I need to add several transitive dependency packages to the rule manually. The following target works:

                          load("@rules_python//python:defs.bzl", "py_test")
                          py_test(
                          name="etils_test",
                          srcs= ["etils_test.py"],
                          deps= [
                          "@pypi//absl_py",
                          "@pypi//etils",
                          "@pypi//simple_parsing",
                          "@pypi//importlib_resources",
                          ],
                          )

                          Note how absl_py is clearly a transitive dependency of etils, as can be seen from the compiled requirements.txt. I wonder if I just misunderstand what the documentation means about extras. It's unclear what the useful_dep in the documentation example is because I don't see any reference to useful_dep "in the example above" in the documentation.

                          🔬 Minimal Reproduction

                          See full reproduction here: main...hartikainen:rules_python:etils

                          🔥 Exception or Error

                          See Above

                          🌍 Your Environment

                          Operating System:

                          $lsb_release -aNo LSB modules are available.Distributor ID: UbuntuDescription: Ubuntu 24.04.1 LTSRelease: 24.04Codename: noble

                          Output of bazel version:

                          $ bazel --versionbazel 8.2.1

                          Rules_python version:

                          43a5acf

                          Anything else relevant?

                          N/A

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Labels

                            No labels
                            No labels

                            Type

                            No type

                            Projects

                            No projects

                              Milestone

                              No milestone

                              Relationships

                              None yet

                              Development

                              No branches or pull requests

                              Issue actions

                              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                              Skip to content

                              Some transitive "extras" dependencies are not automatically included to the target #3352

                              Description

                              @hartikainen

                              🐞 bug report

                              Affected Rule

                              py_{test,binary,library}

                              Is this a regression?

                              I'm not sure. I think things work differently between venvs_site_packages=yes and venvs_site_packages=no. I started seeing this issue when switching over to venvs_site_packages=yes.

                              Description

                              The "Using PyPI" section in rules_python-documentation says:

                              ‘Extras’ dependencies
                              Any “extras” specified in the requirements lock file will be automatically added as transitive dependencies of the package. In the example above, you’d just put requirement("useful_dep") or @pypi//useful_dep.

                              After switching to using venvs_site_packages=yes, I see that the above is not the case for all packages. For example, consider the following simple requirements.in:

                              etils[eapp,epath]
                              

                              and its compiled requirements.txt as follows:

                              Details
                              # This file was autogenerated by uv via the following command:
                              # uv pip compile --no-strip-extras --generate-hashes --emit-index-url ./requirements.in -o ./requirements.txt
                              --index-url https://pypi.org/simple
                              absl-py==2.3.1 \
                              --hash=sha256:a97820526f7fbfd2ec1bce83f3f25e3a14840dac0d8e02a0b71cd75db3f77fc9 \
                              --hash=sha256:eeecf07f0c2a93ace0772c92e596ace6d3d3996c042b2128459aaae2a76de11d
                              # via etils
                              docstring-parser==0.17.0 \
                              --hash=sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912 \
                              --hash=sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708
                              # via simple-parsing
                              etils[eapp, epath]==1.13.0 \
                              --hash=sha256:a5b60c71f95bcd2d43d4e9fb3dc3879120c1f60472bb5ce19f7a860b1d44f607 \
                              --hash=sha256:d9cd4f40fbe77ad6613b7348a18132cc511237b6c076dbb89105c0b520a4c6bb
                              # via -r ./requirements.in
                              fsspec==2025.9.0 \
                              --hash=sha256:19fd429483d25d28b65ec68f9f4adc16c17ea2c7c7bf54ec61360d478fb19c19 \
                              --hash=sha256:530dc2a2af60a414a832059574df4a6e10cce927f6f4a78209390fe38955cfb7
                              # via etils
                              importlib-resources==6.5.2 \
                              --hash=sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c \
                              --hash=sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec
                              # via etils
                              simple-parsing==0.1.7 \
                              --hash=sha256:225e6b35252d68f7894716101fe3bd7e6dd3d30ab7b1c3c023f77a42dbe1336f \
                              --hash=sha256:5276e6c90c157362dd0173d1eecebe58361a66b457129cc9bba13b78a4e85092
                              # via etils
                              typing-extensions==4.15.0 \
                              --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \
                              --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548
                              # via
                              # etils
                              # simple-parsing
                              zipp==3.23.0 \
                              --hash=sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e \
                              --hash=sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166
                              # via etils
                              

                              Based on the documentation referred to above, I'd expect the following py_test target to run without problems:

                              Details
                              load("@rules_python//python:defs.bzl", "py_test")
                              py_test(
                              name="etils_test",
                              srcs= ["etils_test.py"],
                              deps= [
                              "@pypi//etils",
                              ],
                              )

                              where etils_test.py is defined as:

                              frometilsimporteappfrometilsimportepatheapp.better_logging()

                              However, I get the following error:

                              Details
                              bazel run \ --verbose_failures \ //:etils_testINFO: Analyzed target //:etils_test (0 packages loaded, 0 targets configured).INFO: Found 1 target...Target //:etils_test up-to-date: bazel-bin/etils_testINFO: Elapsed time: 0.133s, Critical Path: 0.00sINFO: 1 process: 1 internal.INFO: Build completed successfully, 1 total actionINFO: Running command line: external/bazel_tools/tools/test/test-setup.sh ./etils_testexec ${PAGER:-/usr/bin/less} "$0" || exit 1Executing tests from //:etils_test-----------------------------------------------------------------------------Traceback (most recent call last): File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test_stage2_bootstrap.py", line 499, in <module> main() File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test_stage2_bootstrap.py", line 493, in main _run_py_path(main_filename, args=sys.argv[1:]) File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test_stage2_bootstrap.py", line 287, in _run_py_path runpy.run_path(main_filename, run_name="__main__") File "<frozen runpy>", line 287, in run_path File "<frozen runpy>", line 98, in _run_module_code File "<frozen runpy>", line 88, in _run_code File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/etils_test.py", line 1, in <module> from etils import eapp File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/eapp/__init__.py", line 17, in <module> from etils.eapp.dataclass_flags import make_flags_parser File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/eapp/dataclass_flags.py", line 25, in <module> with _internal.check_missing_deps(): ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/external/rules_python++python+python_3_12_x86_64-unknown-linux-gnu/lib/python3.12/contextlib.py", line 158, in __exit__ self.gen.throw(value) File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/epy/_internal.py", line 48, in check_missing_deps reraise_utils.reraise( File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/epy/reraise_utils.py", line 112, in reraise raise new_exception.with_traceback(e.__traceback__) from e.__cause__ File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/epy/_internal.py", line 46, in check_missing_deps yield File "/home/user/.cache/bazel/_bazel_user/0edd9a2ca9426ffe30008363aa919a47/execroot/_main/bazel-out/k8-fastbuild/bin/etils_test.runfiles/_main/_etils_test.venv/lib/python3.12/site-packages/etils/eapp/dataclass_flags.py", line 27, in <module> from absl import flagsModuleNotFoundError: No module named 'absl'Each etils sub-modules require deps to be installed separately (e.g. `from etils import ecolab` -> `pip install etils[ecolab]`)

                              To make this target work, I need to add several transitive dependency packages to the rule manually. The following target works:

                              load("@rules_python//python:defs.bzl", "py_test")
                              py_test(
                              name="etils_test",
                              srcs= ["etils_test.py"],
                              deps= [
                              "@pypi//absl_py",
                              "@pypi//etils",
                              "@pypi//simple_parsing",
                              "@pypi//importlib_resources",
                              ],
                              )

                              Note how absl_py is clearly a transitive dependency of etils, as can be seen from the compiled requirements.txt. I wonder if I just misunderstand what the documentation means about extras. It's unclear what the useful_dep in the documentation example is because I don't see any reference to useful_dep "in the example above" in the documentation.

                              🔬 Minimal Reproduction

                              See full reproduction here: main...hartikainen:rules_python:etils

                              🔥 Exception or Error

                              See Above

                              🌍 Your Environment

                              Operating System:

                              $lsb_release -aNo LSB modules are available.Distributor ID: UbuntuDescription: Ubuntu 24.04.1 LTSRelease: 24.04Codename: noble

                              Output of bazel version:

                              $ bazel --versionbazel 8.2.1

                              Rules_python version:

                              43a5acf

                              Anything else relevant?

                              N/A

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Labels

                                No labels
                                No labels

                                Type

                                No type

                                Projects

                                No projects

                                  Milestone

                                  No milestone

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions