Repository: conan-io/conan

Issue:
Define policy for conan vs user ConanFile attributes and methods
To reduce possible future collisions when things are added to conan.
E.g.:

- Use "_myattribute" for user defined attributes. Conan reserves all public members
- Use "__myattribute" for user defined atributes. (What happens for inheritance via python_requires?)
- Etc.



Discussion:

Patch:
diff --git a/conans/client/graph/graph.py b/conans/client/graph/graph.py
index dabc5ff3e..16570b31c 100644
--- a/conans/client/graph/graph.py
+++ b/conans/client/graph/graph.py
@@ -275,7 +275,7 @@ class DepsGraph(object):
             if new_level:
                 result.append(new_level)
         return result
-    
+
     def nodes_to_build(self):
         ret = []
         for level in self.by_levels():
diff --git a/conans/client/graph/graph_builder.py b/conans/client/graph/graph_builder.py
index 587dd487b..1704247f7 100644
--- a/conans/client/graph/graph_builder.py
+++ b/conans/client/graph/graph_builder.py
@@ -56,14 +56,14 @@ class DepsGraphBuilder(object):
             if alias:
                 req.conan_reference = alias
 
-        if not hasattr(conanfile, "_evaluated_requires"):
-            conanfile._evaluated_requires = conanfile.requires.copy()
-        elif conanfile.requires != conanfile._evaluated_requires:
+        if not hasattr(conanfile, "_conan_evaluated_requires"):
+            conanfile._conan_evaluated_requires = conanfile.requires.copy()
+        elif conanfile.requires != conanfile._conan_evaluated_requires:
             raise ConanException("%s: Incompatible requirements obtained in different "
                                  "evaluations of 'requirements'\n"
                                  "    Previous requirements: %s\n"
                                  "    New requirements: %s"
-                                 % (conanref, list(conanfile._evaluated_requires.values()),
+                                 % (conanref, list(conanfile._conan_evaluated_requires.values()),
                                     list(conanfile.requires.values())))
 
     def _load_deps(self, node, down_reqs, dep_graph, public_deps, down_ref, down_options,
@@ -179,10 +179,10 @@ class DepsGraphBuilder(object):
                     # So it is necessary to save the "requires" state and restore it before a second
                     # execution of requirements(). It is a shallow copy, if first iteration is
                     # RequireResolve'd or overridden, the inner requirements are modified
-                    if not hasattr(conanfile, "_original_requires"):
-                        conanfile._original_requires = conanfile.requires.copy()
+                    if not hasattr(conanfile, "_conan_original_requires"):
+                        conanfile._conan_original_requires = conanfile.requires.copy()
                     else:
-                        conanfile.requires = conanfile._original_requires.copy()
+                        conanfile.requires = conanfile._conan_original_requires.copy()
 
                     with conanfile_exception_formatter(str(conanfile), "requirements"):
                         conanfile.requirements()
diff --git a/conans/client/graph/graph_manager.py b/conans/client/graph/graph_manager.py
index 32bdb94a9..415e83107 100644
--- a/conans/client/graph/graph_manager.py
+++ b/conans/client/graph/graph_manager.py
@@ -100,8 +100,8 @@ class GraphManager(object):
                 require.conan_reference = require.range_reference = reference
             else:
                 conanfile.requires(str(reference))
-            conanfile._user = reference.user
-            conanfile._channel = reference.channel
+            conanfile._conan_user = reference.user
+            conanfile._conan_channel = reference.channel
 
         # Computing the full dependency graph
         cache_settings = self._client_cache.settings.copy()
diff --git a/conans/client/installer.py b/conans/client/installer.py
index b0fe070eb..119df2cdd 100644
--- a/conans/client/installer.py
+++ b/conans/client/installer.py
@@ -407,7 +407,7 @@ class ConanInstaller(object):
         # Update the info but filtering the package values that not apply to the subtree
         # of this current node and its dependencies.
         subtree_libnames = [node.conan_ref.name for node in node_order]
-        for package_name, env_vars in conan_file._env_values.data.items():
+        for package_name, env_vars in conan_file._conan_env_values.data.items():
             for name, value in env_vars.items():
                 if not package_name or package_name in subtree_libnames or \
                    package_name == conan_file.name:
diff --git a/conans/client/loader.py b/conans/client/loader.py
index de3db350e..05d07feec 100644
--- a/conans/client/loader.py
+++ b/conans/client/loader.py
@@ -159,7 +159,7 @@ class ConanFileLoader(object):
 
         # imports method
         conanfile.imports = parser.imports_method(conanfile)
-        conanfile._env_values.update(processed_profile._env_values)
+        conanfile._conan_env_values.update(processed_profile._env_values)
         return conanfile
 
     def load_virtual(self, references, processed_profile, scope_options=True,
diff --git a/conans/client/tools/win.py b/conans/client/tools/win.py
index d864f8ea8..fb8790e99 100644
--- a/conans/client/tools/win.py
+++ b/conans/client/tools/win.py
@@ -489,4 +489,4 @@ def run_in_windows_bash(conanfile, bashcmd, cwd=None, subsystem=None, msys_mingw
         wincmd = '%s --login -c %s' % (bash_path, escape_windows_cmd(to_run))
         conanfile.output.info('run_in_windows_bash: %s' % wincmd)
         # https://github.com/conan-io/conan/issues/2839 (subprocess=True)
-        return conanfile._runner(wincmd, output=conanfile.output, subprocess=True)
+        return conanfile._conan_runner(wincmd, output=conanfile.output, subprocess=True)
diff --git a/conans/model/conan_file.py b/conans/model/conan_file.py
index f025e5367..798790c00 100644
--- a/conans/model/conan_file.py
+++ b/conans/model/conan_file.py
@@ -109,9 +109,9 @@ class ConanFile(object):
         # an output stream (writeln, info, warn error)
         self.output = output
         # something that can run commands, as os.sytem
-        self._runner = runner
-        self._user = user
-        self._channel = channel
+        self._conan_runner = runner
+        self._conan_user = user
+        self._conan_channel = channel
 
     def initialize(self, settings, env, local=None):
         if isinstance(self.generators, str):
@@ -147,15 +147,15 @@ class ConanFile(object):
         self.deps_user_info = DepsUserInfo()
 
         # user specified env variables
-        self._env_values = env.copy()  # user specified -e
+        self._conan_env_values = env.copy()  # user specified -e
 
     @property
     def env(self):
-        """Apply the self.deps_env_info into a copy of self._env_values (will prioritize the
-        self._env_values, user specified from profiles or -e first, then inherited)"""
+        """Apply the self.deps_env_info into a copy of self._conan_env_values (will prioritize the
+        self._conan_env_values, user specified from profiles or -e first, then inherited)"""
         # Cannot be lazy cached, because it's called in configure node, and we still don't have
         # the deps_env_info objects available
-        tmp_env_values = self._env_values.copy()
+        tmp_env_values = self._conan_env_values.copy()
         tmp_env_values.update(self.deps_env_info)
 
         ret, multiple = tmp_env_values.env_dicts(self.name)
@@ -164,21 +164,21 @@ class ConanFile(object):
 
     @property
     def channel(self):
-        if not self._channel:
-            self._channel = os.getenv("CONAN_CHANNEL")
-            if not self._channel:
+        if not self._conan_channel:
+            self._conan_channel = os.getenv("CONAN_CHANNEL")
+            if not self._conan_channel:
                 raise ConanException("CONAN_CHANNEL environment variable not defined, "
                                      "but self.channel is used in conanfile")
-        return self._channel
+        return self._conan_channel
 
     @property
     def user(self):
-        if not self._user:
-            self._user = os.getenv("CONAN_USERNAME")
-            if not self._user:
+        if not self._conan_user:
+            self._conan_user = os.getenv("CONAN_USERNAME")
+            if not self._conan_user:
                 raise ConanException("CONAN_USERNAME environment variable not defined, "
                                      "but self.user is used in conanfile")
-        return self._user
+        return self._conan_user
 
     def collect_libs(self, folder="lib"):
         self.output.warn("Use 'self.collect_libs' is deprecated, "
@@ -239,14 +239,15 @@ class ConanFile(object):
             ignore_errors=False, run_environment=False):
         def _run():
             if not win_bash:
-                return self._runner(command, output, os.path.abspath(RUN_LOG_NAME), cwd)
+                return self._conan_runner(command, output, os.path.abspath(RUN_LOG_NAME), cwd)
             # FIXME: run in windows bash is not using output
             return tools.run_in_windows_bash(self, bashcmd=command, cwd=cwd, subsystem=subsystem,
                                              msys_mingw=msys_mingw)
         if run_environment:
             with tools.run_environment(self):
                 if os_info.is_macos:
-                    command = 'DYLD_LIBRARY_PATH="%s" %s' % (os.environ.get('DYLD_LIBRARY_PATH', ''), command)
+                    command = 'DYLD_LIBRARY_PATH="%s" %s' % (os.environ.get('DYLD_LIBRARY_PATH', ''),
+                                                             command)
                 retcode = _run()
         else:
             retcode = _run()
@@ -268,7 +269,7 @@ class ConanFile(object):
         raise ConanException("You need to create a method 'test' in your test/conanfile.py")
 
     def __repr__(self):
-        if self.name and self.version and self._channel and self._user:
+        if self.name and self.version and self._conan_channel and self._conan_user:
             return "%s/%s@%s/%s" % (self.name, self.version, self.user, self.channel)
         elif self.name and self.version:
             return "%s/%s@PROJECT" % (self.name, self.version)


Test patch:
diff --git a/conans/test/model/conanfile_test.py b/conans/test/model/conanfile_test.py
new file mode 100644
index 000000000..e515cf5a7
--- /dev/null
+++ b/conans/test/model/conanfile_test.py
@@ -0,0 +1,41 @@
+import unittest
+from conans.model.conan_file import ConanFile
+from conans.model.env_info import EnvValues
+from conans.model.settings import Settings
+from conans.test.utils.tools import TestClient
+
+
+class ConanFileTest(unittest.TestCase):
+    def test_conanfile_naming(self):
+        for member in vars(ConanFile):
+            if member.startswith('_') and not member.startswith("__"):
+                self.assertTrue(member.startswith('_conan'))
+
+        conanfile = ConanFile(None, None)
+        conanfile.initialize(Settings(), EnvValues())
+
+        for member in vars(conanfile):
+            if member.startswith('_') and not member.startswith("__"):
+                self.assertTrue(member.startswith('_conan'))
+
+    def test_conanfile_naming_complete(self):
+        client = TestClient()
+        conanfile = """from conans import ConanFile
+class Pkg(ConanFile):
+    pass
+    def package_info(self):
+        for member in Pkg.__dict__:
+            if member.startswith('_') and not member.startswith("__"):
+                assert(member.startswith('_conan'))
+        for member in vars(self):
+            if member.startswith('_') and not member.startswith("__"):
+                assert(member.startswith('_conan'))
+"""
+        client.save({"conanfile.py": conanfile})
+        client.run("create . PkgA/0.1@user/testing")
+        client.save({"conanfile.py": conanfile.replace("pass",
+                                                       "requires = 'PkgA/0.1@user/testing'")})
+        client.run("create . PkgB/0.1@user/testing")
+        client.save({"conanfile.py": conanfile.replace("pass",
+                                                       "requires = 'PkgB/0.1@user/testing'")})
+        client.run("create . PkgC/0.1@user/testing")
diff --git a/conans/test/util/tools_test.py b/conans/test/util/tools_test.py
index daf39682f..7b8f9e55d 100644
--- a/conans/test/util/tools_test.py
+++ b/conans/test/util/tools_test.py
@@ -864,9 +864,8 @@ ProgramFiles(x86)=C:\Program Files (x86)
                 self.assertEqual(vcvars["PROCESSOR_REVISION"], "9e09")
                 self.assertEqual(vcvars["ProgramFiles(x86)"], "C:\Program Files (x86)")
 
+    @unittest.skipUnless(platform.system() == "Windows", "Requires Windows")
     def run_in_bash_test(self):
-        if platform.system() != "Windows":
-            return
 
         class MockConanfile(object):
             def __init__(self):
@@ -878,22 +877,22 @@ ProgramFiles(x86)=C:\Program Files (x86)
                     def __call__(self, command, output, log_filepath=None,
                                  cwd=None, subprocess=False):  # @UnusedVariable
                         self.command = command
-                self._runner = MyRun()
+                self._conan_runner = MyRun()
 
         conanfile = MockConanfile()
         with patch.object(OSInfo, "bash_path", return_value='bash'):
             tools.run_in_windows_bash(conanfile, "a_command.bat", subsystem="cygwin")
-            self.assertIn("bash", conanfile._runner.command)
-            self.assertIn("--login -c", conanfile._runner.command)
-            self.assertIn("^&^& a_command.bat ^", conanfile._runner.command)
+            self.assertIn("bash", conanfile._conan_runner.command)
+            self.assertIn("--login -c", conanfile._conan_runner.command)
+            self.assertIn("^&^& a_command.bat ^", conanfile._conan_runner.command)
 
         with tools.environment_append({"CONAN_BASH_PATH": "path\\to\\mybash.exe"}):
             tools.run_in_windows_bash(conanfile, "a_command.bat", subsystem="cygwin")
-            self.assertIn('path\\to\\mybash.exe --login -c', conanfile._runner.command)
+            self.assertIn('path\\to\\mybash.exe --login -c', conanfile._conan_runner.command)
 
         with tools.environment_append({"CONAN_BASH_PATH": "path with spaces\\to\\mybash.exe"}):
             tools.run_in_windows_bash(conanfile, "a_command.bat", subsystem="cygwin")
-            self.assertIn('"path with spaces\\to\\mybash.exe" --login -c', conanfile._runner.command)
+            self.assertIn('"path with spaces\\to\\mybash.exe" --login -c', conanfile._conan_runner.command)
 
         # try to append more env vars
         conanfile = MockConanfile()
@@ -901,7 +900,7 @@ ProgramFiles(x86)=C:\Program Files (x86)
             tools.run_in_windows_bash(conanfile, "a_command.bat", subsystem="cygwin",
                                       env={"PATH": "/other/path", "MYVAR": "34"})
             self.assertIn('^&^& PATH=\\^"/cygdrive/other/path:/cygdrive/path/to/somewhere:$PATH\\^" '
-                          '^&^& MYVAR=34 ^&^& a_command.bat ^', conanfile._runner.command)
+                          '^&^& MYVAR=34 ^&^& a_command.bat ^', conanfile._conan_runner.command)
 
     def download_retries_test(self):
         http_server = StoppableThreadBottle()