Repository: python/mypy

Issue:
Duplicate module named '__main__' errors
I'm not sure exactly what the problem is, but I get this weird error when running mypy against a couple scripts in the same directory of zulip.git:

```
tabbott@zaset:~/zulip$ mypy --check-untyped-defs --py2 --silent api/examples/create-user api/examples/edit-message 
api/examples/create-user:1: error: Duplicate module named '__main__'
```

The code for the scripts is here if you want to view or download for debugging; they're both pretty simple:
https://github.com/zulip/zulip/tree/master/api/examples

(Ran into this when trying to run mypy on every python file in the project based on parsing `#!` lines, rather than just running it on files with a `.py` extension).



Discussion:
When a file doesn't have a ".py" extension, mypy assumes its module name to be `__main__`. So you can only analyze one script at a time. I'm not sure how easy it is to fix this right, since there's no reason to assume that the script name is a valid Python module name, and if there's a single script file, the `__main__` assumption is actually more correct (it's what Python itself does!).

Yeah.  The Zulip codebase has 72 scripts without a .py extension; I'm not excited about running a loop that starts up mypy 72 times to do the analysis of those scripts, from a performance perspective; and Zulip is only a moderately-sized Python project.  I'm not sure what the right solution here is though; I wonder if it would make sense to have module scripts as a (fake) module with a name constructed from `__file__`?

A simple approach would be to give additional scripts names such as `__main2__`, `__main3__`, etc. Mypy will show the file name in most error messages so this shouldn't be too confusing.

Yeah, that's better. A name derived from the module name would be wrong: if
e.g. 'scripts/mypy' was executed as if it was a module named 'mypy', then
its "from mypy.main import main" would fail due to circularity.

On Fri, Apr 15, 2016 at 8:03 AM, Jukka Lehtosalo notifications@github.com
wrote:

> A simple approach would be to give additional scripts names such as
> **main2**, **main3**, etc. Mypy will show the file name in most error
> messages so this shouldn't be too confusing.
> 
> —
> You are receiving this because you commented.
> Reply to this email directly or view it on GitHub
> https://github.com/python/mypy/issues/1380#issuecomment-210496491

## 

--Guido van Rossum (python.org/~guido)

Just an update, in Zulip, we did https://github.com/zulip/zulip/pull/1354 (running mypy on each script individually) to be able to check our scripts using mypy.  Unfortunately, it's like 10x slower than running mypy on the entire rest of the codebase, because mypy isn't too fast at starting up.  

So even though we've worked around this, I'd definitely still be excited to see this fixed sometime soon :)

Sorry, this got kind of low on our list of priorities. It should be
relatively easy to fix though -- maybe someone on Zulip can submit a PR?

@sharmaeklavya2 are you interested in taking a look at this?  The `__main2__` approach seems pretty tractable...

I tried to implement this. I manually tested it and it works fine. See my changes here - https://github.com/python/mypy/compare/master...sharmaeklavya2:script-rename?expand=1.

However tests don't pass. It seems that the internal representation of these types has changed and that doesn't match what the tests expect. See the test failure here - https://travis-ci.org/sharmaeklavya2/mypy/builds/147546925

Maybe for the tests you can come up with a way to reset the counter to
zero, assuming you generate `__main__` instead of `__main_0__` for the
first main?

I'm still unclear on the right way to do this, given that it's totally possible that a script may be written in conjunction with a module where the module somehow ends up importing something from `__main__`, and if the script were to somehow be type-checked as if it was called `__main3__` this would break. Maybe we could add a command-line flag that means "for any script passed on the command line, use the script name as the module name rather than `__main__`"?

That sort of flag would solve the problem for me.

(And I'd love to see this issue resolved -- right now we're running mypy on each script separately, which is like 20x slower than the single invocation that checks all the non-scripts)

Though -i might alleviate that!

On Fri, Sep 9, 2016 at 3:33 PM, Tim Abbott notifications@github.com wrote:

> (And I'd love to see this issue resolved -- right now we're running mypy
> on each script separately, which is like 20x slower than the single
> invocation that checks all the non-scripts)
> 
> —
> You are receiving this because you commented.
> Reply to this email directly, view it on GitHub
> https://github.com/python/mypy/issues/1380#issuecomment-246059300, or mute
> the thread
> https://github.com/notifications/unsubscribe-auth/ACwrMliYh1b9OCIfH8Rb-tIqE8mr6aFMks5qod7CgaJpZM4IHv1U
> .

## 

--Guido van Rossum (python.org/~guido)

`-i` doesn't seem to provide much of a performance boost for mypy on our scripts; only like 40%.

Also, it appears to crash when I try to run it on the main Zulip codebase; will report that as its own issue :)

