Repository: pallets/werkzeug

Issue:
import collision between http.py and Python 3's http modules
Here is a (granted, obscure) way to reproduce this bug:
1. `cd` into the `werkzeug` directory (i.e. where `serving.py`, `http.py`, etc. are located).
2. start a Python interpreter
3. type `import test`. This works for Python 2.x, but in Python 3.x this is what happens:
   
   ```
   (venv34) werkzeug $ python
   Python 3.4.1 (default, Sep  6 2014, 18:06:58) 
   [GCC 4.8.2] on linux
   Type "help", "copyright", "credits" or "license" for more information.
   >>> import test
   Traceback (most recent call last):
     File "/home/miguel/Documents/dev/flask/werkzeug/werkzeug/test.py", line 20, in <module>
       from urllib2 import Request as U2Request
   ImportError: No module named 'urllib2'
   
   During handling of the above exception, another exception occurred:
   
   Traceback (most recent call last):
     File "/home/miguel/Documents/dev/flask/werkzeug/werkzeug/http.py", line 26, in <module>
       from urllib2 import parse_http_list as _parse_list_header
   ImportError: No module named 'urllib2'
   
   During handling of the above exception, another exception occurred:
   
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
     File "/home/miguel/Documents/dev/flask/werkzeug/werkzeug/test.py", line 22, in <module>
       from urllib.request import Request as U2Request
     File "/opt/python3.4/lib/python3.4/urllib/request.py", line 88, in <module>
       import http.client
     File "/home/miguel/Documents/dev/flask/werkzeug/werkzeug/http.py", line 28, in <module>
       from urllib.request import parse_http_list as _parse_list_header
   ImportError: cannot import name 'parse_http_list'
   >>> 
   ```

The problem is that Python 3's reorg of `httplib` into `http.xxx` modules causes an import collision between `http.client` and werkzeug's `http.py`, and `http.py` wins because its directory is in `sys.path`.

I agree that the example above is pretty obscure and unlike to ever be a problem. A more likely example is when you start an application with the syntax `python -m werkzeug.serving -r module:app`. In this situation the process starts and then spawns a sub-process for the reloader. The sub-process is going to have the directory where `werkzeug.serving` is located in the `sys.path`, so the bug reproduces there as well.

Would you be open to rename `http.py`? Or do you have a better idea?



Discussion:
This is almost the same as https://github.com/mitsuhiko/werkzeug/issues/461. Adding the werkzeug dir to sys.path happens because of the reloader.


Resolving patch:
diff --git a/werkzeug/_reloader.py b/werkzeug/_reloader.py
--- a/werkzeug/_reloader.py
+++ b/werkzeug/_reloader.py
@@ -65,22 +65,47 @@ def _get_args_for_reloading():
     """
     rv = [sys.executable]
     py_script = os.path.abspath(sys.argv[0])
+    args = sys.argv[1:]
+    # Need to look at main module to determine how it was executed.
+    __main__ = sys.modules["__main__"]
+
+    if __main__.__package__ is None:
+        # Executed a file, like "python app.py".
+        if os.name == "nt":
+            # Windows entry points have ".exe" extension and should be
+            # called directly.
+            if not os.path.exists(py_script) and os.path.exists(py_script + ".exe"):
+                py_script += ".exe"
+
+            if (
+                os.path.splitext(rv[0])[1] == ".exe"
+                and os.path.splitext(py_script)[1] == ".exe"
+            ):
+                rv.pop(0)
+
+        elif os.path.isfile(py_script) and os.access(py_script, os.X_OK):
+            # The file is marked as executable. Nix adds a wrapper that
+            # shouldn't be called with the Python executable.
+            rv.pop(0)
+
+        rv.append(py_script)
+    else:
+        # Executed a module, like "python -m werkzeug.serving".
+        if sys.argv[0] == "-m":
+            # Flask works around previous behavior by putting
+            # "-m flask" in sys.argv.
+            # TODO remove this once Flask no longer misbehaves
+            args = sys.argv
+        else:
+            py_module = __main__.__package__
+            name = os.path.splitext(os.path.basename(py_script))[0]
 
-    if os.name == 'nt' and not os.path.exists(py_script) and \
-       os.path.exists(py_script + '.exe'):
-        py_script += '.exe'
-
-    windows_workaround = (
-        os.path.splitext(rv[0])[1] == '.exe'
-        and os.path.splitext(py_script)[1] == '.exe'
-    )
-    nix_workaround = os.path.isfile(py_script) and os.access(py_script, os.X_OK)
+            if name != "__main__":
+                py_module += "." + name
 
-    if windows_workaround or nix_workaround:
-        rv.pop(0)
+            rv.extend(("-m", py_module.lstrip(".")))
 
-    rv.append(py_script)
-    rv.extend(sys.argv[1:])
+    rv.extend(args)
     return rv
 
 


Test patch: