Repository: conan-io/conan

Issue:
Python reuse code in package_info() method fails importing module
To help us debug your issue please explain:

- [x] I've read the [CONTRIBUTING guide](https://raw.githubusercontent.com/conan-io/conan/develop/.github/CONTRIBUTING.md).
- [x] I've specified the Conan version, operating system version and any tool that can be relevant:
python 3.6.4, conan 1.1.1
- [x] I've explained the steps to reproduce the error or the motivation/use case of the question/suggestion.

*conanfile_HelloPythonConan.py*
```
from conans import ConanFile


class HelloPythonConan(ConanFile):
    name = "HelloPythonConan"
    version = "0.1"
    exports = "__init__.py", "hello.py"
    build_policy = "missing"

    def package(self):
        self.copy('*.py')

    def package_info(self):
        self.env_info.PYTHONPATH.append(self.package_folder)
```

*hello.py*
```
def hello():
    print("Hello World from Python!")
```

``__init__.py``
```
```

*conanfile_HelloPythonReuseConan.py*
```
from conans import ConanFile
import os


class HelloPythonReuseConan(ConanFile):
    name = "HelloPythonReuseConan"
    version = "0.1"
    requires = "HelloPythonConan/0.1@conan/testing"

    def package_info(self):
        from hello import hello
        hello()
```

**Steps to reproduce:**

```
$ conan export conanfile_HelloPythonConan.py conan/testing
$ conan create conanfile_HelloPythonReuseConan.py conan/testing
ERROR: HelloPythonReuseConan/0.1@conan/testing: Error in package_info() method, line 16
        from hello import hello
        ModuleNotFoundError: No module named 'hello'
```
---------
cc/ @kheaactua




Discussion:
I have excatly the same problem.

Resolving patch:
diff --git a/conans/client/installer.py b/conans/client/installer.py
--- a/conans/client/installer.py
+++ b/conans/client/installer.py
@@ -21,6 +21,7 @@
 from conans.util.tracer import log_package_built
 from conans.util.env_reader import get_env
 from conans.client.importer import remove_imports
+from conans.client.tools.env import pythonpath
 
 
 def build_id(conan_file):
@@ -440,13 +441,14 @@ def _call_package_info(conanfile, package_folder):
         conanfile.cpp_info.public_deps = public_deps
         # Once the node is build, execute package info, so it has access to the
         # package folder and artifacts
-        with tools.chdir(package_folder):
-            with conanfile_exception_formatter(str(conanfile), "package_info"):
-                conanfile.package_folder = package_folder
-                conanfile.source_folder = None
-                conanfile.build_folder = None
-                conanfile.install_folder = None
-                conanfile.package_info()
+        with pythonpath(conanfile):  # Minimal pythonpath, not the whole context, make it 50% slower
+            with tools.chdir(package_folder):
+                with conanfile_exception_formatter(str(conanfile), "package_info"):
+                    conanfile.package_folder = package_folder
+                    conanfile.source_folder = None
+                    conanfile.build_folder = None
+                    conanfile.install_folder = None
+                    conanfile.package_info()
 
     def _get_nodes(self, nodes_by_level, skip_nodes):
         """Compute a list of (conan_ref, package_id, conan_file, build_node)
diff --git a/conans/client/tools/env.py b/conans/client/tools/env.py
--- a/conans/client/tools/env.py
+++ b/conans/client/tools/env.py
@@ -9,16 +9,18 @@
 
 @contextmanager
 def pythonpath(conanfile):
-    old_path = sys.path[:]
     python_path = conanfile.env.get("PYTHONPATH", None)
     if python_path:
+        old_path = sys.path[:]
         if isinstance(python_path, list):
             sys.path.extend(python_path)
         else:
             sys.path.append(python_path)
 
-    yield
-    sys.path = old_path
+        yield
+        sys.path = old_path
+    else:
+        yield
 
 
 @contextmanager
@@ -29,18 +31,22 @@ def environment_append(env_vars):
                       => e.j. PATH=/path/1:/path/2
     :return: None
     """
-    old_env = dict(os.environ)
     for name, value in env_vars.items():
         if isinstance(value, list):
             env_vars[name] = os.pathsep.join(value)
-            if name in old_env:
-                env_vars[name] += os.pathsep + old_env[name]
-    os.environ.update(env_vars)
-    try:
+            old = os.environ.get(name)
+            if old:
+                env_vars[name] += os.pathsep + old
+    if env_vars:
+        old_env = dict(os.environ)
+        os.environ.update(env_vars)
+        try:
+            yield
+        finally:
+            os.environ.clear()
+            os.environ.update(old_env)
+    else:
         yield
