Applied a few PEP8 style modifications.

This commit is contained in:
Jannis Leidel
2011-03-30 12:08:49 +02:00
parent 015b28cf39
commit 3ef9789589
16 changed files with 294 additions and 252 deletions

View File

@@ -13,4 +13,4 @@ def get_version():
return version
__version__ = get_version()
__version__ = get_version()

View File

@@ -26,8 +26,11 @@ class Compressor(object):
self.precompilers = settings.COMPRESS_PRECOMPILERS
def split_contents(self):
raise NotImplementedError(
"split_contents must be defined in a subclass")
"""
To be implemented in a subclass, should return an
iterable with three values: kind, value, element
"""
raise NotImplementedError
def get_filename(self, url):
try:
@@ -76,7 +79,8 @@ class Compressor(object):
if kind == "hunk":
# Let's cast BeautifulSoup element to unicode here since
# it will try to encode using ascii internally later
yield unicode(self.filter(value, "input", elem=elem, kind=kind))
yield unicode(
self.filter(value, method="input", elem=elem, kind=kind))
elif kind == "file":
content = ""
try:
@@ -142,7 +146,6 @@ class Compressor(object):
source_file.close()
return content
def filter(self, content, method, **kwargs):
# run compiler
if method == "input":
@@ -160,7 +163,7 @@ class Compressor(object):
@cached_property
def combined(self):
return self.filter(self.concat(), 'output')
return self.filter(self.concat(), method="output")
@cached_property
def hash(self):
@@ -169,7 +172,7 @@ class Compressor(object):
@cached_property
def new_filepath(self):
return os.path.join(settings.COMPRESS_OUTPUT_DIR.strip(os.sep),
self.output_prefix, "%s.%s" % (self.hash, self.type))
self.output_prefix, "%s.%s" % (self.hash, self.type))
def save_file(self):
if self.storage.exists(self.new_filepath):

View File

@@ -7,18 +7,22 @@ from django.utils.hashcompat import sha_constructor
from compressor.conf import settings
def get_hexdigest(plaintext):
return sha_constructor(plaintext).hexdigest()
def get_mtime_cachekey(filename):
return "django_compressor.mtime.%s.%s" % (socket.gethostname(),
get_hexdigest(filename))
def get_offline_cachekey(source):
return ("django_compressor.offline.%s.%s" %
(socket.gethostname(),
get_hexdigest("".join(smart_str(s) for s in source))))
def get_mtime(filename):
if settings.COMPRESS_MTIME_DELAY:
key = get_mtime_cachekey(filename)
@@ -29,9 +33,11 @@ def get_mtime(filename):
return mtime
return os.path.getmtime(filename)
def get_hashed_mtime(filename, length=12):
filename = os.path.realpath(filename)
mtime = str(int(get_mtime(filename)))
return get_hexdigest(mtime)[:length]
cache = get_cache(settings.COMPRESS_CACHE_BACKEND)

View File

@@ -2,12 +2,13 @@ from compressor.conf import settings
from compressor.base import Compressor
from compressor.exceptions import UncompressableFileError
class CssCompressor(Compressor):
template_name = "compressor/css.html"
template_name_inline = "compressor/css_inline.html"
def __init__(self, content=None, output_prefix="css"):
super(CssCompressor, self).__init__(content, output_prefix)
self.template_name = "compressor/css.html"
self.template_name_inline = "compressor/css_inline.html"
self.filters = list(settings.COMPRESS_CSS_FILTERS)
self.type = 'css'
@@ -21,8 +22,8 @@ class CssCompressor(Compressor):
elem_attribs = self.parser.elem_attribs(elem)
if elem_name == 'link' and elem_attribs['rel'] == 'stylesheet':
try:
content = self.parser.elem_content(elem)
data = ('file', self.get_filename(elem_attribs['href']), elem)
data = (
'file', self.get_filename(elem_attribs['href']), elem)
except UncompressableFileError:
if settings.DEBUG:
raise

View File

@@ -17,12 +17,14 @@ class FilterError(Exception):
"""
pass
class ParserError(Exception):
"""
This exception is raised when the parser fails
"""
pass
class OfflineGenerationError(Exception):
"""
Offline compression generation related exceptions

View File

@@ -8,6 +8,7 @@ from compressor.utils import cmd_split
logger = logging.getLogger("compressor.filters")
class FilterBase(object):
def __init__(self, content, filter_type=None, verbose=0):

View File

@@ -2,9 +2,9 @@
# -*- coding: utf-8 -*-
#
# `cssmin.py` - A Python port of the YUI CSS compressor.
#
#
# Copyright (c) 2010 Zachary Voase
#
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
@@ -13,10 +13,10 @@
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
@@ -25,7 +25,7 @@
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
#
#
"""`cssmin` - A Python port of the YUI CSS compressor."""
@@ -41,7 +41,7 @@ __version__ = '0.1.4'
def remove_comments(css):
"""Remove all CSS comment blocks."""
iemac = False
preserve = False
comment_start = css.find("/*")
@@ -49,7 +49,7 @@ def remove_comments(css):
# Preserve comments that look like `/*!...*/`.
# Slicing is used to make sure we don"t get an IndexError.
preserve = css[comment_start + 2:comment_start + 3] == "!"
comment_end = css.find("*/", comment_start + 2)
if comment_end < 0:
if not preserve:
@@ -69,22 +69,22 @@ def remove_comments(css):
else:
comment_start = comment_end + 2
comment_start = css.find("/*", comment_start)
return css
def remove_unnecessary_whitespace(css):
"""Remove unnecessary whitespace characters."""
def pseudoclasscolon(css):
"""
Prevents 'p :link' from becoming 'p:link'.
Translates 'p :link' into 'p ___PSEUDOCLASSCOLON___link'; this is
translated back again later.
"""
regex = re.compile(r"(^|\})(([^\{\:])+\:)+([^\{]*\{)")
match = regex.search(css)
while match:
@@ -94,43 +94,43 @@ def remove_unnecessary_whitespace(css):
css[match.end():]])
match = regex.search(css)
return css
css = pseudoclasscolon(css)
# Remove spaces from before things.
css = re.sub(r"\s+([!{};:>+\(\)\],])", r"\1", css)
# If there is a `@charset`, then only allow one, and move to the beginning.
css = re.sub(r"^(.*)(@charset \"[^\"]*\";)", r"\2\1", css)
css = re.sub(r"^(\s*@charset [^;]+;\s*)+", r"\1", css)
# Put the space back in for a few cases, such as `@media screen` and
# `(-webkit-min-device-pixel-ratio:0)`.
css = re.sub(r"\band\(", "and (", css)
# Put the colons back.
css = css.replace('___PSEUDOCLASSCOLON___', ':')
# Remove spaces from after things.
css = re.sub(r"([!{}:;>+\(\[,])\s+", r"\1", css)
return css
def remove_unnecessary_semicolons(css):
"""Remove unnecessary semicolons."""
return re.sub(r";+\}", "}", css)
def remove_empty_rules(css):
"""Remove empty rules."""
return re.sub(r"[^\}\{]+\{\}", "", css)
def normalize_rgb_colors_to_hex(css):
"""Convert `rgb(51,102,153)` to `#336699`."""
regex = re.compile(r"rgb\s*\(\s*([0-9,\s]+)\s*\)")
match = regex.search(css)
while match:
@@ -143,32 +143,32 @@ def normalize_rgb_colors_to_hex(css):
def condense_zero_units(css):
"""Replace `0(px, em, %, etc)` with `0`."""
return re.sub(r"([\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)", r"\1\2", css)
def condense_multidimensional_zeros(css):
"""Replace `:0 0 0 0;`, `:0 0 0;` etc. with `:0;`."""
css = css.replace(":0 0 0 0;", ":0;")
css = css.replace(":0 0 0;", ":0;")
css = css.replace(":0 0;", ":0;")
# Revert `background-position:0;` to the valid `background-position:0 0;`.
css = css.replace("background-position:0;", "background-position:0 0;")
return css
def condense_floating_points(css):
"""Replace `0.6` with `.6` where possible."""
return re.sub(r"(:|\s)0+\.(\d+)", r"\1.\2", css)
def condense_hex_colors(css):
"""Shorten colors from #AABBCC to #ABC where possible."""
regex = re.compile(r"([^\"'=\s])(\s*)#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])")
match = regex.search(css)
while match:
@@ -184,19 +184,19 @@ def condense_hex_colors(css):
def condense_whitespace(css):
"""Condense multiple adjacent whitespace characters into one."""
return re.sub(r"\s+", " ", css)
def condense_semicolons(css):
"""Condense multiple adjacent semicolon characters into one."""
return re.sub(r";;+", ";", css)
def wrap_css_lines(css, line_length):
"""Wrap the lines of the given CSS to an approximate length."""
lines = []
line_start = 0
for i, char in enumerate(css):
@@ -204,7 +204,7 @@ def wrap_css_lines(css, line_length):
if char == '}' and (i - line_start >= line_length):
lines.append(css[line_start:i + 1])
line_start = i + 1
if line_start < len(css):
lines.append(css[line_start:])
return '\n'.join(lines)
@@ -233,16 +233,16 @@ def cssmin(css, wrap=None):
def main():
import optparse
import sys
p = optparse.OptionParser(
prog="cssmin", version=__version__,
usage="%prog [--wrap N]",
description="""Reads raw CSS from stdin, and writes compressed CSS to stdout.""")
p.add_option(
'-w', '--wrap', type='int', default=None, metavar='N',
help="Wrap output to approximately N chars per line.")
options, args = p.parse_args()
sys.stdout.write(cssmin(sys.stdin.read(), wrap=options.wrap))

View File

@@ -6,6 +6,7 @@ from base64 import b64encode
from compressor.conf import settings
from compressor.filters import FilterBase
class DataUriFilter(FilterBase):
"""Filter for embedding media as data: URIs.
@@ -28,7 +29,8 @@ class DataUriFilter(FilterBase):
# strip query string of file paths
if "?" in url:
url = url.split("?")[0]
return os.path.join(settings.COMPRESS_ROOT, url[len(settings.COMPRESS_URL):])
return os.path.join(
settings.COMPRESS_ROOT, url[len(settings.COMPRESS_URL):])
def data_uri_converter(self, matchobj):
url = matchobj.group(1).strip(' \'"')
@@ -36,7 +38,8 @@ class DataUriFilter(FilterBase):
path = self.get_file_path(url)
if os.stat(path).st_size <= settings.COMPRESS_DATA_URI_MIN_SIZE:
data = b64encode(open(path, 'rb').read())
return 'url("data:%s;base64,%s")' % (mimetypes.guess_type(path)[0], data)
return 'url("data:%s;base64,%s")' % (
mimetypes.guess_type(path)[0], data)
return 'url("%s")' % url

View File

@@ -13,6 +13,7 @@ else:
"standalone version django-staticfiles needs "
"to be installed.")
class CompressorFinder(BaseStorageFinder):
"""
A staticfiles finder that looks in COMPRESS_ROOT

View File

@@ -4,11 +4,11 @@ from compressor.exceptions import UncompressableFileError
class JsCompressor(Compressor):
template_name = "compressor/js.html"
template_name_inline = "compressor/js_inline.html"
def __init__(self, content=None, output_prefix="js"):
super(JsCompressor, self).__init__(content, output_prefix)
self.template_name = "compressor/js.html"
self.template_name_inline = "compressor/js_inline.html"
self.filters = list(settings.COMPRESS_JS_FILTERS)
self.type = 'js'
@@ -19,7 +19,8 @@ class JsCompressor(Compressor):
attribs = self.parser.elem_attribs(elem)
if 'src' in attribs:
try:
self.split_content.append(('file', self.get_filename(attribs['src']), elem))
self.split_content.append(
('file', self.get_filename(attribs['src']), elem))
except UncompressableFileError:
if settings.DEBUG:
raise

View File

