Repository: conan-io/conan
Pull request #17498 (2024-12-18 09:06:40)

fix LocalAPI editables
Changelog: Fix ``LocalAPI`` definition of editables when calling ``editable_add``
Docs: Omit

Close https://github.com/conan-io/conan/issues/17484



Patch:
diff --git a/conan/api/subapi/install.py b/conan/api/subapi/install.py
index 22750c874..5177992d4 100644
--- a/conan/api/subapi/install.py
+++ b/conan/api/subapi/install.py
@@ -22,8 +22,7 @@ class InstallAPI:
         :param remotes:
         """
         app = ConanBasicApp(self.conan_api)
-        installer = BinaryInstaller(app, self.conan_api.config.global_conf,
-                                    self.conan_api.local.editable_packages)
+        installer = BinaryInstaller(app, self.conan_api.config.global_conf, app.editable_packages)
         install_graph = InstallGraph(deps_graph)
         install_graph.raise_errors()
         install_order = install_graph.install_order()
@@ -36,8 +35,7 @@ class InstallAPI:
         :param graph: Dependency graph to intall packages for
         """
         app = ConanBasicApp(self.conan_api)
-        installer = BinaryInstaller(app, self.conan_api.config.global_conf,
-                                    self.conan_api.local.editable_packages)
+        installer = BinaryInstaller(app, self.conan_api.config.global_conf, app.editable_packages)
         installer.install_system_requires(graph, only_info)
 
     def install_sources(self, graph, remotes):
@@ -47,8 +45,7 @@ class InstallAPI:
         :param graph: Dependency graph to install packages for
         """
         app = ConanBasicApp(self.conan_api)
-        installer = BinaryInstaller(app, self.conan_api.config.global_conf,
-                                    self.conan_api.local.editable_packages)
+        installer = BinaryInstaller(app, self.conan_api.config.global_conf, app.editable_packages)
         installer.install_sources(graph, remotes)
 
     # TODO: Look for a better name
diff --git a/conan/api/subapi/local.py b/conan/api/subapi/local.py
index 6145afc1d..24c47d34e 100644
--- a/conan/api/subapi/local.py
+++ b/conan/api/subapi/local.py
@@ -19,9 +19,6 @@ class LocalAPI:
     def __init__(self, conan_api):
         self._conan_api = conan_api
         self.editable_packages = EditablePackages(conan_api.home_folder)
-        editables = conan_api.workspace.editables()
-        if editables:
-            self.editable_packages.edited_refs.update(editables)
 
     @staticmethod
     def get_conanfile_path(path, cwd, py):
@@ -50,30 +47,27 @@ class LocalAPI:
 
     def editable_add(self, path, name=None, version=None, user=None, channel=None, cwd=None,
                      output_folder=None, remotes=None):
-        path = self._conan_api.local.get_conanfile_path(path, cwd, py=True)
+        path = self.get_conanfile_path(path, cwd, py=True)
         app = ConanApp(self._conan_api)
         conanfile = app.loader.load_named(path, name, version, user, channel, remotes=remotes)
         if conanfile.name is None or conanfile.version is None:
             raise ConanException("Editable package recipe should declare its name and version")
         ref = RecipeReference(conanfile.name, conanfile.version, conanfile.user, conanfile.channel)
         # Retrieve conanfile.py from target_path
-        target_path = self._conan_api.local.get_conanfile_path(path=path, cwd=cwd, py=True)
+        target_path = self.get_conanfile_path(path=path, cwd=cwd, py=True)
         output_folder = make_abs_path(output_folder) if output_folder else None
         # Check the conanfile is there, and name/version matches
-        editable_packages = EditablePackages(self._conan_api.home_folder)
-        editable_packages.add(ref, target_path, output_folder=output_folder)
+        self.editable_packages.add(ref, target_path, output_folder=output_folder)
         return ref
 
     def editable_remove(self, path=None, requires=None, cwd=None):
         if path:
             path = make_abs_path(path, cwd)
             path = os.path.join(path, "conanfile.py")
-        editable_packages = EditablePackages(self._conan_api.home_folder)
-        return editable_packages.remove(path, requires)
+        return self.editable_packages.remove(path, requires)
 
     def editable_list(self):
-        editable_packages = EditablePackages(self._conan_api.home_folder)
-        return editable_packages.edited_refs
+        return self.editable_packages.edited_refs
 
     def source(self, path, name=None, version=None, user=None, channel=None, remotes=None):
         """ calls the 'source()' method of the current (user folder) conanfile.py
diff --git a/conan/api/subapi/workspace.py b/conan/api/subapi/workspace.py
index 3a5fe6c49..18c33d40c 100644
--- a/conan/api/subapi/workspace.py
+++ b/conan/api/subapi/workspace.py
@@ -27,7 +27,8 @@ class WorkspaceAPI:
     def config_folder(self):
         return self._workspace.config_folder()
 
-    def editables(self):
+    @property
+    def editable_packages(self):
         return self._workspace.editables()
 
     def open(self, require, remotes, cwd=None):
