Giter Club home page Giter Club logo

typing_copilot's Introduction

typing_copilot

Helper for starting to type-hint large codebases with mypy. When installed, available as the command typing_copilot.

Example output generated when generating a mypy.ini file for the GraphQL compiler project (PR link):

# First, enter the project's virtual environment.
# Make sure the project's dependencies are installed in the environment!
$ poetry shell  # or "pipenv shell" or "source venv/bin/activate" or ...
<...>

$ typing_copilot init
typing_copilot v0.4.0

Running mypy once with laxest settings to establish a baseline. Please wait...

Collecting mypy errors from strictest check configuration. Please wait...

Strict run completed and uncovered 2955 mypy errors. Building the strictest mypy config
such that all configured mypy checks still pass...

> Mypy was unable to find type hints for some 3rd party modules, configuring mypy to
ignore them.
    More info: https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
    Affected modules: ['arrow', 'cached_property', 'funcy', 'neo4j', 'parameterized',
        'pyorient', 'pytest', 'redisgraph', 'setuptools', 'snapshottest', 'sqlalchemy']

> Constructed 126 mypy error suppression rules across 65 modules.

Config generated (306 lines). Validating the new setup before updating your mypy.ini
file. Please wait...

Validation complete. Your mypy.ini file has been updated. Happy type-safe coding!

Motivation

Starting to use mypy in a large codebase can feel like a chicken-and-egg problem:

  • You are unable to turn on meaningful mypy enforcement since a large portion of the codebase is not compliant.
  • It is difficult to get the codebase compliant without mypy enforcement: since proper typing is not enforced, even brand-new code is frequently not fully compliant, and it feels like you are making one step forward and two steps back.

mypy allows specifying different levels of rule enforcement on a per-module basis. However, writing a good per-module mypy configuration is an extremely time-consuming process: mypy must be executed (in a strict configuration) against that module, the resulting errors must be triaged, and an appropriate set of rules for the modules must be produced. Applying this process on a large codebase can easily take hours or days of work.

typing_copilot aims to make this process extremely quick and simple. After installing this package in your project's development environment, running typing_copilot init will autogenerate a mypy.ini file with the strictest set of mypy rules that your code currently passes. In future runs, mypy will automatically use the new mypy.ini file, thereby ensuring that no future code edits violate any typing rules that the current codebase satisfies.

You can then also use the mypy.ini file to see which checks had to be disabled for which of your project's modules, and use that information to guide your future typing efforts. You can also periodically re-run typing_copilot tighten to regenerate a mypy.ini file, in case your project's typing state has improved and stricter rules may now be adopted.

Ideally, consider using typing_copilot in a "ratcheting" fashion, where your project is always on the tightest possible mypy.ini configuration. The easiest way to do so is to run typing_copilot tighten --error-if-can-tighten in your CI environment, which will exit 1 in case your current mypy.ini is not the tightest possible one for your project.

In the future, we hope to add additional functionality to typing_copilot:

  • a command that highlights opportunities where a small amount of work can allow a new rule to be enabled for a new module, allowing you to maximize your project's typing enforcement;
  • support for additional mypy rules.

Usage

  1. Navigate to the root directory of the project on which you'd like to use typing_copilot.
  2. Enter the project's virtualenv, if using one, and ensure the project's own package and its dependencies are installed.
  3. Run typing_copilot:
pip install typing_copilot

typing_copilot init

Currently, typing_copilot is unable to support mypy.ini files that it did not generate. If you are already using mypy but you'd like to transition to using typing_copilot to manage your mypy.ini file, you can make use of the --overwrite flag:

typing_copilot init --overwrite

After creating your initial mypy.ini file with typing_copilot, you can also use typing_copilot to attempt to tighten your mypy.ini configuration. This is useful, for example, if you've recently added type hints to your code and believe that should enable a tighter new mypy.ini configuration. Simply run the following to update your mypy.ini to the tightest passing mypy configuration:

typing_copilot tighten

In a CI environment, typing_copilot can simultaneously ensure both that your code passes mypy checks with the current mypy.ini configuration, and that the current mypy.ini file is the tightest mypy config that your code is able to support. Simply use the --error-if-can-tighten flag in the tighten command:

typing_copilot tighten --error-if-can-tighten

Setting custom top-level mypy flags

Occasionally, it's useful to specify custom top-level mypy flags, such as a list of plugins mypy should use when type-checking your code. To ensure typing_copilot sets those top-level mypy flags in its generated mypy.ini files, add a section like the following example to your project's pyproject.toml:

[tool-typing_copilot.mypy_global_config]
warn_unused_ignores = true
plugins = ["mypy_django_plugin.main"]

Now, when typing_copilot runs, its generated mypy.ini will always contain warn_unused_ignores = true and specify the Django mypy plugin.

If you need to pass additional configuration to the plugin (e.g. set django_settings_module for the Django mypy plugin), please configure it separately from the mypy.ini file. For the Django mypy plugin, for example, prefer configuring it using its own [tool.django-stubs] section in pyproject.toml as suggested in its docs.

How typing_copilot works

typing_copilot init

With this command, typing_copilot will first run mypy using a minimal set of mypy checks which are always enabled and cannot be turned off. You'll need to fix any errors mypy finds using these checks before the command will be able to proceed.

Once the minimal mypy checks pass, typing_copilot init will automatically re-run mypy with the strictest supported set of checks, and collect the reported errors. After analyzing the errors, it will generate the strictest set of checks that will not cause errors, validate them by running mypy against your project one more time, and then create a new mypy.ini file with this new "strictest valid" configuration. We generally refer to this "strictest valid" configuration as the project's "tightest" configuration, hence the tighten command described below.

typing_copilot tighten

With this command, typing_copilot will first run mypy using your current mypy.ini file, ensuring that the current configuration does not produce any mypy errors. Assuming no errors are found, typing_copilot will then follow the same procedure as in the init command to find the tightest mypy configuration your project's current state supports. Finally, it will compare this newly-found tightest configuration against your current mypy.ini configuration, and either update your mypy.ini file or return an error, depending on whether the --error-if-can-tighten is set.

Reporting issues

This is a project I built in my spare time, please be gentle :)

GitHub issues are the preferred avenue for reporting issues with typing_copilot. Please do not email me or any other contributors with questions or issue reports, unless you have our explicit consent to do so.

To ensure the best odds that we can diagnose and fix any problems together, please make sure to include in your issue report the log produced using the --verbose option, together with links to the source code being analyzed by mypy and typing_copilot.

As always, pull requests highly encouraged and gratefully accepted.

typing_copilot's People

Contributors

obi1kenobi avatar dependabot[bot] avatar

Stargazers

Daniel Rice avatar  avatar Jean-Pierre Geslin avatar Mahmood siddiqi avatar Jason Harrison avatar Tyler Yep avatar SangamSwadiK avatar James Logan avatar Nikita avatar Jonathon Belotti avatar  avatar Simon Brugman avatar Iago Alonso avatar  avatar Carlos Cordoba avatar Geoff Cureton avatar Valerii avatar Max Klein avatar Matthew Feickert avatar Andrea PIERRÉ avatar  avatar Arsalan Cheema avatar Vince Broz avatar  avatar Nikolai Røed Kristiansen avatar Vincent Poulailleau avatar Bálint Bokros avatar Chris Coleman avatar Timofei Bondarev avatar S. Co1 avatar Matthew Vernacchia avatar Michael Osthege avatar Marco Edward Gorelli avatar Colin Carroll avatar Luke O'Malley avatar Justin Austin avatar  avatar

Watchers

Colin Carroll avatar  avatar Vince Broz avatar  avatar

typing_copilot's Issues

Failed "typing_copilot init" due to mypy crash during loose config run

Running typing_copilot init on the arviz project repo causes a mypy crash during the loose config mypy run. Here's the mypy crash reproduced with mypy's most recent master branch:

$ mypy --config-file ./custom_mypy.ini -p arviz --show-traceback
arviz/rcparams.py:286: error: Cannot determine consistent method resolution order (MRO) for "RcParams"
/my/code/path/arviz/venv/lib/python3.8/site-packages/xarray/core/dataarray.py:2040: error: INTERNAL ERROR -- Please try using mypy master on Github:
https://mypy.rtfd.io/en/latest/common_issues.html#using-a-development-mypy-build
Please report a bug at https://github.com/python/mypy/issues
version: 0.790+dev.0b4a2c9d8edb57e2b61219c96eefe010073a5c14
Traceback (most recent call last):
  File "/my/code/path/arviz/venv/bin/mypy", line 8, in <module>
    sys.exit(console_entry())
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/__main__.py", line 11, in console_entry
    main(None, sys.stdout, sys.stderr)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/main.py", line 90, in main
    res = build.build(sources, options, None, flush_errors, fscache, stdout, stderr)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/build.py", line 180, in build
    result = _build(
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/build.py", line 254, in _build
    graph = dispatch(sources, manager, stdout)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/build.py", line 2630, in dispatch
    process_graph(graph, manager)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/build.py", line 2953, in process_graph
    process_stale_scc(graph, scc, manager)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/build.py", line 3051, in process_stale_scc
    graph[id].type_check_first_pass()
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/build.py", line 2111, in type_check_first_pass
    self.type_checker().check_first_pass()
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/checker.py", line 294, in check_first_pass
    self.accept(d)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/checker.py", line 401, in accept
    stmt.accept(self)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/nodes.py", line 940, in accept
    return visitor.visit_class_def(self)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/checker.py", line 1723, in visit_class_def
    self.accept(defn.defs)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/checker.py", line 401, in accept
    stmt.accept(self)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/nodes.py", line 1005, in accept
    return visitor.visit_block(self)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/checker.py", line 1972, in visit_block
    self.accept(s)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/checker.py", line 401, in accept
    stmt.accept(self)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/nodes.py", line 677, in accept
    return visitor.visit_func_def(self)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/checker.py", line 726, in visit_func_def
    self._visit_func_def(defn)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/checker.py", line 730, in _visit_func_def
    self.check_func_item(defn, name=defn.name)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/checker.py", line 792, in check_func_item
    self.check_func_def(defn, typ, name)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/checker.py", line 975, in check_func_def
    self.accept(item.body)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/checker.py", line 401, in accept
    stmt.accept(self)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/nodes.py", line 1005, in accept
    return visitor.visit_block(self)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/checker.py", line 1972, in visit_block
    self.accept(s)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/checker.py", line 401, in accept
    stmt.accept(self)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/nodes.py", line 1196, in accept
    return visitor.visit_if_stmt(self)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/checker.py", line 3255, in visit_if_stmt
    self.accept(b)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/checker.py", line 401, in accept
    stmt.accept(self)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/nodes.py", line 1005, in accept
    return visitor.visit_block(self)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/checker.py", line 1972, in visit_block
    self.accept(s)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/checker.py", line 401, in accept
    stmt.accept(self)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/nodes.py", line 1130, in accept
    return visitor.visit_for_stmt(self)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/checker.py", line 3478, in visit_for_stmt
    iterator_type, item_type = self.analyze_iterable_item_type(s.expr)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/checker.py", line 3511, in analyze_iterable_item_type
    return iterator, echk.check_method_call_by_name(nextmethod, iterator, [], [], expr)[0]
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/checkexpr.py", line 2346, in check_method_call_by_name
    method_type = analyze_member_access(method, base_type, context, False, False, True,
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/checkmember.py", line 126, in analyze_member_access
    result = _analyze_member_access(name, typ, mx, override_info)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/checkmember.py", line 143, in _analyze_member_access
    return analyze_instance_member_access(name, typ, mx, override_info)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/checkmember.py", line 225, in analyze_instance_member_access
    return analyze_member_var_access(name, typ, info, mx)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/checkmember.py", line 379, in analyze_member_var_access
    return analyze_var(name, v, itype, info, mx, implicit=implicit)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/checkmember.py", line 581, in analyze_var
    dispatched_type = meet.meet_types(mx.original_type, itype)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/meet.py", line 50, in meet_types
    return t.accept(TypeMeetVisitor(s))
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/types.py", line 833, in accept
    return visitor.visit_instance(self)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/meet.py", line 497, in visit_instance
    args.append(self.meet(ta, sia))
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/meet.py", line 647, in meet
    return meet_types(s, t)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/meet.py", line 50, in meet_types
    return t.accept(TypeMeetVisitor(s))
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/types.py", line 1386, in accept
    return visitor.visit_tuple_type(self)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/meet.py", line 584, in visit_tuple_type
    return TupleType(items, tuple_fallback(t))
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/typeops.py", line 44, in tuple_fallback
    return Instance(info, [join_type_list(typ.items)])
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/join.py", line 531, in join_type_list
    joined = join_types(joined, t)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/join.py", line 105, in join_types
    return t.accept(TypeJoinVisitor(s))
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/types.py", line 833, in accept
    return visitor.visit_instance(self)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/join.py", line 158, in visit_instance
    nominal = join_instances(t, self.s)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/join.py", line 371, in join_instances
    elif t.type.bases and is_subtype_ignoring_tvars(t, s):
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/subtypes.py", line 143, in is_subtype_ignoring_tvars
    return is_subtype(left, right, ignore_type_params=True)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/subtypes.py", line 93, in is_subtype
    return _is_subtype(left, right,
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/subtypes.py", line 135, in _is_subtype
    return left.accept(SubtypeVisitor(orig_right,
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/types.py", line 833, in accept
    return visitor.visit_instance(self)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/subtypes.py", line 264, in visit_instance
    if right.type.is_protocol and is_protocol_implementation(left, right):
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/subtypes.py", line 550, in is_protocol_implementation
    is_compat = is_subtype(subtype, supertype, ignore_pos_arg_names=ignore_names)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/subtypes.py", line 93, in is_subtype
    return _is_subtype(left, right,
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/subtypes.py", line 135, in _is_subtype
    return left.accept(SubtypeVisitor(orig_right,
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/types.py", line 1825, in accept
    return visitor.visit_partial_type(self)
  File "/my/code/path/arviz/venv/lib/python3.8/site-packages/mypy/subtypes.py", line 468, in visit_partial_type
    raise RuntimeError
RuntimeError: 
/my/code/path/arviz/venv/lib/python3.8/site-packages/xarray/core/dataarray.py:2040: : note: use --pdb to drop into pdb

Interestingly, the mypy crash happens with our loose mypy config, but not with mypy's default config. Our loose config is as follows:

[mypy]
no_implicit_optional = False
strict_optional = False
warn_redundant_casts = False
check_untyped_defs = False
disallow_untyped_calls = False
disallow_incomplete_defs = False
disallow_untyped_defs = False
disallow_untyped_decorators = False
ignore_missing_imports = True

To get mypy's default config, simply remove the --config-file ./custom_mypy.ini from the mypy invocation.

Since mypy crashes completely on this, its output cannot be parsed by typing_copilot. This is preventing it from producing actionable advice and enabling its adoption into the arviz project.

Thanks to @ColCarroll for trying out typing_copilot on arviz and pointing out the mypy issue.

Improve UX when users forget to install the package they are analyzing

typing_copilot requires that the package being analyzed is installed in the virtualenv from where its command is invoked. However, this requirement is easy to miss, and if missed, will result in a fairly opaque error message (this was generated when analyzing the arviz package, https://github.com/arviz-devs/arviz ):

Traceback (most recent call last):
  File ".../arviz/venv/bin/typing_copilot", line 8, in <module>
    sys.exit(cli())
  File ".../arviz/venv/lib/python3.8/site-packages/click/core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File ".../arviz/venv/lib/python3.8/site-packages/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File ".../arviz/venv/lib/python3.8/site-packages/click/core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File ".../arviz/venv/lib/python3.8/site-packages/click/core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File ".../arviz/venv/lib/python3.8/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File ".../arviz/venv/lib/python3.8/site-packages/typing_copilot/cli.py", line 245, in init
    final_config_components = _make_strictest_mypy_config_components_from_errors(strict_errors)
  File ".../arviz/venv/lib/python3.8/site-packages/typing_copilot/cli.py", line 57, in _make_strictest_mypy_config_components_from_errors
    first_party_suppressions = get_1st_party_modules_and_suppressions(strict_errors)
  File ".../arviz/venv/lib/python3.8/site-packages/typing_copilot/error_tracker.py", line 251, in get_1st_party_modules_and_suppressions
    needed_setting_to_minimum_covering_modules: Dict[MypyErrorSetting, FrozenSet[str]] = {
  File ".../arviz/venv/lib/python3.8/site-packages/typing_copilot/error_tracker.py", line 252, in <dictcomp>
    error_setting: _consider_replacing_child_modules_with_parent(
  File ".../arviz/venv/lib/python3.8/site-packages/typing_copilot/error_tracker.py", line 167, in _consider_replacing_child_modules_with_parent
    unseen_child_modules_for_parent = {
  File ".../arviz/venv/lib/python3.8/site-packages/typing_copilot/error_tracker.py", line 168, in <dictcomp>
    parent_name: _get_child_module_names_for_module(parent_name)
  File ".../arviz/venv/lib/python3.8/site-packages/typing_copilot/error_tracker.py", line 135, in _get_child_module_names_for_module
    module = importlib.import_module(module_name)
  File "/usr/lib/python3.8/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1014, in _gcd_import
  File "<frozen importlib._bootstrap>", line 991, in _find_and_load
  File "<frozen importlib._bootstrap>", line 961, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "<frozen importlib._bootstrap>", line 1014, in _gcd_import
  File "<frozen importlib._bootstrap>", line 991, in _find_and_load
  File "<frozen importlib._bootstrap>", line 973, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'arviz'

Catch the ModuleNotFoundError error in get_1st_party_modules_and_suppressions and reraise it with a clearer error message that explains the appropriate action to take to resolve it -- simply install into your venv the package you are analyzing.

Mypy error of type "assignment" only discovered during validation run

Encountered a mypy error that is unexpectedly not raised in the minimal mypy run, and is not found and suppressed by the config generated after the strictest mypy run. This should not be possible.

Here is the error output:

Validation failed due to unexpected error(s):
[MypyError(file_path='arviz/data/io_pyjags.py', line_number=28, error_code='assignment', message='error: Incompatible default for argument "save_warmup" (default has type "None", argument has type "bool")')]
Traceback (most recent call last):
  File ".../arviz/venv/bin/typing_copilot", line 8, in <module>
    sys.exit(cli())
  File ".../arviz/venv/lib/python3.8/site-packages/click/core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File ".../arviz/venv/lib/python3.8/site-packages/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File ".../arviz/venv/lib/python3.8/site-packages/click/core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File ".../arviz/venv/lib/python3.8/site-packages/click/core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File ".../arviz/venv/lib/python3.8/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File ".../arviz/venv/lib/python3.8/site-packages/typing_copilot/cli.py", line 254, in init
    unused_ignore_errors = _get_unused_ignore_errors_from_validation_run(final_config)
  File ".../arviz/venv/lib/python3.8/site-packages/typing_copilot/cli.py", line 136, in _get_unused_ignore_errors_from_validation_run
    raise AssertionError(
AssertionError: Validation failed: mypy reported 1 unexpected error(s). Please submit the produced logs so we can update typing-copilot to fix this issue. Apologies for the inconvenience, and thank you for supporting typing-copilot.

To reproduce:

It is not yet clear what the underlying reason is. It might be due to unexpected behavior in mypy that we'll have to work around, or it might be due to a missing suppression in the strictest run analysis code.

support plugins eg django-stubs' mypy_django_plugin

currently the LAX_BASELINE_MYPY_CONFIG doesn't include any plugins, it would be useful if we could load the plugins set in the mypi.ini or pass them to typing-copilot

example_project/polls/models.py:204: error: Need type annotation for 'translation'  [var-annotated]
Found 32 errors in 1 file (checked 230 source files)

Since these errors happen at mypy's most permissive settings, they cannot be suppressed. Please resolve them, then run this command again.

Missing type hints for 3rd party package warning gets shown more than once per package

Example duplicates with identical module names:

WARNING: mypy was not able to find type hints for module 'pytest' since it does not seem to be installed in the current environment. Assuming it has no type hints available.
WARNING: mypy was not able to find type hints for module 'pytest' since it does not seem to be installed in the current environment. Assuming it has no type hints available.

Example duplicates with different module names but part of the same package:

WARNING: mypy was not able to find type hints for module 'pyorient' since it does not seem to be installed in the current environment. Assuming it has no type hints available.
WARNING: mypy was not able to find type hints for module 'pyorient.constants' since it does not seem to be installed in the current environment. Assuming it has no type hints available.
WARNING: mypy was not able to find type hints for module 'pyorient.ogm' since it does not seem to be installed in the current environment. Assuming it has no type hints available.

To avoid overwhelming the user with repetitive output, we'd like each package to show up only once regardless of which of their modules or submodules are lacking type hints.

Mypy 0.800 changes to module-finding may break typing_copilot 0.5.4

Per http://mypy-lang.blogspot.com/2021/01/mypy-0800-released.html mypy starting in version 0.800 is a lot more aggressive at finding Python code to type-check. This may sometimes cause issues, e.g. if it attempts to type-check the autogenerated virtualenv code and produce unfixable (and non-project-related) errors like the following:

venv/bin/activate_this.py:28: error: "str" has no attribute "decode"; maybe "encode"?  [attr-defined]
venv/bin/activate_this.py:31: error: Module has no attribute "real_prefix"  [attr-defined]

To cleanly support mypy>=0.800, we'll need to offer the ability for users to explicitly specify which code they are type-checking and/or the ability to explicitly exclude files or directories from mypy type-checking.

union-attr mypy error reported in strict run, but not in laxest configuration

Validation failed due to unexpected error(s):
[MypyError(file_path='auto_to.py', line_number=1370, error_code='union-attr', message='error: Item "None" of "Optional[datetime]" has no attribute "astimezone"'),
 MypyError(file_path='auto_to.py', line_number=1453, error_code='union-attr', message='error: Item "None" of "Optional[datetime]" has no attribute "astimezone"')]

This is with mypy 0.800 in python 3.9.

Allow overriding top-level mypy.ini settings

Multiple use cases would benefit from the ability to specify a custom top-level mypy configuration:

  • enabling mypy plugins to be used together with typing_copilot: #6
  • allowing power users to suppress individual error codes project-wide, as a measure of last resort: #4, #9
  • specifying which packages mypy should type-check or ignore, to support the mypy changes introduced in 0.800: #10

This configuration could reside in a tool.typing_copilot section of the pyproject.toml file.

Add a better error message when a user runs the compilot in a nested directory

If you're supposed to run the copilot in the parent directory but run it any number of levels in, you'll see a message along the lines of:

AssertionError: Unexpected mypy exit code 2. Process info: CompletedProcess(args=['mypy', '--config-file', '/var/folders/q5/xdhsj6997hl8qghm3bx6v74r0000gn/T/mypy-copilot-kcxuh4uf/mypy.ini', '--show-error-codes', '--error-summary', '.'], returncode=2, stdout='', stderr='. is not a valid Python package name\n'

You will see . is not a valid Python package name whether you're in correct_dir/x or correct_dir/x/y.

As a user of typing-copilot, I first activated poetry shell, installed typing copilot, and then tried running typing-copilot init, which failed with the above error. It's easy to miss the step of changing dirs, so it would be beneficial to wrap this error with a hint / suggestion.

Add a tracing-based test harness for testing typing_copilot behavior

typing_copilot is already quite a complex tool, and is not trivial to test for correctness. To make it easier to test, split it into a "driver" which knows how to interact with mypy and the filesystem, and essentially a "library" that is fed data and is expected to act on it appropriately.

Then, different kinds of drivers could exist:

  • a driver that actually does read/write to disk and invoke mypy
  • a driver that wraps the one described above, but also records the actions taken and their outcomes into a trace file
  • a driver that reads a trace file and asserts that the library follows the trace as expected

The second driver could then be used to automatically generate traces of bug reports. The generated trace files could then be added to the test suite, and the third kind of driver could be used to ensure ongoing correctness in the face of the observed behaviors.

Missing imports sections don't always show the parent module.

It's possible to get typing_copilot to generate mypy.ini files including lines like this:

[mypy-schematics.types.*]
ignore_missing_imports = True

This can make for overly verbose mypy.ini files, if multiple submodule paths like this are individually suppressed. Since it's very likely in that case that the entire schematics package is missing type hints, we should simplify that to the following:

[mypy-schematics.*]
ignore_missing_imports = True

Crash on mypy errors without line numbers

I found that incorrect type arguments to numpy types produce mypy errors lines without line numbers, which in turn crash typing_copilot.

Example of incorrect numpy type argument:

def f(z: np.typing.NDArray[int]) -> None:

(should be NDArray[np.intc])

This produces a mypy error line like:

path/to/module.py: error: Type argument "int" of "dtype" must be a subtype of "generic"

Note the above error line does not include a line number, whereas a typical mypy error line does, e.g.:
path/to/module.py:12: error: ...

The line-number-less mypy error causes an error in typing_copilot's parsing:

 File "{...}/python3.9/site-packages/typing_copilot/cli.py", line 369, in tighten
    errors = get_mypy_errors_from_completed_process(completed_process)
  File "{...}/python3.9/site-packages/typing_copilot/mypy_runner.py", line 93, in get_mypy_errors_from_completed_process
    return [
  File "{...}/python3.9/site-packages/typing_copilot/mypy_runner.py", line 93, in <listcomp>
    return [
  File "{...}/python3.9/site-packages/typing_copilot/mypy_runner.py", line 95, in <genexpr>
    for value in (MypyError.from_mypy_output_line(error_line) for error_line in error_lines)
  File "{...}/python3.9/site-packages/typing_copilot/mypy_runner.py", line 55, in from_mypy_output_line
    line_number = int(line_num.strip())
ValueError: invalid literal for int() with base 10: 'error'

I am using mypy version 0.971 and typing_copilot version 0.7.0

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.