pytest-casewise-package-install


Namepytest-casewise-package-install JSON
Version 0.3.0 PyPI version JSON
download
home_pagehttps://github.com/yourusername/pytest-casewise-package-install
SummaryA pytest plugin for test case-level dynamic dependency management
upload_time2025-10-31 01:37:03
maintainerNone
docs_urlNone
authorpytest-casewise-package-install Contributors
requires_python>=3.8
licenseMIT
keywords pytest plugin package dependency isolation testing
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            pytest-casewise-package-install
================================

A pytest plugin that emulates per-test package installation by generating deterministic stub packages. It lets you validate version-sensitive logic without reaching remote indexes or polluting the interpreter that runs your suite.

Why it helps
------------

- declare package requirements directly on tests through the `with_packages` decorator or marker exposed via the package `pytest_casewise_package_install.markers`
- bundle dependency requirements with parametrized data using helpers from `pytest_casewise_package_install.parametrize_support`
- run short-lived package environments backed by `LocalMultiPackageManager`, which caches stub directories under `~/.local_package_cache`
- rely on `package_env` and `dynamic_packages` fixtures for context-managed or ad hoc installs defined in `pytest_casewise_package_install.fixtures`
- keep the runner clean because `PackageInstallPlugin` backs up and restores `sys.path`, `PYTHONPATH`, and imported modules for every test case

> The manager creates lightweight modules (such as `requests`, `numpy`, `pandas`, `click`, `transformers`, and more) that expose `__version__` and minimal APIs. The process is offline-only; no real `pip install` occurs.

Installation
------------

```bash
pip install pytest-casewise-package-install
```

The plugin auto-registers through the `casewise-package-install` entry point. Importing the package or enabling it in `pytest.ini` is not required.

Quick start
-----------

### decorator usage

```python
from pytest_casewise_package_install import with_packages


@with_packages({"requests": "2.31.0"})
def test_requests_stub():
    import requests

    assert requests.__version__ == "2.31.0"
```

### marker usage

```python
import pytest


@pytest.mark.with_packages(packages={"requests": "2.28.0"})
def test_requests_marker():
    import requests

    assert requests.__version__ == "2.28.0"
```

### multiple packages

```python
from pytest_casewise_package_install import with_packages


@with_packages({"certifi": "2023.7.22", "urllib3": "2.0.4"})
def test_http_stack_versions():
    import certifi
    import urllib3

    assert certifi.__version__ == "2023.7.22"
    assert urllib3.__version__ == "2.0.4"
```

Parametrized tests
------------------

Use `param_with_packages` to pair fixtures with dependency declarations. Each parameter set receives its own `with_packages` mark so the plugin can discover requirements.

```python
import pytest
from pytest_casewise_package_install import param_with_packages


@pytest.mark.parametrize(
    "version, packages",
    [
        param_with_packages("2.31.0", packages={"requests": "2.31.0"}, id="requests-2-31"),
        param_with_packages("2.28.0", packages={"requests": "2.28.0"}, id="requests-2-28"),
        param_with_packages("2.25.0", packages={"requests": "2.25.0"}, id="requests-2-25"),
    ],
)
def test_requests_matrix(version, packages):
    import requests

    assert requests.__version__ == version
```

For larger tables reuse `create_model_test_params`:

```python
from pytest_casewise_package_install import create_model_test_params


MODEL_CONFIGS = [
    {
        "model_name": "bert-base-uncased",
        "packages": {"transformers": "4.30.0", "torch": "2.0.0"},
        "id": "bert-transformers-4-30",
    },
    {
        "model_name": "gpt2",
        "packages": {"transformers": "4.35.0", "torch": "2.1.0"},
        "id": "gpt2-transformers-4-35",
    },
]


@pytest.mark.parametrize("model_name, packages", create_model_test_params(MODEL_CONFIGS))
def test_models(model_name, packages):
    import transformers

    assert transformers.__version__ in {"4.30.0", "4.35.0"}
```

Fixtures
--------

- `package_env`: returns a manager that applies package sets inside a context manager and restores the environment afterward.
- `dynamic_packages`: installs stub packages in-place without automatic teardown when you need to perform multiple assertions after importing modules.

```python
from pytest_casewise_package_install import package_env


def test_numpy_context(package_env):
    with package_env.use_packages({"numpy": "1.24.0"}):
        import numpy as np

        assert np.__version__ == "1.24.0"
```

Stub catalog and cache
----------------------

`LocalMultiPackageManager` maintains two directories:

- `~/.local_package_cache/<package>_<version>` holds generated stubs and metadata about schema version and requested version
- `~/.local_package_cache/active/<package>` mirrors the selected version and is prepended to `sys.path`

Stub generators ship for:

- requests
- numpy
- pandas
- pyyaml (available through the `yaml` alias)
- click
- transformers
- pillow (available through `PIL`)
- tokenizers
- librosa
- six
- certifi
- urllib3

Any other package receives a minimal stub with only `__version__` defined. If a stub cannot be generated successfully the plugin marks the status as `FAILED` and the test exits with `pytest.fail`.

Configuration
-------------

Override defaults in `pytest.ini` or `setup.cfg`:

```ini
[pytest]
casewise_cache_dir = ~/.my_casewise_cache
casewise_auto_cleanup = true
casewise_install_timeout = 300
casewise_verbose = true
```

Environment variables take precedence over config files:

```bash
export CASEWISE_CACHE_DIR=~/.my_casewise_cache
export CASEWISE_AUTO_CLEANUP=true
export CASEWISE_INSTALL_TIMEOUT=300
export CASEWISE_VERBOSE=true
```

The `PluginConfig` loader (see `pytest_casewise_package_install.config`) normalizes these values and falls back to `~/.local_package_cache`, automatic cleanup, a 300 second timeout placeholder, and non-verbose logging.

Test lifecycle
--------------

`PackageInstallPlugin` (in `pytest_casewise_package_install.plugin`) wires into pytest hooks:

1. discover requested packages from parametrized values, markers, or decorator metadata
2. back up `PYTHONPATH`, `sys.path`, and the set of already imported modules before altering the environment
3. ensure required stubs are ready through `LocalMultiPackageManager`
4. prepend stub directories to `sys.path`, refresh imported modules, and expose environment variables so child processes inherit the setup
5. run the test body
6. restore the previous interpreter state and remove modules imported during the test

If the package requirement is not a dictionary the plugin raises `pytest.fail` with a descriptive message to avoid silent misconfiguration.

Examples
--------

Browse the `examples/` directory for working scenarios:

- `test_basic_usage.py`: decorator versus marker usage, multi-package sets, cache reuse
- `test_parametrize_simple.py`: matrix parametrization, helper usage, shared caching
- `test_fixture_usage.py`: `package_env` and `dynamic_packages` in practice
- `test_advanced_usage.py`: class-based tests and mixing strategies

Run them locally:

```bash
cd examples
pytest -v -s
```

Development
-----------

```bash
git clone https://github.com/yourusername/pytest-casewise-package-install
cd pytest-casewise-package-install
pip install -e .
pytest tests -v
```

License
-------

