From a8f18033128bf9eb7893d7c0295bc2c17b8594c5 Mon Sep 17 00:00:00 2001 From: Tom Scogland Date: Wed, 6 Jan 2021 11:29:26 -0800 Subject: [PATCH 1/3] add modules and packages keys to config This adds configuration file support for specifying packages and modules. I tried to preserve existing semantics between the config file and command line. If the command line specifies anything, it overrides the config file, if the config file contains both package/module and files keys, the later key is ignored. Also add tests and basic docs for the new keys. --- docs/source/config_file.rst | 18 +++++++++++++++++ mypy/config_parser.py | 9 +++++++++ mypy/main.py | 9 +++++++-- mypy/options.py | 6 ++++++ test-data/unit/cmdline.test | 40 ++++++++++++++++++++++++++++++++++++- 5 files changed, 79 insertions(+), 3 deletions(-) diff --git a/docs/source/config_file.rst b/docs/source/config_file.rst index 0beef90fb25c..ae09dcdb94d6 100644 --- a/docs/source/config_file.rst +++ b/docs/source/config_file.rst @@ -192,6 +192,24 @@ section of the command line docs. This option may only be set in the global section (``[mypy]``). +.. confval:: modules + + :type: comma-separated list of strings + + A comma-separated list of modules to be type checked. See the + corresponding flag :option:`--module ` for more information. + + This option may only be set in the global section (``[mypy]``). + +.. confval:: packages + + :type: comma-separated list of strings + + A comma-separated list of packages to be type checked. See the + corresponding flag :option:`--package ` for more information. + + This option may only be set in the global section (``[mypy]``). + .. confval:: namespace_packages :type: boolean diff --git a/mypy/config_parser.py b/mypy/config_parser.py index dd79869030e5..e14194960a9a 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -84,6 +84,8 @@ def check_follow_imports(choice: str) -> str: 'custom_typeshed_dir': expand_path, 'mypy_path': lambda s: [expand_path(p.strip()) for p in re.split('[,:]', s)], 'files': split_and_match_files, + 'packages': lambda s: [p.strip() for p in s.split(',')], + 'modules': lambda s: [p.strip() for p in s.split(',')], 'quickstart_file': expand_path, 'junit_xml': expand_path, # These two are for backwards compatibility @@ -278,6 +280,13 @@ def parse_section(prefix: str, template: Options, if 'follow_imports' not in results: results['follow_imports'] = 'error' results[options_key] = v + if key in ('files', 'packages', 'modules'): + if all(('packages' in results.keys() or 'modules' in results.keys(), + results.get('files'))): + print("May only specify one of: module/package, files, or command. Ignoring key.", + file=stderr) + del results[options_key] + continue return results, report_dirs diff --git a/mypy/main.py b/mypy/main.py index ab38f7478b3f..182d13e04907 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -855,8 +855,13 @@ def set_strict_flags() -> None: # Paths listed in the config file will be ignored if any paths, modules or packages # are passed on the command line. - if options.files and not (special_opts.files or special_opts.packages or special_opts.modules): - special_opts.files = options.files + if not (special_opts.files or special_opts.packages or special_opts.modules): + if options.files: + special_opts.files = options.files + if options.modules: + special_opts.modules = options.modules + if options.packages: + special_opts.packages = options.packages # Check for invalid argument combinations. if require_targets: diff --git a/mypy/options.py b/mypy/options.py index e95ed3e0bb46..259ffbe97ab0 100644 --- a/mypy/options.py +++ b/mypy/options.py @@ -212,6 +212,12 @@ def __init__(self) -> None: # supports globbing self.files = None # type: Optional[List[str]] + # A comma-separated list of packages for mypy to type check; + self.packages = None # type: Optional[List[str]] + + # A comma-separated list of modules for mypy to type check; + self.modules = None # type: Optional[List[str]] + # Write junit.xml to given file self.junit_xml = None # type: Optional[str] diff --git a/test-data/unit/cmdline.test b/test-data/unit/cmdline.test index 8fe9f478a077..fb5724878be4 100644 --- a/test-data/unit/cmdline.test +++ b/test-data/unit/cmdline.test @@ -797,6 +797,45 @@ p/a.py:4: error: Argument 1 to "foo" has incompatible type "str"; expected "int" p/b/__init__.py:5: error: Argument 1 to "bar" has incompatible type "str"; expected "int" c.py:2: error: Argument 1 to "bar" has incompatible type "str"; expected "int" +[case testModulesAndPackagesInIni] +# cmd: mypy +[file mypy.ini] +\[mypy] +modules=c +packages=p.a,p.b +[file p/__init__.py] +[file p/a.py] +def foo(x): + # type: (int) -> str + return "x" +foo("wrong") +[file p/b/__init__.py] +from ..a import foo +def bar(a): + # type: (int) -> str + return foo(a) +bar("wrong") +[file c.py] +import p.b +p.b.bar("wrong") +[out] +p/a.py:4: error: Argument 1 to "foo" has incompatible type "str"; expected "int" +p/b/__init__.py:5: error: Argument 1 to "bar" has incompatible type "str"; expected "int" +c.py:2: error: Argument 1 to "bar" has incompatible type "str"; expected "int" + +[case testModulesAndPackagesAndFilesInIni] +# cmd: mypy +[file mypy.ini] +\[mypy] +modules=c +packages=p.a,p.b +files=meh.py +[out] +May only specify one of: module/package, files, or command. Ignoring key. +Can't find package 'p.a' +== Return code: 2 + + [case testSrcPEP420Packages] # cmd: mypy -p anamespace --namespace-packages [file mypy.ini] @@ -1281,4 +1320,3 @@ x = 0 # type: str y = 0 # type: str [out] pkg.py:1: error: Incompatible types in assignment (expression has type "int", variable has type "str") - From 77c5e597214a61cca605767d0c9f41ebdab380b4 Mon Sep 17 00:00:00 2001 From: Tom Scogland Date: Fri, 15 Jan 2021 07:48:07 -0800 Subject: [PATCH 2/3] refactor and clean up error message Addresses comments, adds new test to ensure that the key order is honored as documented and error message presents keys correctly. --- mypy/config_parser.py | 5 ++--- test-data/unit/cmdline.test | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index e14194960a9a..fba12b9f6caa 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -281,9 +281,8 @@ def parse_section(prefix: str, template: Options, results['follow_imports'] = 'error' results[options_key] = v if key in ('files', 'packages', 'modules'): - if all(('packages' in results.keys() or 'modules' in results.keys(), - results.get('files'))): - print("May only specify one of: module/package, files, or command. Ignoring key.", + if (('packages' in results or 'modules' in results) and results.get('files')): + print(f"May only specify one of: module/package or files. Ignoring key: {key}", file=stderr) del results[options_key] continue diff --git a/test-data/unit/cmdline.test b/test-data/unit/cmdline.test index fb5724878be4..8a6cdccfa21f 100644 --- a/test-data/unit/cmdline.test +++ b/test-data/unit/cmdline.test @@ -831,11 +831,25 @@ modules=c packages=p.a,p.b files=meh.py [out] -May only specify one of: module/package, files, or command. Ignoring key. +May only specify one of: module/package or files. Ignoring key: files Can't find package 'p.a' == Return code: 2 +[case testModulesAndPackagesAndFilesInIniModLater] +# cmd: mypy +[file mypy.ini] +\[mypy] +files=meh.py +modules=c +packages=p.a,p.b +[out] +May only specify one of: module/package or files. Ignoring key: modules +May only specify one of: module/package or files. Ignoring key: packages +mypy: cannot read file 'meh.py': No such file or directory +== Return code: 2 + + [case testSrcPEP420Packages] # cmd: mypy -p anamespace --namespace-packages [file mypy.ini] From a2fb1ff8718f89836aeeffbbcec3a88ef715e228 Mon Sep 17 00:00:00 2001 From: Tom Scogland Date: Fri, 15 Jan 2021 08:17:01 -0800 Subject: [PATCH 3/3] factor out f-string --- mypy/config_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index fba12b9f6caa..9ae708ee8298 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -282,7 +282,7 @@ def parse_section(prefix: str, template: Options, results[options_key] = v if key in ('files', 'packages', 'modules'): if (('packages' in results or 'modules' in results) and results.get('files')): - print(f"May only specify one of: module/package or files. Ignoring key: {key}", + print("May only specify one of: module/package or files. Ignoring key:", key, file=stderr) del results[options_key] continue