diff --git a/.gitignore b/.gitignore index 83e0c3f..2351684 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ *.pyc *.pyo *.egg-info +*.log +*.dev.cfg __pycache__ bin build diff --git a/Makefile b/Makefile index ce12eac..4ec6652 100644 --- a/Makefile +++ b/Makefile @@ -8,13 +8,13 @@ clean-build: rm -fr build/ rm -fr dist/ rm -fr *.egg-info - + find . -name '*.log' -exec rm -fr {} \; clean-pyc: - find . -name '*.pyc' -exec rm -f {} + - find . -name '*.pyo' -exec rm -f {} + - find . -name '*~' -exec rm -f {} + - find . -name '__pycache__' -exec rm -fr {} + + find . -name '*.pyc' -exec rm -f {} \; + find . -name '*.pyo' -exec rm -f {} \; + find . -name '*~' -exec rm -f {} \; + find . -name '__pycache__' -exec rm -fr {} \; docs: $(MAKE) -C docs html diff --git a/feedbundle/app.cfg b/feedbundle/app.cfg new file mode 100644 index 0000000..e69de29 diff --git a/feedbundle/app.py b/feedbundle/app.py new file mode 100644 index 0000000..aa29ba0 --- /dev/null +++ b/feedbundle/app.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python +#-*- coding:utf-8 -*- + +import os + +from flask import Flask, request +from werkzeug import import_string + +from feedbundle.corelib.logging import make_file_handler, make_smtp_handler + + +class FeedBundle(Flask): + """The web application.""" + + CONFIG_ENV = "FEEDBUNDLE_CONFIG" + BUILTIN_CONFIG = "app.cfg" + + def __init__(self, import_name=__package__, *args, **kwargs): + super(FeedBundle, self).__init__(import_name, *args, **kwargs) + + #: load built-in and local configuration + self.config.from_pyfile(self.get_absolute_path(self.BUILTIN_CONFIG)) + self.config.from_envvar(self.CONFIG_ENV) + self.config.setdefault("BUILTIN_BLUEPRINTS", []) + self.config.setdefault("BUILTIN_EXTENSIONS", []) + self.config.setdefault("BLUEPRINTS", []) + self.config.setdefault("EXTENSIONS", []) + self.config.setdefault("LOGGING_FILE", None) + self.config.setdefault("LOGGING_EMAILS", []) + + #: initialize the application + self.init_extensions() + self.init_logger() + self.init_blueprints() + + def init_extensions(self): + """Initialize the extensions of the application.""" + #: make a set to contain names of the extensions + extensions = set(self.config['BUILTIN_EXTENSIONS']) + extensions.update(self.config['EXTENSIONS']) + + #: import and install the extensions + for extension_name in extensions: + extension = import_string(extension_name) + extension.init_app(self) + + def init_logger(self): + """Append more handlers to the application logger.""" + #: create a file handler and install it + filepath = self.config['LOGGING_FILE'] + if filepath: + file_handler = make_file_handler(filepath) + self.logger.addHandler(file_handler) + + #: create a email handler and install it + emails = self.config['LOGGING_EMAILS'] + smtp_handler_installed = [False] + + @self.before_first_request + def register_smtp_handler(): + #: defer initialize because of the 'request.host' + if emails and not smtp_handler_installed[0]: + fromaddr = "applog@%s" % request.host + subject = "FeedBundle Application Log" + smtp_handler = make_smtp_handler(fromaddr, emails, subject) + self.logger.addHandler(smtp_handler) + smtp_handler_installed[0] = True + + def init_blueprints(self): + """Register all blueprints to the application.""" + #: make a set to contain names of the blueprints + blueprints = set(self.config['BUILTIN_BLUEPRINTS']) + blueprints.update(self.config['BLUEPRINTS']) + + #: import and install all blueprints + for blueprint_name in blueprints: + package_name = blueprint_name.replace(":", ".").rsplit(".", 1)[0] + self.register_blueprint_by_name(blueprint_name, package_name) + + def register_blueprint_by_name(self, name, package_name): + """Register the blueprint by its name.""" + #: import the blueprint object and its package + blueprint = import_string(name) + package = import_string(package_name) + + #: load the all submodules + for module_name in getattr(package, "__all__", []): + import_string("%s.%s" % (package_name, module_name), silent=True) + + #: register the blueprint + self.register_blueprint(blueprint) + self.logger.info("Loaded %r" % name) + + def get_absolute_path(self, relative_path): + """Create a absolute path from a relative path. + + Example: + >>> app.root_path + '/home/tonyseek/projects/myapp/myapp' + >>> app.get_full_path("../somefile") + '/home/tonyseek/projects/myapp/somefile' + """ + path = os.path.join(self.root_path, relative_path) + return os.path.abspath(path) diff --git a/feedbundle/corelib/__init__.py b/feedbundle/corelib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/feedbundle/corelib/logging.py b/feedbundle/corelib/logging.py new file mode 100644 index 0000000..9f21c59 --- /dev/null +++ b/feedbundle/corelib/logging.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +#-*- coding:utf-8 -*- + +from __future__ import absolute_import + +import logging +import logging.handlers + + +def make_multiline_formatter(): + """Create a logging formatter to dump all information.""" + formatter = ["-" * 78, + "Name: %(name)s", + "Message type: %(levelname)s", + "Location: %(pathname)s:%(lineno)d", + "Module: %(module)s", + "Function: %(funcName)s", + "Time: %(asctime)s", + "Message:", + "%(message)s", + "-" * 78] + return logging.Formatter("\n".join(formatter)) + + +def make_file_handler(path, formatter_factory=make_multiline_formatter, + level=logging.DEBUG): + """Create a file handler to record logging information into a file.""" + handler = logging.handlers.WatchedFileHandler(path) + handler.setLevel(level) + handler.setFormatter(formatter_factory()) + return handler + + +def make_smtp_handler(fromaddr, toaddrs, host="127.0.0.1", + subject="Application Log", + formatter_factory=make_multiline_formatter, + level=logging.ERROR): + """Create a STMP handler to send logging information with email.""" + handler = logging.handlers.SMTPHandler(host, fromaddr, toaddrs, subject) + handler.setLevel(level) + handler.setFormatter(formatter_factory()) + return handler diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..979a390 --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +#-*- coding:utf-8 -*- + +import os + +from flask.ext.script import Manager + +from feedbundle.app import FeedBundle + + +#: be more convenient for developer, +#: automatic to set configuration environment variable. +if os.path.exists("feedbundle.dev.cfg"): + os.environ['FEEDBUNDLE_CONFIG'] = os.path.abspath("feedbundle.dev.cfg") + + +app = FeedBundle() +manager = Manager(app) + + +if __name__ == "__main__": + manager.run()