The Wayback Machine - https://web.archive.org/web/20210104142423/https://github.com/bazelbuild/rules_python/issues/380
Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

bazel test can't import `requests` package (with 0.1.0 pip_install rule) #380

Open
chartinmarrel opened this issue Nov 12, 2020 · 5 comments
Open

Comments

@chartinmarrel
Copy link

@chartinmarrel chartinmarrel commented Nov 12, 2020

Affected Rule

The issue is caused by the rule: pip_install

Is this a regression?

It is specific to the new way (v0.1.0) of install pip dependencies

Description

Followed 0.1.0 migration guide and tried it with a simple python package that has the python requirement requests.
When I run bazel test //... it fails with errors:

[...]
======================================================================
ERROR: external.pip.pypi__requests.requests-2.25.0 (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: external.pip.pypi__requests.requests-2.25.0
Traceback (most recent call last):
  File "/usr/local/Cellar/[email protected]/3.9.0_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/unittest/loader.py", line 470, in _find_test_path
    package = self._get_module_from_name(name)
  File "/usr/local/Cellar/[email protected]/3.9.0_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/unittest/loader.py", line 377, in _get_module_from_name
    __import__(name)
ModuleNotFoundError: No module named 'external.pip.pypi__requests.requests-2'
[...]

🔬 Minimal Reproduction

I forked the repo to host my example code.

git clone [email protected]:chartinmarrel/rules_python.git
cd rules_python/examples/pip_install/my_example
bazel test //... --test_output=errors

If I run bazel build //... and then bazel-bin/test_main, the test succeed with no error.

🔥 Exception or Error


======================================================================
ERROR: external.pip.pypi__certifi.certifi-2020.11.8 (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: external.pip.pypi__certifi.certifi-2020.11.8
Traceback (most recent call last):
  File "/usr/local/Cellar/[email protected]/3.9.0_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/unittest/loader.py", line 470, in _find_test_path
    package = self._get_module_from_name(name)
  File "/usr/local/Cellar/[email protected]/3.9.0_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/unittest/loader.py", line 377, in _get_module_from_name
    __import__(name)
ModuleNotFoundError: No module named 'external.pip.pypi__certifi.certifi-2020'


======================================================================
ERROR: external.pip.pypi__chardet.chardet-3.0.4 (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: external.pip.pypi__chardet.chardet-3.0.4
Traceback (most recent call last):
  File "/usr/local/Cellar/[email protected]/3.9.0_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/unittest/loader.py", line 470, in _find_test_path
    package = self._get_module_from_name(name)
  File "/usr/local/Cellar/[email protected]/3.9.0_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/unittest/loader.py", line 377, in _get_module_from_name
    __import__(name)
ModuleNotFoundError: No module named 'external.pip.pypi__chardet.chardet-3'


======================================================================
ERROR: external.pip.pypi__idna.idna-2.10 (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: external.pip.pypi__idna.idna-2.10
Traceback (most recent call last):
  File "/usr/local/Cellar/[email protected]/3.9.0_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/unittest/loader.py", line 470, in _find_test_path
    package = self._get_module_from_name(name)
  File "/usr/local/Cellar/[email protected]/3.9.0_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/unittest/loader.py", line 377, in _get_module_from_name
    __import__(name)
ModuleNotFoundError: No module named 'external.pip.pypi__idna.idna-2'


======================================================================
ERROR: external.pip.pypi__requests.requests-2.25.0 (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: external.pip.pypi__requests.requests-2.25.0
Traceback (most recent call last):
  File "/usr/local/Cellar/[email protected]/3.9.0_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/unittest/loader.py", line 470, in _find_test_path
    package = self._get_module_from_name(name)
  File "/usr/local/Cellar/[email protected]/3.9.0_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/unittest/loader.py", line 377, in _get_module_from_name
    __import__(name)
ModuleNotFoundError: No module named 'external.pip.pypi__requests.requests-2'


======================================================================
ERROR: external.pip.pypi__urllib3.urllib3-1.26.1 (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: external.pip.pypi__urllib3.urllib3-1.26.1
Traceback (most recent call last):
  File "/usr/local/Cellar/[email protected]/3.9.0_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/unittest/loader.py", line 470, in _find_test_path
    package = self._get_module_from_name(name)
  File "/usr/local/Cellar/[email protected]/3.9.0_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/unittest/loader.py", line 377, in _get_module_from_name
    __import__(name)
ModuleNotFoundError: No module named 'external.pip.pypi__urllib3.urllib3-1'

🌍 Your Environment

Operating System:

macOS Catalina 10.15.7

Output of bazel version:

Build label: 3.7.0
Build target: bazel-out/darwin-opt/bin/src/main/java/com/google/devtools/build/lib/bazel/BazelServer_deploy.jar
Build time: Tue Oct 20 13:31:07 2020 (1603200667)
Build timestamp: 1603200667
Build timestamp as int: 1603200667

Rules_python version:

0.1.0

Anything else relevant?

@thundergolfer
Copy link
Collaborator

@thundergolfer thundergolfer commented Nov 19, 2020

Hey @chartinmarrel, firstly thanks for the reproduction. Made it much easier to follow up your issue.

Your problem here is not particular to the requests package. The problem comes from the way you're invoking the unittest module.

I made this change in your test_main.py:

if __name__ == '__main__':
    # loader = unittest.TestLoader()
    # suite = loader.discover(".")
    # runner = unittest.TextTestRunner()
    # sys.exit(not runner.run(suite).wasSuccessful())
    unittest.main()

and the tests pass. When I look in bazel-my_example/external/pip/ it looks to me that the pip_install has behaved correctly on your requirements.txt file. It's something about suite = loader.discover(".") that's causing issues, because the "." is likely 'escaping' the py_test execution sandbox and running into trouble.

Is there a particular reason you're using unittest's test discovery? It's behaviour it not compatible with Bazel. py_test targets should be a self-contained execution, and not call programs that explore Bazel's execution root to find other programs to run.

@thundergolfer
Copy link
Collaborator

@thundergolfer thundergolfer commented Nov 19, 2020

As a bit of extra detail, If we look at this line: ModuleNotFoundError: No module named 'external.pip.pypi__urllib3.urllib3-1'

What this shows is that it's tried to import a Python file under bazel-my_example/external/pip/pypi__urllib3/urllib3-1.26.2.dist-info/ and has converted that path to bazel-my_example.external.pip.pypi__urllib3.urllib3-1.26.2.dist-info, which mishandles the period characters.

@chartinmarrel
Copy link
Author

@chartinmarrel chartinmarrel commented Nov 19, 2020

Thanks for the explanation!
The reason I did not use unittest.main() is because it was failing to discover the tests when I run bazel test. Unless I import the test cases explicitly in the main test runner.
To reproduce this, I refactored my_example to show you the problem.
I have the following structure:

lib
  |__ BUILD.bazel
  |__ __init__.py
  |__ main.py
test
  |__ BUILD.bazel
  |__ __init__.py
  |__ entrypoint_one.py
  |__ entrypoint_two.py
  |__ test_hello.py
BUILD.bazel
requirements.txt
WORKSPACE

Here is the content of the test/BUILD.bazel file:

load("@rules_python//python:defs.bzl", "py_test")

py_test(
    name = "test1",
    visibility = ["//visibility:public"],
    main = "entrypoint_one.py",
    python_version = "PY3",
    srcs = ["entrypoint_one.py", "test_hello.py"],
    deps = ["//lib:main"],
)

py_test(
    name = "test2",
    visibility = ["//visibility:public"],
    main = "entrypoint_two.py",
    python_version = "PY3",
    srcs = ["entrypoint_two.py", "test_hello.py"],
    deps = ["//lib:main"],
)

entrypoint_one imports the test explicitly:

import unittest
from test.test_hello import TestHello

if __name__ == '__main__':
    unittest.main()

entrypoint_two counts on unittest to discover test_hello:

import unittest

if __name__ == '__main__':
    unittest.main()

When I run bazel test //test:test1 --test_output=all it shows that 1 test was successfully run.
When I run bazel test //test:test2 --test_output=all it shows that 0 test was run.

Is there another way to use py_test rules without importing explicitly the tests ?

@chartinmarrel
Copy link
Author

@chartinmarrel chartinmarrel commented Nov 19, 2020

Actually a colleague of mine noticed that I could use my initial code if I replace discover(".") by discover("tests_folder")
For example:

if __name__ == '__main__':
    loader = unittest.TestLoader()
    suite = loader.discover("tests")
    runner = unittest.TextTestRunner()
    sys.exit(not runner.run(suite).wasSuccessful())

This will not mess up with dependency resolution

@thundergolfer
Copy link
Collaborator

@thundergolfer thundergolfer commented Nov 20, 2020

it was failing to discover the tests when I run bazel test. Unless I import the test cases explicitly in the main test runner.

Yeah this is expected, as Bazel is not designed to discover and run tests that aren't statically defined as included in a _test target.

There's test_suite for grouping a number of test targets together. I would discourage using test discovery functionality to crawl a directory during Bazel test execution. Here's a Java example BUILD and a Python example BUILD from the Tensorflow codebase that shows a more standard way to write test targets.

Are you happy if I close this issue, as it's not a problem with the importing of requests?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
2 participants
You can’t perform that action at this time.