Skip to content

Commit adb3658

Browse files
authored
Merge pull request #13864 from bluetech/config-cleanups-2
Some more config cleanups
2 parents a28c08e + 2079b91 commit adb3658

File tree

8 files changed

+244
-212
lines changed

8 files changed

+244
-212
lines changed

src/_pytest/config/__init__.py

Lines changed: 131 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,29 @@ def filter_traceback_for_conftest_import_failure(
142142
return filter_traceback(entry) and "importlib" not in str(entry.path).split(os.sep)
143143

144144

145+
def print_conftest_import_error(e: ConftestImportFailure, file: TextIO) -> None:
146+
exc_info = ExceptionInfo.from_exception(e.cause)
147+
tw = TerminalWriter(file)
148+
tw.line(f"ImportError while loading conftest '{e.path}'.", red=True)
149+
exc_info.traceback = exc_info.traceback.filter(
150+
filter_traceback_for_conftest_import_failure
151+
)
152+
exc_repr = (
153+
exc_info.getrepr(style="short", chain=False)
154+
if exc_info.traceback
155+
else exc_info.exconly()
156+
)
157+
formatted_tb = str(exc_repr)
158+
for line in formatted_tb.splitlines():
159+
tw.line(line.rstrip(), red=True)
160+
161+
162+
def print_usage_error(e: UsageError, file: TextIO) -> None:
163+
tw = TerminalWriter(file)
164+
for msg in e.args:
165+
tw.line(f"ERROR: {msg}\n", red=True)
166+
167+
145168
def main(
146169
args: list[str] | os.PathLike[str] | None = None,
147170
plugins: Sequence[str | _PluggyPlugin] | None = None,
@@ -167,34 +190,19 @@ def main(
167190
try:
168191
config = _prepareconfig(new_args, plugins)
169192
except ConftestImportFailure as e:
170-
exc_info = ExceptionInfo.from_exception(e.cause)
171-
tw = TerminalWriter(sys.stderr)
172-
tw.line(f"ImportError while loading conftest '{e.path}'.", red=True)
173-
exc_info.traceback = exc_info.traceback.filter(
174-
filter_traceback_for_conftest_import_failure
175-
)
176-
exc_repr = (
177-
exc_info.getrepr(style="short", chain=False)
178-
if exc_info.traceback
179-
else exc_info.exconly()
180-
)
181-
formatted_tb = str(exc_repr)
182-
for line in formatted_tb.splitlines():
183-
tw.line(line.rstrip(), red=True)
193+
print_conftest_import_error(e, file=sys.stderr)
184194
return ExitCode.USAGE_ERROR
185-
else:
195+
196+
try:
197+
ret: ExitCode | int = config.hook.pytest_cmdline_main(config=config)
186198
try:
187-
ret: ExitCode | int = config.hook.pytest_cmdline_main(config=config)
188-
try:
189-
return ExitCode(ret)
190-
except ValueError:
191-
return ret
192-
finally:
193-
config._ensure_unconfigure()
199+
return ExitCode(ret)
200+
except ValueError:
201+
return ret
202+
finally:
203+
config._ensure_unconfigure()
194204
except UsageError as e:
195-
tw = TerminalWriter(sys.stderr)
196-
for msg in e.args:
197-
tw.line(f"ERROR: {msg}\n", red=True)
205+
print_usage_error(e, file=sys.stderr)
198206
return ExitCode.USAGE_ERROR
199207
finally:
200208
if old_pytest_version is None:
@@ -1006,7 +1014,7 @@ class InvocationParams:
10061014
plugins: Sequence[str | _PluggyPlugin] | None
10071015
"""Extra plugins, might be `None`."""
10081016
dir: pathlib.Path
1009-
"""The directory from which :func:`pytest.main` was invoked. :type: pathlib.Path"""
1017+
"""The directory from which :func:`pytest.main` was invoked."""
10101018

10111019
def __init__(
10121020
self,
@@ -1042,9 +1050,6 @@ def __init__(
10421050
*,
10431051
invocation_params: InvocationParams | None = None,
10441052
) -> None:
1045-
from .argparsing import FILE_OR_DIR
1046-
from .argparsing import Parser
1047-
10481053
if invocation_params is None:
10491054
invocation_params = self.InvocationParams(
10501055
args=(), plugins=None, dir=pathlib.Path.cwd()
@@ -1062,9 +1067,8 @@ def __init__(
10621067
:type: InvocationParams
10631068
"""
10641069

1065-
_a = FILE_OR_DIR
10661070
self._parser = Parser(
1067-
usage=f"%(prog)s [options] [{_a}] [{_a}] [...]",
1071+
usage=f"%(prog)s [options] [{FILE_OR_DIR}] [{FILE_OR_DIR}] [...]",
10681072
processopt=self._processopt,
10691073
_ispytest=True,
10701074
)
@@ -1100,8 +1104,6 @@ def __init__(
11001104
def rootpath(self) -> pathlib.Path:
11011105
"""The path to the :ref:`rootdir <rootdir>`.
11021106
1103-
:type: pathlib.Path
1104-
11051107
.. versionadded:: 6.1
11061108
"""
11071109
return self._rootpath
@@ -1164,7 +1166,7 @@ def pytest_cmdline_parse(
11641166
elif (
11651167
getattr(self.option, "help", False) or "--help" in args or "-h" in args
11661168
):
1167-
self._parser._getparser().print_help()
1169+
self._parser.optparser.print_help()
11681170
sys.stdout.write(
11691171
"\nNOTE: displaying only minimal help due to UsageError.\n\n"
11701172
)
@@ -1247,19 +1249,18 @@ def pytest_load_initial_conftests(self, early_config: Config) -> None:
12471249
),
12481250
)
12491251

1250-
def _consider_importhook(self, args: Sequence[str]) -> None:
1252+
def _consider_importhook(self) -> None:
12511253
"""Install the PEP 302 import hook if using assertion rewriting.
12521254
12531255
Needs to parse the --assert=<mode> option from the commandline
12541256
and find all the installed plugins to mark them for rewriting
12551257
by the importhook.
12561258
"""
1257-
ns, _unknown_args = self._parser.parse_known_and_unknown_args(args)
1258-
mode = getattr(ns, "assertmode", "plain")
1259+
mode = getattr(self.known_args_namespace, "assertmode", "plain")
12591260

1260-
disable_autoload = getattr(ns, "disable_plugin_autoload", False) or bool(
1261-
os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD")
1262-
)
1261+
disable_autoload = getattr(
1262+
self.known_args_namespace, "disable_plugin_autoload", False
1263+
) or bool(os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"))
12631264
if mode == "rewrite":
12641265
import _pytest.assertion
12651266

@@ -1363,94 +1364,6 @@ def _decide_args(
13631364
result = [str(invocation_dir)]
13641365
return result, source
13651366

1366-
def _preparse(self, args: list[str], addopts: bool = True) -> None:
1367-
if addopts:
1368-
env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
1369-
if len(env_addopts):
1370-
args[:] = (
1371-
self._validate_args(shlex.split(env_addopts), "via PYTEST_ADDOPTS")
1372-
+ args
1373-
)
1374-
1375-
ns, unknown_args = self._parser.parse_known_and_unknown_args(
1376-
args, namespace=copy.copy(self.option)
1377-
)
1378-
rootpath, inipath, inicfg, ignored_config_files = determine_setup(
1379-
inifile=ns.inifilename,
1380-
override_ini=ns.override_ini,
1381-
args=ns.file_or_dir + unknown_args,
1382-
rootdir_cmd_arg=ns.rootdir or None,
1383-
invocation_dir=self.invocation_params.dir,
1384-
)
1385-
self._rootpath = rootpath
1386-
self._inipath = inipath
1387-
self._ignored_config_files = ignored_config_files
1388-
self.inicfg = inicfg
1389-
self._parser.extra_info["rootdir"] = str(self.rootpath)
1390-
self._parser.extra_info["inifile"] = str(self.inipath)
1391-
self._parser.addini("addopts", "Extra command line options", "args")
1392-
self._parser.addini("minversion", "Minimally required pytest version")
1393-
self._parser.addini(
1394-
"pythonpath", type="paths", help="Add paths to sys.path", default=[]
1395-
)
1396-
self._parser.addini(
1397-
"required_plugins",
1398-
"Plugins that must be present for pytest to run",
1399-
type="args",
1400-
default=[],
1401-
)
1402-
1403-
if addopts:
1404-
args[:] = (
1405-
self._validate_args(self.getini("addopts"), "via addopts config") + args
1406-
)
1407-
1408-
self.known_args_namespace = self._parser.parse_known_args(
1409-
args, namespace=copy.copy(self.option)
1410-
)
1411-
self._checkversion()
1412-
self._consider_importhook(args)
1413-
self._configure_python_path()
1414-
self.pluginmanager.consider_preparse(args, exclude_only=False)
1415-
if (
1416-
not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD")
1417-
and not self.known_args_namespace.disable_plugin_autoload
1418-
):
1419-
# Autoloading from distribution package entry point has
1420-
# not been disabled.
1421-
self.pluginmanager.load_setuptools_entrypoints("pytest11")
1422-
# Otherwise only plugins explicitly specified in PYTEST_PLUGINS
1423-
# are going to be loaded.
1424-
self.pluginmanager.consider_env()
1425-
1426-
self.known_args_namespace = self._parser.parse_known_args(
1427-
args, namespace=copy.copy(self.known_args_namespace)
1428-
)
1429-
1430-
self._validate_plugins()
1431-
self._warn_about_skipped_plugins()
1432-
1433-
if self.known_args_namespace.confcutdir is None:
1434-
if self.inipath is not None:
1435-
confcutdir = str(self.inipath.parent)
1436-
else:
1437-
confcutdir = str(self.rootpath)
1438-
self.known_args_namespace.confcutdir = confcutdir
1439-
try:
1440-
self.hook.pytest_load_initial_conftests(
1441-
early_config=self, args=args, parser=self._parser
1442-
)
1443-
except ConftestImportFailure as e:
1444-
if self.known_args_namespace.help or self.known_args_namespace.version:
1445-
# we don't want to prevent --help/--version to work
1446-
# so just let it pass and print a warning at the end
1447-
self.issue_config_time_warning(
1448-
PytestConfigWarning(f"could not load initial conftests: {e.path}"),
1449-
stacklevel=2,
1450-
)
1451-
else:
1452-
raise
1453-
14541367
@hookimpl(wrapper=True)
14551368
def pytest_collection(self) -> Generator[None, object, object]:
14561369
# Validate invalid configuration keys after collection is done so we
@@ -1534,18 +1447,103 @@ def parse(self, args: list[str], addopts: bool = True) -> None:
15341447
assert self.args == [], (
15351448
"can only parse cmdline args at most once per Config object"
15361449
)
1450+
15371451
self.hook.pytest_addhooks.call_historic(
15381452
kwargs=dict(pluginmanager=self.pluginmanager)
15391453
)
1540-
self._preparse(args, addopts=addopts)
1541-
self._parser.after_preparse = True # type: ignore
1454+
1455+
if addopts:
1456+
env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
1457+
if len(env_addopts):
1458+
args[:] = (
1459+
self._validate_args(shlex.split(env_addopts), "via PYTEST_ADDOPTS")
1460+
+ args
1461+
)
1462+
1463+
ns = self._parser.parse_known_args(args, namespace=copy.copy(self.option))
1464+
rootpath, inipath, inicfg, ignored_config_files = determine_setup(
1465+
inifile=ns.inifilename,
1466+
override_ini=ns.override_ini,
1467+
args=ns.file_or_dir,
1468+
rootdir_cmd_arg=ns.rootdir or None,
1469+
invocation_dir=self.invocation_params.dir,
1470+
)
1471+
self._rootpath = rootpath
1472+
self._inipath = inipath
1473+
self._ignored_config_files = ignored_config_files
1474+
self.inicfg = inicfg
1475+
self._parser.extra_info["rootdir"] = str(self.rootpath)
1476+
self._parser.extra_info["inifile"] = str(self.inipath)
1477+
1478+
self._parser.addini("addopts", "Extra command line options", "args")
1479+
self._parser.addini("minversion", "Minimally required pytest version")
1480+
self._parser.addini(
1481+
"pythonpath", type="paths", help="Add paths to sys.path", default=[]
1482+
)
1483+
self._parser.addini(
1484+
"required_plugins",
1485+
"Plugins that must be present for pytest to run",
1486+
type="args",
1487+
default=[],
1488+
)
1489+
1490+
if addopts:
1491+
args[:] = (
1492+
self._validate_args(self.getini("addopts"), "via addopts config") + args
1493+
)
1494+
1495+
self.known_args_namespace = self._parser.parse_known_args(
1496+
args, namespace=copy.copy(self.option)
1497+
)
1498+
self._checkversion()
1499+
self._consider_importhook()
1500+
self._configure_python_path()
1501+
self.pluginmanager.consider_preparse(args, exclude_only=False)
1502+
if (
1503+
not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD")
1504+
and not self.known_args_namespace.disable_plugin_autoload
1505+
):
1506+
# Autoloading from distribution package entry point has
1507+
# not been disabled.
1508+
self.pluginmanager.load_setuptools_entrypoints("pytest11")
1509+
# Otherwise only plugins explicitly specified in PYTEST_PLUGINS
1510+
# are going to be loaded.
1511+
self.pluginmanager.consider_env()
1512+
1513+
self._parser.parse_known_args(args, namespace=self.known_args_namespace)
1514+
1515+
self._validate_plugins()
1516+
self._warn_about_skipped_plugins()
1517+
1518+
if self.known_args_namespace.confcutdir is None:
1519+
if self.inipath is not None:
1520+
confcutdir = str(self.inipath.parent)
1521+
else:
1522+
confcutdir = str(self.rootpath)
1523+
self.known_args_namespace.confcutdir = confcutdir
1524+
try:
1525+
self.hook.pytest_load_initial_conftests(
1526+
early_config=self, args=args, parser=self._parser
1527+
)
1528+
except ConftestImportFailure as e:
1529+
if self.known_args_namespace.help or self.known_args_namespace.version:
1530+
# we don't want to prevent --help/--version to work
1531+
# so just let it pass and print a warning at the end
1532+
self.issue_config_time_warning(
1533+
PytestConfigWarning(f"could not load initial conftests: {e.path}"),
1534+
stacklevel=2,
1535+
)
1536+
else:
1537+
raise
1538+
15421539
try:
1543-
parsed = self._parser.parse(args, namespace=self.option)
1540+
self._parser.parse(args, namespace=self.option)
15441541
except PrintHelp:
15451542
return
1543+
15461544
self.args, self.args_source = self._decide_args(
1547-
args=getattr(parsed, FILE_OR_DIR),
1548-
pyargs=self.known_args_namespace.pyargs,
1545+
args=getattr(self.option, FILE_OR_DIR),
1546+
pyargs=self.option.pyargs,
15491547
testpaths=self.getini("testpaths"),
15501548
invocation_dir=self.invocation_params.dir,
15511549
rootpath=self.rootpath,

0 commit comments

Comments
 (0)