@@ -21,28 +21,30 @@ from compressor.utils import walk, any, import_module
class Command(NoArgsCommand):
help = "Generate the compressor content outside of the request/response cycle"
help = "Compress content outside of the request/response cycle"
option_list = NoArgsCommand.option_list + (
make_option('--extension', '-e', action='append', dest='extensions',
help='The file extension(s) to examine (default: ".html", '
'separate multiple extensions with commas, or use -e '
'multiple times)'),
make_option('-f', '--force', default=False, action='store_true', dest='force',
make_option('-f', '--force', default=False, action='store_true',
help="Force generation of compressor content even if "
"COMPRESS setting is not True."),
make_option('--follow-links', default=False, action='store_true', dest='follow_links',
"COMPRESS setting is not True.", dest='force'),
make_option('--follow-links', default=False, action='store_true',
help="Follow symlinks when traversing the COMPRESS_ROOT "
"(which defaults to MEDIA_ROOT). Be aware that using this "
"can lead to infinite recursion if a link points to a parent "
"directory of itself."),
"directory of itself.", dest='follow_links'),
)
def get_loaders(self):
from django.template.loader import template_source_loaders
if template_source_loaders is None:
try:
from django.template.loader import find_template as finder_func
from django.template.loader import (
find_template as finder_func)
except ImportError:
from django.template.loader import find_template_source as finder_func
from django.template.loader import (
find_template_source as finder_func)
try:
source, name = finder_func('test')
except TemplateDoesNotExist:
@@ -71,7 +73,8 @@ class Command(NoArgsCommand):
for loader in self.get_loaders():
try:
module = import_module(loader.__module__)
get_template_sources = getattr(module, 'get_template_sources', None)
get_template_sources = getattr(module,
'get_template_sources', None)
if get_template_sources is None:
get_template_sources = loader.get_template_sources
paths.update(list(get_template_sources('')))
@@ -89,7 +92,8 @@ class Command(NoArgsCommand):
log.write("Considering paths:\n\t" + "\n\t".join(paths) + "\n")
templates = set()
for path in paths:
for root, dirs, files in walk(path, followlinks=options.get('followlinks', False)):
for root, dirs, files in walk(path,
followlinks=options.get('followlinks', False)):
templates.update(os.path.join(root, name)
for name in files if any(fnmatch(name, "*%s" % glob)
for glob in extensions))
@@ -126,7 +130,8 @@ class Command(NoArgsCommand):
compressor_nodes.setdefault(template_name, []).extend(nodes)
if not compressor_nodes:
raise OfflineGenerationError("No 'compress' template tags found in templates.")
raise OfflineGenerationError(
"No 'compress' template tags found in templates.")
if verbosity > 0:
log.write("Found 'compress' tags in:\n\t" +
@@ -175,18 +180,19 @@ class Command(NoArgsCommand):
for i, ext in enumerate(ext_list):
if not ext.startswith('.'):
ext_list[i] = '.%s' % ext_list[i]
# we don't want *.py files here because of the way non-*.py files
# are handled in make_messages() (they are copied to file.ext.py files to
# trick xgettext to parse them as Python files)
return set([x for x in ext_list if x != '.py'])
return set(ext_list)
def handle_noargs(self, **options):
if not settings.COMPRESS_ENABLED and not options.get("force"):
raise CommandError("Compressor is disabled. Set COMPRESS settting or use --force to override.")
raise CommandError(
"Compressor is disabled. Set COMPRESS "
"settting or use --force to override.")
if not settings.COMPRESS_OFFLINE:
if not options.get("force"):
raise CommandError("Offline compressiong is disabled. Set COMPRESS_OFFLINE or use the --force to override.")
warnings.warn("COMPRESS_OFFLINE is not set. Offline generated "
"cache will not be used.")
raise CommandError(
"Offline compressiong is disabled. Set "
"COMPRESS_OFFLINE or use the --force to override.")
warnings.warn(
"COMPRESS_OFFLINE is not set to True. "
"Offline generated cache will not be used.")
self.compress(sys.stdout, **options)

View File

@@ -2,8 +2,11 @@ from django.utils.encoding import smart_unicode
from compressor.exceptions import ParserError
class ParserBase(object):
class ParserBase(object):
"""
Base parser to be subclassed when creating an own parser.
"""
def __init__(self, content):
self.content = content
@@ -43,6 +46,7 @@ class ParserBase(object):
"""
raise NotImplementedError
class BeautifulSoupParser(ParserBase):
_soup = None
@@ -57,7 +61,7 @@ class BeautifulSoupParser(ParserBase):
return self._soup
def css_elems(self):
return self.soup.findAll({'link' : True, 'style' : True})
return self.soup.findAll({'link': True, 'style': True})
def js_elems(self):
return self.soup.findAll('script')
@@ -74,6 +78,7 @@ class BeautifulSoupParser(ParserBase):
def elem_str(self, elem):
return smart_unicode(elem)
class LxmlParser(ParserBase):
_tree = None
@@ -110,4 +115,5 @@ class LxmlParser(ParserBase):
def elem_str(self, elem):
from lxml import etree
return smart_unicode(etree.tostring(elem, method='html', encoding=unicode))
return smart_unicode(
etree.tostring(elem, method='html', encoding=unicode))

View File

@@ -1,5 +1,3 @@
import os
from django import VERSION as DJANGO_VERSION
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
@@ -38,16 +36,16 @@ class CompressorSettings(AppSettings):
# the cache backend to use
CACHE_BACKEND = None
# rebuilds the cache every 30 days if nothing has changed.
REBUILD_TIMEOUT = 60 * 60 * 24 * 30 # 30 days
REBUILD_TIMEOUT = 60 * 60 * 24 * 30 # 30 days
# the upper bound on how long any compression should take to be generated
# (used against dog piling, should be a lot smaller than REBUILD_TIMEOUT
MINT_DELAY = 30 # seconds
MINT_DELAY = 30 # seconds
# check for file changes only after a delay
MTIME_DELAY = 10 # seconds
# enables the offline cache -- a cache that is filled by the compress management command
MTIME_DELAY = 10 # seconds
# enables the offline cache -- also filled by the compress command
OFFLINE = False
# invalidates the offline cache after one year
OFFLINE_TIMEOUT = 60 * 60 * 24 * 365 # 1 year
OFFLINE_TIMEOUT = 60 * 60 * 24 * 365 # 1 year
# The context to be used when compressing the files "offline"
OFFLINE_CONTEXT = {}
@@ -60,7 +58,8 @@ class CompressorSettings(AppSettings):
if not value:
value = settings.MEDIA_ROOT
if not value:
raise ImproperlyConfigured("The COMPRESS_ROOT setting must be set.")
raise ImproperlyConfigured(
"The COMPRESS_ROOT setting must be set.")
# In case staticfiles is used, make sure the FileSystemFinder is
# installed, and if it is, check if COMPRESS_ROOT is listed in
# STATICFILES_DIRS to allow finding compressed files
@@ -79,14 +78,14 @@ class CompressorSettings(AppSettings):
return value
def configure_url(self, value):
# Falls back to the 1.3 STATIC_URL setting by default or falls back to MEDIA_URL
# Uses Django 1.3's STATIC_URL by default or falls back to MEDIA_URL
if value is None:
value = getattr(settings, 'STATIC_URL', None)
value = getattr(settings, "STATIC_URL", None)
if not value:
value = settings.MEDIA_URL
if not value.endswith('/'):
raise ImproperlyConfigured('The URL settings (e.g. COMPRESS_URL) '
'must have a trailing slash.')
if not value.endswith("/"):
raise ImproperlyConfigured("The URL settings (e.g. COMPRESS_URL) "
"must have a trailing slash.")
return value
def configure_cache_backend(self, value):
@@ -105,9 +104,9 @@ class CompressorSettings(AppSettings):
def configure_offline_context(self, value):
if not value:
value = {
'MEDIA_URL': settings.MEDIA_URL,
"MEDIA_URL": settings.MEDIA_URL,
}
# Adds the 1.3 STATIC_URL setting to the context if available
if getattr(settings, 'STATIC_URL', None):
value['STATIC_URL'] = settings.STATIC_URL
if getattr(settings, "STATIC_URL", None):
value["STATIC_URL"] = settings.STATIC_URL
return value

View File

@@ -7,6 +7,7 @@ from django.utils.functional import LazyObject
from compressor.conf import settings
class CompressorFileStorage(FileSystemStorage):
"""
Standard file system storage for files handled by django-compressor.
@@ -40,6 +41,7 @@ class CompressorFileStorage(FileSystemStorage):
self.delete(name)
return name
class GzipCompressorFileStorage(CompressorFileStorage):
"""
The standard compressor file system storage that gzips storage files

View File

@@ -33,11 +33,13 @@ class CompressorNode(template.Node):
if (time.time() > refresh_time) and not refreshed:
# Store the stale value while the cache
# revalidates for another MINT_DELAY seconds.
self.cache_set(key, val, timeout=settings.COMPRESS_MINT_DELAY, refreshed=True)
self.cache_set(key, val, refreshed=True,
timeout=settings.COMPRESS_MINT_DELAY)
return None
return val
def cache_set(self, key, val, timeout=settings.COMPRESS_REBUILD_TIMEOUT, refreshed=False):
def cache_set(self, key, val, refreshed=False,
timeout=settings.COMPRESS_REBUILD_TIMEOUT):
refresh_time = timeout + time.time()
real_timeout = timeout + settings.COMPRESS_MINT_DELAY
packed_val = (val, refresh_time, refreshed)
@@ -47,13 +49,15 @@ class CompressorNode(template.Node):
return "%s.%s.%s" % (compressor.cachekey, self.mode, self.kind)
def render(self, context, forced=False):
if (settings.COMPRESS_ENABLED and settings.COMPRESS_OFFLINE) and not forced:
if (settings.COMPRESS_ENABLED and
settings.COMPRESS_OFFLINE) and not forced:
key = get_offline_cachekey(self.nodelist)
content = cache.get(key)
if content:
return content
content = self.nodelist.render(context)
if (not settings.COMPRESS_ENABLED or not len(content.strip())) and not forced:
if (not settings.COMPRESS_ENABLED or
not len(content.strip())) and not forced:
return content
compressor = self.compressor_cls(content)
cachekey = self.cache_key(compressor)

View File

@@ -8,14 +8,17 @@ from django.conf import settings
from compressor.exceptions import FilterError
try:
any = any
any
except NameError:
def any(seq):
for item in seq:
if item:
return True
return False
def get_class(class_string, exception=FilterError):
"""
Convert a string version of a function name to the callable object.
@@ -32,6 +35,7 @@ def get_class(class_string, exception=FilterError):
return cls
raise exception('Failed to import %s' % class_string)
def get_mod_func(callback):
"""
Converts 'django.views.news.stories.story_detail' to
@@ -41,7 +45,8 @@ def get_mod_func(callback):
dot = callback.rindex('.')
except ValueError:
return callback, ''
return callback[:dot], callback[dot+1:]
return callback[:dot], callback[dot + 1:]
def walk(root, topdown=True, onerror=None, followlinks=False):
"""
@@ -56,208 +61,210 @@ def walk(root, topdown=True, onerror=None, followlinks=False):
for link_dirpath, link_dirnames, link_filenames in walk(p):
yield (link_dirpath, link_dirnames, link_filenames)
# Taken from Django 1.3-beta1 and before that from Python 2.7 with permission from/by the original author.
# Taken from Django 1.3 and before that from Python 2.7
# with permission from the original author.
def _resolve_name(name, package, level):
"""Return the absolute name of the module to be imported."""
if not hasattr(package, 'rindex'):
raise ValueError("'package' not set to a string")
dot = len(package)
for x in xrange(level, 1, -1):
try:
dot = package.rindex('.', 0, dot)
except ValueError:
raise ValueError("attempted relative import beyond top-level "
"package")
return "%s.%s" % (package[:dot], name)
"""Return the absolute name of the module to be imported."""
if not hasattr(package, 'rindex'):
raise ValueError("'package' not set to a string")
dot = len(package)
for x in xrange(level, 1, -1):
try:
dot = package.rindex('.', 0, dot)
except ValueError:
raise ValueError("attempted relative import beyond top-level "
"package")
return "%s.%s" % (package[:dot], name)
def import_module(name, package=None):
"""Import a module.
"""Import a module.
The 'package' argument is required when performing a relative import. It
specifies the package to use as the anchor point from which to resolve the
relative import to an absolute import.
The 'package' argument is required when performing a relative import. It
specifies the package to use as the anchor point from which to resolve the
relative import to an absolute import.
"""
if name.startswith('.'):
if not package:
raise TypeError("relative imports require the 'package' argument")
level = 0
for character in name:
if character != '.':
break
level += 1
name = _resolve_name(name[level:], package, level)
__import__(name)
return sys.modules[name]
"""
if name.startswith('.'):
if not package:
raise TypeError("relative imports require the 'package' argument")
level = 0
for character in name:
if character != '.':
break
level += 1
name = _resolve_name(name[level:], package, level)
__import__(name)
return sys.modules[name]
class AppSettings(object):
"""
An app setting object to be used for handling app setting defaults
gracefully and providing a nice API for them. Say you have an app
called ``myapp`` and want to define a few defaults, and refer to the
defaults easily in the apps code. Add a ``settings.py`` to your app::
"""
An app setting object to be used for handling app setting defaults
gracefully and providing a nice API for them. Say you have an app
called ``myapp`` and want to define a few defaults, and refer to the
defaults easily in the apps code. Add a ``settings.py`` to your app::
from path.to.utils import AppSettings
from path.to.utils import AppSettings
class MyAppSettings(AppSettings):
SETTING_1 = "one"
SETTING_2 = (
"two",
)
class MyAppSettings(AppSettings):
SETTING_1 = "one"
SETTING_2 = (
"two",
)
Then initialize the setting with the correct prefix in the location of
of your choice, e.g. ``conf.py`` of the app module::
Then initialize the setting with the correct prefix in the location of
of your choice, e.g. ``conf.py`` of the app module::
settings = MyAppSettings(prefix="MYAPP")
settings = MyAppSettings(prefix="MYAPP")
The ``MyAppSettings`` instance will automatically look at Django's
global setting to determine each of the settings and respect the
provided ``prefix``. E.g. adding this to your site's ``settings.py``
will set the ``SETTING_1`` setting accordingly::
The ``MyAppSettings`` instance will automatically look at Django's
global setting to determine each of the settings and respect the
provided ``prefix``. E.g. adding this to your site's ``settings.py``
will set the ``SETTING_1`` setting accordingly::
MYAPP_SETTING_1 = "uno"
MYAPP_SETTING_1 = "uno"
Usage
-----
Usage
-----
Instead of using ``from django.conf import settings`` as you would
usually do, you can switch to using your apps own settings module
to access the app settings::
Instead of using ``from django.conf import settings`` as you would
usually do, you can switch to using your apps own settings module
to access the app settings::
from myapp.conf import settings
from myapp.conf import settings
print myapp_settings.MYAPP_SETTING_1
print myapp_settings.MYAPP_SETTING_1
``AppSettings`` instances also work as pass-throughs for other
global settings that aren't related to the app. For example the
following code is perfectly valid::
``AppSettings`` instances also work as pass-throughs for other
global settings that aren't related to the app. For example the
following code is perfectly valid::
from myapp.conf import settings
from myapp.conf import settings
if "myapp" in settings.INSTALLED_APPS:
print "yay, myapp is installed!"
if "myapp" in settings.INSTALLED_APPS:
print "yay, myapp is installed!"
Custom handling
---------------
Custom handling
---------------
Each of the settings can be individually configured with callbacks.
For example, in case a value of a setting depends on other settings
or other dependencies. The following example sets one setting to a
different value depending on a global setting::
Each of the settings can be individually configured with callbacks.
For example, in case a value of a setting depends on other settings
or other dependencies. The following example sets one setting to a
different value depending on a global setting::
from django.conf import settings
from django.conf import settings
class MyCustomAppSettings(AppSettings):
ENABLED = True
class MyCustomAppSettings(AppSettings):
ENABLED = True
def configure_enabled(self, value):
return value and not self.DEBUG
def configure_enabled(self, value):
return value and not self.DEBUG
custom_settings = MyCustomAppSettings("MYAPP")
custom_settings = MyCustomAppSettings("MYAPP")
The value of ``custom_settings.MYAPP_ENABLED`` will vary depending on the
value of the global ``DEBUG`` setting.
The value of ``custom_settings.MYAPP_ENABLED`` will vary depending on the
value of the global ``DEBUG`` setting.
Each of the app settings can be customized by providing
a method ``configure_<lower_setting_name>`` that takes the default
value as defined in the class attributes as the only parameter.
The method needs to return the value to be use for the setting in
question.
"""
def __dir__(self):
return sorted(list(set(self.__dict__.keys() + dir(settings))))
Each of the app settings can be customized by providing
a method ``configure_<lower_setting_name>`` that takes the default
value as defined in the class attributes as the only parameter.
The method needs to return the value to be use for the setting in
question.
"""
def __dir__(self):
return sorted(list(set(self.__dict__.keys() + dir(settings))))
__members__ = lambda self: self.__dir__()
__members__ = lambda self: self.__dir__()
def __getattr__(self, name):
if name.startswith(self._prefix):
raise AttributeError("%r object has no attribute %r" %
(self.__class__.__name__, name))
return getattr(settings, name)
def __getattr__(self, name):
if name.startswith(self._prefix):
raise AttributeError("%r object has no attribute %r" %
(self.__class__.__name__, name))
return getattr(settings, name)
def __setattr__(self, name, value):
super(AppSettings, self).__setattr__(name, value)
if name in dir(settings):
setattr(settings, name, value)
def __setattr__(self, name, value):
super(AppSettings, self).__setattr__(name, value)
if name in dir(settings):
setattr(settings, name, value)
def __init__(self, prefix):
super(AppSettings, self).__setattr__('_prefix', prefix)
for name, value in filter(self.issetting, getmembers(self.__class__)):
prefixed_name = "%s_%s" % (prefix.upper(), name.upper())
value = getattr(settings, prefixed_name, value)
callback = getattr(self, "configure_%s" % name.lower(), None)
if callable(callback):
value = callback(value)
delattr(self.__class__, name)
setattr(self, prefixed_name, value)
def __init__(self, prefix):
super(AppSettings, self).__setattr__('_prefix', prefix)
for name, value in filter(self.issetting, getmembers(self.__class__)):
prefixed_name = "%s_%s" % (prefix.upper(), name.upper())
value = getattr(settings, prefixed_name, value)
callback = getattr(self, "configure_%s" % name.lower(), None)
if callable(callback):
value = callback(value)
delattr(self.__class__, name)
setattr(self, prefixed_name, value)
def issetting(self, (name, value)):
return name == name.upper()
def issetting(self, (name, value)):
return name == name.upper()
class cached_property(object):
"""Property descriptor that caches the return value
of the get function.
"""Property descriptor that caches the return value
of the get function.
*Examples*
*Examples*
.. code-block:: python
.. code-block:: python
@cached_property
def connection(self):
return Connection()
@cached_property
def connection(self):
return Connection()
@connection.setter # Prepares stored value
def connection(self, value):
if value is None:
raise TypeError("Connection must be a connection")
return value
@connection.setter # Prepares stored value
def connection(self, value):
if value is None:
raise TypeError("Connection must be a connection")
return value
@connection.deleter
def connection(self, value):
# Additional action to do at del(self.attr)
if value is not None:
print("Connection %r deleted" % (value, ))
"""
@connection.deleter
def connection(self, value):
# Additional action to do at del(self.attr)
if value is not None:
print("Connection %r deleted" % (value, ))
"""
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.__get = fget
self.__set = fset
self.__del = fdel
self.__doc__ = doc or fget.__doc__
self.__name__ = fget.__name__
self.__module__ = fget.__module__
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.__get = fget
self.__set = fset
self.__del = fdel
self.__doc__ = doc or fget.__doc__
self.__name__ = fget.__name__
self.__module__ = fget.__module__
def __get__(self, obj, type=None):
if obj is None:
return self
try:
return obj.__dict__[self.__name__]
except KeyError:
value = obj.__dict__[self.__name__] = self.__get(obj)
return value
def __get__(self, obj, type=None):
if obj is None:
return self
try:
return obj.__dict__[self.__name__]
except KeyError:
value = obj.__dict__[self.__name__] = self.__get(obj)
return value
def __set__(self, obj, value):
if obj is None:
return self
if self.__set is not None:
value = self.__set(obj, value)
obj.__dict__[self.__name__] = value
def __set__(self, obj, value):
if obj is None:
return self
if self.__set is not None:
value = self.__set(obj, value)
obj.__dict__[self.__name__] = value
def __delete__(self, obj):
if obj is None:
return self
try:
value = obj.__dict__.pop(self.__name__)
except KeyError:
pass
else:
if self.__del is not None:
self.__del(obj, value)
def __delete__(self, obj):
if obj is None:
return self
try:
value = obj.__dict__.pop(self.__name__)
except KeyError:
pass
else:
if self.__del is not None:
self.__del(obj, value)
def setter(self, fset):
return self.__class__(self.__get, fset, self.__del)
def setter(self, fset):
return self.__class__(self.__get, fset, self.__del)
def deleter(self, fdel):
return self.__class__(self.__get, self.__set, fdel)
def deleter(self, fdel):
return self.__class__(self.__get, self.__set, fdel)