From 3ef9789589316b696b11398c4769a0ef29d689ae Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Wed, 30 Mar 2011 12:08:49 +0200 Subject: [PATCH] Applied a few PEP8 style modifications. --- compressor/__init__.py | 2 +- compressor/base.py | 15 +- compressor/cache.py | 6 + compressor/css.py | 9 +- compressor/exceptions.py | 2 + compressor/filters/base.py | 1 + compressor/filters/cssmin/cssmin.py | 68 ++--- compressor/filters/datauri.py | 7 +- compressor/finders.py | 1 + compressor/js.py | 7 +- compressor/management/commands/compress.py | 44 +-- compressor/parser.py | 12 +- compressor/settings.py | 31 +- compressor/storage.py | 2 + compressor/templatetags/compress.py | 12 +- compressor/utils.py | 327 +++++++++++---------- 16 files changed, 294 insertions(+), 252 deletions(-) diff --git a/compressor/__init__.py b/compressor/__init__.py index e0bbcd9..a073fff 100644 --- a/compressor/__init__.py +++ b/compressor/__init__.py @@ -13,4 +13,4 @@ def get_version(): return version -__version__ = get_version() \ No newline at end of file +__version__ = get_version() diff --git a/compressor/base.py b/compressor/base.py index 4c7e6a4..b6e2e39 100644 --- a/compressor/base.py +++ b/compressor/base.py @@ -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): diff --git a/compressor/cache.py b/compressor/cache.py index f2778a9..4b9ebb4 100644 --- a/compressor/cache.py +++ b/compressor/cache.py @@ -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) diff --git a/compressor/css.py b/compressor/css.py index deba219..3fab0cd 100644 --- a/compressor/css.py +++ b/compressor/css.py @@ -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 diff --git a/compressor/exceptions.py b/compressor/exceptions.py index 94f965e..b251ce6 100644 --- a/compressor/exceptions.py +++ b/compressor/exceptions.py @@ -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 diff --git a/compressor/filters/base.py b/compressor/filters/base.py index b32db73..869a68c 100644 --- a/compressor/filters/base.py +++ b/compressor/filters/base.py @@ -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): diff --git a/compressor/filters/cssmin/cssmin.py b/compressor/filters/cssmin/cssmin.py index 42b0d84..1668355 100644 --- a/compressor/filters/cssmin/cssmin.py +++ b/compressor/filters/cssmin/cssmin.py @@ -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)) diff --git a/compressor/filters/datauri.py b/compressor/filters/datauri.py index a72cb57..82f50f7 100644 --- a/compressor/filters/datauri.py +++ b/compressor/filters/datauri.py @@ -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 diff --git a/compressor/finders.py b/compressor/finders.py index e49db97..fcb71c7 100644 --- a/compressor/finders.py +++ b/compressor/finders.py @@ -13,6 +13,7 @@ else: "standalone version django-staticfiles needs " "to be installed.") + class CompressorFinder(BaseStorageFinder): """ A staticfiles finder that looks in COMPRESS_ROOT diff --git a/compressor/js.py b/compressor/js.py index dddb9d0..4657f00 100644 --- a/compressor/js.py +++ b/compressor/js.py @@ -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 diff --git a/compressor/management/commands/compress.py b/compressor/management/commands/compress.py index 5414b3f..253cace 100644 --- a/compressor/management/commands/compress.py +++ b/compressor/management/commands/compress.py @@ -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) diff --git a/compressor/parser.py b/compressor/parser.py index 2052e5a..8ac09a6 100644 --- a/compressor/parser.py +++ b/compressor/parser.py @@ -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)) diff --git a/compressor/settings.py b/compressor/settings.py index c6f4c82..319db03 100644 --- a/compressor/settings.py +++ b/compressor/settings.py @@ -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 diff --git a/compressor/storage.py b/compressor/storage.py index 8f0adea..755c51f 100644 --- a/compressor/storage.py +++ b/compressor/storage.py @@ -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 diff --git a/compressor/templatetags/compress.py b/compressor/templatetags/compress.py index c6a7038..6f74ca7 100644 --- a/compressor/templatetags/compress.py +++ b/compressor/templatetags/compress.py @@ -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) diff --git a/compressor/utils.py b/compressor/utils.py index de84cbe..756f5bb 100644 --- a/compressor/utils.py +++ b/compressor/utils.py @@ -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_`` 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_`` 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)