diff --git a/conan/cli/cli.py b/conan/cli/cli.py
index 766b04f32..bd6c15a24 100644
--- a/conan/cli/cli.py
+++ b/conan/cli/cli.py
@@ -249,7 +249,7 @@ def _warn_python_version():
 def _warn_frozen_center(conan_api):
     remotes = conan_api.remotes.list()
     for r in remotes:
-        if r.url == "https://center.conan.io":
+        if r.url == "https://center.conan.io" and not r.disabled:
             ConanOutput().warning(
                 "The remote 'https://center.conan.io' is now frozen and has been replaced by 'https://center2.conan.io'. \n"
                 "Starting from Conan 2.9.2, the default remote is 'center2.conan.io'. \n"
diff --git a/conan/internal/api/local/editable.py b/conan/internal/api/local/editable.py
index 6a3360488..c2e543e13 100644
--- a/conan/internal/api/local/editable.py
+++ b/conan/internal/api/local/editable.py
@@ -12,7 +12,10 @@ EDITABLE_PACKAGES_FILE = 'editable_packages.json'
 
 
 class EditablePackages:
-    def __init__(self, cache_folder):
+    def __init__(self, cache_folder=None):
+        if cache_folder is None:
+            self._edited_refs = {}
+            return
         self._edited_file = normpath(join(cache_folder, EDITABLE_PACKAGES_FILE))
         if os.path.exists(self._edited_file):
             edited = load(self._edited_file)
@@ -22,6 +25,17 @@ class EditablePackages:
         else:
             self._edited_refs = {}  # {ref: {"path": path, "layout": layout}}
 
+    def update_copy(self, ws_editables):
+        """
+        Create a new instance with the union of the editable packages of self and other
+        """
+        if ws_editables is None:
+            return self
+        result = EditablePackages()
+        result._edited_refs = self._edited_refs.copy()
+        result._edited_refs.update(ws_editables)
+        return result
+
     @property
     def edited_refs(self):
         return self._edited_refs
diff --git a/conan/internal/conan_app.py b/conan/internal/conan_app.py
index 1ba4a6579..14d114248 100644
--- a/conan/internal/conan_app.py
+++ b/conan/internal/conan_app.py
@@ -55,6 +55,9 @@ class ConanBasicApp:
         auth_manager = ConanApiAuthManager(self.requester, cache_folder, localdb, global_conf)
         # Handle remote connections
         self.remote_manager = RemoteManager(self.cache, auth_manager, cache_folder)
+        global_editables = conan_api.local.editable_packages
+        ws_editables = conan_api.workspace.editable_packages
+        self.editable_packages = global_editables.update_copy(ws_editables)
 
 
 class ConanApp(ConanBasicApp):
@@ -63,8 +66,8 @@ class ConanApp(ConanBasicApp):
         - LocalAPI to read editable packages
         """
         super().__init__(conan_api)
-        self.proxy = ConanProxy(self, conan_api.local.editable_packages)
-        self.range_resolver = RangeResolver(self, self.global_conf, conan_api.local.editable_packages)
+        self.proxy = ConanProxy(self, self.editable_packages)
+        self.range_resolver = RangeResolver(self, self.global_conf, self.editable_packages)
 
         self.pyreq_loader = PyRequireLoader(self, self.global_conf)
         cmd_wrap = CmdWrapper(HomePaths(self.cache_folder).wrapper_path)


Test patch:
diff --git a/test/integration/command/remote_test.py b/test/integration/command/remote_test.py
index 8a5b5b53e..4aab1d82d 100644
--- a/test/integration/command/remote_test.py
+++ b/test/integration/command/remote_test.py
@@ -366,6 +366,9 @@ def test_using_frozen_center():
     c.run("remote update conancenter --url=https://center.conan.io")
     assert "The remote 'https://center.conan.io' is now frozen and has been replaced by 'https://center2.conan.io'." in c.out
 
+    c.run("remote disable conancenter")
+    assert "The remote 'https://center.conan.io' is now frozen and has been replaced by 'https://center2.conan.io'." not in c.out
+
 
 def test_wrong_remotes_json_file():
     c = TestClient(light=True)
diff --git a/test/integration/conan_api/test_local_api.py b/test/integration/conan_api/test_local_api.py
new file mode 100644
index 000000000..df681ce29
--- /dev/null
+++ b/test/integration/conan_api/test_local_api.py
@@ -0,0 +1,18 @@
+import os
+
+from conan.api.conan_api import ConanAPI
+from conan.test.assets.genconanfile import GenConanfile
+from conan.test.utils.test_files import temp_folder
+from conans.model.recipe_ref import RecipeReference
+from conans.util.files import save
+
+
+def test_local_api():
+    # https://github.com/conan-io/conan/issues/17484
+    current_folder = temp_folder()
+    cache_folder = temp_folder()
+    save(os.path.join(current_folder, "conanfile.py"), str(GenConanfile("foo", "1.0")))
+    api = ConanAPI(cache_folder)
+    assert api.local.editable_packages.edited_refs == {}
+    api.local.editable_add(".", cwd=current_folder)
+    assert list(api.local.editable_packages.edited_refs) == [RecipeReference.loads("foo/1.0")]