10
10
from usethis ._config_file import (
11
11
CodespellRCManager ,
12
12
CoverageRCManager ,
13
+ DotPytestINIManager ,
13
14
DotRuffTOMLManager ,
15
+ PytestINIManager ,
14
16
RuffTOMLManager ,
15
17
ToxINIManager ,
16
18
)
40
42
)
41
43
from usethis ._io import KeyValueFileManager
42
44
43
- ResolutionT : TypeAlias = Literal ["first" ]
45
+ ResolutionT : TypeAlias = Literal ["first" , "bespoke" ]
44
46
45
47
46
48
class ConfigSpec (BaseModel ):
@@ -289,6 +291,7 @@ def get_active_config_file_managers(self) -> set[KeyValueFileManager]:
289
291
config_spec = self .get_config_spec ()
290
292
resolution = config_spec .resolution
291
293
if resolution == "first" :
294
+ # N.B. keep this roughly in sync with the bespoke logic for pytest
292
295
for (
293
296
relative_path ,
294
297
file_manager ,
@@ -311,6 +314,12 @@ def get_active_config_file_managers(self) -> set[KeyValueFileManager]:
311
314
)
312
315
raise NotImplementedError (msg )
313
316
return {preferred_file_manager }
317
+ elif resolution == "bespoke" :
318
+ msg = (
319
+ "The bespoke resolution method is not yet implemented for the tool "
320
+ f"{ self .name } ."
321
+ )
322
+ raise NotImplementedError (msg )
314
323
else :
315
324
assert_never (resolution )
316
325
@@ -901,32 +910,114 @@ def get_config_spec(self) -> ConfigSpec:
901
910
"log_cli_level" : "INFO" , # include all >=INFO level log messages (sp-repo-review)
902
911
"minversion" : "7" , # minimum pytest version (sp-repo-review)
903
912
}
913
+ value_ini = value .copy ()
914
+ # https://docs.pytest.org/en/stable/reference/reference.html#confval-xfail_strict
915
+ value_ini ["xfail_strict" ] = "True" # stringify boolean
904
916
905
917
return ConfigSpec .from_flat (
906
- file_managers = [PyprojectTOMLManager ()],
907
- resolution = "first" ,
918
+ file_managers = [
919
+ PytestINIManager (),
920
+ DotPytestINIManager (),
921
+ PyprojectTOMLManager (),
922
+ ToxINIManager (),
923
+ SetupCFGManager (),
924
+ ],
925
+ resolution = "bespoke" ,
908
926
config_items = [
909
927
ConfigItem (
910
928
description = "Overall Config" ,
911
- root = {Path ("pyproject.toml" ): ConfigEntry (keys = ["tool" , "pytest" ])},
929
+ root = {
930
+ Path ("pytest.ini" ): ConfigEntry (keys = []),
931
+ Path (".pytest.ini" ): ConfigEntry (keys = []),
932
+ Path ("pyproject.toml" ): ConfigEntry (keys = ["tool" , "pytest" ]),
933
+ Path ("tox.ini" ): ConfigEntry (keys = ["pytest" ]),
934
+ Path ("setup.cfg" ): ConfigEntry (keys = ["tool:pytest" ]),
935
+ },
912
936
),
913
937
ConfigItem (
914
938
description = "INI-Style Options" ,
915
939
root = {
940
+ Path ("pytest.ini" ): ConfigEntry (
941
+ keys = ["pytest" ], value = value_ini
942
+ ),
943
+ Path (".pytest.ini" ): ConfigEntry (
944
+ keys = ["pytest" ], value = value_ini
945
+ ),
916
946
Path ("pyproject.toml" ): ConfigEntry (
917
947
keys = ["tool" , "pytest" , "ini_options" ], value = value
918
- )
948
+ ),
949
+ Path ("tox.ini" ): ConfigEntry (keys = ["pytest" ], value = value_ini ),
950
+ Path ("setup.cfg" ): ConfigEntry (
951
+ keys = ["tool:pytest" ], value = value_ini
952
+ ),
919
953
},
920
954
),
921
955
],
922
956
)
923
957
924
958
def get_managed_files (self ) -> list [Path ]:
925
- return [Path ("pytest.ini" ), Path ("tests/conftest.py" )]
959
+ return [Path (".pytest.ini" ), Path ( " pytest.ini" ), Path ("tests/conftest.py" )]
926
960
927
961
def get_associated_ruff_rules (self ) -> list [str ]:
928
962
return ["PT" ]
929
963
964
+ def get_active_config_file_managers (self ) -> set [KeyValueFileManager ]:
965
+ # This is a variant of the "first" method
966
+ config_spec = self .get_config_spec ()
967
+ assert config_spec .resolution == "bespoke"
968
+ # As per https://docs.pytest.org/en/stable/reference/customize.html#finding-the-rootdir
969
+ # Files will only be matched for configuration if:
970
+ # - pytest.ini: will always match and take precedence, even if empty.
971
+ # - pyproject.toml: contains a [tool.pytest.ini_options] table.
972
+ # - tox.ini: contains a [pytest] section.
973
+ # - setup.cfg: contains a [tool:pytest] section.
974
+ # Finally, a pyproject.toml file will be considered the configfile if no other
975
+ # match was found, in this case even if it does not contain a
976
+ # [tool.pytest.ini_options] table
977
+ # Also, the docs mention that the hidden .pytest.ini variant is allowed, in my
978
+ # experimentation is takes precedence over pyproject.toml but not pytest.ini.
979
+
980
+ for (
981
+ relative_path ,
982
+ file_manager ,
983
+ ) in config_spec .file_manager_by_relative_path .items ():
984
+ path = Path .cwd () / relative_path
985
+ if path .exists () and path .is_file ():
986
+ if isinstance (file_manager , PyprojectTOMLManager ):
987
+ if ["tool" , "pytest" , "ini_options" ] in file_manager :
988
+ return {file_manager }
989
+ else :
990
+ continue
991
+ return {file_manager }
992
+
993
+ # Second chance for pyproject.toml
994
+ for (
995
+ relative_path ,
996
+ file_manager ,
997
+ ) in config_spec .file_manager_by_relative_path .items ():
998
+ path = Path .cwd () / relative_path
999
+ if (
1000
+ path .exists ()
1001
+ and path .is_file ()
1002
+ and isinstance (file_manager , PyprojectTOMLManager )
1003
+ ):
1004
+ return {file_manager }
1005
+
1006
+ file_managers = config_spec .file_manager_by_relative_path .values ()
1007
+ if not file_managers :
1008
+ return set ()
1009
+
1010
+ # Use the preferred default file since there's no existing file.
1011
+ preferred_file_manager = self .preferred_file_manager ()
1012
+ if preferred_file_manager not in file_managers :
1013
+ msg = (
1014
+ f"The preferred file manager '{ preferred_file_manager } ' is not "
1015
+ f"among the file managers '{ file_managers } ' for the tool "
1016
+ f"'{ self .name } '"
1017
+ )
1018
+ raise NotImplementedError (msg )
1019
+ return {preferred_file_manager }
1020
+
930
1021
931
1022
class RequirementsTxtTool (Tool ):
932
1023
# https://pip.pypa.io/en/stable/reference/requirements-file-format/
0 commit comments