Repository: python/mypy

Issue:
Unbound local due to missing "partial\n" in py.typed
* _What are the versions of mypy and Python you are using?_
  mypy: 0.641
  Python: 3.6.5 built with pyenv on macOS 10.13.6
* _What is the actual behavior/output?_
  Crash with traceback.
* _What is the behavior/output you expect?_
  Either not crashing or exiting with a helpful error message.
  *  _Do you see the same issue after installing mypy from Git master?_
    I have not tried but seeing where the obvious error in the code is, I do not see any difference in *master* at this time.
* _What are the mypy flags you are using? (For example --strict-optional)_
  No flags are needed to cause the failure.

I am attempting to create a stub-only package for a 3rd party package. In attempting to figure out why it is not working, I created an empty `py.typed` alongside the installed stubfiles (I know PEP 561 says it is not necessary... but it does not forbid it either). Afterwards, I get the following `UnboundLocalError` exception when I run `mypy`, which goes away when I create it with `printf "partial\n" > .../py.typed`:

```
Traceback (most recent call last):
  File "/my/virtualenv/bin/mypy", line 11, in <module>
    sys.exit(console_entry())
  File "/my/virtualenv/lib/python3.6/site-packages/mypy/__main__.py", line 7, in console_entry
    main(None)
  File "/my/virtualenv/lib/python3.6/site-packages/mypy/main.py", line 92, in main
    res = build.build(sources, options, None, flush_errors, fscache)
  File "/my/virtualenv/lib/python3.6/site-packages/mypy/build.py", line 155, in build
    result = _build(sources, options, alt_lib_path, flush_errors, fscache)
  File "/my/virtualenv/lib/python3.6/site-packages/mypy/build.py", line 204, in _build
    graph = dispatch(sources, manager)
  File "/my/virtualenv/lib/python3.6/site-packages/mypy/build.py", line 2118, in dispatch
    graph = load_graph(sources, manager)
            if pkg_id not in self.ns_ancestors and self.fscache.isdir(path):
  File "/my/virtualenv/lib/python3.6/site-packages/mypy/build.py", line 2320, in load_graph
    ancestor_for=st)
  File "/my/virtualenv/lib/python3.6/site-packages/mypy/build.py", line 1402, in __init__
    ancestor_for, root_source, skip_diagnose=temporary)
  File "/my/virtualenv/lib/python3.6/site-packages/mypy/build.py", line 1941, in find_module_and_diagnose
    path = manager.find_module_cache.find_module(file_id)
  File "/my/virtualenv/lib/python3.6/site-packages/mypy/modulefinder.py", line 108, in find_module
    self.results[id] = self._find_module(id)
  File "/my/virtualenv/lib/python3.6/site-packages/mypy/modulefinder.py", line 159, in _find_module
    third_party_inline_dirs.append((runtime_path, True))
UnboundLocalError: local variable 'runtime_path' referenced before assignment
```

The cause of the error is obvious:

```
 157                       if fscache.read(stub_typed_file).decode().strip() == 'partial':
 158                           runtime_path = os.path.join(pkg_dir, dir_chain)
 159                       third_party_inline_dirs.append((runtime_path, True))
```

`runtime_path` is not assigned outside of the `if` conditional on line 157 and not checked before the `append` on line 159.

It seems like there should be an `else` clause that at least exits without a traceback with a helpful error message, like 'Either {stub_typed_file} should not exist or it should have "partial\n" at the beginning.' Alternatively, the `py.typed` file could be ignored as it is redundant.

But I am confused about this behavior with the `py.typed` marker file in a "<...>-stubs" package: PEP 561 says that if the stubs package is partial, it must include `py.typed` with "partial\n", which implies to me that if the `py.typed` file exists _without_ "partial\n", then it indicates the package is not partial (which might be the default assumption and therefore redundant). Perhaps this is an ambiguity I should take up with the PEP 561 author?


Discussion:
@ethanhs Can you triage this? This sounds like a release blocker. #5960 
> This sounds like a release blocker. 

That's obviously your call, but I should emphasize that this only happened because I was blundering about, trying something that was expressly outside the defined scope of PEP-561, in an attempt to get a stub-only distribution & package for a 3rd party distribution & package recognized by mypy (OTOH, the question of why my stub-only it isn't being recognized might itself be....)

Resolving patch:
diff --git a/mypy/modulefinder.py b/mypy/modulefinder.py
--- a/mypy/modulefinder.py
+++ b/mypy/modulefinder.py
@@ -156,10 +156,14 @@ def _find_module(self, id: str) -> Optional[str]:
                         # package if installed.
                         if fscache.read(stub_typed_file).decode().strip() == 'partial':
                             runtime_path = os.path.join(pkg_dir, dir_chain)
-                        third_party_inline_dirs.append((runtime_path, True))
-                        # if the package is partial, we don't verify the module, as
-                        # the partial stub package may not have a __init__.pyi
-                        third_party_stubs_dirs.append((path, False))
+                            third_party_inline_dirs.append((runtime_path, True))
+                            # if the package is partial, we don't verify the module, as
+                            # the partial stub package may not have a __init__.pyi
+                            third_party_stubs_dirs.append((path, False))
+                        else:
+                            # handle the edge case where people put a py.typed file
+                            # in a stub package, but it isn't partial
+                            third_party_stubs_dirs.append((path, True))
                     else:
                         third_party_stubs_dirs.append((path, True))
             non_stub_match = self._find_module_non_stub_helper(components, pkg_dir)


Test patch: