diff --git a/conans/client/graph/python_requires.py b/conans/client/graph/python_requires.py index 8f2576200df..78178c745d4 100644 --- a/conans/client/graph/python_requires.py +++ b/conans/client/graph/python_requires.py @@ -54,7 +54,12 @@ def __getitem__(self, item): try: return self._pyrequires[item] except KeyError: - raise ConanException("'%s' is not a python_require" % item) + # https://github.com/conan-io/conan/issues/8546 + # Transitive pyrequires are accessed by inheritance derived classes + try: + return self._transitive[item] + except KeyError: + raise ConanException("'%s' is not a python_require" % item) def __setitem__(self, key, value): # single item assignment, direct diff --git a/conans/test/integration/py_requires/python_requires_test.py b/conans/test/integration/py_requires/python_requires_test.py index 73bfa7f8ed1..be3aa71db1d 100644 --- a/conans/test/integration/py_requires/python_requires_test.py +++ b/conans/test/integration/py_requires/python_requires_test.py @@ -149,8 +149,8 @@ class MyConanfileBase(ConanFile): self.assertIn("Pkg/0.1@user/testing: My cool package!", client.out) self.assertIn("Pkg/0.1@user/testing: My cool package_info!", client.out) - def test_transitive_access_error(self): - # https://github.com/conan-io/conan/issues/5529 + @staticmethod + def test_transitive_access(): client = TestClient() client.save({"conanfile.py": GenConanfile()}) client.run("export . base/1.0@user/channel") @@ -171,18 +171,18 @@ def build(self): self.python_requires["base"] """) client.save({"conanfile.py": conanfile}) - client.run("create . pkg/0.1@user/channel", assert_error=True) - self.assertIn("'base' is not a python_require", client.out) + client.run("create . pkg/0.1@user/channel") + assert "pkg/0.1@user/channel: Created package" in client.out conanfile = textwrap.dedent(""" - from conans import ConanFile - class Pkg(ConanFile): - python_requires = "helper/1.0@user/channel" - python_requires_extend = "base.HelloConan" - """) + from conans import ConanFile + class Pkg(ConanFile): + python_requires = "helper/1.0@user/channel" + python_requires_extend = "base.HelloConan" + """) client.save({"conanfile.py": conanfile}) - client.run("create . pkg/0.1@user/channel", assert_error=True) - self.assertIn("'base' is not a python_require", client.out) + client.run("create . pkg/0.1@user/channel") + assert "pkg/0.1@user/channel: Created package" in client.out def test_multiple_requires_error(self): client = TestClient() @@ -853,3 +853,119 @@ def build_id(self): self.assertIn("Pkg/0.1@user/testing: My cool build!", client.out) self.assertIn("Pkg/0.1@user/testing: My cool package!", client.out) self.assertIn("Pkg/0.1@user/testing: My cool package_info!", client.out) + + +def test_transitive_python_requires(): + # https://github.com/conan-io/conan/issues/8546 + client = TestClient() + conanfile = textwrap.dedent(""" + from conans import ConanFile + myvar = 123 + def myfunct(): + return 234 + class SharedFunction(ConanFile): + name = "shared-function" + version = "1.0" + """) + client.save({"conanfile.py": conanfile}) + client.run("export . @user/channel") + + conanfile = textwrap.dedent(""" + from conans import ConanFile + class BaseClass(ConanFile): + name = "base-class" + version = "1.0" + python_requires = "shared-function/1.0@user/channel" + def build(self): + pyreqs = self.python_requires + v = pyreqs["shared-function"].module.myvar # v will be 123 + f = pyreqs["shared-function"].module.myfunct() # f will be 234 + self.output.info("%s, %s" % (v, f)) + """) + client.save({"conanfile.py": conanfile}) + client.run("export . user/channel") + + conanfile = textwrap.dedent(""" + from conans import ConanFile + class Consumer(ConanFile): + name = "consumer" + version = "1.0" + python_requires = "base-class/1.0@user/channel" + python_requires_extend = "base-class.BaseClass" + """) + client.save({"conanfile.py": conanfile}) + client.run("create . @user/channel") + assert "consumer/1.0@user/channel: Calling build()\nconsumer/1.0@user/channel: 123, 234" in \ + client.out + + +def test_transitive_diamond_python_requires(): + client = TestClient() + conanfile = textwrap.dedent(""" + from conans import ConanFile + myvar = 123 + def myfunct(): + return 234 + class SharedFunction(ConanFile): + name = "shared-function" + version = "1.0" + """) + client.save({"conanfile.py": conanfile}) + client.run("export . @user/channel") + + conanfile = textwrap.dedent(""" + from conans import ConanFile + myvar = 222 + def myfunct(): + return 2222 + class SharedFunction2(ConanFile): + name = "shared-function2" + version = "1.0" + """) + client.save({"conanfile.py": conanfile}) + client.run("export . @user/channel") + + conanfile = textwrap.dedent(""" + from conans import ConanFile + class BaseClass(ConanFile): + name = "base-class" + version = "1.0" + python_requires = "shared-function/1.0@user/channel", "shared-function2/1.0@user/channel" + def build(self): + pyreqs = self.python_requires + v = pyreqs["shared-function"].module.myvar # v will be 123 + f = pyreqs["shared-function2"].module.myfunct() # f will be 2222 + self.output.info("%s, %s" % (v, f)) + """) + client.save({"conanfile.py": conanfile}) + client.run("export . user/channel") + + conanfile = textwrap.dedent(""" + from conans import ConanFile + class BaseClass2(ConanFile): + name = "base-class2" + version = "1.0" + python_requires = "shared-function/1.0@user/channel", "shared-function2/1.0@user/channel" + def package(self): + pyreqs = self.python_requires + v = pyreqs["shared-function2"].module.myvar # v will be 222 + f = pyreqs["shared-function"].module.myfunct() # f will be 234 + self.output.info("%s, %s" % (v, f)) + """) + client.save({"conanfile.py": conanfile}) + client.run("export . user/channel") + + conanfile = textwrap.dedent(""" + from conans import ConanFile + class Consumer(ConanFile): + name = "consumer" + version = "1.0" + python_requires = "base-class/1.0@user/channel", "base-class2/1.0@user/channel" + python_requires_extend = "base-class.BaseClass", "base-class2.BaseClass2" + """) + client.save({"conanfile.py": conanfile}) + client.run("create . @user/channel") + assert "consumer/1.0@user/channel: Calling build()\nconsumer/1.0@user/channel: 123, 2222" in \ + client.out + assert "consumer/1.0@user/channel: Calling package()\nconsumer/1.0@user/channel: 222, 234" in \ + client.out