Repository: meltano/meltano

Issue:
Fix `KeyError: venv_name` and similar errors in lockfiles



Discussion:

Resolving patch:
diff --git a/src/meltano/core/plugin_lock_service.py b/src/meltano/core/plugin_lock_service.py
--- a/src/meltano/core/plugin_lock_service.py
+++ b/src/meltano/core/plugin_lock_service.py
@@ -7,7 +7,8 @@
 
 from structlog.stdlib import get_logger
 
-from meltano.core.plugin.base import BasePlugin, PluginRef, StandalonePlugin, Variant
+from meltano.core.plugin.base import PluginRef, StandalonePlugin, Variant
+from meltano.core.plugin.project_plugin import ProjectPlugin
 from meltano.core.project import Project
 
 logger = get_logger(__name__)
@@ -42,7 +43,7 @@ def __init__(self, project: Project):
 
     def save(
         self,
-        plugin: BasePlugin,
+        plugin: ProjectPlugin,
         *,
         overwrite: bool = False,
         exists_ok: bool = False,
@@ -58,11 +59,12 @@ def save(
             LockfileAlreadyExistsError: If the lockfile already exists and is not
                 flagged for overwriting.
         """
-        variant = None if plugin.variant == Variant.DEFAULT_NAME else plugin.variant
-
-        logger.info(f"Locking a {type(plugin)}")
+        base_plugin = plugin.parent
+        variant = (
+            None if base_plugin.variant == Variant.DEFAULT_NAME else base_plugin.variant
+        )
 
-        plugin_def = plugin.definition
+        plugin_def = base_plugin.definition
         path = self.project.plugin_lock_path(
             plugin_def.type,
             plugin_def.name,
@@ -76,13 +78,13 @@ def save(
                 plugin,
             )
 
-        variant = plugin_def.find_variant(plugin.variant)
+        variant = plugin_def.find_variant(base_plugin.variant)
         locked_def = StandalonePlugin.from_variant(
             variant,
-            plugin.name,
-            plugin.namespace,
-            plugin.type,
-            label=plugin.label,
+            base_plugin.name,
+            base_plugin.namespace,
+            base_plugin.type,
+            label=base_plugin.label,
         )
 
         with path.open("w") as lockfile:
diff --git a/src/meltano/core/project_add_service.py b/src/meltano/core/project_add_service.py
--- a/src/meltano/core/project_add_service.py
+++ b/src/meltano/core/project_add_service.py
@@ -80,7 +80,7 @@ def add(
 
             if lock and not added.is_custom():
                 self.plugins_service.lock_service.save(
-                    added.parent,
+                    added,
                     exists_ok=plugin.inherit_from is not None,
                 )
 


Test patch:
diff --git a/tests/meltano/core/test_plugin_lock_service.py b/tests/meltano/core/test_plugin_lock_service.py
--- a/tests/meltano/core/test_plugin_lock_service.py
+++ b/tests/meltano/core/test_plugin_lock_service.py
@@ -1,7 +1,11 @@
 import pytest
 
 from meltano.core.plugin.base import BasePlugin, PluginDefinition, PluginType
-from meltano.core.plugin_lock_service import PluginLockService
+from meltano.core.plugin.project_plugin import ProjectPlugin
+from meltano.core.plugin_lock_service import (
+    LockfileAlreadyExistsError,
+    PluginLockService,
+)
 from meltano.core.project import Project
 
 
@@ -14,7 +18,8 @@ def subject(
     ):
         return PluginLockService(project)
 
-    def test_save(self, subject: PluginLockService, project: Project):
+    @pytest.fixture
+    def plugin(self):
         plugin_definition = PluginDefinition(
             PluginType.EXTRACTORS,
             name="tap-locked",
@@ -37,11 +42,33 @@ def test_save(self, subject: PluginLockService, project: Project):
             ],
         )
         variant = plugin_definition.find_variant("meltano")
-        plugin = BasePlugin(plugin_definition, variant)
-        assert plugin.variant == "meltano"
+        base_plugin = BasePlugin(plugin_definition, variant)
+
+        plugin = ProjectPlugin(
+            base_plugin.type,
+            base_plugin.name,
+            variant=variant,
+        )
+        plugin.parent = base_plugin
+        return plugin
 
-        lock_path = project.plugin_lock_path(plugin.type, plugin.name, "meltano")
+    def test_save(
+        self,
+        subject: PluginLockService,
+        project: Project,
+        plugin: ProjectPlugin,
+    ):
+        lock_path = project.plugin_lock_path(
+            plugin.type,
+            plugin.name,
+            plugin.variant.name,
+        )
         assert not lock_path.exists()
 
         subject.save(plugin)
         assert lock_path.exists()
+
+        with pytest.raises(LockfileAlreadyExistsError) as exc_info:
+            subject.save(plugin)
+
+        assert exc_info.value.plugin == plugin  # noqa: WPS441