-    finally:
-        os.environ.clear()
-        os.environ.update(old_env)
 
 
 @contextmanager
diff --git a/conans/model/env_info.py b/conans/model/env_info.py
--- a/conans/model/env_info.py
+++ b/conans/model/env_info.py
@@ -153,26 +153,30 @@ def env_dicts(self, package_name):
         ret = {}
         ret_multi = {}
         # First process the global variables
-        for package, pairs in self._sorted_data:
-            for name, value in pairs.items():
-                if package is None:
-                    if isinstance(value, list):
-                        ret_multi[name] = value
-                    else:
-                        ret[name] = value
+
+        global_pairs = self._data.get(None)
+        own_pairs = self._data.get(package_name)
+
+        if global_pairs:
+            for name, value in global_pairs.items():
+                if isinstance(value, list):
+                    ret_multi[name] = value
+                else:
+                    ret[name] = value
 
         # Then the package scoped vars, that will override the globals
-        for package, pairs in self._sorted_data:
-            for name, value in pairs.items():
-                if package == package_name:
-                    if isinstance(value, list):
-                        ret_multi[name] = value
-                        if name in ret:  # Already exists a global variable, remove it
-                            del ret[name]
-                    else:
-                        ret[name] = value
-                        if name in ret_multi:  # Already exists a list global variable, remove it
-                            del ret_multi[name]
+        if own_pairs:
+            for name, value in own_pairs.items():
+                if isinstance(value, list):
+                    ret_multi[name] = value
+                    if name in ret:  # Already exists a global variable, remove it
+                        del ret[name]
+                else:
+                    ret[name] = value
+                    if name in ret_multi:  # Already exists a list global variable, remove it
+                        del ret_multi[name]
+
+        # FIXME: This dict is only used doing a ret.update(ret_multi). Unnecessary?
         return ret, ret_multi
 
     def __repr__(self):


Test patch:
diff --git a/conans/test/integration/python_build_test.py b/conans/test/integration/python_build_test.py
--- a/conans/test/integration/python_build_test.py
+++ b/conans/test/integration/python_build_test.py
@@ -65,6 +65,63 @@ def package_info(self):
 
 class PythonBuildTest(unittest.TestCase):
 
+    def reuse_package_info_test(self):
+        # https://github.com/conan-io/conan/issues/2644
+        client = TestClient()
+        client.save({CONANFILE: conanfile, "__init__.py": "", "mytest.py": test})
+        client.run("export . lasote/stable")
+        reuse = """from conans import ConanFile, tools
+class ToolsTest(ConanFile):
+    name = "Consumer"
+    version = "0.1"
+    requires = "conantool/1.0@lasote/stable"
+
+    def package_info(self):
+        import mytest
+        mytest.bar(self.output)
+"""
+        client.save({CONANFILE: reuse}, clean_first=True)
+        client.run("create . conan/testing")
+        self.assertIn("Consumer/0.1@conan/testing: Hello Bar", client.out)
+
+    def reuse_build_test(self):
+        # https://github.com/conan-io/conan/issues/2644
+        client = TestClient()
+        client.save({CONANFILE: conanfile, "__init__.py": "", "mytest.py": test})
+        client.run("export . lasote/stable")
+        reuse = """from conans import ConanFile, tools
+class ToolsTest(ConanFile):
+    name = "Consumer"
+    version = "0.1"
+    requires = "conantool/1.0@lasote/stable"
+
+    def build(self):
+        import mytest
+        mytest.foo(self.output)
+"""
+        client.save({CONANFILE: reuse}, clean_first=True)
+        client.run("create . conan/testing")
+        self.assertIn("Consumer/0.1@conan/testing: Hello Foo", client.out)
+
+    def reuse_source_test(self):
+        # https://github.com/conan-io/conan/issues/2644
+        client = TestClient()
+        client.save({CONANFILE: conanfile, "__init__.py": "", "mytest.py": test})
+        client.run("export . lasote/stable")
+        reuse = """from conans import ConanFile, tools
+class ToolsTest(ConanFile):
+    name = "Consumer"
+    version = "0.1"
+    requires = "conantool/1.0@lasote/stable"
+
+    def source(self):
+        import mytest
+        mytest.baz(self.output)
+"""
+        client.save({CONANFILE: reuse}, clean_first=True)
+        client.run("create . conan/testing")
+        self.assertIn("Consumer/0.1@conan/testing: Hello Baz", client.out)
+
     def reuse_test(self):
         client = TestClient()
         client.save({CONANFILE: conanfile, "__init__.py": "", "mytest.py": test})