18
18
from . import Config
19
19
from iniconfig import IniConfig # NOQA: F401
20
20
21
+ PARSE_RESULT = Optional [Dict [str , Union [str , List [str ]]]]
22
+
21
23
22
24
def _parse_ini_config (path : Path ) -> "IniConfig" :
23
25
"""Parse the given generic '.ini' file using legacy IniConfig parser, returning
@@ -33,52 +35,94 @@ def _parse_ini_config(path: Path) -> "IniConfig":
33
35
raise UsageError (str (exc )) from exc
34
36
35
37
36
- def load_config_dict_from_file (
37
- filepath : Path ,
38
- ) -> Optional [Dict [str , Union [str , List [str ]]]]:
39
- """Load pytest configuration from the given file path, if supported.
38
+ def _parse_pytest_ini (path : Path ) -> PARSE_RESULT :
39
+ """Parse the legacy pytest.ini and return the contents of the pytest section
40
40
41
- Return None if the file does not contain valid pytest configuration.
41
+ if the file exists and lacks a pytest section, consider it empty"""
42
+ iniconfig = _parse_ini_config (path )
43
+
44
+ if "pytest" in iniconfig :
45
+ return dict (iniconfig ["pytest" ].items ())
46
+ else :
47
+ # "pytest.ini" files are always the source of configuration, even if empty.
48
+ return {}
49
+
50
+
51
+ def _parse_ini_file (path : Path ) -> PARSE_RESULT :
52
+ """Parses .ini files with expected pytest.ini sections
53
+
54
+ todo: investigate if tool:pytest should be added
42
55
"""
56
+ iniconfig = _parse_ini_config (path )
43
57
44
- # Configuration from ini files are obtained from the [ pytest] section, if present.
45
- if filepath . suffix == ".ini" :
46
- iniconfig = _parse_ini_config ( filepath )
58
+ if " pytest" in iniconfig :
59
+ return dict ( iniconfig [ "pytest" ]. items ())
60
+ return None
47
61
48
- if "pytest" in iniconfig :
49
- return dict (iniconfig ["pytest" ].items ())
50
- else :
51
- # "pytest.ini" files are always the source of configuration, even if empty.
52
- if filepath .name == "pytest.ini" :
53
- return {}
54
62
55
- # '.cfg' files are considered if they contain a "[tool:pytest]" section.
56
- elif filepath .suffix == ".cfg" :
57
- iniconfig = _parse_ini_config (filepath )
63
+ def _parse_cfg_file (path : Path ) -> PARSE_RESULT :
64
+ """Parses .cfg files, specifically used for setup.cfg support
58
65
59
- if "tool:pytest" in iniconfig .sections :
60
- return dict (iniconfig ["tool:pytest" ].items ())
61
- elif "pytest" in iniconfig .sections :
62
- # If a setup.cfg contains a "[pytest]" section, we raise a failure to indicate users that
63
- # plain "[pytest]" sections in setup.cfg files is no longer supported (#3086).
64
- fail (CFG_PYTEST_SECTION .format (filename = "setup.cfg" ), pytrace = False )
66
+ tool:pytest as section name is required
67
+ """
68
+
69
+ iniconfig = _parse_ini_config (path )
70
+
71
+ if "tool:pytest" in iniconfig .sections :
72
+ return dict (iniconfig ["tool:pytest" ].items ())
73
+ elif "pytest" in iniconfig .sections :
74
+ # If a setup.cfg contains a "[pytest]" section, we raise a failure to indicate users that
75
+ # plain "[pytest]" sections in setup.cfg files is no longer supported (#3086).
76
+ fail (CFG_PYTEST_SECTION .format (filename = "setup.cfg" ), pytrace = False )
77
+ else :
78
+ return None
65
79
66
- # '.toml' files are considered if they contain a [tool.pytest.ini_options] table.
67
- elif filepath .suffix == ".toml" :
68
- import toml
69
80
70
- config = toml .load (str (filepath ))
81
+ def _parse_pyproject_ini_options (
82
+ path : Path ,
83
+ ) -> PARSE_RESULT :
84
+ """Load backward compatible ini options from pyproject.toml"""
71
85
72
- result = config .get ("tool" , {}).get ("pytest" , {}).get ("ini_options" , None )
73
- if result is not None :
74
- # TOML supports richer data types than ini files (strings, arrays, floats, ints, etc),
75
- # however we need to convert all scalar values to str for compatibility with the rest
76
- # of the configuration system, which expects strings only.
77
- def make_scalar (v : object ) -> Union [str , List [str ]]:
78
- return v if isinstance (v , list ) else str (v )
86
+ import toml
79
87
80
- return { k : make_scalar ( v ) for k , v in result . items ()}
88
+ config = toml . load ( path )
81
89
90
+ result = config .get ("tool" , {}).get ("pytest" , {}).get ("ini_options" , None )
91
+ if result is not None :
92
+ # TOML supports richer data types than ini files (strings, arrays, floats, ints, etc),
93
+ # however we need to convert all scalar values to str for compatibility with the rest
94
+ # of the configuration system, which expects strings only.
95
+ def make_scalar (v : object ) -> Union [str , List [str ]]:
96
+ return v if isinstance (v , list ) else str (v )
97
+
98
+ return {k : make_scalar (v ) for k , v in result .items ()}
99
+ else :
100
+ return None
101
+
102
+
103
+ CONFIG_LOADERS = {
104
+ "pytest.ini" : _parse_pytest_ini ,
105
+ "pyproject.toml" : _parse_pyproject_ini_options ,
106
+ "tox.ini" : _parse_ini_file ,
107
+ "setup.cfg" : _parse_cfg_file ,
108
+ }
109
+
110
+ CONFIG_SUFFIXES = {
111
+ ".ini" : _parse_ini_file ,
112
+ ".cfg" : _parse_cfg_file ,
113
+ ".toml" : _parse_pyproject_ini_options ,
114
+ }
115
+
116
+
117
+ def load_config_dict_from_file (path : Path ) -> PARSE_RESULT :
118
+ """Load pytest configuration from the given file path, if supported.
119
+
120
+ Return None if the file does not contain valid pytest configuration.
121
+ """
122
+ if path .name in CONFIG_LOADERS :
123
+ return CONFIG_LOADERS [path .name ](path )
124
+ if path .suffix in CONFIG_SUFFIXES :
125
+ return CONFIG_SUFFIXES [path .suffix ](path )
82
126
return None
83
127
84
128
@@ -87,24 +131,19 @@ def locate_config(
87
131
) -> Tuple [Optional [Path ], Optional [Path ], Dict [str , Union [str , List [str ]]]]:
88
132
"""Search in the list of arguments for a valid ini-file for pytest,
89
133
and return a tuple of (rootdir, inifile, cfg-dict)."""
90
- config_names = [
91
- "pytest.ini" ,
92
- "pyproject.toml" ,
93
- "tox.ini" ,
94
- "setup.cfg" ,
95
- ]
134
+
96
135
args = [x for x in args if not str (x ).startswith ("-" )]
97
136
if not args :
98
137
args = [Path .cwd ()]
99
138
for arg in args :
100
139
argpath = absolutepath (arg )
101
140
for base in (argpath , * argpath .parents ):
102
- for config_name in config_names :
141
+ for config_name , loader in CONFIG_LOADERS . items () :
103
142
p = base / config_name
104
143
if p .is_file ():
105
- ini_config = load_config_dict_from_file (p )
106
- if ini_config is not None :
107
- return base , p , ini_config
144
+ config = loader (p )
145
+ if config is not None :
146
+ return base , p , config
108
147
return None , None , {}
109
148
110
149
0 commit comments