Repository: conan-io/conan

Issue:
Conan doesn't keep the username to log to server anymore
From conan 1.8,

When an authentication is required by the conan server, the username is now always asked event though it was specified by conan user. In older version, only the password was required.

To reproduce:
```
$ conan user -c
$ conan user username
Changed user of remote 'server' from 'None' (anonymous) to 'username'
$ conan search -r server *
Please log in to "server" to perform this action. Execute "conan user" command.
Remote 'server' username:
```

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.
- [X] I've explained the steps to reproduce the error or the motivation/use case of the question/suggestion.



Discussion:

Patch:
diff --git a/conans/client/conan_api.py b/conans/client/conan_api.py
index 21db43b9f..aadb27449 100644
--- a/conans/client/conan_api.py
+++ b/conans/client/conan_api.py
@@ -248,7 +248,7 @@ class ConanAPIV1(object):
 
     def _init_manager(self, action_recorder):
         """Every api call gets a new recorder and new manager"""
-        return ConanManager(self._client_cache, self._user_io,
+        return ConanManager(self._client_cache, self._user_io, self._runner,
                             self._remote_manager, action_recorder, self._registry,
                             self._graph_manager, self._plugin_manager)
 
diff --git a/conans/client/graph/graph.py b/conans/client/graph/graph.py
index b41050ff3..16570b31c 100644
--- a/conans/client/graph/graph.py
+++ b/conans/client/graph/graph.py
@@ -56,11 +56,10 @@ class Node(object):
         return [edge.dst for edge in self.dependencies]
 
     def public_neighbors(self):
-        return [edge.dst for edge in self.dependencies
-                if not edge.private and not edge.build_require]
+        return [edge.dst for edge in self.dependencies if not edge.private]
 
     def private_neighbors(self):
-        return [edge.dst for edge in self.dependencies if edge.private or edge.build_require]
+        return [edge.dst for edge in self.dependencies if edge.private]
 
     def inverse_neighbors(self):
         return [edge.src for edge in self.dependants]
@@ -107,11 +106,10 @@ class Node(object):
 
 
 class Edge(object):
-    def __init__(self, src, dst, private=False, build_require=False):
+    def __init__(self, src, dst, private=False):
         self.src = src
         self.dst = dst
         self.private = private
-        self.build_require = build_require
 
     def __eq__(self, other):
         return self.src == self.src and self.dst == other.dst
@@ -136,7 +134,7 @@ class DepsGraph(object):
 
         for e in graph.root.dependencies:
             e.src = node
-            e.build_require = build_require
+            e.private = True
 
         node.dependencies = graph.root.dependencies + node.dependencies
 
@@ -145,9 +143,9 @@ class DepsGraph(object):
             self.root = node
         self.nodes.add(node)
 
-    def add_edge(self, src, dst, private=False, build_require=False):
+    def add_edge(self, src, dst, private=False):
         assert src in self.nodes and dst in self.nodes
-        edge = Edge(src, dst, private, build_require)
+        edge = Edge(src, dst, private)
         src.add_edge(edge)
         dst.add_edge(edge)
 
@@ -259,11 +257,11 @@ class DepsGraph(object):
             for dep in node.dependencies:
                 src = result_node
                 dst = nodes_map[dep.dst]
-                result.add_edge(src, dst, dep.private, dep.build_require)
+                result.add_edge(src, dst, dep.private)
             for dep in node.dependants:
                 src = nodes_map[dep.src]
                 dst = result_node
-                result.add_edge(src, dst, dep.private, dep.build_require)
+                result.add_edge(src, dst, dep.private)
 
         return result
 
diff --git a/conans/client/graph/graph_builder.py b/conans/client/graph/graph_builder.py
index 0808a817d..b10b4efc4 100644
--- a/conans/client/graph/graph_builder.py
+++ b/conans/client/graph/graph_builder.py
@@ -22,10 +22,11 @@ class DepsGraphBuilder(object):
         self._workspace = workspace
         self._recorder = recorder
 
-    def load_graph(self, root_node, check_updates, update, remote_name, processed_profile):
+    def load_graph(self, conanfile, check_updates, update, remote_name, processed_profile):
         check_updates = check_updates or update
         dep_graph = DepsGraph()
         # compute the conanfile entry point for this dependency graph
+        root_node = Node(None, conanfile)
         dep_graph.add_node(root_node)
         public_deps = {}  # {name: Node} dict with public nodes, so they are not added again
         aliased = {}
diff --git a/conans/client/graph/graph_manager.py b/conans/client/graph/graph_manager.py
index b151ea93f..415e83107 100644
--- a/conans/client/graph/graph_manager.py
+++ b/conans/client/graph/graph_manager.py
@@ -7,7 +7,7 @@ from conans.errors import conanfile_exception_formatter, ConanException
 from conans.model.conan_file import get_env_context_manager
 from conans.client.graph.graph_builder import DepsGraphBuilder
 from conans.client.graph.graph_binaries import GraphBinariesAnalyzer
-from conans.client.graph.graph import BINARY_BUILD, BINARY_WORKSPACE, Node
+from conans.client.graph.graph import BINARY_BUILD, BINARY_WORKSPACE
 from conans.client import settings_preprocessor
 from conans.client.output import ScopedOutput
 from conans.client.graph.build_mode import BuildMode
@@ -64,8 +64,8 @@ class GraphManager(object):
         cache_settings.remove_undefined()
         processed_profile = ProcessedProfile(cache_settings, profile, None)
         if conanfile_path.endswith(".py"):
-            conanfile = self._loader.load_conanfile(conanfile_path, output, consumer=True,
-                                                    local=True, processed_profile=processed_profile)
+            conanfile = self._loader.load_conanfile(conanfile_path, output, consumer=True, local=True,
+                                                    processed_profile=processed_profile)
         else:
             conanfile = self._loader.load_conanfile_txt(conanfile_path, output, processed_profile)
 
@@ -84,13 +84,12 @@ class GraphManager(object):
         settings_preprocessor.preprocess(cache_settings)
         processed_profile = ProcessedProfile(cache_settings, profile, create_reference=None)
         conanfile = self._loader.load_virtual([reference], processed_profile)
-        root_node = Node(None, conanfile)
-        graph = builder.load_graph(root_node, check_updates=False, update=False, remote_name=None,
+        graph = builder.load_graph(conanfile, check_updates=False, update=False, remote_name=None,
                                    processed_profile=processed_profile)
         return graph
 
-    def load_graph(self, reference, create_reference, profile, build_mode, check_updates, update,
-                   remote_name, recorder, workspace):
+    def load_graph(self, reference, create_reference, profile, build_mode, check_updates, update, remote_name,
+                   recorder, workspace):
 
         def _inject_require(conanfile, reference):
             """ test_package functionality requires injecting the tested package as requirement
@@ -117,16 +116,14 @@ class GraphManager(object):
         else:
             output = ScopedOutput("PROJECT", self._output)
             if reference.endswith(".py"):
-                conanfile = self._loader.load_conanfile(reference, output, processed_profile,
-                                                        consumer=True)
+                conanfile = self._loader.load_conanfile(reference, output, processed_profile, consumer=True)
                 if create_reference:  # create with test_package
                     _inject_require(conanfile, create_reference)
             else:
                 conanfile = self._loader.load_conanfile_txt(reference, output, processed_profile)
 
         build_mode = BuildMode(build_mode, self._output)
-        root_node = Node(None, conanfile)
-        deps_graph = self._load_graph(root_node, check_updates, update,
+        deps_graph = self._load_graph(conanfile, check_updates, update,
                                       build_mode=build_mode, remote_name=remote_name,
                                       profile_build_requires=profile.build_requires,
                                       recorder=recorder, workspace=workspace,
@@ -169,39 +166,29 @@ class GraphManager(object):
 
             if package_build_requires:
                 node.conanfile.build_requires_options.clear_unscoped_options()
-                build_requires_options = node.conanfile.build_requires_options
-                virtual = self._loader.load_virtual(package_build_requires.values(),
-                                                    scope_options=False,
-                                                    build_requires_options=build_requires_options,
+                virtual = self._loader.load_virtual(package_build_requires.values(), scope_options=False,
+                                                    build_requires_options=node.conanfile.build_requires_options,
                                                     processed_profile=processed_profile)
-                virtual_node = Node(None, virtual)
-                build_requires_package_graph = self._load_graph(virtual_node, check_updates, update,
-                                                                build_mode, remote_name,
-                                                                profile_build_requires,
-                                                                recorder, workspace,
-                                                                processed_profile)
+                build_requires_package_graph = self._load_graph(virtual, check_updates, update, build_mode,
+                                                                remote_name, profile_build_requires,
+                                                                recorder, workspace, processed_profile)
                 graph.add_graph(node, build_requires_package_graph, build_require=True)
 
             if new_profile_build_requires:
                 node.conanfile.build_requires_options.clear_unscoped_options()
-                build_requires_options = node.conanfile.build_requires_options
-                virtual = self._loader.load_virtual(new_profile_build_requires.values(),
-                                                    scope_options=False,
-                                                    build_requires_options=build_requires_options,
+                virtual = self._loader.load_virtual(new_profile_build_requires.values(), scope_options=False,
+                                                    build_requires_options=node.conanfile.build_requires_options,
                                                     processed_profile=processed_profile)
-                virtual_node = Node(None, virtual)
-                build_requires_profile_graph = self._load_graph(virtual_node, check_updates, update,
-                                                                build_mode, remote_name,
-                                                                new_profile_build_requires,
-                                                                recorder, workspace,
-                                                                processed_profile)
+
+                build_requires_profile_graph = self._load_graph(virtual, check_updates, update, build_mode,
+                                                                remote_name, new_profile_build_requires,
+                                                                recorder, workspace, processed_profile)
                 graph.add_graph(node, build_requires_profile_graph, build_require=True)
 
-    def _load_graph(self, root_node, check_updates, update, build_mode, remote_name,
+    def _load_graph(self, conanfile, check_updates, update, build_mode, remote_name,
                     profile_build_requires, recorder, workspace, processed_profile):
-        builder = DepsGraphBuilder(self._proxy, self._output, self._loader, self._resolver,
-                                   workspace, recorder)
-        graph = builder.load_graph(root_node, check_updates, update, remote_name, processed_profile)
+        builder = DepsGraphBuilder(self._proxy, self._output, self._loader, self._resolver, workspace, recorder)
+        graph = builder.load_graph(conanfile, check_updates, update, remote_name, processed_profile)
         if build_mode is None:
             return graph
         binaries_analyzer = GraphBinariesAnalyzer(self._client_cache, self._output,
diff --git a/conans/client/loader.py b/conans/client/loader.py
index be11c5519..05d07feec 100644
--- a/conans/client/loader.py
+++ b/conans/client/loader.py
@@ -1,23 +1,22 @@
-import imp
-import inspect
 import os
 import sys
-
+import imp
+import inspect
 import uuid
 
-from conans.client.generators import registered_generators
 from conans.client.loader_txt import ConanFileTextLoader
-from conans.client.output import ScopedOutput
-from conans.client.tools.files import chdir
 from conans.errors import ConanException, NotFoundException
 from conans.model.conan_file import ConanFile
-from conans.model.conan_generator import Generator
 from conans.model.options import OptionsValues
-from conans.model.profile import Profile
 from conans.model.ref import ConanFileReference
 from conans.model.settings import Settings
 from conans.model.values import Values
 from conans.util.files import load
+from conans.client.output import ScopedOutput
+from conans.model.profile import Profile
+from conans.client.generators import registered_generators
+from conans.model.conan_generator import Generator
+from conans.client.tools.files import chdir
 
 
 class ProcessedProfile(object):
@@ -188,7 +187,7 @@ class ConanFileLoader(object):
         return conanfile
 
 
-def _parse_module(conanfile_module, module_id):
+def _parse_module(conanfile_module, filename):
     """ Parses a python in-memory module, to extract the classes, mainly the main
     class defining the Recipe, but also process possible existing generators
     @param conanfile_module: the module to be processed
@@ -196,16 +195,16 @@ def _parse_module(conanfile_module, module_id):
     """
     result = None
     for name, attr in conanfile_module.__dict__.items():
-        if (name.startswith("_") or not inspect.isclass(attr) or
-                attr.__dict__.get("__module__") != module_id):
+        if name[0] == "_":
             continue
-
-        if issubclass(attr, ConanFile) and attr != ConanFile:
+        if (inspect.isclass(attr) and issubclass(attr, ConanFile) and attr != ConanFile and
+                attr.__dict__["__module__"] == filename):
             if result is None:
                 result = attr
             else:
                 raise ConanException("More than 1 conanfile in the file")
-        elif issubclass(attr, Generator) and attr != Generator:
+        if (inspect.isclass(attr) and issubclass(attr, Generator) and attr != Generator and
+                attr.__dict__["__module__"] == filename):
             registered_generators.add(attr.__name__, attr)
 
     if result is None:
@@ -225,15 +224,31 @@ def _parse_file(conan_file_path):
     if not os.path.exists(conan_file_path):
         raise NotFoundException("%s not found!" % conan_file_path)
 
+    filename = os.path.splitext(os.path.basename(conan_file_path))[0]
+
     try:
-        module_id = str(uuid.uuid1())
         current_dir = os.path.dirname(conan_file_path)
         sys.path.append(current_dir)
+        old_modules = list(sys.modules.keys())
         with chdir(current_dir):
             sys.dont_write_bytecode = True
-            loaded = imp.load_source(module_id, conan_file_path)
+            loaded = imp.load_source(filename, conan_file_path)
             loaded.python_requires = _invalid_python_requires
             sys.dont_write_bytecode = False
+        # Put all imported files under a new package name
+        module_id = uuid.uuid1()
+        added_modules = set(sys.modules).difference(old_modules)
+        for added in added_modules:
+            module = sys.modules[added]
+            if module:
+                try:
+                    folder = os.path.dirname(module.__file__)
+                except AttributeError:  # some module doesn't have __file__
+                    pass
+                else:
+                    if folder.startswith(current_dir):
+                        module = sys.modules.pop(added)
+                        sys.modules["%s.%s" % (module_id, added)] = module
     except Exception:
         import traceback
         trace = traceback.format_exc().split('\n')
@@ -242,4 +257,4 @@ def _parse_file(conan_file_path):
     finally:
         sys.path.pop()
 
-    return loaded, module_id
+    return loaded, filename
diff --git a/conans/client/manager.py b/conans/client/manager.py
index eada0f99a..75f9f3fd8 100644
--- a/conans/client/manager.py
+++ b/conans/client/manager.py
@@ -18,12 +18,16 @@ from conans.client.graph.printer import print_graph
 
 
 class ConanManager(object):
-    def __init__(self, client_cache, user_io, remote_manager,
+    """ Manage all the commands logic  The main entry point for all the client
+    business logic
+    """
+    def __init__(self, client_cache, user_io, runner, remote_manager,
                  recorder, registry, graph_manager, plugin_manager):
         assert isinstance(user_io, UserIO)
         assert isinstance(client_cache, ClientCache)
         self._client_cache = client_cache
         self._user_io = user_io
+        self._runner = runner
         self._remote_manager = remote_manager
         self._recorder = recorder
         self._registry = registry
diff --git a/conans/client/userio.py b/conans/client/userio.py
index 77eb86112..bfb09038d 100644
--- a/conans/client/userio.py
+++ b/conans/client/userio.py
@@ -39,9 +39,11 @@ class UserIO(object):
     def request_login(self, remote_name, username=None):
         """Request user to input their name and password
         :param username If username is specified it only request password"""
-        if self._interactive:
-            self.out.write("Remote '%s' username: " % remote_name)
-        username = self.get_username(remote_name)
+
+        if not username:
+            if self._interactive:
+                self.out.write("Remote '%s' username: " % remote_name)
+            username = self.get_username(remote_name)
 
         if self._interactive:
             self.out.write('Please enter a password for "%s" account: ' % username)


Test patch:
diff --git a/conans/test/client/userio.py b/conans/test/client/userio.py
new file mode 100644
index 000000000..52ba144c4
--- /dev/null
+++ b/conans/test/client/userio.py
@@ -0,0 +1,25 @@
+# coding=utf-8
+
+import unittest
+from mock import mock
+
+from conans.client.userio import UserIO
+
+
+class UserIOTest(unittest.TestCase):
+
+    @mock.patch("conans.client.userio.UserIO.get_username", return_value="username")
+    @mock.patch("conans.client.userio.UserIO.get_password", return_value="passwd")
+    def test_request_login(self, m1, m2):
+        user_io = UserIO()
+
+        # Use mocked ones
+        u, p = user_io.request_login(remote_name="lol")
+        self.assertEqual(u, "username")
+        self.assertEqual(p, "passwd")
+
+        # Use from argument
+        username = "it's me!"
+        u, p = user_io.request_login(remote_name="lol", username=username)
+        self.assertEqual(u, username)
+        self.assertEqual(p, "passwd")
diff --git a/conans/test/model/fake_retriever.py b/conans/test/model/fake_retriever.py
index ef82e4809..329bfadfa 100644
--- a/conans/test/model/fake_retriever.py
+++ b/conans/test/model/fake_retriever.py
@@ -1,10 +1,9 @@
 import os
 
-from conans.client.graph.graph import Node
-from conans.model.ref import ConanFileReference
-from conans.paths import CONANFILE
 from conans.test.utils.test_files import temp_folder
+from conans.model.ref import ConanFileReference
 from conans.tools import save
+from conans.paths import CONANFILE
 
 
 class Retriever(object):
@@ -16,9 +15,8 @@ class Retriever(object):
     def root(self, content, processed_profile):
         conan_path = os.path.join(self.folder, "root.py")
         save(conan_path, content)
-        conanfile = self.loader.load_conanfile(conan_path, self.output, processed_profile,
-                                               consumer=True)
-        return Node(None, conanfile)
+        conanfile = self.loader.load_conanfile(conan_path, self.output, processed_profile, consumer=True)
+        return conanfile
 
     def conan(self, conan_ref, content):
         if isinstance(conan_ref, str):
diff --git a/conans/test/model/version_ranges_test.py b/conans/test/model/version_ranges_test.py
index 62f7a408e..35e442021 100644
--- a/conans/test/model/version_ranges_test.py
+++ b/conans/test/model/version_ranges_test.py
@@ -1,18 +1,17 @@
-from collections import namedtuple, Counter
 import unittest
+from collections import namedtuple, Counter
 
-from parameterized import parameterized
-
+from conans.test.utils.tools import TestBufferConanOutput
+from conans.paths import SimplePaths
 from conans.client.graph.graph_builder import DepsGraphBuilder
-from conans.client.graph.python_requires import ConanPythonRequire
-from conans.client.graph.range_resolver import RangeResolver, satisfying
-from conans.client.loader import ConanFileLoader, ProcessedProfile
-from conans.errors import ConanException
 from conans.model.ref import ConanFileReference
+from conans.client.loader import ConanFileLoader, ProcessedProfile
 from conans.model.requires import Requirements
-from conans.paths import SimplePaths
+from conans.client.graph.range_resolver import RangeResolver, satisfying
+from parameterized import parameterized
+from conans.errors import ConanException
 from conans.test.model.fake_retriever import Retriever
-from conans.test.utils.tools import TestBufferConanOutput
+from conans.client.graph.python_requires import ConanPythonRequire
 
 
 class BasicMaxVersionTest(unittest.TestCase):
@@ -21,8 +20,7 @@ class BasicMaxVersionTest(unittest.TestCase):
         result = satisfying(["1.1.1", "1.1.11", "1.1.21", "1.1.111"], "", output)
         self.assertEqual(result, "1.1.111")
         # prereleases are ordered
-        result = satisfying(["1.1.1-a.1", "1.1.1-a.11", "1.1.1-a.111", "1.1.1-a.21"], "~1.1.1-a",
-                            output)
+        result = satisfying(["1.1.1-a.1", "1.1.1-a.11", "1.1.1-a.111", "1.1.1-a.21"], "~1.1.1-a", output)
         self.assertEqual(result, "1.1.1-a.111")
         result = satisfying(["1.1.1", "1.1.1-11", "1.1.1-111", "1.1.1-21"], "", output)
         self.assertEqual(result, "1.1.1")
@@ -133,8 +131,7 @@ class VersionRangesTest(unittest.TestCase):
         self.remote_search = MockSearchRemote()
         paths = SimplePaths(self.retriever.folder)
         self.resolver = RangeResolver(self.output, paths, self.remote_search)
-        self.builder = DepsGraphBuilder(self.retriever, self.output, self.loader, self.resolver,
-                                        None, None)
+        self.builder = DepsGraphBuilder(self.retriever, self.output, self.loader, self.resolver, None, None)
 
         for v in ["0.1", "0.2", "0.3", "1.1", "1.1.2", "1.2.1", "2.1", "2.2.1"]:
             say_content = """