Skip to content

Commit 26db8cb

Browse files
committed
Support per-module strict config
Supersedes #12174 I'd been rewriting that PR in place, but is easier to just make a new one. See #18070 for the extra checks related change.
1 parent 0cc21d9 commit 26db8cb

6 files changed

Lines changed: 47 additions & 24 deletions

File tree

mypy/config_parser.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ def _find_config_file(
301301

302302
def parse_config_file(
303303
options: Options,
304-
set_strict_flags: Callable[[], None],
304+
strict_flag_assignments: Sequence[tuple[str, object]],
305305
filename: str | None,
306306
stdout: TextIO | None = None,
307307
stderr: TextIO | None = None,
@@ -327,6 +327,9 @@ def parse_config_file(
327327
options.config_file = file_read
328328
os.environ["MYPY_CONFIG_FILE_DIR"] = os.path.dirname(os.path.abspath(file_read))
329329

330+
def set_strict_flags(updates: dict[str, object]) -> None:
331+
updates.update(strict_flag_assignments)
332+
330333
if "mypy" not in parser:
331334
if filename or os.path.basename(file_read) not in defaults.SHARED_CONFIG_NAMES:
332335
print(f"{file_read}: No [mypy] section in config file", file=stderr)
@@ -340,11 +343,16 @@ def parse_config_file(
340343
setattr(options, k, v)
341344
options.report_dirs.update(report_dirs)
342345

346+
def set_strict_flags_section(updates: dict[str, object]) -> None:
347+
for dest, value in strict_flag_assignments:
348+
if dest in PER_MODULE_OPTIONS:
349+
updates[dest] = value
350+
343351
for name, section in parser.items():
344352
if name.startswith("mypy-"):
345353
prefix = get_prefix(file_read, name)
346354
updates, report_dirs = parse_section(
347-
prefix, options, set_strict_flags, section, config_types, stderr
355+
prefix, options, set_strict_flags_section, section, config_types, stderr
348356
)
349357
if report_dirs:
350358
print(
@@ -483,7 +491,7 @@ def destructure_overrides(toml_data: dict[str, Any]) -> dict[str, Any]:
483491
def parse_section(
484492
prefix: str,
485493
template: Options,
486-
set_strict_flags: Callable[[], None],
494+
set_strict_flags: Callable[[dict[str, object]], None],
487495
section: Mapping[str, Any],
488496
config_types: dict[str, Any],
489497
stderr: TextIO = sys.stderr,
@@ -576,7 +584,7 @@ def parse_section(
576584
continue
577585
if key == "strict":
578586
if v:
579-
set_strict_flags()
587+
set_strict_flags(results)
580588
continue
581589
results[options_key] = v
582590

@@ -675,7 +683,7 @@ def parse_mypy_comments(
675683
stderr = StringIO()
676684
strict_found = False
677685

678-
def set_strict_flags() -> None:
686+
def set_strict_flags(updates: dict[str, object]) -> None:
679687
nonlocal strict_found
680688
strict_found = True
681689

mypy/main.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1397,21 +1397,15 @@ def process_options(
13971397
parser.error(f"Cannot find config file '{config_file}'")
13981398

13991399
options = Options()
1400-
strict_option_set = False
1401-
1402-
def set_strict_flags() -> None:
1403-
nonlocal strict_option_set
1404-
strict_option_set = True
1405-
for dest, value in strict_flag_assignments:
1406-
setattr(options, dest, value)
14071400

14081401
# Parse config file first, so command line can override.
1409-
parse_config_file(options, set_strict_flags, config_file, stdout, stderr)
1402+
parse_config_file(options, strict_flag_assignments, config_file, stdout, stderr)
14101403

14111404
# Set strict flags before parsing (if strict mode enabled), so other command
14121405
# line options can override.
14131406
if getattr(dummy, "special-opts:strict"):
1414-
set_strict_flags()
1407+
for dest, value in strict_flag_assignments:
1408+
setattr(options, dest, value)
14151409

14161410
# Override cache_dir if provided in the environment
14171411
environ_cache_dir = os.getenv("MYPY_CACHE_DIR", "")
@@ -1529,9 +1523,6 @@ def set_strict_flags() -> None:
15291523
if options.logical_deps:
15301524
options.cache_fine_grained = True
15311525

1532-
if options.strict_concatenate and not strict_option_set:
1533-
print("Warning: --strict-concatenate is deprecated; use --extra-checks instead")
1534-
15351526
# Set target.
15361527
if special_opts.modules + special_opts.packages:
15371528
options.build_type = BuildType.MODULE

mypy/options.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ def __init__(self) -> None:
238238
# Disable treating bytearray and memoryview as subtypes of bytes
239239
self.strict_bytes = False
240240

241-
# Deprecated, use extra_checks instead.
241+
# Make arguments prepended via Concatenate be truly positional-only.
242242
self.strict_concatenate = False
243243

244244
# Enable additional checks that are technically correct but impractical.

mypy/stubtest.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2279,15 +2279,12 @@ def test_stubs(args: _Arguments, use_builtins_fixtures: bool = False) -> int:
22792279
options.abs_custom_typeshed_dir = os.path.abspath(options.custom_typeshed_dir)
22802280
options.config_file = args.mypy_config_file
22812281
options.use_builtins_fixtures = use_builtins_fixtures
2282+
options.per_module_options = {}
22822283
options.show_traceback = args.show_traceback
22832284
options.pdb = args.pdb
22842285

22852286
if options.config_file:
2286-
2287-
def set_strict_flags() -> None: # not needed yet
2288-
return
2289-
2290-
parse_config_file(options, set_strict_flags, options.config_file, sys.stdout, sys.stderr)
2287+
parse_config_file(options, [], options.config_file, sys.stdout, sys.stderr)
22912288

22922289
def error_callback(msg: str) -> typing.NoReturn:
22932290
print(_style("error:", color="red", bold=True), msg)

mypy/test/testfinegrained.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ def get_options(self, source: str, testcase: DataDrivenTestCase, build_cache: bo
161161

162162
for name, _ in testcase.files:
163163
if "mypy.ini" in name or "pyproject.toml" in name:
164-
parse_config_file(options, lambda: None, name)
164+
parse_config_file(options, [], name)
165165
break
166166

167167
return options

test-data/unit/check-flags.test

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2152,6 +2152,33 @@ disallow_subclassing_any = true
21522152
module = 'm'
21532153
disallow_subclassing_any = false
21542154

2155+
[case testStrictPerModule]
2156+
# flags: --config-file tmp/mypy.ini
2157+
2158+
import strictmodule
2159+
import loosemodule
2160+
a = 0 # type: ignore
2161+
2162+
[file strictmodule.py]
2163+
def foo(): # E: Function is missing a return type annotation \
2164+
# N: Use "-> None" if function does not return a value
2165+
1 + "asdf" # E: Unsupported operand types for + ("int" and "str")
2166+
2167+
a = 0 # type: ignore # E: Unused "type: ignore" comment
2168+
2169+
2170+
[file loosemodule.py]
2171+
def foo():
2172+
1 + "asdf"
2173+
2174+
a = 0 # type: ignore
2175+
1 + "asdf" # E: Unsupported operand types for + ("int" and "str")
2176+
2177+
[file mypy.ini]
2178+
[mypy]
2179+
strict = False
2180+
[mypy-strictmodule]
2181+
strict = True
21552182

21562183
[case testNoImplicitOptionalPerModule]
21572184
# flags: --config-file tmp/mypy.ini

0 commit comments

Comments
 (0)