[Git][debian-gis-team/pytest-recording][upstream] New upstream version 0.13.4

Antonio Valentino (@antonio.valentino) gitlab at salsa.debian.org
Sun Aug 10 17:57:34 BST 2025



Antonio Valentino pushed to branch upstream at Debian GIS Project / pytest-recording


Commits:
2b52d032 by Antonio Valentino at 2025-08-09T10:07:37+00:00
New upstream version 0.13.4
- - - - -


22 changed files:

- .coveragerc
- .github/workflows/build.yml
- .github/workflows/release.yml
- .pre-commit-config.yaml
- .relint.yml
- CONTRIBUTING.rst
- README.rst
- docs/changelog.rst
- mypy.ini
- pyproject.toml
- src/pytest_recording/_vcr.py
- src/pytest_recording/hooks.py
- src/pytest_recording/plugin.py
- + src/pytest_recording/py.typed
- src/pytest_recording/utils.py
- + tests/__init__.py
- tests/test_blocking_network.py
- tests/test_plugin.py
- tests/test_recording.py
- tests/test_replaying.py
- tests/test_utils.py
- tox.ini


Changes:

=====================================
.coveragerc
=====================================
@@ -11,5 +11,5 @@ source =
 [report]
 show_missing = true
 precision = 2
-exclude_lines =
-   pragma: no cover
+exclude_also =
+   if TYPE_CHECKING:


=====================================
.github/workflows/build.yml
=====================================
@@ -16,7 +16,7 @@ on:
 jobs:
   pre-commit:
     name: Generic pre-commit checks
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-24.04
     steps:
       - uses: actions/checkout at v4
         with:
@@ -24,14 +24,14 @@ jobs:
 
       - uses: actions/setup-python at v5
         with:
-          python-version: 3.11
+          python-version: 3.13
 
       - run: pip install pre-commit
       - run: SKIP=mypy pre-commit run --all-files
 
   mypy:
     name: Mypy
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-24.04
     steps:
       - uses: actions/checkout at v4
         with:
@@ -39,41 +39,66 @@ jobs:
 
       - uses: actions/setup-python at v5
         with:
-          python-version: 3.11
+          python-version: 3.13
 
       - run: pip install pre-commit
       - run: pre-commit run mypy --all-files
 
   tests:
-    name: tests_${{ matrix.tox_job }}
+    name: Test ${{ matrix.tox_job }} on ${{ matrix.os_version }}
     runs-on: ${{ matrix.os_version }}
     strategy:
       matrix:
         include:
-          - tox_job: py37
-            python: "3.7"
-            os_version: "ubuntu-latest"
-          - tox_job: py38
-            python: "3.8"
-            os_version: "ubuntu-latest"
           - tox_job: py39
             python: "3.9"
-            os_version: "ubuntu-latest"
-          - tox_job: no_pycurl
-            python: "3.8"
-            os_version: "ubuntu-latest"
+            os_version: "ubuntu-24.04"
+          - tox_job: py39-no_pycurl
+            python: "3.9"
+            os_version: "ubuntu-24.04"
+          - tox_job: pypy3-no_pycurl
+            python: "pypy-3.10"
+            os_version: "ubuntu-24.04"
+          - tox_job: vcr_431
+            python: "3.9"
+            os_version: "ubuntu-24.04"
+          - tox_job: py310
+            python: "3.10"
+            os_version: "ubuntu-24.04"
+          - tox_job: py311
+            python: "3.11"
+            os_version: "ubuntu-24.04"
+          - tox_job: py312
+            python: "3.12"
+            os_version: "ubuntu-24.04"
+          - tox_job: py313
+            python: "3.13"
+            os_version: "ubuntu-24.04"
+
+          - tox_job: py39
+            python: "3.9"
+            os_version: "windows-2022"
+          - tox_job: py39-no_pycurl
+            python: "3.9"
+            os_version: "windows-2022"
+          - tox_job: pypy3-no_pycurl
+            python: "pypy-3.10"
+            os_version: "windows-2022"
           - tox_job: vcr_431
-            python: "3.8"
-            os_version: "ubuntu-latest"
+            python: "3.9"
+            os_version: "windows-2022"
           - tox_job: py310
             python: "3.10"
-            os_version: "ubuntu-latest"
+            os_version: "windows-2022"
           - tox_job: py311
             python: "3.11"
-            os_version: "ubuntu-latest"
+            os_version: "windows-2022"
           - tox_job: py312
             python: "3.12"
-            os_version: "ubuntu-latest"
+            os_version: "windows-2022"
+          - tox_job: py313
+            python: "3.13"
+            os_version: "windows-2022"
 
     steps:
       - uses: actions/checkout at v4
@@ -86,7 +111,9 @@ jobs:
 
       - run: pip install tox coverage
 
-      - run: sudo apt update && sudo apt install libcurl4-openssl-dev libssl-dev
+      - name: Install system deps on Linux
+        if: runner.os == 'Linux'
+        run: sudo apt update && sudo apt install -y libcurl4-openssl-dev libssl-dev
 
       - name: Run ${{ matrix.tox_job }} tox job
         run: tox -e ${{ matrix.tox_job }}
@@ -96,6 +123,6 @@ jobs:
       - run: coverage xml -i
 
       - name: Upload coverage to Codecov
-        uses: codecov/codecov-action at v4.5.0
+        uses: codecov/codecov-action at v5.4.2
         with:
           file: ./coverage.xml


=====================================
.github/workflows/release.yml
=====================================
@@ -10,10 +10,10 @@ jobs:
     runs-on: ubuntu-latest
     steps:
     - uses: actions/checkout at master
-    - name: Set up Python 3.11
+    - name: Set up Python 3.13
       uses: actions/setup-python at v5
       with:
-        python-version: 3.11
+        python-version: 3.13
 
     - run: pip install hatch
 


=====================================
.pre-commit-config.yaml
=====================================
@@ -1,9 +1,9 @@
 default_language_version:
-  python: python3.11
+  python: python3.13
 
 repos:
   - repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.5.0
+    rev: v5.0.0
     hooks:
       - id: check-yaml
       - id: end-of-file-fixer
@@ -20,34 +20,33 @@ repos:
       - id: gitlint
 
   - repo: https://github.com/adrienverge/yamllint
-    rev: v1.33.0
+    rev: v1.35.1
     hooks:
       - id: yamllint
 
   - repo: https://github.com/igorshubovych/markdownlint-cli
-    rev: v0.37.0
+    rev: v0.44.0
     hooks:
       - id: markdownlint
         language_version: system
 
   - repo: https://github.com/codingjoe/relint
-    rev: 3.1.0
+    rev: 3.3.1
     hooks:
       - id: relint
 
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.7.1
+    rev: v1.15.0
     hooks:
       - id: mypy
-        exclude: ^(docs/|tests/|setup.py).*$
-        additional_dependencies: [ "types-pycurl" ]
+        additional_dependencies: [ "types-pycurl", "types-PyYAML", "types-requests" ]
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.1.7
+    rev: v0.9.5
     hooks:
       - id: ruff-format
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.1.7
+    rev: v0.9.5
     hooks:
       - id: ruff


=====================================
.relint.yml
=====================================
@@ -1,24 +1,11 @@
-- name: Fix it now
-  pattern: "[fF][iI][xX][mM][eE]"
-  filename:
-    - "*.py"
-
 - name: No sys.path changes
   pattern: "sys\\.path\\.append|sys\\.path\\.insert"
-  filename:
-    - "src/**.py"
+  filePattern: src/.*\.py
 
 - name: IPython debug leftover
   pattern: "IPython\\.embed()"
-  filename:
-    - "*.py"
-
-- name: Leftover print
-  pattern: "print\\("
-  filename:
-    - ^(?!.*conftest).*\.py$
+  filePattern: .*\.py
 
 - name: Use relative imports
   pattern: "import pytest_recording|from pytest_recording"
-  filename:
-    - "src/**.py"
+  filePattern: src/.*\.py


=====================================
CONTRIBUTING.rst
=====================================
@@ -50,7 +50,7 @@ Submitting Pull Requests
 #. Follow **PEP-8** for naming and `ruff <https://github.com/astral-sh/ruff>`_ for formatting.
 #. Tests are run using ``tox``::
 
-    tox -e py37
+    tox -e py39
 
    The test environments above are usually enough to cover most cases locally.
 


=====================================
README.rst
=====================================
@@ -3,7 +3,7 @@ pytest-recording
 
 |codecov| |Build| |Version| |Python versions| |License|
 
-A pytest plugin that records network interactions in your tests via VCR.py.
+A pytest plugin powered by VCR.py to record and replay HTTP traffic.
 
 Features
 --------
@@ -22,8 +22,9 @@ This project can be installed via pip:
 
     pip install pytest-recording
 
-⚠️This project is not compatible with `pytest-vcr`, make sure to uninstall before ⚠️
+⚠️ Incompatibility Warning
 
+  If you have ``pytest-vcr`` installed, please uninstall it before using ``pytest-recording``, as the two plugins are incompatible.
 
 Usage
 -----
@@ -242,13 +243,37 @@ To run the tests:
 
 For more information, take a look at `our contributing guide <https://github.com/kiwicom/pytest-recording/blob/master/CONTRIBUTING.rst>`_
 
+Test Isolation for Package Maintainers
+--------------------------------------
+
+When running pytest-based tests in a packaging environment, globally installed plugins can break test suites by  
+injecting unexpected hooks or fixtures (e.g. ``pytest-pretty``) that your code isn’t designed for.  
+
+To guarantee a clean, reproducible test run:
+
+.. code-block:: console
+
+    export PYTEST_DISABLE_PLUGIN_AUTOLOAD=1
+    export PYTEST_PLUGINS=pytest_httpbin.plugin,pytest_mock,pytest_recording.plugin
+
+- **PYTEST_DISABLE_PLUGIN_AUTOLOAD**  
+  Disables loading of any plugins via setuptools entry-points; only those you explicitly list  
+  will be activated.
+
+- **PYTEST_PLUGINS**  
+  Comma-separated list of plugin modules pytest should load (the core plugin manager still  
+  discovers builtin plugins and `conftest.py`).
+
+Include these exports in your package’s build or CI script so that system-wide pytest plugins  
+(e.g. linting, formatting, or unrelated test helpers) cannot interfere with your tests.
+
 Python support
 --------------
 
 Pytest-recording supports:
 
-- CPython 3.7, 3.8, 3.9, 3.10, 3.11, and 3.12
-- PyPy 7 (3.6)
+- CPython 3.9, 3.10, 3.11, 3.12, and 3.13
+- PyPy 7.3 (3.10)
 
 License
 -------


=====================================
docs/changelog.rst
=====================================
@@ -6,6 +6,16 @@ Changelog
 `Unreleased`_
 -------------
 
