Repository: conan-io/conan

Issue:
[question] why isn't python requires updated with --update?
To help us debug your issue please explain:

- [x] I've read the [CONTRIBUTING guide](https://github.com/conan-io/conan/blob/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.

```
Conan Version 1.12.2
Distributor ID:	Ubuntu
Description:	Ubuntu 16.04.5 LTS
Release:	16.04
Codename:	xenial
```

I see from documentation at:
https://docs.conan.io/en/latest/extending/python_requires.html

That python_requires are not updated with --update 
`They are not automatically updated with the --update argument from remotes.`

But why? Every time I update the python_requires I need to inform all users to clear their cache for those packages to be updated? Seems very cumbersome.



Discussion:
The main reason was that due to the "inversion of control" that is necessary to process python-requires, passing user information to the resolution of python-requires was quite ugly.

The architecture has improved a little bit, so I think it is possible, adding ``self.update`` and ``self.check_updates`` to ``ConanPythonRequires``, I'll submit a draft PR, to be considered for 1.14

Resolving patch:
diff --git a/conans/client/conan_api.py b/conans/client/conan_api.py
--- a/conans/client/conan_api.py
+++ b/conans/client/conan_api.py
@@ -236,8 +236,8 @@ def __init__(self, cache, user_io, runner, remote_manager, hook_manager, request
 
         self._proxy = ConanProxy(cache, self._user_io.out, remote_manager)
         resolver = RangeResolver(cache, self._proxy)
-        python_requires = ConanPythonRequire(self._proxy, resolver)
-        self._loader = ConanFileLoader(self._runner, self._user_io.out, python_requires)
+        self.python_requires = ConanPythonRequire(self._proxy, resolver)
+        self._loader = ConanFileLoader(self._runner, self._user_io.out, self.python_requires)
 
         self._graph_manager = GraphManager(self._user_io.out, self._cache,
                                            self._remote_manager, self._loader, self._proxy,
@@ -318,6 +318,8 @@ def test(self, path, reference, profile_names=None, settings=None, options=None,
         options = options or []
         env = env or []
 
+        self.python_requires.enable_remotes(update=update, remote_name=remote_name)
+
         conanfile_path = _get_conanfile_path(path, cwd, py=True)
         cwd = cwd or get_cwd()
         graph_info = get_graph_info(profile_names, settings, options, env, cwd, None,
@@ -354,6 +356,8 @@ def create(self, conanfile_path, name=None, version=None, user=None, channel=Non
             recorder = ActionRecorder()
             conanfile_path = _get_conanfile_path(conanfile_path, cwd, py=True)
 
+            self.python_requires.enable_remotes(update=update, remote_name=remote_name)
+
             conanfile = self._loader.load_export(conanfile_path, name, version, user, channel)
             ref = ConanFileReference(conanfile.name, conanfile.version, conanfile.user,
                                      conanfile.channel)
@@ -476,6 +480,8 @@ def workspace_install(self, path, settings=None, options=None, env=None,
         cwd = cwd or get_cwd()
         abs_path = os.path.normpath(os.path.join(cwd, path))
 
+        self.python_requires.enable_remotes(update=update, remote_name=remote_name)
+
         workspace = Workspace(abs_path, self._cache)
         graph_info = get_graph_info(profile_name, settings, options, env, cwd, None,
                                     self._cache, self._user_io.out)
@@ -526,6 +532,7 @@ def install_reference(self, reference, settings=None, options=None, env=None,
                 generators = False
 
             mkdir(install_folder)
+            self.python_requires.enable_remotes(update=update, remote_name=remote_name)
             manager = self._init_manager(recorder)
             manager.install(ref_or_path=reference, install_folder=install_folder,
                             remote_name=remote_name, graph_info=graph_info, build_modes=build,
@@ -558,6 +565,7 @@ def install(self, path="", name=None, version=None, user=None, channel=None,
 
             install_folder = _make_abs_path(install_folder, cwd)
             conanfile_path = _get_conanfile_path(path, cwd, py=None)
+            self.python_requires.enable_remotes(update=update, remote_name=remote_name)
             manager = self._init_manager(recorder)
             manager.install(ref_or_path=conanfile_path,
                             install_folder=install_folder,
@@ -628,6 +636,7 @@ def info_build_order(self, reference, settings=None, options=None, env=None,
         reference, graph_info = self._info_args(reference, install_folder, profile_names,
                                                 settings, options, env)
         recorder = ActionRecorder()
+        self.python_requires.enable_remotes(check_updates=check_updates, remote_name=remote_name)
         deps_graph, _ = self._graph_manager.load_graph(reference, None, graph_info, ["missing"],
                                                        check_updates, False, remote_name,
                                                        recorder)
@@ -640,6 +649,7 @@ def info_nodes_to_build(self, reference, build_modes, settings=None, options=Non
         reference, graph_info = self._info_args(reference, install_folder, profile_names,
                                                 settings, options, env)
         recorder = ActionRecorder()
+        self.python_requires.enable_remotes(check_updates=check_updates, remote_name=remote_name)
         deps_graph, conanfile = self._graph_manager.load_graph(reference, None, graph_info,
                                                                build_modes, check_updates,
                                                                False, remote_name, recorder)
@@ -652,6 +662,8 @@ def info(self, reference, remote_name=None, settings=None, options=None, env=Non
         reference, graph_info = self._info_args(reference, install_folder, profile_names,
                                                 settings, options, env)
         recorder = ActionRecorder()
+        # FIXME: Using update as check_update?
+        self.python_requires.enable_remotes(check_updates=update, remote_name=remote_name)
         deps_graph, conanfile = self._graph_manager.load_graph(reference, None, graph_info, build,
                                                                update, False, remote_name,
                                                                recorder)
diff --git a/conans/client/graph/python_requires.py b/conans/client/graph/python_requires.py
--- a/conans/client/graph/python_requires.py
+++ b/conans/client/graph/python_requires.py
@@ -17,9 +17,19 @@ def __init__(self, proxy, range_resolver):
         self._range_resolver = range_resolver
         self._requires = None
         self.valid = True
+        self._check_updates = False
+        self._update = False
+        self._remote_name = None
+
+    def enable_remotes(self, check_updates=False, update=False, remote_name=None):
+        self._check_updates = check_updates
+        self._update = update
+        self._remote_name = remote_name
 
     def invalidate_caches(self):
         self._cached_requires = {}
+        self.check_updates = False
+        self.update = False
 
     @contextmanager
     def capture_requires(self):
@@ -37,7 +47,8 @@ def _look_for_require(self, require):
             self._range_resolver.resolve(requirement, "python_require", update=False,
                                          remote_name=None)
             ref = requirement.ref
-            result = self._proxy.get_recipe(ref, False, False, remote_name=None,
+            result = self._proxy.get_recipe(ref, self._check_updates, self._update,
+                                            remote_name=self._remote_name,
                                             recorder=ActionRecorder())
             path, _, _, new_ref = result
             module, conanfile = parse_conanfile(conanfile_path=path, python_requires=self)


Test patch:
diff --git a/conans/test/functional/python_requires/python_requires_test.py b/conans/test/functional/python_requires/python_requires_test.py
--- a/conans/test/functional/python_requires/python_requires_test.py
+++ b/conans/test/functional/python_requires/python_requires_test.py
@@ -6,6 +6,7 @@
     NO_SETTINGS_PACKAGE_ID, create_local_git_repo
 from conans.paths import CONANFILE
 from parameterized import parameterized
+import time
 
 
 class PythonExtendTest(unittest.TestCase):
@@ -443,6 +444,39 @@ def build(self):
         self.assertIn("Pkg/0.1@lasote/testing: MyHelperOutput!", client.out)
         self.assertIn("Pkg/0.1@lasote/testing: MyOtherHelperOutput!", client.out)
 
+    def update_test(self):
+        client = TestClient(servers={"default": TestServer()},
+                            users={"default": [("lasote", "mypass")]})
+        conanfile = """from conans import ConanFile
+somevar = 42
+class MyConanfileBase(ConanFile):
+    pass
+"""
+        client.save({"conanfile.py": conanfile})
+        client.run("export . MyConanfileBase/1.1@lasote/testing")
+        client.run("upload * --confirm")
+
+        client2 = TestClient(servers=client.servers, users={"default": [("lasote", "mypass")]})
+
+        reuse = """from conans import python_requires
+base = python_requires("MyConanfileBase/1.1@lasote/testing")
+class PkgTest(base.MyConanfileBase):
+    def configure(self):
+        self.output.info("PYTHON REQUIRE VAR %s" % base.somevar)
+"""
+
+        client2.save({"conanfile.py": reuse})
+        client2.run("install .")
+        self.assertIn("conanfile.py: PYTHON REQUIRE VAR 42", client2.out)
+
+        client.save({"conanfile.py": conanfile.replace("42", "143")})
+        time.sleep(1)  # guarantee time offset
+        client.run("export . MyConanfileBase/1.1@lasote/testing")
+        client.run("upload * --confirm")
+
+        client2.run("install . --update")
+        self.assertIn("conanfile.py: PYTHON REQUIRE VAR 143", client2.out)
+
 
 class PythonRequiresNestedTest(unittest.TestCase):