Repository: tophat/syrupy

Issue:
`--snapshot-default-extension` doesn't support pytest 7 `pythonpath`
**Describe the bug**

`--snapshot-default-extension` doesn't support pytest 7's `pythonpath` configuration option, for pytest-only additions to the Python path.

For my project, I'm using `--snapshot-default-extension` so the right extension and serializer are in place, before Syrupy begins its reporting. My Syrupy extensions are for tests only, so they live outside of my src/ folder. Only the src/ folder of my project seems to be on the default Python path. So when running tests, I need to tell Syrupy about my extensions, somehow. I'd love to use the vanilla `pytest` command directly, configured in pyproject.toml, without having to pass a custom `PYTHONPATH` to `pytest` every time.

**To reproduce**

See my branch, [john-kurkowski/syrupy#default-extension-pythonpath](https://github.com/john-kurkowski/syrupy/compare/main..default-extension-pythonpath). In the final commit,  https://github.com/john-kurkowski/syrupy/commit/ea9779371583253c03b0bdf47c09ca6f5526d909, switching from modifying `sys.path` to setting pytest's `--pythonpath` breaks 2/3 of the branch's test cases. **EDIT:** pytest's `pythonpath` an INI configuration option, not CLI.

```diff
diff --git a/tests/integration/test_snapshot_option_extension.py b/tests/integration/test_snapshot_option_extension.py
index de8e807..42b2eec 100644
--- a/tests/integration/test_snapshot_option_extension.py
+++ b/tests/integration/test_snapshot_option_extension.py
@@ -26,11 +26,11 @@ def testfile(testdir):
     return testdir
 
 
-def test_snapshot_default_extension_option_success(monkeypatch, testfile):
-    monkeypatch.syspath_prepend(testfile.tmpdir)
-
+def test_snapshot_default_extension_option_success(testfile):
     result = testfile.runpytest(
         "-v",
+        "--pythonpath",
+        testfile.tmpdir,
         "--snapshot-update",
         "--snapshot-default-extension",
         "extension_file.MySingleFileExtension",
@@ -63,11 +63,11 @@ def test_snapshot_default_extension_option_module_not_found(testfile):
     assert result.ret
 
 
-def test_snapshot_default_extension_option_member_not_found(monkeypatch, testfile):
-    monkeypatch.syspath_prepend(testfile.tmpdir)
-
+def test_snapshot_default_extension_option_member_not_found(testfile):
     result = testfile.runpytest(
         "-v",
+        "--pythonpath",
+        testfile.tmpdir,
         "--snapshot-update",
         "--snapshot-default-extension",
         "extension_file.DoesNotExistExtension",
```

**Expected behavior**

Tests in my branch should pass.

**Environment:**

- OS: macOS
- Syrupy Version: 4.0.1
- Python Version: 3.11.1

**Workaround**

Set `PYTHONPATH` prior to invoking the pytest CLI. 

```sh
PYTHONPATH=path/to/my/extensions/folder pytest --snapshot-default-extension some_module.SomeExtension
```



Discussion:
noahnu: Tests are failing on windows
john-kurkowski: I see. This'll be fun to track down without Windows. 😄 I'll noodle on it.

> ```
> FAILED tests/integration/test_snapshot_option_extension_pythonpath.py::test_snapshot_default_extension_option_success - Failed:  nomatch: '1 snapshot generated\\.'
> …
> FAILED tests/integration/test_snapshot_option_extension_pythonpath.py::test_snapshot_default_extension_option_failure - Failed:  nomatch: ".*: Member 'DoesNotExistExtension' not found.*"
> …
> ================== 2 failed, 243 passed, 4 xpassed in 30.35s ==================
> ```

Patch:
diff --git a/poetry.lock b/poetry.lock
index 069852d..79bedb2 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1173,14 +1173,14 @@ jeepney = ">=0.6"
 
 [[package]]
 name = "semver"
-version = "2.13.0"
-description = "Python helper for Semantic Versioning (http://semver.org/)"
+version = "3.0.0"
+description = "Python helper for Semantic Versioning (https://semver.org)"
 category = "dev"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+python-versions = ">=3.7"
 files = [
-    {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"},
-    {file = "semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"},
+    {file = "semver-3.0.0-py3-none-any.whl", hash = "sha256:ab4f69fb1d1ecfb5d81f96411403d7a611fa788c45d252cf5b408025df3ab6ce"},
+    {file = "semver-3.0.0.tar.gz", hash = "sha256:94df43924c4521ec7d307fc86da1531db6c2c33d9d5cdc3e64cca0eb68569269"},
 ]
 
 [[package]]
@@ -1329,4 +1329,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more
 [metadata]
 lock-version = "2.0"
 python-versions = '>=3.8.1,<4'
-content-hash = "20ccfa73b2257c72d63a634b9381c9210f0c961511558b679caa42e3bd7558ee"
+content-hash = "b1de5497b88df972689ae2b2a96f6cfc61d5270b5839b66a98cc12a261d9473d"
diff --git a/pyproject.toml b/pyproject.toml
index 1cc9512..8c9ded1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -47,7 +47,7 @@ flake8-bugbear = '^23.2.13'
 flake8-builtins = '^2.1.0'
 flake8-comprehensions = '^3.10.1'
 twine = '^4.0.2'
-semver = '^2.13.0'
+semver = '^3.0.0'
 setuptools-scm = '^7.1.0'
 debugpy = '^1.6.6'
 
diff --git a/src/syrupy/__init__.py b/src/syrupy/__init__.py
index 5f1a646..560ddb0 100644
--- a/src/syrupy/__init__.py
+++ b/src/syrupy/__init__.py
@@ -41,11 +41,6 @@ def __import_extension(value: Optional[str]) -> Any:
         raise argparse.ArgumentTypeError(e)
 
 
-def __default_extension_option(value: Optional[str]) -> Any:
-    __import_extension(value)
-    return value
-
-
 def pytest_addoption(parser: Any) -> None:
     """
     Exposes snapshot plugin configuration to pytest.
@@ -78,7 +73,6 @@ def pytest_addoption(parser: Any) -> None:
     # all pytest options to be serializable.
     group.addoption(
         "--snapshot-default-extension",
-        type=__default_extension_option,
         default=None,
         dest="default_extension",
         help="Specify the default snapshot extension",
diff --git a/src/syrupy/utils.py b/src/syrupy/utils.py
index c086173..4dd08cd 100644
--- a/src/syrupy/utils.py
+++ b/src/syrupy/utils.py
@@ -48,11 +48,14 @@ def import_module_member(path: str) -> Any:
             )
         )
     try:
-        return getattr(import_module(module_name), module_member_name)
+        module = import_module(module_name)
     except ModuleNotFoundError:
         raise FailedToLoadModuleMember(
             gettext("Module '{}' does not exist.").format(module_name)
         )
+
+    try:
+        return getattr(module, module_member_name)
     except AttributeError:
         raise FailedToLoadModuleMember(
             gettext("Member '{}' not found in module '{}'.").format(
diff --git a/tasks/build.py b/tasks/build.py
index ef05530..e66f769 100644
--- a/tasks/build.py
+++ b/tasks/build.py
@@ -58,7 +58,7 @@ def release(ctx, dry_run=True, version=None):
     """
     Build and publish package to pypi index based on scm version
     """
-    from semver import parse_version_info
+    from semver.version import Version
 
     if not dry_run and not os.environ.get("CI"):
         print("This is a CI only command")
@@ -72,7 +72,8 @@ def release(ctx, dry_run=True, version=None):
         exit(1)
 
     try:
-        should_publish_to_pypi = not dry_run and parse_version_info(version)
+        Version.parse(version)
+        should_publish_to_pypi = not dry_run
     except ValueError:
         should_publish_to_pypi = False
 


Test patch:
diff --git a/tests/integration/test_snapshot_option_extension.py b/tests/integration/test_snapshot_option_extension.py
index 546da3f..884f92a 100644
--- a/tests/integration/test_snapshot_option_extension.py
+++ b/tests/integration/test_snapshot_option_extension.py
@@ -37,12 +37,7 @@ def test_snapshot_default_extension_option_failure(testfile):
         "--snapshot-default-extension",
         "syrupy.extensions.amber.DoesNotExistExtension",
     )
-    result.stderr.re_match_lines(
-        (
-            r".*error: argument --snapshot-default-extension"
-            r": Member 'DoesNotExistExtension' not found.*",
-        )
-    )
+    result.stdout.re_match_lines((r".*: Member 'DoesNotExistExtension' not found.*",))
     assert not Path(
         testfile.tmpdir, "__snapshots__", "test_file", "test_default.raw"
     ).exists()
diff --git a/tests/integration/test_snapshot_option_extension_pythonpath.py b/tests/integration/test_snapshot_option_extension_pythonpath.py
new file mode 100644
index 0000000..3570154
--- /dev/null
+++ b/tests/integration/test_snapshot_option_extension_pythonpath.py
@@ -0,0 +1,101 @@
+import textwrap
+from pathlib import Path
+
+import pytest
+
+import syrupy
+
+SUBDIR = "subdir_not_on_default_path"
+
+
+@pytest.fixture(autouse=True)
+def cache_clear():
+    syrupy.__import_extension.cache_clear()
+
+
+@pytest.fixture
+def testfile(pytester):
+    subdir = pytester.mkpydir(SUBDIR)
+
+    Path(
+        subdir,
+        "extension_file.py",
+    ).write_text(
+        data=textwrap.dedent(
+            """
+            from syrupy.extensions.single_file import SingleFileSnapshotExtension
+            class MySingleFileExtension(SingleFileSnapshotExtension):
+                pass
+            """
+        ),
+        encoding="utf-8",
+    )
+
+    pytester.makepyfile(
+        test_file=(
+            """
+            def test_default(snapshot):
+                assert b"default extension serializer" == snapshot
+            """
+        )
+    )
+
+    return pytester
+
+
+def test_snapshot_default_extension_option_success(testfile):
+    testfile.makeini(
+        f"""
+        [pytest]
+        pythonpath =
+            {Path(testfile.path, SUBDIR).as_posix()}
+    """
+    )
+
+    result = testfile.runpytest(
+        "-v",
+        "--snapshot-update",
+        "--snapshot-default-extension",
+        "extension_file.MySingleFileExtension",
+    )
+    result.stdout.re_match_lines((r"1 snapshot generated\."))
+    assert Path(
+        testfile.path, "__snapshots__", "test_file", "test_default.raw"
+    ).exists()
+    assert not result.ret
+
+
+def test_snapshot_default_extension_option_module_not_found(testfile):
+    result = testfile.runpytest(
+        "-v",
+        "--snapshot-update",
+        "--snapshot-default-extension",
+        "extension_file.MySingleFileExtension",
+    )
+    result.stdout.re_match_lines((r".*: Module 'extension_file' does not exist.*",))
+    assert not Path(
+        testfile.path, "__snapshots__", "test_file", "test_default.raw"
+    ).exists()
+    assert result.ret
+
+
+def test_snapshot_default_extension_option_failure(testfile):
+    testfile.makeini(
+        f"""
+        [pytest]
+        pythonpath =
+            {Path(testfile.path, SUBDIR).as_posix()}
+    """
+    )
+
+    result = testfile.runpytest(
+        "-v",
+        "--snapshot-update",
+        "--snapshot-default-extension",
+        "extension_file.DoesNotExistExtension",
+    )
+    result.stdout.re_match_lines((r".*: Member 'DoesNotExistExtension' not found.*",))
+    assert not Path(
+        testfile.path, "__snapshots__", "test_file", "test_default.raw"
+    ).exists()
+    assert result.ret