Repository: bottlepy/bottle

Issue:
SyntaxWarning in Python 3.8 tests due to using is to compare literals.
Comparison of literals using "is" now raises SyntaxWarning from Python 3.8 . Use `==` to fix the warnings. This is a good beginner issue.

```
 test/test_server.py:56
  /home/runner/work/bottle/bottle/test/test_server.py:56: SyntaxWarning: "is" with a literal. Did you mean "=="?
    if rv is 128: # Import error

test/test_server.py:62
  /home/runner/work/bottle/bottle/test/test_server.py:62: SyntaxWarning: "is" with a literal. Did you mean "=="?
    if rv is 3: # Port in use
```

Discussion:

Patch:
diff --git a/bottle.py b/bottle.py
index df867c1..45e85d1 100755
--- a/bottle.py
+++ b/bottle.py
@@ -474,10 +474,7 @@ class Router(object):
         verb = environ['REQUEST_METHOD'].upper()
         path = environ['PATH_INFO'] or '/'
 
-        if verb == 'HEAD':
-            methods = ['PROXY', verb, 'GET', 'ANY']
-        else:
-            methods = ['PROXY', verb, 'ANY']
+        methods = ('PROXY', 'HEAD', 'GET', 'ANY') if verb == 'HEAD' else ('PROXY', verb, 'ANY')
 
         for method in methods:
             if method in self.static and path in self.static[method]:
@@ -575,7 +572,7 @@ class Route(object):
                     callback = plugin(callback)
             except RouteReset:  # Try again with changed configuration.
                 return self._make_callback()
-            if not callback is self.callback:
+            if callback is not self.callback:
                 update_wrapper(callback, self.callback)
         return callback
 
@@ -2425,7 +2422,7 @@ class ConfigDict(dict):
         for section in conf.sections():
             for key in conf.options(section):
                 value = conf.get(section, key)
-                if section not in ['bottle', 'ROOT']:
+                if section not in ('bottle', 'ROOT'):
                     key = section + '.' + key
                 self[key.lower()] = value
         return self
@@ -2626,7 +2623,7 @@ class AppStack(list):
 class WSGIFileWrapper(object):
     def __init__(self, fp, buffer_size=1024 * 64):
         self.fp, self.buffer_size = fp, buffer_size
-        for attr in ('fileno', 'close', 'read', 'readlines', 'tell', 'seek'):
+        for attr in 'fileno', 'close', 'read', 'readlines', 'tell', 'seek':
             if hasattr(fp, attr): setattr(self, attr, getattr(fp, attr))
 
     def __iter__(self):
@@ -2719,7 +2716,7 @@ class ResourceManager(object):
         """ Search for a resource and return an absolute file path, or `None`.
 
             The :attr:`path` list is searched in order. The first match is
-            returend. Symlinks are followed. The result is cached to speed up
+            returned. Symlinks are followed. The result is cached to speed up
             future lookups. """
         if name not in self.cache or DEBUG:
             for path in self.path:
@@ -2881,7 +2878,7 @@ def static_file(filename, root,
 
     root = os.path.join(os.path.abspath(root), '')
     filename = os.path.abspath(os.path.join(root, filename.strip('/\\')))
-    headers = headers or {}
+    headers = headers.copy() if headers else {}
 
     if not filename.startswith(root):
         return HTTPError(403, "Access denied.")
@@ -3054,7 +3051,7 @@ def _parse_http_header(h):
 
 def _parse_qsl(qs):
     r = []
-    for pair in qs.replace(';', '&').split('&'):
+    for pair in qs.split('&'):
         if not pair: continue
         nv = pair.split('=', 1)
         if len(nv) != 2: nv.append('')
@@ -3231,8 +3228,8 @@ class ServerAdapter(object):
         pass
 
     def __repr__(self):
-        args = ', '.join(['%s=%s' % (k, repr(v))
-                          for k, v in self.options.items()])
+        args = ', '.join('%s=%s' % (k, repr(v))
+                          for k, v in self.options.items())
         return "%s(%s)" % (self.__class__.__name__, args)
 
 
@@ -3517,7 +3514,7 @@ class BjoernServer(ServerAdapter):
 
     def run(self, handler):
         from bjoern import run
-        run(handler, self.host, self.port)
+        run(handler, self.host, self.port, reuse_port=True)
 
 class AsyncioServerAdapter(ServerAdapter):
     """ Extend ServerAdapter for adding custom event loop """
@@ -4034,7 +4031,7 @@ class StplParser(object):
     # This huge pile of voodoo magic splits python code into 8 different tokens.
     # We use the verbose (?x) regex mode to make this more manageable
 
-    _re_tok = _re_inl = r'''(
+    _re_tok = r'''(
         [urbURB]*
         (?:  ''(?!')
             |""(?!")
diff --git a/docs/changelog.rst b/docs/changelog.rst
index a8f000c..34887e7 100755
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -12,7 +12,7 @@ Release 0.13
 
 .. rubric:: Dropped support for Python 2.5, 2.6, 3.1, 3.2 and 3.3
 
-These three Python versions are no longer maintained by the Python Software Foundation and reached their end of life a long time ago. Keeping up support for ancient Python versions hinders adaptation of new features and serves no real purpose. Even Debian 7 (wheezy) and Ubuntu 12.4 (precise), both outdated, ship with Python 2.7.3 and 3.2.3 already. For this reason, we decided to drop support for Python 2.5, 2.6, 3.1, 3.2 and 3.1. The updated list of tested and supported python releases is as follows:
+These five Python versions are no longer maintained by the Python Software Foundation and reached their end of life a long time ago. Keeping up support for ancient Python versions hinders adaptation of new features and serves no real purpose. Even Debian 7 (wheezy) and Ubuntu 12.04 (precise), both outdated, ship with Python 2.7.3 and 3.2.3 already. For this reason, we decided to drop support for Python 2.5, 2.6, 3.1, 3.2 and 3.3. The updated list of tested and supported python releases is as follows:
 
  * Python 2.7 (>= 2.7.3)
  * Python 3.4
diff --git a/docs/plugins/index.rst b/docs/plugins/index.rst
index 1db65da..ada5833 100644
--- a/docs/plugins/index.rst
+++ b/docs/plugins/index.rst
@@ -14,6 +14,9 @@ Have a look at :ref:`plugins` for general questions about plugins (installation,
 `Bottle-Cork <http://cork.firelet.net/>`_
 	Cork provides a simple set of methods to implement Authentication and Authorization in web applications based on Bottle.
 
+`Bottle-Cors-plugin <http://pypi.org/project/bottle-cors-plugin/>`_
+	Cors-plugin is the easiest way to implement cors on your bottle web application
+
 `Bottle-Extras <http://pypi.python.org/pypi/bottle-extras/>`_
 	Meta package to install the bottle plugin collection.
 
@@ -61,7 +64,7 @@ Have a look at :ref:`plugins` for general questions about plugins (installation,
 
 `bottle-jwt <https://github.com/agile4you/bottle-jwt/>`_
 	JSON Web Token authentication plugin for bottle.py
-	
+
 `Bottle-jwt <https://github.com/agalera/bottlejwt>`_
 	JWT integration for bottle
 
@@ -76,4 +79,3 @@ Have a look at :ref:`plugins` for general questions about plugins (installation,
 
 
 Plugins listed here are not part of Bottle or the Bottle project, but developed and maintained by third parties.
-
diff --git a/docs/recipes.rst b/docs/recipes.rst
index cfda5ba..c1c3b0d 100755
--- a/docs/recipes.rst
+++ b/docs/recipes.rst
@@ -1,6 +1,6 @@
 .. module:: bottle
 
-.. _beaker: http://beaker.groovie.org/
+.. _beaker: https://beaker.readthedocs.io/en/latest/
 .. _mod_python: http://www.modpython.org/
 .. _mod_wsgi: http://code.google.com/p/modwsgi/
 .. _werkzeug: http://werkzeug.pocoo.org/documentation/dev/debug.html
diff --git a/setup.py b/setup.py
index b0f5685..11b531c 100755
--- a/setup.py
+++ b/setup.py
@@ -4,7 +4,7 @@ import sys
 from setuptools import setup
 
 if sys.version_info < (2, 7):
-    raise NotImplementedError("Sorry, you need at least Python 2.7 or Python 3.4+ to use bottle.")
+    raise NotImplementedError("Sorry, you need at least Python 2.7 or Python 3.6+ to use bottle.")
 
 import bottle
 
@@ -33,9 +33,9 @@ setup(name='bottle',
                    'Topic :: Software Development :: Libraries :: Application Frameworks',
                    'Programming Language :: Python :: 2.7',
                    'Programming Language :: Python :: 3',
-                   'Programming Language :: Python :: 3.4',
-                   'Programming Language :: Python :: 3.5',
                    'Programming Language :: Python :: 3.6',
                    'Programming Language :: Python :: 3.7',
+                   'Programming Language :: Python :: 3.8',
+                   'Programming Language :: Python :: 3.9',
                    ],
       )
diff --git a/tox.ini b/tox.ini
index 2d19901..a1b674b 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = py27,py34,py35,py36,py37,py27-most
+envlist = py27,py36,py37,py38,py39,py27-most
 
 [testenv]
 deps=Mako


Test patch:
diff --git a/test/test_mount.py b/test/test_mount.py
index 582c087..1fbef40 100644
--- a/test/test_mount.py
+++ b/test/test_mount.py
@@ -6,12 +6,17 @@ class TestAppMounting(ServerTestBase):
     def setUp(self):
         ServerTestBase.setUp(self)
         self.subapp = bottle.Bottle()
-        @self.subapp.route('')
+
         @self.subapp.route('/')
-        @self.subapp.route('/test/:test')
+        @self.subapp.route('/test/<test>')
         def test(test='foo'):
             return test
 
+    def test_mount_unicode_path_bug602(self):
+        self.app.mount('/mount/', self.subapp)
+        self.assertBody('äöü', '/mount/test/äöü')
+        self.app.route('/route/<param>', callback=lambda param: param)
+        self.assertBody('äöü', '/route/äöü')
 
     def test_mount_order_bug581(self):
         self.app.mount('/test/', self.subapp)
diff --git a/test/test_sendfile.py b/test/test_sendfile.py
index 622d992..d6693c2 100755
--- a/test/test_sendfile.py
+++ b/test/test_sendfile.py
@@ -142,3 +142,13 @@ class TestSendFile(unittest.TestCase):
         self.assertEqual([(10, 100)], r('bytes=10-'))
         self.assertEqual([(5, 11)],  r('bytes=5-10'))
         self.assertEqual([(10, 100), (90, 100), (5, 11)],  r('bytes=10-,-10,5-10'))
+
+    def test_custom_headers(self):
+        """ SendFile: Custom headers """
+        headers = {'X-Custom-Header': 'test-value'}
+        headers_orig = headers.copy()
+        res = static_file(basename, root=root, headers=headers)
+        self.assertTrue('X-Custom-Header' in res.headers)
+        self.assertEqual('test-value', res.headers['X-Custom-Header'])
+        # Check the passed in headers dict isn't modified.
+        self.assertEqual(headers_orig, headers)
diff --git a/test/test_server.py b/test/test_server.py
index 0a48e41..b7732e0 100644
--- a/test/test_server.py
+++ b/test/test_server.py
@@ -53,13 +53,13 @@ class TestServer(unittest.TestCase):
             rv = self.p.poll()
             if rv is None:
                 raise AssertionError("Server took too long to start up.")
-            if rv is 128: # Import error
+            if rv == 128: # Import error
                 if os.environ.get('CI') != 'true' or \
                         os.environ.get('TRAVIS_PYTHON_VERSION') not in ('2.7', '3.6'):
                     tools.warn("Skipping %r test (ImportError)." % self.server)
                     self.skip = True
                     return
-            if rv is 3: # Port in use
+            if rv == 3: # Port in use
                 continue
             raise AssertionError("Server exited with error code %d" % rv)
         raise AssertionError("Could not find a free port to test server.")