+`0.13.4`_ - 2025-04-24
+----------------------
+
+- ``AttributeError`` on Windows. `#172`_
+
+`0.13.3`_ - 2025-04-24
+----------------------
+
+- Limit generated cassette names to prevent ``OSError``. `#172`_
+
 `0.13.2`_ - 2024-07-09
 ----------------------
 
@@ -204,7 +214,9 @@ Added
 
 - Initial public release
 
-.. _Unreleased: https://github.com/kiwicom/pytest-recording/compare/v0.13.2...HEAD
+.. _Unreleased: https://github.com/kiwicom/pytest-recording/compare/v0.13.4...HEAD
+.. _0.13.4: https://github.com/kiwicom/pytest-recording/compare/v0.13.3...v0.13.4
+.. _0.13.3: https://github.com/kiwicom/pytest-recording/compare/v0.13.2...v0.13.3
 .. _0.13.2: https://github.com/kiwicom/pytest-recording/compare/v0.13.1...v0.13.2
 .. _0.13.1: https://github.com/kiwicom/pytest-recording/compare/v0.13.0...v0.13.1
 .. _0.13.0: https://github.com/kiwicom/pytest-recording/compare/v0.12.2...v0.13.0
@@ -229,6 +241,8 @@ Added
 .. _0.3.0: https://github.com/kiwicom/pytest-recording/compare/v0.2.0...v0.3.0
 .. _0.2.0: https://github.com/kiwicom/pytest-recording/compare/v0.1.0...v0.2.0
 
+.. _#174: https://github.com/kiwicom/pytest-recording/issues/174
+.. _#172: https://github.com/kiwicom/pytest-recording/issues/172
 .. _#145: https://github.com/kiwicom/pytest-recording/issues/145
 .. _#118: https://github.com/kiwicom/pytest-recording/pull/118
 .. _#99: https://github.com/kiwicom/pytest-recording/pull/99


=====================================
mypy.ini
=====================================
@@ -1,5 +1,5 @@
 [mypy]
-python_version = 3.11
+python_version = 3.13
 show_error_context = true
 verbosity = 0
 ignore_missing_imports = false
@@ -14,3 +14,10 @@ disallow_untyped_calls = true
 disallow_untyped_defs = true
 disallow_incomplete_defs = true
 disallow_untyped_decorators = true
+
+[mypy-tests.*]
+disallow_untyped_calls = false
+disallow_untyped_defs = false
+
+[mypy-vcr.*]
+ignore_missing_imports = True


=====================================
pyproject.toml
=====================================
@@ -4,12 +4,12 @@ build-backend = "hatchling.build"
 
 [project]
 name = "pytest-recording"
-version = "0.13.2"
-description = "A pytest plugin that allows you recording of network interactions via VCR.py"
+version = "0.13.4"
+description = "A pytest plugin powered by VCR.py to record and replay HTTP traffic"
 keywords = ["pytest", "vcr", "network", "mock"]
 authors = [{ name = "Dmitry Dygalo", email = "dmitry at dygalo.dev" }]
 maintainers = [{ name = "Dmitry Dygalo", email = "dmitry at dygalo.dev" }]