Never mind, the crash seems to have been fixed in mypy since the 0.4.3 (which we're currently stuck on because of the buggy ujson stubs in 0.4.4).  Still only a ~30% performance improvement, so not going to make the 20x situation feel acceptable.

30% is way less than expected -- sounds like incremental is not working as intended for some reason. 

@gvanrossum Do you have hints on debugging incremental checking performance?

About the original issue: What about keeping track of whether we have multiple scripts / `__main__` modules to type check, and if yes, give an error when trying to import from `__main__` anywhere, as it's ambiguous? I'd expect imports from `__main__` to be rare.

The -i code in the current repo is supposedly much faster (Michael's last
set of perf fixes landed last week). If you could somehow get past the
ujson stubs (are they easy to fix), it would be interesting to compare the
speed.

On Tue, Sep 13, 2016 at 3:13 AM, Jukka Lehtosalo notifications@github.com
wrote:

> About the original issue: What about keeping track of whether we have
> multiple scripts / **main** modules to type check, and if yes, give an
> error when trying to import from **main** anywhere, as it's ambiguous?
> I'd expect imports from **main** to be rare.
> 
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> https://github.com/python/mypy/issues/1380#issuecomment-246637123, or mute
> the thread
> https://github.com/notifications/unsubscribe-auth/ACwrMp83tb1rBwfN53TtzawRqZ8guHgqks5qpndKgaJpZM4IHv1U
> .

## 

--Guido van Rossum (python.org/~guido)

The ujson stubs are likely easy to fix, just needs some testing to make sure we change the right set of functions.  But also for testing it's easy to run whatever version; there will just be some extra errors :), which is irrelevant for profiling.  0.4.3 crashes with our full codebase, but with 0.4.4 I saw 15s => 11s for checking our main project; with latest master it went from 11s => 4s for the main project.  And running with a single 1k line script went from 1.5s to 0.2s.  So yeah, `-i` is a lot faster with master.  I guess I'll write a PR for the ujson issue.

(However, it's not entirely irrelevant what ujson stubs you have for
profiling -- modules with errors in them are not cached, and neither is
anything depending on them.)

Thanks! It's really amazing to have you involved in our project.

On Tue, Sep 13, 2016 at 8:40 AM, Tim Abbott notifications@github.com
wrote:

> The ujson stubs are likely easy to fix, just needs some testing to make
> sure we change the right set of functions. But also for testing it's easy
> to run whatever version; there will just be some extra errors :), which is
> irrelevant for profiling. 0.4.3 crashes with our full codebase, but with
> 0.4.4 I saw 15s => 11s for checking our main project; with latest master it
> went from 11s => 4s for the main project. And running with a single 1k line
> script went from 1.5s to 0.2s. So yeah, -i is a lot faster with master. I
> guess I'll write a PR for the ujson issue.
> 
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> https://github.com/python/mypy/issues/1380#issuecomment-246724307, or mute
> the thread
> https://github.com/notifications/unsubscribe-auth/ACwrMke04uwuHept-KOmRy_m7xNZ3Vi8ks5qpsPbgaJpZM4IHv1U
> .

## 

--Guido van Rossum (python.org/~guido)


Resolving patch:
diff --git a/mypy/main.py b/mypy/main.py
--- a/mypy/main.py
+++ b/mypy/main.py
@@ -189,6 +189,8 @@ def process_options(args: List[str],
                         help="dump type inference stats")
     parser.add_argument('--custom-typing', metavar='MODULE', dest='custom_typing_module',
                         help="use a custom typing module")
+    parser.add_argument('--scripts-are-modules', action='store_true',
+                        help="Script x becomes module x instead of __main__")
     # hidden options
     # --shadow-file a.py tmp.py will typecheck tmp.py in place of a.py.
     # Useful for tools to make transformations to a file to get more
@@ -319,7 +321,8 @@ def process_options(args: List[str],
                          .format(f))
                 targets.extend(sub_targets)
             else:
-                targets.append(BuildSource(f, None, None))
+                mod = os.path.basename(f) if options.scripts_are_modules else None
+                targets.append(BuildSource(f, mod, None))
         return targets, options
 
 
diff --git a/mypy/options.py b/mypy/options.py
--- a/mypy/options.py
+++ b/mypy/options.py
@@ -47,6 +47,9 @@ def __init__(self) -> None:
         # Files in which to allow strict-Optional related errors
         self.strict_optional_whitelist = None   # type: Optional[List[str]]
 
+        # Use script name instead of __main__
+        self.scripts_are_modules = False
+
         # -- development options --
         self.verbosity = 0  # More verbose messages (for troubleshooting)
         self.pdb = False


Test patch:
diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test
--- a/test-data/unit/check-modules.test
+++ b/test-data/unit/check-modules.test
@@ -1131,3 +1131,20 @@ reveal_type(x)
 [out]
 main:1: error: Incompatible types in assignment (expression has type "int", variable has type "str")
 main:2: error: Revealed type is 'builtins.str'
+
+-- Scripts and __main__
+
+[case testScriptsAreModules]
+# flags: --scripts-are-modules
+[file a]
+pass
+[file b]
+pass
+
+[case testScriptsAreNotModules]
+# cmd: mypy a b
+[file a]
+pass
+[file b]
+pass
+[out]