88A ` pytest ` plugin that sets environment variables from ` pyproject.toml ` , ` pytest.toml ` , ` .pytest.toml ` , or ` pytest.ini `
99configuration files. It can also load variables from ` .env ` files.
1010
11+ <!-- mdformat-toc start --slug=github --no-anchors --maxlevel=6 --minlevel=2 -->
12+
13+ - [ Installation] ( #installation )
14+ - [ Quick start] ( #quick-start )
15+ - [ How-to guides] ( #how-to-guides )
16+ - [ Load variables from ` .env ` files] ( #load-variables-from-env-files )
17+ - [ Control variable behavior] ( #control-variable-behavior )
18+ - [ Set different environments for test suites] ( #set-different-environments-for-test-suites )
19+ - [ Reference] ( #reference )
20+ - [ TOML configuration format] ( #toml-configuration-format )
21+ - [ INI configuration format] ( #ini-configuration-format )
22+ - [ ` .env ` file format] ( #env-file-format )
23+ - [ CLI options] ( #cli-options )
24+ - [ ` --envfile PATH ` ] ( #--envfile-path )
25+ - [ ` --pytest-env-verbose ` ] ( #--pytest-env-verbose )
26+ - [ Explanation] ( #explanation )
27+ - [ Precedence] ( #precedence )
28+ - [ File discovery] ( #file-discovery )
29+ - [ Choosing a configuration format] ( #choosing-a-configuration-format )
30+
31+ <!-- mdformat-toc end -->
32+
1133## Installation
1234
1335``` shell
@@ -37,35 +59,6 @@ def test_database_connection():
3759
3860## How-to guides
3961
40- ### Set different environments for test suites
41-
42- Create a subdirectory config to override parent settings:
43-
44- ```
45- project/
46- ├── pyproject.toml # [tool.pytest_env] DB_HOST = "prod-db"
47- └── tests_integration/
48- ├── pytest.toml # [pytest_env] DB_HOST = "test-db"
49- └── test_api.py
50- ```
51-
52- Running ` pytest tests_integration/ ` uses the subdirectory configuration.
53-
54- ### Switch environments at runtime
55-
56- Use the ` --envfile ` CLI option to override or extend your configuration:
57-
58- ``` shell
59- # Override all configured env files with a different one.
60- pytest --envfile .env.local
61-
62- # Add an additional env file to those already configured.
63- pytest --envfile +.env.override
64- ```
65-
66- Override mode loads only the specified file. Extend mode (prefix with ` + ` ) loads configuration files first, then the CLI
67- file. Variables in the CLI file take precedence.
68-
6962### Load variables from ` .env ` files
7063
7164Specify ` .env ` files in your configuration:
@@ -83,48 +76,53 @@ SECRET_KEY='my-secret-key'
8376DEBUG=" true"
8477```
8578
86- Files are loaded before inline variables, so inline configuration takes precedence.
87-
88- ### Expand variables using other environment variables
79+ Files are loaded before inline variables, so inline configuration takes precedence. To switch ` .env ` files at runtime
80+ without changing configuration, use the ` --envfile ` CLI option:
8981
90- Reference existing environment variables in values:
91-
92- ``` toml
93- [tool .pytest_env ]
94- RUN_PATH = { value = " /run/path/{USER}" , transform = true }
82+ ``` shell
83+ pytest --envfile .env.local # ignore configured env_files, load only this file
84+ pytest --envfile +.env.override # load configured env_files first, then this file on top
9585```
9686
97- The ` {USER} ` placeholder expands to the current user's name.
98-
99- ### Set conditional defaults
87+ ### Control variable behavior
10088
101- Only set a variable if it does not already exist:
89+ Variables set as plain values are assigned directly. For more control, use inline tables with the ` transform ` ,
90+ ` skip_if_set ` , and ` unset ` keys:
10291
10392``` toml
10493[tool .pytest_env ]
94+ SIMPLE = " value"
95+ RUN_PATH = { value = " /run/path/{USER}" , transform = true }
10596HOME = { value = " ~/tmp" , skip_if_set = true }
97+ TEMP_VAR = { unset = true }
10698```
10799
108- This leaves ` HOME ` unchanged if already set, otherwise sets it to ` ~/tmp ` .
100+ ` transform ` expands ` {VAR} ` placeholders using existing environment variables. ` skip_if_set ` leaves the variable
101+ unchanged when it already exists. ` unset ` removes it entirely (different from setting to empty string).
109102
110- ### Remove variables from the environment
103+ ### Set different environments for test suites
111104
112- Unset a variable completely (different from setting to empty string) :
105+ Create a subdirectory config to override parent settings :
113106
114- ``` toml
115- [tool .pytest_env ]
116- DATABASE_URL = { unset = true }
107+ ```
108+ project/
109+ ├── pyproject.toml # [tool.pytest_env] DB_HOST = "prod-db"
110+ └── tests_integration/
111+ ├── pytest.toml # [pytest_env] DB_HOST = "test-db"
112+ └── test_api.py
117113```
118114
115+ Running ` pytest tests_integration/ ` uses the subdirectory configuration. The plugin walks up the directory tree and
116+ stops at the first file containing a ` pytest_env ` section, so subdirectory configs naturally override parent configs.
117+
119118## Reference
120119
121120### TOML configuration format
122121
123- Define environment variables under ` [tool.pytest_env] ` in ` pyproject.toml ` , or ` [pytest_env] ` in ` pytest.toml ` or
122+ Define environment variables under ` [tool.pytest_env] ` in ` pyproject.toml ` , or ` [pytest_env] ` in ` pytest.toml ` /
124123` .pytest.toml ` :
125124
126125``` toml
127- # pyproject.toml
128126[tool .pytest_env ]
129127SIMPLE_VAR = " value"
130128NUMBER_VAR = 42
@@ -133,10 +131,8 @@ CONDITIONAL = { value = "default", skip_if_set = true }
133131REMOVED = { unset = true }
134132```
135133
136- Each key is the environment variable name. Values can be:
137-
138- - ** Plain values** : Cast to string and set directly.
139- - ** Inline tables** : Objects with the following keys:
134+ Each key is the environment variable name. Values can be plain values (cast to string) or inline tables with the
135+ following keys:
140136
141137| Key | Type | Description |
142138| ------------- | ------ | ---------------------------------------------------------------------------- |
@@ -171,13 +167,13 @@ env = [
171167
172168Prefix flags modify behavior. Flags are case-insensitive and can be combined in any order (e.g., ` R:D:KEY=VALUE ` ):
173169
174- | Flag | Description |
175- | ---- | ------------------------------------------------------------------- |
176- | ` D: ` | Default — only set if the variable is not already defined. |
177- | ` R: ` | Raw — skip ` {VAR} ` expansion (INI expands by default, unlike TOML). |
178- | ` U: ` | Unset — remove the variable from the environment entirely. |
170+ | Flag | Description |
171+ | ---- | -------------------------------------------------------------------- |
172+ | ` D: ` | Default -- only set if the variable is not already defined. |
173+ | ` R: ` | Raw -- skip ` {VAR} ` expansion (INI expands by default, unlike TOML). |
174+ | ` U: ` | Unset -- remove the variable from the environment entirely. |
179175
180- ** Note ** : In INI format, variable expansion is enabled by default. In TOML format, it requires ` transform = true ` .
176+ In INI format variable expansion is enabled by default. In TOML format it requires ` transform = true ` .
181177
182178### ` .env ` file format
183179
@@ -195,13 +191,8 @@ env_files =
195191 .env.test
196192```
197193
198- Files are parsed by [ python-dotenv] ( https://github.com/theskumar/python-dotenv ) and support:
199-
200- - ` KEY=VALUE ` lines
201- - ` # ` comments
202- - ` export ` prefix
203- - Quoted values with escape sequences in double quotes
204- - ` ${VAR:-default} ` expansion
194+ Files are parsed by [ python-dotenv] ( https://github.com/theskumar/python-dotenv ) and support ` KEY=VALUE ` lines, ` # `
195+ comments, ` export ` prefix, quoted values with escape sequences in double quotes, and ` ${VAR:-default} ` expansion.
205196
206197Example ` .env ` file:
207198
@@ -213,83 +204,71 @@ MESSAGE="hello\nworld"
213204API_KEY=${FALLBACK_KEY:- default_key}
214205```
215206
216- Missing ` .env ` files are silently skipped. Paths are resolved relative to the project root.
207+ Missing ` .env ` files from configuration are silently skipped. Paths are resolved relative to the project root.
217208
218- ### CLI option: ` --envfile `
209+ ### CLI options
219210
220- Override or extend configuration-based ` env_files ` at runtime:
211+ #### ` --envfile PATH `
221212
222- ``` shell
223- pytest --envfile PATH # Override mode
224- pytest --envfile +PATH # Extend mode
225- ```
213+ Override or extend configuration-based ` env_files ` at runtime.
226214
227- ** Override mode** (` --envfile PATH ` ): Loads only the specified file, ignoring all ` env_files ` from configuration.
215+ ** Override mode** (` --envfile PATH ` ): loads only the specified file, ignoring all ` env_files ` from configuration.
228216
229- ** Extend mode** (` --envfile +PATH ` ): Loads configuration files first in their normal order, then loads the CLI file.
217+ ** Extend mode** (` --envfile +PATH ` ): loads configuration files first in their normal order, then loads the CLI file.
230218Variables from the CLI file override those from configuration files.
231219
232220Unlike configuration-based ` env_files ` , CLI-specified files must exist. Missing files raise ` FileNotFoundError ` . Paths
233221are resolved relative to the project root.
234222
235- ## Explanation
223+ #### ` --pytest-env-verbose `
236224
237- ### Configuration precedence
225+ Print all environment variable assignments in the test session header. Each line shows the action (` SET ` , ` SKIP ` , or
226+ ` UNSET ` ), the variable name with its final value, and the source file:
238227
239- When multiple configuration sources define the same variable, the following precedence rules apply (highest to lowest):
228+ ```
229+ pytest-env:
230+ SET DATABASE_URL=postgres://localhost/test (from /path/to/.env)
231+ SET DEBUG=true (from /path/to/pyproject.toml)
232+ SKIP HOME=/Users/me (from /path/to/pyproject.toml)
233+ UNSET TEMP_VAR (from /path/to/pyproject.toml)
234+ ```
240235
241- 1 . Inline variables in configuration files (TOML or INI format)
242- 1 . Variables from ` .env ` files loaded via ` env_files `
243- 1 . Variables already present in the environment (unless ` skip_if_set = false ` or no ` D: ` flag)
236+ Useful for debugging when multiple env files, inline configuration, and CLI options interact.
244237
245- When using ` --envfile ` , CLI files take precedence over configuration-based ` env_files ` , but inline variables still win.
238+ ## Explanation
246239
247- ### Configuration format precedence
240+ ### Precedence
248241
249- When multiple configuration formats are present :
242+ When multiple sources define the same variable, precedence applies in this order (highest to lowest) :
250243
251- 1 . TOML native format (` [pytest_env] ` or ` [tool.pytest_env] ` ) takes precedence over INI format.
252- 1 . Among TOML files, the first file with a ` pytest_env ` section is used, checked in order: ` pytest.toml ` ,
253- ` .pytest.toml ` , ` pyproject.toml ` .
254- 1 . If no TOML file contains ` pytest_env ` , the plugin falls back to INI-style ` env ` configuration.
244+ 1 . Inline variables in configuration files (TOML or INI format).
245+ 1 . Variables from ` .env ` files loaded via ` env_files ` . When using ` --envfile ` , CLI files take precedence over
246+ configuration-based ` env_files ` .
247+ 1 . Variables already present in the environment (preserved when ` skip_if_set = true ` or ` D: ` flag is used).
248+
249+ When multiple configuration formats are present, TOML native format (` [pytest_env] ` / ` [tool.pytest_env] ` ) takes
250+ precedence over INI format. Among TOML files, the first file with a ` pytest_env ` section wins, checked in order:
251+ ` pytest.toml ` , ` .pytest.toml ` , ` pyproject.toml ` . If no TOML file contains ` pytest_env ` , the plugin falls back to
252+ INI-style ` env ` configuration.
255253
256254### File discovery
257255
258256The plugin walks up the directory tree starting from pytest's resolved configuration directory. For each directory, it
259257checks ` pytest.toml ` , ` .pytest.toml ` , and ` pyproject.toml ` in order, stopping at the first file containing a
260- ` pytest_env ` section.
261-
262- This means subdirectory configurations take precedence over parent configurations, allowing you to have different
263- settings for integration tests versus unit tests.
264-
265- ### When to use TOML vs INI format
266-
267- Use the ** TOML native format** (` [pytest_env] ` ) when:
268-
269- - You need fine-grained control over expansion and conditional setting.
270- - Your configuration is complex with multiple inline tables.
271- - You prefer explicit ` transform = true ` for variable expansion.
272-
273- Use the ** INI format** (` env ` key) when:
274-
275- - You want simple ` KEY=VALUE ` pairs with minimal syntax.
276- - You prefer expansion by default (add ` R: ` to disable).
277- - You are migrating from an existing INI-based setup.
278-
279- Both formats are fully supported and can coexist (TOML takes precedence if both are present).
280-
281- ### When to use ` .env ` files vs inline configuration
282-
283- Use ** ` .env ` files** when:
258+ ` pytest_env ` section. This means subdirectory configurations take precedence over parent configurations, allowing
259+ different settings for integration tests versus unit tests.
284260
285- - You have many environment variables that would clutter your config file.
286- - You want to share environment configuration with other tools (e.g., Docker, shell scripts).
287- - You need different ` .env ` files for different environments (dev, staging, prod).
261+ ### Choosing a configuration format
288262
289- Use ** inline configuration** when:
263+ ** TOML native format** (` [pytest_env] ` ) is best when you need fine-grained control over expansion and conditional
264+ setting, or when your configuration uses multiple inline tables. Variable expansion requires explicit
265+ ` transform = true ` .
290266
291- - You have a small number of test-specific variables.
292- - You want variables to be version-controlled alongside test configuration.
293- - You need features like ` transform ` , ` skip_if_set ` , or ` unset ` that ` .env ` files do not support.
267+ ** INI format** (` env ` key) is best for simple ` KEY=VALUE ` pairs with minimal syntax. Variable expansion is on by default
268+ (use ` R: ` to disable). Both formats are fully supported and can coexist -- TOML takes precedence if both are present.
294269
295- You can combine both approaches. Inline variables always take precedence over ` .env ` files.
270+ ** ` .env ` files** work well when you have many variables that would clutter your config file, want to share environment
271+ configuration with other tools (Docker, shell scripts), or need different files for different environments. ** Inline
272+ configuration** is better for a small number of test-specific variables that should be version-controlled, or when you
273+ need ` transform ` , ` skip_if_set ` , or ` unset ` . You can combine both -- inline variables always take precedence over ` .env `
274+ files.
0 commit comments