MIT License

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/yourusername/pytest-casewise-package-install",
    "name": "pytest-casewise-package-install",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "pytest, plugin, package, dependency, isolation, testing",
    "author": "pytest-casewise-package-install Contributors",
    "author_email": null,
    "download_url": "https://files.pythonhosted.org/packages/64/ba/567500e0a35fe6f20c8825d1f08642d8eee63d5e0d66ce95b5ce5d7eb0f1/pytest_casewise_package_install-0.3.0.tar.gz",
    "platform": null,
    "description": "pytest-casewise-package-install\n================================\n\nA pytest plugin that emulates per-test package installation by generating deterministic stub packages. It lets you validate version-sensitive logic without reaching remote indexes or polluting the interpreter that runs your suite.\n\nWhy it helps\n------------\n\n- declare package requirements directly on tests through the `with_packages` decorator or marker exposed via the package `pytest_casewise_package_install.markers`\n- bundle dependency requirements with parametrized data using helpers from `pytest_casewise_package_install.parametrize_support`\n- run short-lived package environments backed by `LocalMultiPackageManager`, which caches stub directories under `~/.local_package_cache`\n- rely on `package_env` and `dynamic_packages` fixtures for context-managed or ad hoc installs defined in `pytest_casewise_package_install.fixtures`\n- keep the runner clean because `PackageInstallPlugin` backs up and restores `sys.path`, `PYTHONPATH`, and imported modules for every test case\n\n> The manager creates lightweight modules (such as `requests`, `numpy`, `pandas`, `click`, `transformers`, and more) that expose `__version__` and minimal APIs. The process is offline-only; no real `pip install` occurs.\n\nInstallation\n------------\n\n```bash\npip install pytest-casewise-package-install\n```\n\nThe plugin auto-registers through the `casewise-package-install` entry point. Importing the package or enabling it in `pytest.ini` is not required.\n\nQuick start\n-----------\n\n### decorator usage\n\n```python\nfrom pytest_casewise_package_install import with_packages\n\n\n@with_packages({\"requests\": \"2.31.0\"})\ndef test_requests_stub():\n    import requests\n\n    assert requests.__version__ == \"2.31.0\"\n```\n\n### marker usage\n\n```python\nimport pytest\n\n\n@pytest.mark.with_packages(packages={\"requests\": \"2.28.0\"})\ndef test_requests_marker():\n    import requests\n\n    assert requests.__version__ == \"2.28.0\"\n```\n\n### multiple packages\n\n```python\nfrom pytest_casewise_package_install import with_packages\n\n\n@with_packages({\"certifi\": \"2023.7.22\", \"urllib3\": \"2.0.4\"})\ndef test_http_stack_versions():\n    import certifi\n    import urllib3\n\n    assert certifi.__version__ == \"2023.7.22\"\n    assert urllib3.__version__ == \"2.0.4\"\n```\n\nParametrized tests\n------------------\n\nUse `param_with_packages` to pair fixtures with dependency declarations. Each parameter set receives its own `with_packages` mark so the plugin can discover requirements.\n\n```python\nimport pytest\nfrom pytest_casewise_package_install import param_with_packages\n\n\n@pytest.mark.parametrize(\n    \"version, packages\",\n    [\n        param_with_packages(\"2.31.0\", packages={\"requests\": \"2.31.0\"}, id=\"requests-2-31\"),\n        param_with_packages(\"2.28.0\", packages={\"requests\": \"2.28.0\"}, id=\"requests-2-28\"),\n        param_with_packages(\"2.25.0\", packages={\"requests\": \"2.25.0\"}, id=\"requests-2-25\"),\n    ],\n)\ndef test_requests_matrix(version, packages):\n    import requests\n\n    assert requests.__version__ == version\n```\n\nFor larger tables reuse `create_model_test_params`:\n\n```python\nfrom pytest_casewise_package_install import create_model_test_params\n\n\nMODEL_CONFIGS = [\n    {\n        \"model_name\": \"bert-base-uncased\",\n        \"packages\": {\"transformers\": \"4.30.0\", \"torch\": \"2.0.0\"},\n        \"id\": \"bert-transformers-4-30\",\n    },\n    {\n        \"model_name\": \"gpt2\",\n        \"packages\": {\"transformers\": \"4.35.0\", \"torch\": \"2.1.0\"},\n        \"id\": \"gpt2-transformers-4-35\",\n    },\n]\n\n\n@pytest.mark.parametrize(\"model_name, packages\", create_model_test_params(MODEL_CONFIGS))\ndef test_models(model_name, packages):\n    import transformers\n\n    assert transformers.__version__ in {\"4.30.0\", \"4.35.0\"}\n```\n\nFixtures\n--------\n\n- `package_env`: returns a manager that applies package sets inside a context manager and restores the environment afterward.\n- `dynamic_packages`: installs stub packages in-place without automatic teardown when you need to perform multiple assertions after importing modules.\n\n```python\nfrom pytest_casewise_package_install import package_env\n\n\ndef test_numpy_context(package_env):\n    with package_env.use_packages({\"numpy\": \"1.24.0\"}):\n        import numpy as np\n\n        assert np.__version__ == \"1.24.0\"\n```\n\nStub catalog and cache\n----------------------\n\n`LocalMultiPackageManager` maintains two directories:\n\n- `~/.local_package_cache/<package>_<version>` holds generated stubs and metadata about schema version and requested version\n- `~/.local_package_cache/active/<package>` mirrors the selected version and is prepended to `sys.path`\n\nStub generators ship for:\n\n- requests\n- numpy\n- pandas\n- pyyaml (available through the `yaml` alias)\n- click\n- transformers\n- pillow (available through `PIL`)\n- tokenizers\n- librosa\n- six\n- certifi\n- urllib3\n\nAny other package receives a minimal stub with only `__version__` defined. If a stub cannot be generated successfully the plugin marks the status as `FAILED` and the test exits with `pytest.fail`.\n\nConfiguration\n-------------\n\nOverride defaults in `pytest.ini` or `setup.cfg`:\n\n```ini\n[pytest]\ncasewise_cache_dir = ~/.my_casewise_cache\ncasewise_auto_cleanup = true\ncasewise_install_timeout = 300\ncasewise_verbose = true\n```\n\nEnvironment variables take precedence over config files:\n\n```bash\nexport CASEWISE_CACHE_DIR=~/.my_casewise_cache\nexport CASEWISE_AUTO_CLEANUP=true\nexport CASEWISE_INSTALL_TIMEOUT=300\nexport CASEWISE_VERBOSE=true\n```\n\nThe `PluginConfig` loader (see `pytest_casewise_package_install.config`) normalizes these values and falls back to `~/.local_package_cache`, automatic cleanup, a 300 second timeout placeholder, and non-verbose logging.\n\nTest lifecycle\n--------------\n\n`PackageInstallPlugin` (in `pytest_casewise_package_install.plugin`) wires into pytest hooks:\n\n1. discover requested packages from parametrized values, markers, or decorator metadata\n2. back up `PYTHONPATH`, `sys.path`, and the set of already imported modules before altering the environment\n3. ensure required stubs are ready through `LocalMultiPackageManager`\n4. prepend stub directories to `sys.path`, refresh imported modules, and expose environment variables so child processes inherit the setup\n5. run the test body\n6. restore the previous interpreter state and remove modules imported during the test\n\nIf the package requirement is not a dictionary the plugin raises `pytest.fail` with a descriptive message to avoid silent misconfiguration.\n\nExamples\n--------\n\nBrowse the `examples/` directory for working scenarios:\n\n- `test_basic_usage.py`: decorator versus marker usage, multi-package sets, cache reuse\n- `test_parametrize_simple.py`: matrix parametrization, helper usage, shared caching\n- `test_fixture_usage.py`: `package_env` and `dynamic_packages` in practice\n- `test_advanced_usage.py`: class-based tests and mixing strategies\n\nRun them locally:\n\n```bash\ncd examples\npytest -v -s\n```\n\nDevelopment\n-----------\n\n```bash\ngit clone https://github.com/yourusername/pytest-casewise-package-install\ncd pytest-casewise-package-install\npip install -e .\npytest tests -v\n```\n\nLicense\n-------\n\nMIT License\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "A pytest plugin for test case-level dynamic dependency management",
    "version": "0.3.0",
    "project_urls": {
        "Homepage": "https://github.com/yourusername/pytest-casewise-package-install",
        "Issues": "https://github.com/yourusername/pytest-casewise-package-install/issues",
        "Repository": "https://github.com/yourusername/pytest-casewise-package-install"
    },
    "split_keywords": [
        "pytest",
        " plugin",
        " package",
        " dependency",
        " isolation",
        " testing"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "060fdf328e8f2564f6773226b7feab21ce0019c9544b67dc582eac79237f9140",
                "md5": "3c12fb22141864d8376740dd2571a80c",
                "sha256": "d869cc33492ec02f9a6377290f2a4a6147f9ddb10d220474d680b55c4059c590"
            },
            "downloads": -1,
            "filename": "pytest_casewise_package_install-0.3.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "3c12fb22141864d8376740dd2571a80c",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 18189,
            "upload_time": "2025-10-31T01:37:02",
            "upload_time_iso_8601": "2025-10-31T01:37:02.847043Z",
            "url": "https://files.pythonhosted.org/packages/06/0f/df328e8f2564f6773226b7feab21ce0019c9544b67dc582eac79237f9140/pytest_casewise_package_install-0.3.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "64ba567500e0a35fe6f20c8825d1f08642d8eee63d5e0d66ce95b5ce5d7eb0f1",
                "md5": "c2824b981b64219c3db4d27d24388753",
                "sha256": "42c4e7d7a4e5b4441d16dc47ef6db01f48503d976b3b24fd4c82f30741c480f5"
            },
            "downloads": -1,
            "filename": "pytest_casewise_package_install-0.3.0.tar.gz",
            "has_sig": false,
            "md5_digest": "c2824b981b64219c3db4d27d24388753",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 41183,
            "upload_time": "2025-10-31T01:37:03",
            "upload_time_iso_8601": "2025-10-31T01:37:03.952827Z",
            "url": "https://files.pythonhosted.org/packages/64/ba/567500e0a35fe6f20c8825d1f08642d8eee63d5e0d66ce95b5ce5d7eb0f1/pytest_casewise_package_install-0.3.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-10-31 01:37:03",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "yourusername",
    "github_project": "pytest-casewise-package-install",
    "github_not_found": true,
    "lcname": "pytest-casewise-package-install"
}
        
Elapsed time: 0.75263s