Skip to content

Commit d0589a2

Browse files
committed
Switch to explicit static folders
1 parent 38107c7 commit d0589a2

File tree

4 files changed

+68
-37
lines changed

4 files changed

+68
-37
lines changed

flask/app.py

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from __future__ import with_statement
1313

14+
import os
1415
from threading import Lock
1516
from datetime import timedelta, datetime
1617
from itertools import chain
@@ -90,9 +91,9 @@ class Flask(_PackageBoundObject):
9091
9192
:param import_name: the name of the application package
9293
:param static_path: can be used to specify a different path for the
93-
static files on the web. Defaults to ``/static``.
94-
This does not affect the folder the files are served
95-
*from*.
94+
static folder. Defaults to ``'static'``. Setting
95+
this to `None` disables the default folder for
96+
static files.
9697
"""
9798

9899
#: The class that is used for request objects. See :class:`~flask.Request`
@@ -103,14 +104,6 @@ class Flask(_PackageBoundObject):
103104
#: :class:`~flask.Response` for more information.
104105
response_class = Response
105106

106-
#: Path for the static files. If you don't want to use static files
107-
#: you can set this value to `None` in which case no URL rule is added
108-
#: and the development server will no longer serve any static files.
109-
#:
110-
#: This is the default used for application and modules unless a
111-
#: different value is passed to the constructor.
112-
static_path = '/static'
113-
114107
#: The debug flag. Set this to `True` to enable debugging of the
115108
#: application. In debug mode the debugger will kick in when an unhandled
116109
#: exception ocurrs and the integrated server will automatically reload
@@ -198,10 +191,8 @@ class Flask(_PackageBoundObject):
198191
'MAX_CONTENT_LENGTH': None
199192
})
200193

201-
def __init__(self, import_name, static_path=None):
202-
_PackageBoundObject.__init__(self, import_name)
203-
if static_path is not None:
204-
self.static_path = static_path
194+
def __init__(self, import_name, static_path='static'):
195+
_PackageBoundObject.__init__(self, import_name, static_path)
205196

206197
#: The configuration dictionary as :class:`Config`. This behaves
207198
#: exactly like a regular dictionary but supports additional methods
@@ -273,14 +264,13 @@ def __init__(self, import_name, static_path=None):
273264
#: app.url_map.converters['list'] = ListConverter
274265
self.url_map = Map()
275266

276-
# register the static folder for the application. Do that even
277-
# if the folder does not exist. First of all it might be created
278-
# while the server is running (usually happens during development)
279-
# but also because google appengine stores static files somewhere
280-
# else when mapped with the .yml file.
281-
self.add_url_rule(self.static_path + '/<path:filename>',
282-
endpoint='static',
283-
view_func=self.send_static_file)
267+
# register the static folder for the application if necessary.
268+
# The basename of the static path is the URL prefix.
269+
if self.static_path is not None:
270+
self.add_url_rule('/%s/<path:filename>' %
271+
os.path.basename(self.static_path),
272+
endpoint='static',
273+
view_func=self.send_static_file)
284274

285275
#: The Jinja2 environment. It is created from the
286276
#: :attr:`jinja_options`.

flask/helpers.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -429,22 +429,38 @@ def _get_package_path(name):
429429

430430
class _PackageBoundObject(object):
431431

432-
def __init__(self, import_name):
432+
def __init__(self, import_name, static_path=None):
433433
#: The name of the package or module. Do not change this once
434434
#: it was set by the constructor.
435435
self.import_name = import_name
436436

437437
#: Where is the app root located?
438438
self.root_path = _get_package_path(self.import_name)
439439

440+
if static_path is not None:
441+
static_path = os.path.join(self.root_path, static_path)
442+
443+
#: the path to the static files or None if this object does
444+
#: not export any static files under a standard name.
445+
self.static_path = static_path
446+
440447
@property
441448
def has_static_folder(self):
442449
"""This is `True` if the package bound object's container has a
443450
folder named ``'static'``.
444451
445452
.. versionadded:: 0.5
453+
454+
.. versionchanged:: 0.7
455+
This propery got deprecated because it is unreliable under
456+
certain hosting setups such as Google's Appengine. This will
457+
be gone in Flask 1.0
446458
"""
447-
return os.path.isdir(os.path.join(self.root_path, 'static'))
459+
from warnings import warn
460+
warn(DeprecationWarning('has_static_folder is deprecated. Pass '
461+
'explicitly a static path now if you want a default static '
462+
'folder for an application or module.'), stacklevel=2)
463+
return os.path.isdir(self.static_path)
448464

449465
@cached_property
450466
def jinja_loader(self):
@@ -460,8 +476,7 @@ def send_static_file(self, filename):
460476
461477
.. versionadded:: 0.5
462478
"""
463-
return send_from_directory(os.path.join(self.root_path, 'static'),
464-
filename)
479+
return send_from_directory(self.static_path, filename)
465480

466481
def open_resource(self, resource):
467482
"""Opens a resource from the application's resource folder. To see
@@ -482,6 +497,10 @@ def open_resource(self, resource):
482497
contents = f.read()
483498
do_something_with(contents)
484499
500+
Note that on Google Appengine static files are deployed to an entirely
501+
different server with a proper app.yml and because of that, this method
502+
is unable to open static files on such an environment.
503+
485504
:param resource: the name of the resource. To access resources within
486505
subfolders use forward slashes as separator.
487506
"""

flask/module.py

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,36 @@
99
:license: BSD, see LICENSE for more details.
1010
"""
1111

12+
import os
1213
from .helpers import _PackageBoundObject, _endpoint_from_view_func
1314

1415

15-
def _register_module(module, static_path):
16+
def _register_module(module):
1617
"""Internal helper function that returns a function for recording
1718
that registers the `send_static_file` function for the module on
1819
the application if necessary. It also registers the module on
1920
the application.
2021
"""
2122
def _register(state):
2223
state.app.modules[module.name] = module
23-
# do not register the rule if the static folder of the
24-
# module is the same as the one from the application.
25-
if state.app.root_path == module.root_path:
26-
return
27-
path = static_path
24+
path = module.static_path
25+
# XXX: backwards compatibility. This will go away in 1.0
26+
if path is None and module._backwards_compat_static_path:
27+
path = module.static_path
28+
if path == app.static_path:
29+
return
30+
from warnings import warn
31+
warn(DeprecationWarning('With Flask 0.7 the static folder '
32+
'for modules became explicit due to problems with the '
33+
'existing system on Google Appengine and multiple '
34+
'modules with the same prefix.\n'
35+
'Pass ``static_path=\'static\'`` to the Module '
36+
'constructor if you want to use static folders.\n'
37+
'This backwards compatibility support will go away in '
38+
'Flask 1.0'), stacklevel=3)
2839
if path is None:
29-
path = state.app.static_path
40+
return
41+
path = '/' + os.path.basename(path)
3042
if state.url_prefix:
3143
path = state.url_prefix + path
3244
state.app.add_url_rule(path + '/<path:filename>',
@@ -112,18 +124,27 @@ def login():
112124
This does not affect the folder the files are served
113125
*from*.
114126
"""
127+
_backwards_compat_static_path = False
115128

116129
def __init__(self, import_name, name=None, url_prefix=None,
117130
static_path=None, subdomain=None):
118131
if name is None:
119132
assert '.' in import_name, 'name required if package name ' \
120133
'does not point to a submodule'
121134
name = import_name.rsplit('.', 1)[1]
122-
_PackageBoundObject.__init__(self, import_name)
135+
_PackageBoundObject.__init__(self, import_name, static_path)
123136
self.name = name
124137
self.url_prefix = url_prefix
125138
self.subdomain = subdomain
126-
self._register_events = [_register_module(self, static_path)]
139+
self._register_events = [_register_module(self)]
140+
141+
# XXX: backwards compatibility, see _register_module. This
142+
# will go away in 1.0
143+
if self.static_path is None:
144+
path = os.path.join(self.root_path, 'static')
145+
if os.path.isdir(path):
146+
self.static_path = path
147+
self._backwards_compat_static_path = True
127148

128149
def route(self, rule, **options):
129150
"""Like :meth:`Flask.route` but for a module. The endpoint for the

tests/moduleapp/apps/admin/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import os
12
from flask import Module, render_template
23

34

4-
admin = Module(__name__, url_prefix='/admin')
5+
admin = Module(__name__, url_prefix='/admin', static_path='static')
56

67

78
@admin.route('/')

0 commit comments

Comments
 (0)