Repository: tweag/FawltyDeps

Issue:
environment.yml with pip and -e .
**Describe the bug**
pip with -e ./ in an environment.yml file causes:

```
ERROR:fawltydeps.extract_deps.environment_yml_parser:Failed to parse Pip dependencies in environment.yml: Expected package name at the start of dependency specifier
    -e ./
    ^

```

**To Reproduce**
Steps to reproduce the behavior:


```
name: myenv
channels:
  - conda-forge
dependencies:
  # Core Python and conda packages
  - python=3.12
  - pip
  - setuptools

  # Pip packages
  - pip:
    - -e ./
```

**Expected behavior**
Either just ignore or not throw ERROR

**Environment**
- using micromamba in linux runner for CI

Hints from issue comments:
@jherland: Thanks for reporting! I don't think FawltyDeps has a good understanding of editable dependencies, yet, but this "self-dependency" appears to be a common case and probably worthy of a special workaround to simply ignore it.@jherland: Upon inspecting the code, I notice that we already have similar workarounds for ignoring self-dependencies in `pyproject.toml` and `pixi.toml` files, so adding a similar workaround for `environment.yml` (and `requirements.txt`) files should be straightforward...@aacovski: > Upon inspecting the code, I notice that we already have similar workarounds for ignoring self-dependencies in `pyproject.toml` and `pixi.toml` files, so adding a similar workaround for `environment.yml` (and `requirements.txt`) files should be straightforward...

yes, I was wondering if I could just ignore it in my pyproject.toml but it doesn't fail the check: I guess it's more of a warning than an error? It doesn't need a workaround luckily

Solution patch:
diff --git a/fawltydeps/extract_deps/environment_yml_parser.py b/fawltydeps/extract_deps/environment_yml_parser.py
index d5a9d0c..bcacd2c 100644
--- a/fawltydeps/extract_deps/environment_yml_parser.py
+++ b/fawltydeps/extract_deps/environment_yml_parser.py
@@ -64,7 +64,15 @@ def parse_environment_yml_deps(
                 yield parse_item(dep_item, source)
             except ValueError as e:
                 # InvalidCondaRequirement or packaging.requirements.InvalidRequirement
-                logger.error(f"{error_msg} {e}")
+                # Skip editable self-dependencies silently:
+                if dep_item.removesuffix("/") in {
+                    "-e .",
+                    "--editable .",
+                    "--editable=.",
+                }:
+                    pass
+                else:
+                    logger.error(f"{error_msg} {e}")
         elif isinstance(dep_item, dict) and len(dep_item) == 1 and "pip" in dep_item:
             pip_deps = dep_item.get("pip")
             yield from parse_environment_yml_deps(
diff --git a/fawltydeps/extract_deps/requirements_parser.py b/fawltydeps/extract_deps/requirements_parser.py
index e62a3a9..3d08a38 100644
--- a/fawltydeps/extract_deps/requirements_parser.py
+++ b/fawltydeps/extract_deps/requirements_parser.py
@@ -27,7 +27,7 @@ def parse_requirements_txt(path: Path) -> Iterator[DeclaredDependency]:
     source = Location(path)
     parsed = RequirementsFile.from_file(path)
     for dep in parsed.requirements:
-        if dep.name:
+        if dep.name:  # This transparently skips pip options like '-e .'
             yield DeclaredDependency(dep.name, source)
 
     if parsed.invalid_lines and logger.isEnabledFor(logging.DEBUG):


Test patch:
diff --git a/tests/test_extract_deps_environment_yml.py b/tests/test_extract_deps_environment_yml.py
index c53685d..fd15eb7 100644
--- a/tests/test_extract_deps_environment_yml.py
+++ b/tests/test_extract_deps_environment_yml.py
@@ -218,17 +218,40 @@ from fawltydeps.types import DeclaredDependency, Location
             ],
             id="cartopy_example",
         ),
+        pytest.param(
+            """\
+            name: myenv
+            channels:
+              - conda-forge
+            dependencies:
+              # Core Python and conda packages
+              - python=3.12
+              - pip
+              - setuptools
+
+              # Pip packages
+              - pip:
+                - -e ./
+                - -e .
+                - --editable .
+                - --editable=.
+            """,
+            ["pip", "setuptools"],
+            id="ignore_editable_self_dependency",
+        ),
     ],
 )
 def test_parse_environment_yml__wellformed_dependencies__yields_dependencies(
-    write_tmp_files, environment_yml, expected_deps
+    write_tmp_files, environment_yml, expected_deps, caplog
 ):
     tmp_path = write_tmp_files({"environment.yml": environment_yml})
     path = tmp_path / "environment.yml"
 
+    caplog.set_level(logging.ERROR)
     result = list(parse_environment_yml(path))
     expected = [DeclaredDependency(dep, Location(path)) for dep in expected_deps]
     assert result == expected
+    assert caplog.text == ""  # No error messages
 
 
 @dataclass
diff --git a/tests/test_extract_deps_success.py b/tests/test_extract_deps_success.py
index 0504fdd..267d094 100644
--- a/tests/test_extract_deps_success.py
+++ b/tests/test_extract_deps_success.py
@@ -58,10 +58,12 @@ from .utils import (
         pytest.param(
             """\
             -e .
+            --editable .
+            --editable=.
             click >=1.2
             """,
             ["click"],
-            id="requirements_with_option__ignores_option",
+            id="requirements_with_options__ignores_options",
         ),
         pytest.param(
             """\