diff --git a/conan/api/model.py b/conan/api/model.py index e0c6fe91dd3..4c6132bbd98 100644 --- a/conan/api/model.py +++ b/conan/api/model.py @@ -25,6 +25,7 @@ def __init__(self, name, url, verify_ssl=True, disabled=False, allowed_packages= self.disabled = disabled self.allowed_packages = allowed_packages self.remote_type = remote_type + self.caching = {} def __eq__(self, other): if other is None: @@ -45,6 +46,9 @@ def __str__(self): def __repr__(self): return str(self) + def invalidate_cache(self): + self.caching = {} + class MultiPackagesList: def __init__(self): diff --git a/conans/client/remote_manager.py b/conans/client/remote_manager.py index 5e6d21b7511..1257b889c2c 100644 --- a/conans/client/remote_manager.py +++ b/conans/client/remote_manager.py @@ -187,7 +187,13 @@ def _get_package(self, layout, pref, remote, scoped_output, metadata): raise def search_recipes(self, remote, pattern): - return self._call_remote(remote, "search", pattern) + cached_method = remote.caching.setdefault("search_recipes", {}) + try: + return cached_method[pattern] + except KeyError: + result = self._call_remote(remote, "search", pattern) + cached_method[pattern] = result + return result def search_packages(self, remote, ref): packages = self._call_remote(remote, "search_packages", ref) @@ -234,7 +240,14 @@ def get_latest_package_reference(self, pref, remote, info=None) -> PkgReference: if k in ("shared", "fPIC", "header_only")] if options: headers['Conan-PkgID-Options'] = ';'.join(options) - return self._call_remote(remote, "get_latest_package_reference", pref, headers=headers) + + cached_method = remote.caching.setdefault("get_latest_package_reference", {}) + try: + return cached_method[pref] + except KeyError: + result = self._call_remote(remote, "get_latest_package_reference", pref, headers=headers) + cached_method[pref] = result + return result def get_recipe_revision_reference(self, ref, remote) -> bool: assert ref.revision is not None, "recipe_exists needs a revision" diff --git a/test/integration/command_v2/custom_commands_test.py b/test/integration/command_v2/custom_commands_test.py index c28b9d50e43..cea9c844b00 100644 --- a/test/integration/command_v2/custom_commands_test.py +++ b/test/integration/command_v2/custom_commands_test.py @@ -419,3 +419,53 @@ def mycommand(conan_api, parser, *args, **kwargs): c.run("export .") c.run("mycommand myremote myurl") assert json.loads(c.stdout) == {"myremote": "myurl"} + + +class TestCommandsRemoteCaching: + def test_remotes_cache(self): + complex_command = textwrap.dedent(""" + import json + from conan.cli.command import conan_command + from conan.api.output import ConanOutput + + @conan_command() + def mycache(conan_api, parser, *args, **kwargs): + \""" this is a command with subcommands \""" + + remotes = conan_api.remotes.list() + host = conan_api.profiles.get_profile(["default"]) + build = conan_api.profiles.get_profile(["default"]) + + deps_graph = conan_api.graph.load_graph_requires(["pkg/0.1"], None, host, build, + None, remotes, None, None) + conan_api.graph.analyze_binaries(deps_graph, None, remotes=remotes) + + # SECOND RUN!!! + # This will break if the caching is not working + remotes[0].url = "broken" + deps_graph = conan_api.graph.load_graph_requires(["pkg/0.1"], None, host, build, + None, remotes, None, None) + conan_api.graph.analyze_binaries(deps_graph, None, remotes=remotes) + + # Now invalidate the cache and see how it breaks + try: + remotes[0].invalidate_cache() + deps_graph = conan_api.graph.load_graph_requires(["pkg/0.1"], None, host, build, + None, remotes, None, None) + conan_api.graph.analyze_binaries(deps_graph, None, remotes=remotes) + except Exception as e: + ConanOutput().warning(f"Cache invalidated, as expected: {e}") + """) + + c = TestClient(default_server_user=True) + c.save_home({"extensions/commands/cmd_mycache.py": complex_command}) + + c.save({"dep/conanfile.py": GenConanfile("dep", "0.1"), + "pkg/conanfile.py": GenConanfile("pkg", "0.1").with_require("dep/0.1")}) + c.run("create dep") + c.run("create pkg") + c.run("upload * -r=default -c") + c.run("remove * -c") + c.run("mycache") + # Does not break unexpectedly, caching is working + assert "WARN: Cache invalidated, as expected: Invalid URL 'broken" in c.out