@@ -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+
145168def 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 "\n NOTE: 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