pytest-inject-config 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Avi Zaguri
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,3 @@
1
+ include pyproject.toml
2
+ include README.md
3
+ include LICENSE
@@ -0,0 +1,146 @@
1
+ Metadata-Version: 2.4
2
+ Name: pytest-inject-config
3
+ Version: 1.0.0
4
+ Summary: A pytest plugin that loads a JSON or YAML file given by --config-path and injects it into test classes as self.config.
5
+ Author: Avi Zaguri
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/aviz92/pytest-inject-config
8
+ Project-URL: Repository, https://github.com/aviz92/pytest-inject-config
9
+ Project-URL: Issues, https://github.com/aviz92/pytest-inject-config/issues
10
+ Keywords: pytest,plugin,testing,config,configuration,json,yaml
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Framework :: Pytest
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Topic :: Software Development :: Testing
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Requires-Python: >=3.12
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: pytest>=9.2.0
21
+ Requires-Dist: pyyaml>=6.0.3
22
+ Dynamic: license-file
23
+
24
+ ![PyPI version](https://img.shields.io/pypi/v/pytest-inject-config)
25
+ ![Python](https://img.shields.io/badge/python->=3.12-blue)
26
+ ![Development Status](https://img.shields.io/badge/status-stable-green)
27
+ ![Maintenance](https://img.shields.io/maintenance/yes/2026)
28
+ ![PyPI](https://img.shields.io/pypi/dm/pytest-inject-config)
29
+ ![License](https://img.shields.io/pypi/l/pytest-inject-config)
30
+
31
+ ---
32
+
33
+ # 💡 pytest-inject-config
34
+ A pytest plugin that loads a config file given by `--config-path` and injects it into every test class as `self.config`.
35
+ The format is chosen by the file extension: JSON (`.json`) and YAML (`.yaml`, `.yml`).
36
+ Run the same test suite against different environments by switching a single flag.
37
+
38
+ ---
39
+
40
+ ## 📦 Installation
41
+ ```bash
42
+ uv add pytest-inject-config
43
+ ```
44
+
45
+ ---
46
+
47
+ ## 🚀 Features
48
+ - ✅ **One flag, any environment** — point `--config-path` at a different JSON file per environment
49
+ - ✅ **Isolated per class** — each test class gets its own copy, so a test that mutates its config can't affect others
50
+ - ✅ **Fails fast** — a missing file, an unsupported extension, or invalid content stops the run at startup with a clear usage error
51
+ - ✅ **JSON and YAML** — chosen by extension; only known extensions are accepted, so adding formats later never changes how an existing file is read
52
+ - ✅ **Safe YAML** — YAML is loaded with `safe_load`, so a config file can never construct Python objects or run code
53
+ - ✅ **Read once** — the file is loaded at startup, not once per class
54
+ - ✅ **Zero footprint without the flag** — nothing is registered and no fixture runs
55
+
56
+ ---
57
+
58
+ ## 🛠️ How to Use
59
+ 1. Create a config file per environment, in JSON:
60
+ ```json
61
+ {
62
+ "host": "10.0.0.1",
63
+ "port": 8080
64
+ }
65
+ ```
66
+ or in YAML:
67
+ ```yaml
68
+ host: 10.0.0.1
69
+ port: 8080
70
+ ```
71
+ 2. Read it from your test classes through `self.config`:
72
+ ```python
73
+ class TestDevice:
74
+ def test_connect(self):
75
+ assert connect(self.config["host"], self.config["port"])
76
+ ```
77
+ 3. Run with the file for the environment you want:
78
+ ```bash
79
+ pytest --config-path=config/staging.json
80
+ ```
81
+
82
+ ---
83
+
84
+ ## ⚙️ Behavior
85
+ | Situation | Result |
86
+ |---|---|
87
+ | Test inside a class | `self.config` holds the file's content as a `dict` |
88
+ | Top level is not a mapping (a list, a number, a string) | `UsageError` at startup |
89
+ | Module-level test function | Unaffected |
90
+ | Relative path | Resolved from the directory pytest is run in |
91
+ | Extension | Decides the format, case-insensitive. Supported: `.json`, `.yaml`, `.yml` |
92
+ | Unsupported or missing extension | `UsageError` at startup (exit code 4), even if the content would parse |
93
+ | Empty file, or a top-level `null` | `UsageError` at startup |
94
+ | YAML with several documents (`---`) or Python tags (`!!python/...`) | `UsageError` at startup |
95
+ | Missing file, directory, non-UTF-8 or invalid content | `UsageError` at startup (exit code 4), no tests run |
96
+ | No `--config-path` | Nothing is injected; no fixture runs |
97
+
98
+ ---
99
+
100
+ ## ▶️ Usage Examples
101
+ ### Select the environment in CI
102
+ ```bash
103
+ pytest --config-path="config/${TARGET_ENV}.yaml"
104
+ ```
105
+
106
+ ### Set a default in `pytest.ini`
107
+ ```ini
108
+ [pytest]
109
+ addopts =
110
+ --config-path=config/local.json
111
+ ```
112
+
113
+ ---
114
+
115
+ ## 🔁 Migrating from pytest-plugins
116
+ This plugin was previously `add_config_parameters` in `pytest-plugins`, where it never worked.
117
+ Both register the `--config-path` option, so don't install them together until you're on a `pytest-plugins`
118
+ release without it.
119
+
120
+ ---
121
+
122
+ ## 🤝 Contributing
123
+ 1. Fork the repository
124
+ 2. Create a feature branch: `git checkout -b feature/my-addition`
125
+ 3. Make your changes
126
+ 4. Commit your changes: `git commit -m 'Add my addition'`
127
+ 5. Push to the branch: `git push origin feature/my-addition`
128
+ 6. Open a Pull Request
129
+
130
+ Additions that promote clean, productive, and maintainable development are always welcome.
131
+
132
+ ---
133
+
134
+ ## 📄 License
135
+ MIT License — see [LICENSE](LICENSE) for details.
136
+
137
+ ---
138
+
139
+ ## 🙏 Thanks
140
+ Thanks for exploring this repository! <br>
141
+ Happy coding!
142
+
143
+ [![GitHub](https://img.shields.io/badge/GitHub-aviz92-181717?logo=github)](https://github.com/aviz92)
144
+ &nbsp; [![PyPI](https://img.shields.io/badge/PyPI-aviz-3775A9?logo=pypi)](https://pypi.org/user/aviz/)
145
+ &nbsp; [![Blog](https://img.shields.io/badge/Blog-aviz92.github.io-0066CC?logo=googlechrome)](https://aviz92.github.io/)
146
+ &nbsp; [![LinkedIn](https://img.shields.io/badge/LinkedIn-avi--zaguri-0A66C2?logo=linkedin)](https://www.linkedin.com/in/avi-zaguri-41869b11b)
@@ -0,0 +1,123 @@
1
+ ![PyPI version](https://img.shields.io/pypi/v/pytest-inject-config)
2
+ ![Python](https://img.shields.io/badge/python->=3.12-blue)
3
+ ![Development Status](https://img.shields.io/badge/status-stable-green)
4
+ ![Maintenance](https://img.shields.io/maintenance/yes/2026)
5
+ ![PyPI](https://img.shields.io/pypi/dm/pytest-inject-config)
6
+ ![License](https://img.shields.io/pypi/l/pytest-inject-config)
7
+
8
+ ---
9
+
10
+ # 💡 pytest-inject-config
11
+ A pytest plugin that loads a config file given by `--config-path` and injects it into every test class as `self.config`.
12
+ The format is chosen by the file extension: JSON (`.json`) and YAML (`.yaml`, `.yml`).
13
+ Run the same test suite against different environments by switching a single flag.
14
+
15
+ ---
16
+
17
+ ## 📦 Installation
18
+ ```bash
19
+ uv add pytest-inject-config
20
+ ```
21
+
22
+ ---
23
+
24
+ ## 🚀 Features
25
+ - ✅ **One flag, any environment** — point `--config-path` at a different JSON file per environment
26
+ - ✅ **Isolated per class** — each test class gets its own copy, so a test that mutates its config can't affect others
27
+ - ✅ **Fails fast** — a missing file, an unsupported extension, or invalid content stops the run at startup with a clear usage error
28
+ - ✅ **JSON and YAML** — chosen by extension; only known extensions are accepted, so adding formats later never changes how an existing file is read
29
+ - ✅ **Safe YAML** — YAML is loaded with `safe_load`, so a config file can never construct Python objects or run code
30
+ - ✅ **Read once** — the file is loaded at startup, not once per class
31
+ - ✅ **Zero footprint without the flag** — nothing is registered and no fixture runs
32
+
33
+ ---
34
+
35
+ ## 🛠️ How to Use
36
+ 1. Create a config file per environment, in JSON:
37
+ ```json
38
+ {
39
+ "host": "10.0.0.1",
40
+ "port": 8080
41
+ }
42
+ ```
43
+ or in YAML:
44
+ ```yaml
45
+ host: 10.0.0.1
46
+ port: 8080
47
+ ```
48
+ 2. Read it from your test classes through `self.config`:
49
+ ```python
50
+ class TestDevice:
51
+ def test_connect(self):
52
+ assert connect(self.config["host"], self.config["port"])
53
+ ```
54
+ 3. Run with the file for the environment you want:
55
+ ```bash
56
+ pytest --config-path=config/staging.json
57
+ ```
58
+
59
+ ---
60
+
61
+ ## ⚙️ Behavior
62
+ | Situation | Result |
63
+ |---|---|
64
+ | Test inside a class | `self.config` holds the file's content as a `dict` |
65
+ | Top level is not a mapping (a list, a number, a string) | `UsageError` at startup |
66
+ | Module-level test function | Unaffected |
67
+ | Relative path | Resolved from the directory pytest is run in |
68
+ | Extension | Decides the format, case-insensitive. Supported: `.json`, `.yaml`, `.yml` |
69
+ | Unsupported or missing extension | `UsageError` at startup (exit code 4), even if the content would parse |
70
+ | Empty file, or a top-level `null` | `UsageError` at startup |
71
+ | YAML with several documents (`---`) or Python tags (`!!python/...`) | `UsageError` at startup |
72
+ | Missing file, directory, non-UTF-8 or invalid content | `UsageError` at startup (exit code 4), no tests run |
73
+ | No `--config-path` | Nothing is injected; no fixture runs |
74
+
75
+ ---
76
+
77
+ ## ▶️ Usage Examples
78
+ ### Select the environment in CI
79
+ ```bash
80
+ pytest --config-path="config/${TARGET_ENV}.yaml"
81
+ ```
82
+
83
+ ### Set a default in `pytest.ini`
84
+ ```ini
85
+ [pytest]
86
+ addopts =
87
+ --config-path=config/local.json
88
+ ```
89
+
90
+ ---
91
+
92
+ ## 🔁 Migrating from pytest-plugins
93
+ This plugin was previously `add_config_parameters` in `pytest-plugins`, where it never worked.
94
+ Both register the `--config-path` option, so don't install them together until you're on a `pytest-plugins`
95
+ release without it.
96
+
97
+ ---
98
+
99
+ ## 🤝 Contributing
100
+ 1. Fork the repository
101
+ 2. Create a feature branch: `git checkout -b feature/my-addition`
102
+ 3. Make your changes
103
+ 4. Commit your changes: `git commit -m 'Add my addition'`
104
+ 5. Push to the branch: `git push origin feature/my-addition`
105
+ 6. Open a Pull Request
106
+
107
+ Additions that promote clean, productive, and maintainable development are always welcome.
108
+
109
+ ---
110
+
111
+ ## 📄 License
112
+ MIT License — see [LICENSE](LICENSE) for details.
113
+
114
+ ---
115
+
116
+ ## 🙏 Thanks
117
+ Thanks for exploring this repository! <br>
118
+ Happy coding!
119
+
120
+ [![GitHub](https://img.shields.io/badge/GitHub-aviz92-181717?logo=github)](https://github.com/aviz92)
121
+ &nbsp; [![PyPI](https://img.shields.io/badge/PyPI-aviz-3775A9?logo=pypi)](https://pypi.org/user/aviz/)
122
+ &nbsp; [![Blog](https://img.shields.io/badge/Blog-aviz92.github.io-0066CC?logo=googlechrome)](https://aviz92.github.io/)
123
+ &nbsp; [![LinkedIn](https://img.shields.io/badge/LinkedIn-avi--zaguri-0A66C2?logo=linkedin)](https://www.linkedin.com/in/avi-zaguri-41869b11b)
@@ -0,0 +1,57 @@
1
+ [build-system]
2
+ requires = ["setuptools", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pytest-inject-config"
7
+ version = "1.0.0"
8
+ description = "A pytest plugin that loads a JSON or YAML file given by --config-path and injects it into test classes as self.config."
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Avi Zaguri" },]
13
+
14
+ keywords = [
15
+ "pytest",
16
+ "plugin",
17
+ "testing",
18
+ "config",
19
+ "configuration",
20
+ "json",
21
+ "yaml",
22
+ ]
23
+
24
+ classifiers = [
25
+ "Development Status :: 5 - Production/Stable",
26
+ "Framework :: Pytest",
27
+ "Intended Audience :: Developers",
28
+ "Topic :: Software Development :: Testing",
29
+ "Operating System :: OS Independent",
30
+ "Programming Language :: Python :: 3.12",
31
+ ]
32
+
33
+ dependencies = [
34
+ "pytest>=9.2.0",
35
+ "pyyaml>=6.0.3",
36
+ ]
37
+
38
+ [dependency-groups]
39
+ dev = [
40
+ "python-base-command>=0.1.8",
41
+ "pre-commit>=4.7.0",
42
+ "setuptools>=84.0.0",
43
+ "wheel>=0.45.1",
44
+ ]
45
+
46
+ [project.urls]
47
+ Homepage = "https://github.com/aviz92/pytest-inject-config"
48
+ Repository = "https://github.com/aviz92/pytest-inject-config"
49
+ Issues = "https://github.com/aviz92/pytest-inject-config/issues"
50
+
51
+ [tool.setuptools.packages.find]
52
+ where = ["."]
53
+ include = ["pytest_inject_config*"]
54
+ namespaces = false
55
+
56
+ [project.entry-points.pytest11]
57
+ inject_config = "pytest_inject_config.plugin"
@@ -0,0 +1,3 @@
1
+ from pytest_inject_config.const import CLASS_ATTRIBUTE, FLAG
2
+
3
+ __all__ = ["CLASS_ATTRIBUTE", "FLAG"]
@@ -0,0 +1,8 @@
1
+ from typing import Any
2
+
3
+ import pytest
4
+
5
+ FLAG = "--config-path"
6
+ CLASS_ATTRIBUTE = "config"
7
+ INJECTOR_PLUGIN_NAME = "inject-config-injector"
8
+ CONFIG_DATA_KEY = pytest.StashKey[Any]()
@@ -0,0 +1,20 @@
1
+ """Registered by ``plugin.pytest_configure`` only when ``--config-path`` is set; never loaded on its own."""
2
+
3
+ import copy
4
+
5
+ import pytest
6
+
7
+ from pytest_inject_config.const import CLASS_ATTRIBUTE, CONFIG_DATA_KEY
8
+
9
+
10
+ # Why a separate module, and not a fixture in plugin.py that returns early when the flag isn't set:
11
+ # plugin.py is always registered (it is the entry point), so a fixture there would be attached to every test class
12
+ # in every run and would execute even without --config-path. This module is registered only when the flag is set,
13
+ # so without it pytest never sees the fixture and nothing runs.
14
+ # It is a module-level function rather than a method on a plugin object because class-scoped fixtures defined as
15
+ # instance methods are deprecated (PytestRemovedIn10Warning).
16
+ @pytest.fixture(scope="class", autouse=True)
17
+ def inject_config_into_class(request: pytest.FixtureRequest) -> None:
18
+ if request.cls is not None:
19
+ # A copy per class, so a test that mutates its config can't leak into other classes.
20
+ setattr(request.cls, CLASS_ATTRIBUTE, copy.deepcopy(request.config.stash[CONFIG_DATA_KEY]))
@@ -0,0 +1,100 @@
1
+ """Load a config file given by ``--config-path`` and inject its content into every test class as ``self.config``.
2
+
3
+ The format is chosen by the file extension (``.json``, ``.yaml``, ``.yml``); any other extension is rejected, so
4
+ adding a format later never changes how an existing file is read. Every loader has the same shape: it takes the
5
+ path, loads it with its own library, and returns a mapping. File-level errors (missing, not UTF-8, unreadable)
6
+ are handled once, in ``load_config``.
7
+
8
+ Without the flag the injector module is never registered, so no fixture runs. The file is read once, at startup;
9
+ a missing, unreadable or invalid file stops the run with a usage error before any test is collected.
10
+ """
11
+
12
+ import json
13
+ from collections.abc import Callable
14
+ from pathlib import Path
15
+ from typing import Any
16
+
17
+ import pytest
18
+ import yaml
19
+
20
+ from pytest_inject_config import injector
21
+ from pytest_inject_config.const import CLASS_ATTRIBUTE, CONFIG_DATA_KEY, FLAG, INJECTOR_PLUGIN_NAME
22
+
23
+ ConfigLoader = Callable[[Path], dict[str, Any]]
24
+
25
+
26
+ def ensure_mapping(data: Any, path: Path) -> dict[str, Any]:
27
+ if data is None:
28
+ raise pytest.UsageError(f"{FLAG}: {path} is empty or null")
29
+ if not isinstance(data, dict):
30
+ raise pytest.UsageError(f"{FLAG}: top level of {path} must be a mapping, got {type(data).__name__}")
31
+ return data
32
+
33
+
34
+ def load_json_config(path: Path) -> dict[str, Any]:
35
+ with path.open(encoding="utf-8") as config_file:
36
+ try:
37
+ data = json.load(config_file)
38
+ except json.JSONDecodeError as e:
39
+ raise pytest.UsageError(f"{FLAG}: invalid JSON in {path}: {e}") from e
40
+ return ensure_mapping(data=data, path=path)
41
+
42
+
43
+ def load_yaml_config(path: Path) -> dict[str, Any]:
44
+ with path.open(encoding="utf-8") as config_file:
45
+ # safe_load only: full YAML loading can construct arbitrary Python objects (code execution) from the file.
46
+ try:
47
+ data = yaml.safe_load(config_file)
48
+ except yaml.YAMLError as e:
49
+ raise pytest.UsageError(f"{FLAG}: invalid YAML in {path}: {e}") from e
50
+ return ensure_mapping(data=data, path=path)
51
+
52
+
53
+ # Keyed by lowercase file extension. To support a new format, write a load_<format>_config and add it here.
54
+ CONFIG_LOADERS: dict[str, ConfigLoader] = {
55
+ ".json": load_json_config,
56
+ ".yaml": load_yaml_config,
57
+ ".yml": load_yaml_config,
58
+ }
59
+
60
+
61
+ def pytest_addoption(parser: pytest.Parser) -> None:
62
+ parser.addoption(
63
+ FLAG,
64
+ action="store",
65
+ type=Path,
66
+ default=None,
67
+ metavar="PATH",
68
+ help=f"Load a config file ({', '.join(CONFIG_LOADERS)}) and expose its content to test classes "
69
+ f"as `self.{CLASS_ATTRIBUTE}`.",
70
+ )
71
+
72
+
73
+ def pytest_configure(config: pytest.Config) -> None:
74
+ if (config_path := config.getoption(FLAG)) is None:
75
+ return
76
+
77
+ config.stash[CONFIG_DATA_KEY] = load_config(path=config_path)
78
+ config.pluginmanager.register(injector, INJECTOR_PLUGIN_NAME)
79
+
80
+
81
+ def get_config_loader(path: Path) -> ConfigLoader:
82
+ suffix = path.suffix.lower()
83
+ if (loader := CONFIG_LOADERS.get(suffix)) is None:
84
+ supported = ", ".join(CONFIG_LOADERS)
85
+ raise pytest.UsageError(
86
+ f"{FLAG}: unsupported format {suffix or '(no extension)'!r} in {path}, supported: {supported}"
87
+ )
88
+ return loader
89
+
90
+
91
+ def load_config(path: Path) -> dict[str, Any]:
92
+ loader = get_config_loader(path=path)
93
+ try:
94
+ return loader(path)
95
+ except FileNotFoundError as e:
96
+ raise pytest.UsageError(f"{FLAG}: file not found: {path}") from e
97
+ except UnicodeDecodeError as e:
98
+ raise pytest.UsageError(f"{FLAG}: {path} is not UTF-8 text: {e}") from e
99
+ except OSError as e:
100
+ raise pytest.UsageError(f"{FLAG}: cannot read {path}: {e}") from e
@@ -0,0 +1,146 @@
1
+ Metadata-Version: 2.4
2
+ Name: pytest-inject-config
3
+ Version: 1.0.0
4
+ Summary: A pytest plugin that loads a JSON or YAML file given by --config-path and injects it into test classes as self.config.
5
+ Author: Avi Zaguri
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/aviz92/pytest-inject-config
8
+ Project-URL: Repository, https://github.com/aviz92/pytest-inject-config
9
+ Project-URL: Issues, https://github.com/aviz92/pytest-inject-config/issues
10
+ Keywords: pytest,plugin,testing,config,configuration,json,yaml
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Framework :: Pytest
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Topic :: Software Development :: Testing
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Requires-Python: >=3.12
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: pytest>=9.2.0
21
+ Requires-Dist: pyyaml>=6.0.3
22
+ Dynamic: license-file
23
+
24
+ ![PyPI version](https://img.shields.io/pypi/v/pytest-inject-config)
25
+ ![Python](https://img.shields.io/badge/python->=3.12-blue)
26
+ ![Development Status](https://img.shields.io/badge/status-stable-green)
27
+ ![Maintenance](https://img.shields.io/maintenance/yes/2026)
28
+ ![PyPI](https://img.shields.io/pypi/dm/pytest-inject-config)
29
+ ![License](https://img.shields.io/pypi/l/pytest-inject-config)
30
+
31
+ ---
32
+
33
+ # 💡 pytest-inject-config
34
+ A pytest plugin that loads a config file given by `--config-path` and injects it into every test class as `self.config`.
35
+ The format is chosen by the file extension: JSON (`.json`) and YAML (`.yaml`, `.yml`).
36
+ Run the same test suite against different environments by switching a single flag.
37
+
38
+ ---
39
+
40
+ ## 📦 Installation
41
+ ```bash
42
+ uv add pytest-inject-config
43
+ ```
44
+
45
+ ---
46
+
47
+ ## 🚀 Features
48
+ - ✅ **One flag, any environment** — point `--config-path` at a different JSON file per environment
49
+ - ✅ **Isolated per class** — each test class gets its own copy, so a test that mutates its config can't affect others
50
+ - ✅ **Fails fast** — a missing file, an unsupported extension, or invalid content stops the run at startup with a clear usage error
51
+ - ✅ **JSON and YAML** — chosen by extension; only known extensions are accepted, so adding formats later never changes how an existing file is read
52
+ - ✅ **Safe YAML** — YAML is loaded with `safe_load`, so a config file can never construct Python objects or run code
53
+ - ✅ **Read once** — the file is loaded at startup, not once per class
54
+ - ✅ **Zero footprint without the flag** — nothing is registered and no fixture runs
55
+
56
+ ---
57
+
58
+ ## 🛠️ How to Use
59
+ 1. Create a config file per environment, in JSON:
60
+ ```json
61
+ {
62
+ "host": "10.0.0.1",
63
+ "port": 8080
64
+ }
65
+ ```
66
+ or in YAML:
67
+ ```yaml
68
+ host: 10.0.0.1
69
+ port: 8080
70
+ ```
71
+ 2. Read it from your test classes through `self.config`:
72
+ ```python
73
+ class TestDevice:
74
+ def test_connect(self):
75
+ assert connect(self.config["host"], self.config["port"])
76
+ ```
77
+ 3. Run with the file for the environment you want:
78
+ ```bash
79
+ pytest --config-path=config/staging.json
80
+ ```
81
+
82
+ ---
83
+
84
+ ## ⚙️ Behavior
85
+ | Situation | Result |
86
+ |---|---|
87
+ | Test inside a class | `self.config` holds the file's content as a `dict` |
88
+ | Top level is not a mapping (a list, a number, a string) | `UsageError` at startup |
89
+ | Module-level test function | Unaffected |
90
+ | Relative path | Resolved from the directory pytest is run in |
91
+ | Extension | Decides the format, case-insensitive. Supported: `.json`, `.yaml`, `.yml` |
92
+ | Unsupported or missing extension | `UsageError` at startup (exit code 4), even if the content would parse |
93
+ | Empty file, or a top-level `null` | `UsageError` at startup |
94
+ | YAML with several documents (`---`) or Python tags (`!!python/...`) | `UsageError` at startup |
95
+ | Missing file, directory, non-UTF-8 or invalid content | `UsageError` at startup (exit code 4), no tests run |
96
+ | No `--config-path` | Nothing is injected; no fixture runs |
97
+
98
+ ---
99
+
100
+ ## ▶️ Usage Examples
101
+ ### Select the environment in CI
102
+ ```bash
103
+ pytest --config-path="config/${TARGET_ENV}.yaml"
104
+ ```
105
+
106
+ ### Set a default in `pytest.ini`
107
+ ```ini
108
+ [pytest]
109
+ addopts =
110
+ --config-path=config/local.json
111
+ ```
112
+
113
+ ---
114
+
115
+ ## 🔁 Migrating from pytest-plugins
116
+ This plugin was previously `add_config_parameters` in `pytest-plugins`, where it never worked.
117
+ Both register the `--config-path` option, so don't install them together until you're on a `pytest-plugins`
118
+ release without it.
119
+
120
+ ---
121
+
122
+ ## 🤝 Contributing
123
+ 1. Fork the repository
124
+ 2. Create a feature branch: `git checkout -b feature/my-addition`
125
+ 3. Make your changes
126
+ 4. Commit your changes: `git commit -m 'Add my addition'`
127
+ 5. Push to the branch: `git push origin feature/my-addition`
128
+ 6. Open a Pull Request
129
+
130
+ Additions that promote clean, productive, and maintainable development are always welcome.
131
+
132
+ ---
133
+
134
+ ## 📄 License
135
+ MIT License — see [LICENSE](LICENSE) for details.
136
+
137
+ ---
138
+
139
+ ## 🙏 Thanks
140
+ Thanks for exploring this repository! <br>
141
+ Happy coding!
142
+
143
+ [![GitHub](https://img.shields.io/badge/GitHub-aviz92-181717?logo=github)](https://github.com/aviz92)
144
+ &nbsp; [![PyPI](https://img.shields.io/badge/PyPI-aviz-3775A9?logo=pypi)](https://pypi.org/user/aviz/)
145
+ &nbsp; [![Blog](https://img.shields.io/badge/Blog-aviz92.github.io-0066CC?logo=googlechrome)](https://aviz92.github.io/)
146
+ &nbsp; [![LinkedIn](https://img.shields.io/badge/LinkedIn-avi--zaguri-0A66C2?logo=linkedin)](https://www.linkedin.com/in/avi-zaguri-41869b11b)
@@ -0,0 +1,14 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ pytest_inject_config/__init__.py
6
+ pytest_inject_config/const.py
7
+ pytest_inject_config/injector.py
8
+ pytest_inject_config/plugin.py
9
+ pytest_inject_config.egg-info/PKG-INFO
10
+ pytest_inject_config.egg-info/SOURCES.txt
11
+ pytest_inject_config.egg-info/dependency_links.txt
12
+ pytest_inject_config.egg-info/entry_points.txt
13
+ pytest_inject_config.egg-info/requires.txt
14
+ pytest_inject_config.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [pytest11]
2
+ inject_config = pytest_inject_config.plugin
@@ -0,0 +1,2 @@
1
+ pytest>=9.2.0
2
+ pyyaml>=6.0.3
@@ -0,0 +1 @@
1
+ pytest_inject_config
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+