-requires-python = ">=3.7"
+requires-python = ">=3.9"
 license = "MIT"
 readme = "README.rst"
 classifiers = [
@@ -18,17 +18,15 @@ classifiers = [
     "Intended Audience :: Developers",
     "License :: OSI Approved :: MIT License",
     "Topic :: Software Development :: Testing",
+    "Typing :: Typed",
     "Programming Language :: Python",
     "Programming Language :: Python :: 3 :: Only",
     "Programming Language :: Python :: 3",
-    "Programming Language :: Python :: 3.5",
-    "Programming Language :: Python :: 3.6",
-    "Programming Language :: Python :: 3.7",
-    "Programming Language :: Python :: 3.8",
     "Programming Language :: Python :: 3.9",
     "Programming Language :: Python :: 3.10",
     "Programming Language :: Python :: 3.11",
     "Programming Language :: Python :: 3.12",
+    "Programming Language :: Python :: 3.13",
     "Programming Language :: Python :: Implementation :: CPython",
     "Programming Language :: Python :: Implementation :: PyPy",
     "Operating System :: OS Independent",
@@ -42,7 +40,7 @@ tests = [
     "pytest-httpbin",
     "pytest-mock",
     "requests",
-    "Werkzeug==3.0.3"
+    "Werkzeug==3.1.3"
 ]
 dev = ["pytest_recording[tests]"]
 
@@ -57,6 +55,9 @@ recording = "pytest_recording.plugin"
 
 [tool.ruff]
 line-length = 120
+target-version = "py39"
+
+[tool.ruff.lint]
 select = [
     "E", # pycodestyle errors
     "W", # pycodestyle warnings
@@ -64,6 +65,9 @@ select = [
     "C", # flake8-comprehensions
     "B", # flake8-bugbear
     "D", # pydocstyle
+    "I", # isort
+    "T", # prints
+    "FIX", # fixme comments
 ]
 ignore = [
     "E501", # Line too long, handled by ruff
@@ -80,11 +84,10 @@ ignore = [
     "D213", # Multiline summary second line
     "D401", # Imperative mood
 ]
-target-version = "py37"
 
 [tool.ruff.format]
 skip-magic-trailing-comma = false
 
-[tool.ruff.isort]
+[tool.ruff.lint.isort]
 known-first-party = ["pytest_recording"]
 known-third-party = ["_pytest", "packaging", "pytest", "vcr", "yaml"]


=====================================
src/pytest_recording/_vcr.py
=====================================
@@ -1,3 +1,4 @@
+import hashlib
 import os
 from dataclasses import dataclass
 from itertools import chain, starmap
@@ -14,12 +15,19 @@ from vcr.serialize import deserialize
 try:
     # VCR.py >=5
     from vcr.cassette import CassetteNotFoundError
-except ImportError:
+except ImportError:  # pragma: no cover
     # VCR.py <5
     CassetteNotFoundError = ValueError
 
 from .utils import ConfigType, merge_kwargs, unique, unpack
 
+try:
+    # Try to get max filename length on Unix-like systems
+    MAX_FILENAME_LEN = os.pathconf(".", "PC_NAME_MAX")
+except (AttributeError, ValueError, OSError):
+    # Fallback for Windows or unsupported systems
+    MAX_FILENAME_LEN = 255
+
 
 def load_cassette(cassette_path: str, serializer: ModuleType) -> Tuple[List, List]:
     try:
@@ -63,6 +71,14 @@ def use_cassette(
 ) -> CassetteContextDecorator:
     """Create a VCR instance and return an appropriate context manager for the given cassette configuration."""
     merged_config = merge_kwargs(config, markers)
+
+    # Check `default_cassette` to prevent it from being too long.
+    suffix = merged_config.get("serializer", ".yaml")
+    if len(default_cassette) + len(suffix) > MAX_FILENAME_LEN:
+        hash_part = hashlib.md5(default_cassette.encode()).hexdigest()
+        prefix = default_cassette[: MAX_FILENAME_LEN - len(suffix) - len(hash_part) - 3]
+        default_cassette = f"{prefix}...{hash_part}"
+
     if "record_mode" in merged_config:
         record_mode = merged_config["record_mode"]
     path_transformer = get_path_transformer(merged_config)


=====================================
src/pytest_recording/hooks.py
=====================================
@@ -1,6 +1,7 @@
-from _pytest.config import Config
 from typing import TYPE_CHECKING
 
+from _pytest.config import Config
+
 if TYPE_CHECKING:
     from vcr import VCR
 


=====================================
src/pytest_recording/plugin.py
=====================================
@@ -1,5 +1,5 @@
 import os
-from typing import Any, Dict, Iterator, List, Optional, TYPE_CHECKING
+from typing import TYPE_CHECKING, Any, Dict, Iterator, List, Optional
 
 import pytest
 from _pytest.config import Config, PytestPluginManager
@@ -185,7 +185,7 @@ def default_cassette_name(request: SubRequest) -> str:
     marker = request.node.get_closest_marker("default_cassette")
     if marker is not None:
         assert marker.args, (
-            "You should pass the cassette name as an argument " "to the `pytest.mark.default_cassette` marker"
+            "You should pass the cassette name as an argument to the `pytest.mark.default_cassette` marker"
         )
         return marker.args[0]
     return get_default_cassette_name(request.cls, request.node.name)


=====================================
src/pytest_recording/py.typed
=====================================


=====================================
src/pytest_recording/utils.py
=====================================
@@ -1,6 +1,7 @@
 from copy import deepcopy
 from itertools import chain
 from typing import Any, Dict, Iterable, Iterator, List
+
 from _pytest.mark.structures import Mark
 
 ConfigType = Dict[str, Any]


=====================================
tests/__init__.py
=====================================
@@ -0,0 +1 @@
+# this file is only here so mypy can see it as a module and allow custom rules


=====================================
tests/test_blocking_network.py
=====================================
@@ -1,6 +1,23 @@
+import json
+import sys
+from io import BytesIO
+from socket import AF_INET, SOCK_RAW, SOCK_STREAM, socket
+
 import pytest
+import requests
+import vcr.errors
 from packaging import version
 
+from pytest_recording.network import blocking_context
+
+# Windows doesn’t have AF_NETLINK & AF_UNIX
+try:
+    from socket import AF_NETLINK, AF_UNIX
+except ImportError:
+    AF_NETLINK = None  # type: ignore[assignment]
+    AF_UNIX = None  # type: ignore[assignment]
+
+
 try:
     import pycurl
 except ImportError as exc:
@@ -8,7 +25,11 @@ except ImportError as exc:
         # Case with different SSL backends should be loud and visible
         # Could happen with development when environment is recreated (e.g. locally)
         raise
-    pycurl = None
+    pycurl = None  # type: ignore[assignment]
+
+
+skip_netlink = pytest.mark.skipif(AF_NETLINK is None, reason="AF_NETLINK not available on this platform")
+skip_unix = pytest.mark.skipif(AF_UNIX is None, reason="AF_UNIX not available on this platform")
 
 
 def assert_network_blocking(testdir, dirname):
@@ -130,77 +151,49 @@ def test_no_blocking(httpbin):
     result.assert_outcomes(passed=1)
 
 
-def test_unix_socket(testdir):
-    testdir.makepyfile(
-        """
-from socket import socket, AF_UNIX, SOCK_STREAM
-import pytest
-
-def call(socket_name):
-    s = socket(AF_UNIX, SOCK_STREAM)
+def call(socket_name, family, type):
+    s = socket(family, type)
     try:
         return s.connect(socket_name)
     finally:
         s.close()
 
+
+ at skip_unix
 @pytest.mark.block_network(allowed_hosts=["./allowed_socket"])
-def test_allowed():
+def test_block_network_allowed_socket():
     # Error from actual socket call, that means it was not blocked
     with pytest.raises(IOError):
-        call("./allowed_socket")
+        call("./allowed_socket", AF_UNIX, SOCK_STREAM)
+
 
+ at skip_unix
 @pytest.mark.block_network(allowed_hosts=["./allowed_socket"])
-def test_blocked():
+def test_block_network_blocked_socket():
     with pytest.raises(RuntimeError, match=r"^Network is disabled$"):
-        call("./blocked_socket")
-    """
-    )
-
-    result = testdir.runpytest()
-    result.assert_outcomes(passed=2)
+        call("./blocked_socket", AF_UNIX, SOCK_STREAM)
 
 
-def test_other_socket(testdir):
-    # When not AF_UNIX, AF_INET or AF_INET6 socket is used
-    testdir.makepyfile(
-        """
-from socket import socket, AF_NETLINK, SOCK_RAW
-import pytest
-
-def call():
-    s = socket(AF_NETLINK, SOCK_RAW)
-    try:
-        return s.connect((0, 0))
-    finally:
-        s.close()
-
+# When not AF_UNIX, AF_INET or AF_INET6 socket is used
+# Then socket.socket.connect call is blocked, even if resource name is in the allowed list
+ at skip_netlink
 @pytest.mark.block_network(allowed_hosts=["./allowed_socket", "127.0.0.1", "0"])
 def test_blocked():
     with pytest.raises(RuntimeError, match=r"^Network is disabled$"):
-        call()
-    """
-    )
-    # Then socket.socket.connect call is blocked, even if resource name is in the allowed list
-    result = testdir.runpytest()
-    result.assert_outcomes(passed=1)
+        call((0, 0), AF_NETLINK, SOCK_RAW)
 
 
-def test_block_network(testdir):
-    # When record is disabled
-    testdir.makepyfile(
-        """
-import socket
-import pytest
-import requests
-import vcr.errors
+# When record is disabled
+
 
 @pytest.mark.block_network
 @pytest.mark.vcr
 def test_with_vcr_mark(httpbin):
     with pytest.raises(vcr.errors.CannotOverwriteExistingCassetteException, match=r"overwrite existing cassette"):
         requests.get(httpbin.url + "/ip")
-    assert socket.socket.connect.__name__ == "network_guard"
-    assert socket.socket.connect_ex.__name__ == "network_guard"
+    assert socket.connect.__name__ == "network_guard"
+    assert socket.connect_ex.__name__ == "network_guard"
+
 
 @pytest.mark.block_network
 def test_no_vcr_mark(httpbin):
@@ -211,19 +204,15 @@ def test_no_vcr_mark(httpbin):
 @pytest.mark.block_network(allowed_hosts=["127.0.0.2"])
 def test_no_vcr_mark_bytes():
     with pytest.raises(RuntimeError, match=r"^Network is disabled$"):
-        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
+        with socket(AF_INET, SOCK_STREAM) as sock:
             sock.connect((b"127.0.0.1", 80))
 
+
 @pytest.mark.block_network(allowed_hosts=["127.0.0.2"])
 def test_no_vcr_mark_bytearray():
     with pytest.raises(RuntimeError, match=r"^Network is disabled$"):
-        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
+        with socket(AF_INET, SOCK_STREAM) as sock:
             sock.connect((bytearray(b"127.0.0.1"), 80))
-    """
-    )
-
-    result = testdir.runpytest("-s")
-    result.assert_outcomes(passed=4)
 
 
 @pytest.mark.parametrize(
@@ -337,81 +326,57 @@ def test_no_vcr_mark(httpbin):
     assert cassette_path.exists()
 
 
+# When pycurl is used for network access
+# It should be blocked as well
 @pytest.mark.skipif(pycurl is None, reason="Requires pycurl installed.")
-def test_pycurl(testdir):
-    # When pycurl is used for network access
-    testdir.makepyfile(
-        r"""
-import json
-import sys
-import pytest
-import pycurl
-from io import BytesIO
-
-
 @pytest.mark.block_network
-def test_error(httpbin):
+def test_pycurl_error(httpbin):
     buffer = BytesIO()
     c = pycurl.Curl()
-    c.setopt(c.URL, httpbin.url + "/ip")
-    c.setopt(c.WRITEDATA, buffer)
+    c.setopt(c.URL, httpbin.url + "/ip")  # type: ignore[attr-defined]
+    c.setopt(c.WRITEDATA, buffer)  # type: ignore[attr-defined]
     with pytest.raises(RuntimeError, match=r"^Network is disabled$"):
         c.perform()
     c.close()
 
-def test_work(httpbin):
+
+ at pytest.mark.skipif(pycurl is None, reason="Requires pycurl installed.")
+def test_pycurl_work(httpbin):
     buffer = BytesIO()
     c = pycurl.Curl()
-    c.setopt(c.URL, httpbin.url + "/ip")
-    c.setopt(c.WRITEDATA, buffer)
+    c.setopt(c.URL, httpbin.url + "/ip")  # type: ignore[attr-defined]
+    c.setopt(c.WRITEDATA, buffer)  # type: ignore[attr-defined]
     c.perform()
     c.close()
-    assert json.loads(buffer.getvalue()) == {"origin":"127.0.0.1"}
-    """
-    )
-
-    result = testdir.runpytest()
-    # It should be blocked as well
-    result.assert_outcomes(passed=2)
+    assert json.loads(buffer.getvalue()) == {"origin": "127.0.0.1"}
 
 
- at pytest.mark.skipif(pycurl is None, reason="Requires pycurl installed.")
-def test_pycurl_with_allowed_hosts(testdir):
-    # When pycurl is used for network access
-    testdir.makepyfile(
-        r"""
-import json
-import sys
-import pytest
-import pycurl
-from io import BytesIO
+# When pycurl is used for network access
+# It should be blocked as well
 
 
+ at pytest.mark.skipif(pycurl is None, reason="Requires pycurl installed.")
 @pytest.mark.block_network(allowed_hosts=["127.0.0.*", "127.0.1.1"])
-def test_allowed(httpbin):
+def test_pycurl_with_allowed_hosts_allowed(httpbin):
     buffer = BytesIO()
     c = pycurl.Curl()
-    c.setopt(c.URL, httpbin.url + "/ip")
-    c.setopt(c.WRITEDATA, buffer)
+    c.setopt(c.URL, httpbin.url + "/ip")  # type: ignore[attr-defined]
+    c.setopt(c.WRITEDATA, buffer)  # type: ignore[attr-defined]
     c.perform()
     c.close()
-    assert json.loads(buffer.getvalue()) == {"origin":"127.0.0.1"}
+    assert json.loads(buffer.getvalue()) == {"origin": "127.0.0.1"}
+
 
+ at pytest.mark.skipif(pycurl is None, reason="Requires pycurl installed.")
 @pytest.mark.block_network(allowed_hosts=["127.0.0.*", "127.0.1.1"])
-def test_blocked(httpbin):
+def test_pycurl_with_allowed_hosts_blocked(httpbin):
     buffer = BytesIO()
     c = pycurl.Curl()
-    c.setopt(c.URL, "http://example.com")
-    c.setopt(c.WRITEDATA, buffer)
+    c.setopt(c.URL, "http://example.com")  # type: ignore[attr-defined]
+    c.setopt(c.WRITEDATA, buffer)  # type: ignore[attr-defined]
     with pytest.raises(RuntimeError, match=r"^Network is disabled$"):
         c.perform()
     c.close()
-    """
-    )
-
-    result = testdir.runpytest("-s")
-    # It should be blocked as well
-    result.assert_outcomes(passed=2)
 
 
 @pytest.mark.skipif(pycurl is None, reason="Requires pycurl installed.")
@@ -419,9 +384,9 @@ def test_pycurl_setattr():
     # When pycurl is used for network access
     # And an attribute is set on an instance
     curl = pycurl.Curl()
-    curl.attr = 42
+    curl.attr = 42  # type: ignore[attr-defined]
     # Then it should be proxied to the original Curl instance itself
-    assert curl.handle.attr == 42
+    assert curl.handle.attr == 42  # type: ignore[attr-defined]
 
 
 @pytest.mark.skipif(pycurl is None, reason="Requires pycurl installed.")
@@ -434,50 +399,31 @@ def test_pycurl_url_error():
         curl.perform()
 
 
+# When pycurl is patched
+# Patched module should be hashable - use case for auto-reloaders and similar (e.g. in Django)
+# The patch should behave as close to real modules as possible
 @pytest.mark.skipif(pycurl is None, reason="Requires pycurl installed.")
-def test_sys_modules(testdir):
-    # When pycurl is patched
-    testdir.makepyfile(
-        """
-import sys
-import pytest
-
 @pytest.mark.block_network
 def test_sys_modules():
     set(sys.modules.values())
-    """
-    )
 
-    result = testdir.runpytest()
-    # Patched module should be hashable - use case for auto-reloaders and similar (e.g. in Django)
-    # The patch should behave as close to real modules as possible
-    result.assert_outcomes(passed=1)
 
+# When a critical error happened and the `network.disable` ctx manager is interrupted on `yield`
+# Then socket and pycurl should be unpatched anyway
+# NOTE. In reality, it is not likely to happen - e.g. if pytest will partially crash and will not call the teardown
+# part of the generator, but this try/finally implementation could also guard against errors on manual
 
-def test_critical_error(testdir):
-    # When a critical error happened and the `network.disable` ctx manager is interrupted on `yield`
-    testdir.makepyfile(
-        """
-import socket
-from pytest_recording.network import blocking_context
 
 def test_critical_error():
     try:
         with blocking_context():
-            assert socket.socket.connect.__name__ == "network_guard"
-            assert socket.socket.connect_ex.__name__ == "network_guard"
+            assert socket.connect.__name__ == "network_guard"
+            assert socket.connect_ex.__name__ == "network_guard"
             raise ValueError
     except ValueError:
         pass
-    assert socket.socket.connect.__name__ == "connect"
-    assert socket.socket.connect_ex.__name__ == "connect_ex"
-    """
-    )
-    result = testdir.runpytest()
-    # Then socket and pycurl should be unpatched anyway
-    result.assert_outcomes(passed=1)
-    # NOTE. In reality, it is not likely to happen - e.g. if pytest will partially crash and will not call the teardown
-    # part of the generator, but this try/finally implementation could also guard against errors on manual
+    assert socket.connect.__name__ == "connect"
+    assert socket.connect_ex.__name__ == "connect_ex"
 
 
 IS_PYTEST_ABOVE_54 = version.parse(pytest.__version__) >= version.parse("5.4.0")


=====================================
tests/test_plugin.py
=====================================
@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-
 import pytest
+
 from pytest_recording.plugin import RECORD_MODES
 
 


=====================================
tests/test_recording.py
=====================================
@@ -1,5 +1,6 @@
 import json
 import string
+from typing import Any
 
 import pytest
 import yaml
@@ -148,6 +149,7 @@ def test_cassette_recording_rewrite(testdir):
     test_function_size = test_function_cassette_path.size()
     assert test_function_size
     # Cassette should contain uuid as response
+    cassette: Any
     with open(str(test_function_cassette_path), encoding="utf8") as cassette:
         cassette = yaml.load(cassette, Loader=yaml.BaseLoader)
         test_function_cassette_uuid = cassette["interactions"][0]["response"]["body"]["string"]
@@ -185,7 +187,7 @@ def test_custom_cassette_name(testdir):
         import pytest
         import requests
 
-        @pytest.mark.vcr("{}")
+        @pytest.mark.vcr(r"{}")
         def test_with_network(httpbin):
             assert requests.get(httpbin.url + "/get").status_code == 200
     """.format(cassette)
@@ -207,7 +209,7 @@ def test_custom_cassette_name_rewrite(testdir):
         import pytest
         import requests
 
-        @pytest.mark.vcr("{}")
+        @pytest.mark.vcr(r"{}")
         def test_with_network(httpbin):
             assert requests.get(httpbin.url + "/uuid").status_code == 200
     """.format(cassette)
@@ -220,6 +222,7 @@ def test_custom_cassette_name_rewrite(testdir):
     # And writing will happen to the default cassette
     cassette_size = cassette.size()
     assert cassette_size
+    file: Any
     with open(str(cassette), encoding="utf8") as file:
         file = yaml.load(file, Loader=yaml.BaseLoader)
         uuid = file["interactions"][0]["response"]["body"]["string"]
@@ -242,7 +245,7 @@ def test_default_cassette_recording(testdir, ip_response_cassette):
 import pytest
 import requests
 
-pytestmark = [pytest.mark.vcr("{}")]
+pytestmark = [pytest.mark.vcr(r"{}")]
 
 def test_network(httpbin):
     assert requests.get(httpbin.url + "/ip").status_code == 200
@@ -298,7 +301,7 @@ pytestmark = [pytest.mark.vcr()]
 def test_network(httpbin):
     assert requests.get(httpbin.url + "/ip").status_code == 200
 
- at pytest.mark.vcr("{}", serializer="json")
+ at pytest.mark.vcr(r"{}", serializer="json")
 def test_custom_name(httpbin):
     assert requests.get(httpbin.url + "/ip").status_code == 200
     """.format(custom_cassette_path)
@@ -326,8 +329,8 @@ def test_custom_name(httpbin):
 import pytest
 import requests
 
- at pytest.mark.vcr("{}")
- at pytest.mark.vcr("{}")
+ at pytest.mark.vcr(r"{}")
+ at pytest.mark.vcr(r"{}")
 def test_with_network(httpbin):
     assert requests.get(httpbin.url + "/get").status_code == 200
 """,
@@ -335,9 +338,9 @@ def test_with_network(httpbin):
 import pytest
 import requests
 
-pytestmark = pytest.mark.vcr("{}")
+pytestmark = pytest.mark.vcr(r"{}")
 
- at pytest.mark.vcr("{}")
+ at pytest.mark.vcr(r"{}")
 def test_with_network(httpbin):
     assert requests.get(httpbin.url + "/get").status_code == 200
 """,
@@ -409,3 +412,30 @@ def test_two(vcr):
     # Different kwargs should be merged properly
     result = testdir.runpytest()
     result.assert_outcomes(passed=2)
+
+
+def test_long_cassette_name(testdir):
+    testdir.makepyfile(
+        """
+import pytest
+import requests
+
+ at pytest.mark.vcr
+ at pytest.mark.parametrize(
+    "data",
+    [
+        'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' * 5,
+    ],
+)
+def test_with_parametrize(httpbin,data):
+    assert requests.get(httpbin.url + "/get").status_code == 200
+    """
+    )
+
+    result = testdir.runpytest("--record-mode=all")
+    result.assert_outcomes(passed=1)
+
+    cassette_path = testdir.tmpdir.join(
+        "cassettes/test_long_cassette_name/test_with_parametrize[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefg...e6273968ceb1173c9b9ca1a67463cdd4.yaml"
+    )
+    assert cassette_path.size()


=====================================
tests/test_replaying.py
=====================================
@@ -1,27 +1,17 @@
 import pytest
+import requests
 import vcr
+
 from pytest_recording._vcr import load_cassette
 
 VCR_VERSION = tuple(map(int, vcr.__version__.split(".")))
 
 
-def test_no_cassette(testdir):
+ at pytest.mark.vcr
+def test_no_cassete_vcr_used():
     """If pytest.mark.vcr is applied and there is no cassette - an exception happens."""
-    testdir.makepyfile(
-        """
-        import pytest
-        import requests
-        import vcr
-
-        @pytest.mark.vcr
-        def test_vcr_used():
-            with pytest.raises(vcr.errors.CannotOverwriteExistingCassetteException):
-                requests.get('http://localhost/get')
-    """
-    )
-
-    result = testdir.runpytest()
-    result.assert_outcomes(passed=1)
+    with pytest.raises(vcr.errors.CannotOverwriteExistingCassetteException):
+        requests.get("http://localhost/get")
 
 
 def test_combine_cassettes(testdir, get_response_cassette, ip_response_cassette):
@@ -30,8 +20,8 @@ def test_combine_cassettes(testdir, get_response_cassette, ip_response_cassette)
 import pytest
 import requests
 
- at pytest.mark.vcr("{}")
- at pytest.mark.vcr("{}")
+ at pytest.mark.vcr(r"{}")
+ at pytest.mark.vcr(r"{}")
 def test_combined():
     assert requests.get("http://httpbin.org/get").text == '{{"get": true}}'
     assert requests.get("http://httpbin.org/ip").text == '{{"ip": true}}'
@@ -52,9 +42,9 @@ import pytest
 import requests
 import vcr
 
-pytestmark = pytest.mark.vcr("{}")
+pytestmark = pytest.mark.vcr(r"{}")
 
- at pytest.mark.vcr("{}")
+ at pytest.mark.vcr(r"{}")
 def test_combined():
     assert requests.get("http://httpbin.org/get").text == '{{"get": true}}'
     assert requests.get("http://httpbin.org/ip").text == '{{"ip": true}}'
@@ -80,7 +70,7 @@ import vcr
 
 pytestmark = pytest.mark.vcr()
 
- at pytest.mark.vcr("{}")
+ at pytest.mark.vcr(r"{}")
 def test_combined():
     assert requests.get("http://httpbin.org/get").text == '{{"get": true}}'
 """.format(get_response_cassette)
@@ -109,7 +99,7 @@ def override_before_request(request):
 
 pytestmark = pytest.mark.vcr(before_record_request=before_request)
 
-GET_CASSETTE = "{}"
+GET_CASSETTE = r"{}"
 
 @pytest.mark.vcr
 def test_custom_path(vcr):
@@ -154,7 +144,7 @@ def test_multiple_cassettes_in_mark(testdir, get_response_cassette, ip_response_
 import pytest
 import requests
 
- at pytest.mark.vcr("{}", "{}")
+ at pytest.mark.vcr(r"{}", r"{}")
 def test_custom_path():
     assert requests.get("http://httpbin.org/get").text == '{{"get": true}}'
     assert requests.get("http://httpbin.org/ip").text == '{{"ip": true}}'
@@ -172,7 +162,7 @@ def test_repeated_cassettes(testdir, mocker, get_response_cassette):
 import pytest
 import requests
 
-CASSETTE = "{}"
+CASSETTE = r"{}"
 
 pytestmark = [pytest.mark.vcr(CASSETTE)]
 
@@ -197,9 +187,9 @@ def test_class_mark(testdir, get_response_cassette, ip_response_cassette):
 import pytest
 import requests
 
-pytestmark = [pytest.mark.vcr("{}")]
+pytestmark = [pytest.mark.vcr(r"{}")]
 
- at pytest.mark.vcr("{}")
+ at pytest.mark.vcr(r"{}")
 class TestSomething:
 
     @pytest.mark.vcr()
@@ -220,7 +210,7 @@ def test_own_mark(testdir, get_response_cassette, create_file, ip_cassette):
 import pytest
 import requests
 
-pytestmark = [pytest.mark.vcr("{}")]
+pytestmark = [pytest.mark.vcr(r"{}")]
 
 
 def test_own():
@@ -342,7 +332,7 @@ import pytest
 import requests
 
 
- at pytest.mark.vcr("{}")
+ at pytest.mark.vcr(r"{}")
 def test_feature():
     assert requests.get("http://httpbin.org/get").text == '{{"get": true}}'
     assert requests.get("http://httpbin.org/ip").text == '{{"ip": true}}'


=====================================
tests/test_utils.py
=====================================
@@ -1,4 +1,5 @@
 import pytest
+
 from pytest_recording.utils import unique
 
 


=====================================
tox.ini
=====================================
@@ -1,5 +1,5 @@
 [tox]
-envlist = py{37,38,39,310,311,312,py3},no_pycurl,vcr_431,coverage-report
+envlist = py{39,310,311,312,313},py{39,py3}-no_pycurl,vcr_431,coverage-report
 
 [testenv]
 setenv =
@@ -14,12 +14,14 @@ deps =
     pytest-mock
     requests
     werkzeug<2.1.0
+    pycurl --global-option="--with-{env:PYCURL_SSL_LIBRARY}"
+    xdist: pytest-xdist
 commands =
-    pip install pycurl --global-option="--with-{env:PYCURL_SSL_LIBRARY}"
     coverage run --source=pytest_recording -m pytest {posargs:tests}
 
-[testenv:no_pycurl]
-basepython = python3.8
+# pypy3 + pycurl does not work
+# https://github.com/pypy/pypy/issues/3958
+[testenv:py{39,py3}-no_pycurl]
 deps =
     coverage
     pytest>=3.0
@@ -30,7 +32,7 @@ deps =
 commands = coverage run --source=pytest_recording -m pytest {posargs:tests}
 
 [testenv:vcr_431]
-basepython = python3.8
+basepython = python3.9
 deps =
     coverage
     pytest>=3.0
@@ -38,19 +40,19 @@ deps =
     pytest-mock
     requests
     werkzeug<2.1.0
+    vcrpy<4.4.0
 commands =
-    pip install vcrpy<4.4.0
     coverage run --source=pytest_recording -m pytest {posargs:tests}
 
 [testenv:coverage-report]
 description = Report coverage over all measured test runs.
-basepython = python3.8
+basepython = python3.9
 deps = coverage
 skip_install = true
-depends = {py37,py38,py39,py310,py311,py312,pypy3}
+depends = {py39,py310,py311,py312,py313,pypy3}
 commands =
     coverage combine
-    coverage report
+    coverage report --fail-under=100 --show-missing --skip-covered
 
 [testenv:build]
 deps = pep517



View it on GitLab: https://salsa.debian.org/debian-gis-team/pytest-recording/-/commit/2b52d032750b86719470b1fccbabfa2efbd7ca2f

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/pytest-recording/-/commit/2b52d032750b86719470b1fccbabfa2efbd7ca2f
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20250810/d4d5ca58/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list