This commit is contained in:
Lance Edgar 2012-07-09 03:46:23 -05:00
commit 3b208904f8
15 changed files with 471 additions and 370 deletions

View file

@ -1,5 +1,34 @@
0.1a6
-----
- Fixed MANIFEST.in file.
0.1a5
-----
- Added :mod:`edbob.csv` module.
- Tweaked logging configuration and initialization semantics.
0.1a4
-----
- Fixed call to sleep() in filemon service.
0.1a3
-----
- Various tweaks to Pyramid code.
0.1a2
-----
- Add ``win32.send_data_to_printer()`` function.
- Various tweaks to Pyramid code.
0.1a1
-----
- Initial version
- Initial version

View file

@ -1,2 +1,18 @@
include *.txt
include COPYING.txt README.txt CHANGES.txt
include ez_setup.py
include edbob/pyramid/static/favicon.ico
include edbob/pyramid/static/robots.txt
include edbob/pyramid/static/css/*.css
include edbob/pyramid/static/js/*.js
include edbob/scaffolds/edbob/CHANGES.txt
include edbob/scaffolds/edbob/+package+/pyramid/static/favicon.ico
include edbob/scaffolds/edbob/+package+/pyramid/static/robots.txt
recursive-include edbob/pyramid/static/img *.jpg *.png
recursive-include edbob/pyramid/templates *.mako
recursive-include edbob/scaffolds/edbob *.py
recursive-include edbob/scaffolds/edbob *_tmpl
recursive-include edbob/scaffolds/edbob/+package+/pyramid/templates *.mako

View file

@ -1 +1 @@
__version__ = '0.1a3'
__version__ = '0.1a7'

View file

@ -70,15 +70,17 @@ class AppConfigParser(ConfigParser.SafeConfigParser):
file, and passes that to ``logging.config.fileConfig()``.
"""
if self.getboolean(self.appname, 'basic_logging', default=False):
edbob.basic_logging(self.appname)
path = edbob.temp_path(suffix='.conf')
self.save(path)
try:
logging.config.fileConfig(path)
except ConfigParser.NoSectionError:
pass
os.remove(path)
if self.getboolean('edbob', 'basic_logging', default=False):
edbob.basic_logging()
if self.getboolean('edbob', 'configure_logging', default=False):
path = edbob.temp_path(suffix='.conf')
self.save(path)
try:
logging.config.fileConfig(path, disable_existing_loggers=False)
except ConfigParser.NoSectionError:
pass
os.remove(path)
log.debug("Configured logging")
def get(self, section, option, raw=False, vars=None, default=None):
"""
@ -243,7 +245,6 @@ class AppConfigParser(ConfigParser.SafeConfigParser):
config.has_option('edbob', 'include_config')):
include = config.get('edbob', 'include_config')
if include:
log.debug("Including config: %s" % include)
for p in eval(include):
self.read_path(os.path.abspath(p))
ConfigParser.SafeConfigParser.read(self, path)

View file

@ -71,7 +71,9 @@ def basic_logging():
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter(
'%(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s'))
logging.getLogger().addHandler(handler)
root = logging.getLogger()
root.addHandler(handler)
root.setLevel(logging.INFO)
def get_uuid():

71
edbob/csv.py Normal file
View file

@ -0,0 +1,71 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
################################################################################
#
# edbob -- Pythonic Software Framework
# Copyright © 2010-2012 Lance Edgar
#
# This file is part of edbob.
#
# edbob is free software: you can redistribute it and/or modify it under the
# terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
# more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
``edbob.csv`` -- CSV File Utilities
"""
from __future__ import absolute_import
import codecs
import csv
class UTF8Recoder(object):
"""
Iterator that reads an encoded stream and reencodes the input to UTF-8.
.. note::
This class was stolen from the Python 2.7 documentation.
"""
def __init__(self, fileobj, encoding):
self.reader = codecs.getreader(encoding)(fileobj)
def __iter__(self):
return self
def next(self):
return self.reader.next().encode('utf_8')
class UnicodeReader(object):
"""
A CSV reader which will iterate over lines in a CSV file, which is encoded
in the given encoding.
.. note::
This class was stolen from the Python 2.7 documentation.
"""
def __init__(self, fileobj, dialect=csv.excel, encoding='utf_8', **kwargs):
fileobj = UTF8Recoder(fileobj, encoding)
self.reader = csv.reader(fileobj, dialect=dialect, **kwargs)
def __iter__(self):
return self
def next(self):
row = self.reader.next()
return [unicode(x, 'utf_8') for x in row]

View file

@ -1,184 +1,184 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
################################################################################
#
# edbob -- Pythonic Software Framework
# Copyright © 2010-2012 Lance Edgar
#
# This file is part of edbob.
#
# edbob is free software: you can redistribute it and/or modify it under the
# terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
# more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
``edbob.db`` -- Database Framework
"""
from __future__ import absolute_import
from sqlalchemy import engine_from_config, MetaData
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
import edbob
# __all__ = ['engines', 'engine', 'Session', 'metadata',
# 'get_setting', 'save_setting']
__all__ = ['engines', 'engine', 'Session', 'get_setting', 'save_setting']
inited = False
engines = None
engine = None
Session = sessionmaker()
Base = declarative_base()
# metadata = None
def init(config):
"""
Initializes the database connection(s); called by :func:`edbob.init()` if
config includes something like::
.. highlight:: ini
[edbob]
init = ['edbob.db']
[edbob.db]
sqlalchemy.urls = {
'default': 'postgresql://user:pass@localhost/edbob,
}
This function reads connection info from ``config`` and builds a dictionary
or :class:`sqlalchemy.Engine` instances accordingly. It also extends the
root ``edbob`` namespace with the ORM classes (:class:`edbob.Person`,
:class:`edbob.User`, etc.), as well as a few other things
(e.g. :attr:`edbob.engine`, :attr:`edbob.Session`, :attr:`edbob.metadata`).
"""
import edbob.db
# from edbob.db import classes
from edbob.db import model
from edbob.db import enum
# from edbob.db.model import get_metadata
# from edbob.db.mappers import make_mappers
from edbob.db.extensions import extend_framework
# global inited, engines, engine, metadata
global inited, engines, engine
keys = config.get('edbob.db', 'sqlalchemy.keys')
if keys:
keys = keys.split()
else:
keys = ['default']
engines = {}
cfg = config.get_dict('edbob.db')
for key in keys:
try:
engines[key] = engine_from_config(cfg, 'sqlalchemy.%s.' % key)
except KeyError:
if key == 'default':
try:
engines[key] = engine_from_config(cfg)
except KeyError:
pass
engine = engines.get('default')
if engine:
Base.metadata.bind = engine
# metadata = get_metadata(bind=engine)
# make_mappers(metadata)
extend_framework()
edbob.graft(edbob, edbob.db)
# edbob.graft(edbob, classes)
edbob.graft(edbob, model)
edbob.graft(edbob, enum)
inited = True
def get_setting(name, session=None):
"""
Returns a setting from the database.
"""
_session = session
if not session:
session = Session()
setting = session.query(edbob.Setting).get(name)
if setting:
setting = setting.value
if not _session:
session.close()
return setting
def save_setting(name, value, session=None):
"""
Saves a setting to the database.
"""
_session = session
if not session:
session = Session()
setting = session.query(edbob.Setting).get(name)
if not setting:
setting = edbob.Setting(name=name)
session.add(setting)
setting.value = value
if not _session:
session.commit()
session.close()
def get_core_metadata():
"""
Returns a :class:`sqlalchemy.MetaData` instance containing only those
:class:`sqlalchemy.Table`s which are part of the core ``edbob`` schema.
"""
from edbob.db import model
meta = MetaData()
for name in model.__all__:
if name != 'Base':
obj = getattr(model, name)
if isinstance(obj, type) and issubclass(obj, model.Base):
obj.__table__.tometadata(meta)
return meta
def needs_session(func):
"""
Decorator which adds helpful session handling.
"""
def wrapped(*args, **kwargs):
session = kwargs.pop('session', None)
_orig_session = session
if not session:
session = Session()
res = func(session, *args, **kwargs)
if not _orig_session:
session.commit()
session.close()
return res
return wrapped
#!/usr/bin/env python
# -*- coding: utf-8 -*-
################################################################################
#
# edbob -- Pythonic Software Framework
# Copyright © 2010-2012 Lance Edgar
#
# This file is part of edbob.
#
# edbob is free software: you can redistribute it and/or modify it under the
# terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
# more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
``edbob.db`` -- Database Framework
"""
from __future__ import absolute_import
from sqlalchemy import engine_from_config, MetaData
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
import edbob
# __all__ = ['engines', 'engine', 'Session', 'metadata',
# 'get_setting', 'save_setting']
__all__ = ['engines', 'engine', 'Session', 'get_setting', 'save_setting']
inited = False
engines = None
engine = None
Session = sessionmaker()
Base = declarative_base()
# metadata = None
def init(config):
"""
Initializes the database connection(s); called by :func:`edbob.init()` if
config includes something like::
.. highlight:: ini
[edbob]
init = ['edbob.db']
[edbob.db]
sqlalchemy.urls = {
'default': 'postgresql://user:pass@localhost/edbob,
}
This function reads connection info from ``config`` and builds a dictionary
or :class:`sqlalchemy.Engine` instances accordingly. It also extends the
root ``edbob`` namespace with the ORM classes (:class:`edbob.Person`,
:class:`edbob.User`, etc.), as well as a few other things
(e.g. :attr:`edbob.engine`, :attr:`edbob.Session`, :attr:`edbob.metadata`).
"""
import edbob.db
# from edbob.db import classes
from edbob.db import model
from edbob.db import enum
# from edbob.db.model import get_metadata
# from edbob.db.mappers import make_mappers
from edbob.db.extensions import extend_framework
# global inited, engines, engine, metadata
global inited, engines, engine
keys = config.get('edbob.db', 'sqlalchemy.keys')
if keys:
keys = keys.split()
else:
keys = ['default']
engines = {}
cfg = config.get_dict('edbob.db')
for key in keys:
try:
engines[key] = engine_from_config(cfg, 'sqlalchemy.%s.' % key)
except KeyError:
if key == 'default':
try:
engines[key] = engine_from_config(cfg)
except KeyError:
pass
engine = engines.get('default')
if engine:
Base.metadata.bind = engine
# metadata = get_metadata(bind=engine)
# make_mappers(metadata)
extend_framework()
edbob.graft(edbob, edbob.db)
# edbob.graft(edbob, classes)
edbob.graft(edbob, model)
edbob.graft(edbob, enum)
inited = True
def get_setting(name, session=None):
"""
Returns a setting from the database.
"""
_session = session
if not session:
session = Session()
setting = session.query(edbob.Setting).get(name)
if setting:
setting = setting.value
if not _session:
session.close()
return setting
def save_setting(name, value, session=None):
"""
Saves a setting to the database.
"""
_session = session
if not session:
session = Session()
setting = session.query(edbob.Setting).get(name)
if not setting:
setting = edbob.Setting(name=name)
session.add(setting)
setting.value = value
if not _session:
session.commit()
session.close()
def get_core_metadata():
"""
Returns a :class:`sqlalchemy.MetaData` instance containing only those
:class:`sqlalchemy.Table`s which are part of the core ``edbob`` schema.
"""
from edbob.db import model
meta = MetaData()
for name in model.__all__:
if name != 'Base':
obj = getattr(model, name)
if isinstance(obj, type) and issubclass(obj, model.Base):
obj.__table__.tometadata(meta)
return meta
def needs_session(func):
"""
Decorator which adds helpful session handling.
"""
def wrapped(*args, **kwargs):
session = kwargs.pop('session', None)
_orig_session = session
if not session:
session = Session()
res = func(session, *args, **kwargs)
if not _orig_session:
session.commit()
session.close()
return res
return wrapped

View file

@ -1,46 +1,46 @@
# A generic, single database configuration.
[alembic]
# path to migration scripts
script_location = schema
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
sqlalchemy.url = driver://user:pass@localhost/dbname
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
# A generic, single database configuration.
[alembic]
# path to migration scripts
script_location = schema
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
sqlalchemy.url = driver://user:pass@localhost/dbname
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

View file

@ -109,7 +109,7 @@ class FileMonitorService(win32serviceutil.ServiceFramework):
while not file_is_free(path):
# TODO: Add configurable timeout so long-open files can't hijack
# our prcessing.
time.sleep(0.25)
win32api.SleepEx(250, True)
for action in self.monitored[key].actions:
if isinstance(action, tuple):
func = action[0]

View file

@ -1,98 +1,98 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
################################################################################
#
# edbob -- Pythonic Software Framework
# Copyright © 2010-2012 Lance Edgar
#
# This file is part of edbob.
#
# edbob is free software: you can redistribute it and/or modify it under the
# terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
# more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
``edbob.initialization`` -- Initialization Framework
"""
import os
import logging
import edbob
from edbob.configuration import (
AppConfigParser,
default_system_paths,
default_user_paths,
)
__all__ = ['init']
log = logging.getLogger(__name__)
def init(appname='edbob', *args, **kwargs):
"""
Initializes the edbob framework, typically by first reading some config
file(s) to determine which interfaces to engage. This must normally be
called prior to doing anything really useful, as it is responsible for
extending the live API in-place.
The meaning of ``args`` is as follows:
If ``args`` is empty, the ``EDBOB_CONFIG`` environment variable is first
consulted. If it is nonempty, then its value is split according to
``os.pathsep`` and the resulting sequence is passed to
``edbob.config.read()``.
If both ``args`` and ``EDBOB_CONFIG`` are empty, the "standard" locations
are assumed, and the results of calling both
:func:`edbob.configuration.default_system_paths()` and
:func:`edbob.configuration.default_user_paths()` are passed on to
``edbob.config.read()``.
Any other values in ``args`` will be passed directly to
``edbob.config.read()`` and so will be interpreted there. Basically they
are assumed to be either strings, or sequences of strings, which represent
paths to various config files, each being read in the order in which it
appears within ``args``. (Configuration is thereby cascaded such that the
file read last will override those before it.)
"""
config = AppConfigParser(appname)
if args:
config_paths = list(args)
elif os.environ.get('EDBOB_CONFIG'):
config_paths = os.environ['EDBOB_CONFIG'].split(os.pathsep)
else:
config_paths = default_system_paths(appname) + default_user_paths(appname)
shell = bool(kwargs.get('shell'))
for paths in config_paths:
config.read(paths, recurse=not shell)
config.configure_logging()
default_modules = 'edbob.time'
modules = config.get('edbob', 'init', default=default_modules)
if modules:
for name in modules.split(','):
name = name.strip()
module = __import__(name, globals(), locals(), fromlist=['init'])
getattr(module, 'init')(config)
# config.inited.append(name)
# config.inited.append('edbob')
edbob.graft(edbob, locals(), 'config')
edbob.inited = True
#!/usr/bin/env python
# -*- coding: utf-8 -*-
################################################################################
#
# edbob -- Pythonic Software Framework
# Copyright © 2010-2012 Lance Edgar
#
# This file is part of edbob.
#
# edbob is free software: you can redistribute it and/or modify it under the
# terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
# more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
``edbob.initialization`` -- Initialization Framework
"""
import os
import logging
import edbob
from edbob.configuration import (
AppConfigParser,
default_system_paths,
default_user_paths,
)
__all__ = ['init']
log = logging.getLogger(__name__)
def init(appname='edbob', *args, **kwargs):
"""
Initializes the edbob framework, typically by first reading some config
file(s) to determine which interfaces to engage. This must normally be
called prior to doing anything really useful, as it is responsible for
extending the live API in-place.
The meaning of ``args`` is as follows:
If ``args`` is empty, the ``EDBOB_CONFIG`` environment variable is first
consulted. If it is nonempty, then its value is split according to
``os.pathsep`` and the resulting sequence is passed to
``edbob.config.read()``.
If both ``args`` and ``EDBOB_CONFIG`` are empty, the "standard" locations
are assumed, and the results of calling both
:func:`edbob.configuration.default_system_paths()` and
:func:`edbob.configuration.default_user_paths()` are passed on to
``edbob.config.read()``.
Any other values in ``args`` will be passed directly to
``edbob.config.read()`` and so will be interpreted there. Basically they
are assumed to be either strings, or sequences of strings, which represent
paths to various config files, each being read in the order in which it
appears within ``args``. (Configuration is thereby cascaded such that the
file read last will override those before it.)
"""
config = AppConfigParser(appname)
if args:
config_paths = list(args)
elif os.environ.get('EDBOB_CONFIG'):
config_paths = os.environ['EDBOB_CONFIG'].split(os.pathsep)
else:
config_paths = default_system_paths(appname) + default_user_paths(appname)
shell = bool(kwargs.get('shell'))
for paths in config_paths:
config.read(paths, recurse=not shell)
config.configure_logging()
default_modules = 'edbob.time'
modules = config.get('edbob', 'init', default=default_modules)
if modules:
for name in modules.split(','):
name = name.strip()
module = __import__(name, globals(), locals(), fromlist=['init'])
getattr(module, 'init')(config)
# config.inited.append(name)
# config.inited.append('edbob')
edbob.graft(edbob, locals(), 'config')
edbob.inited = True

View file

@ -51,7 +51,6 @@ def main(global_config, **settings):
# Configure edbob. Note that this is done last, primarily to allow logging
# to leverage edbob's config inheritance.
edbob.basic_logging()
edbob.init('{{package}}', os.path.abspath(settings['edbob.config']))
return config.make_wsgi_app()

View file

@ -27,10 +27,7 @@ whatever = you like
use = egg:{{package}}
pyramid.reload_templates = true
pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.debug_templates = true
pyramid.debug_all = true
pyramid.default_locale_name = en
pyramid.includes =
pyramid_debugtoolbar
@ -51,9 +48,6 @@ port = 6543
[edbob]
include_config = ['%(here)s/production.ini']
[edbob.db]
sqlalchemy.url = sqlite:///{{package}}.sqlite
####################
# logging
@ -62,8 +56,5 @@ sqlalchemy.url = sqlite:///{{package}}.sqlite
[logger_root]
level = INFO
[logger_edbob]
level = INFO
[logger_{{package_logger}}]
level = DEBUG

View file

@ -21,10 +21,7 @@ whatever = you like
use = egg:{{package}}
pyramid.reload_templates = false
pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.debug_templates = false
pyramid.debug_all = false
pyramid.default_locale_name = en
# Hack so edbob can find this file from within WSGI app.
@ -56,6 +53,8 @@ sqlalchemy.url = postgresql://user:pass@localhost/{{package}}
[edbob]
init = edbob.time, edbob.db
basic_logging = True
configure_logging = True
# shell.python = ipython
[edbob.db]
@ -82,7 +81,7 @@ timezone = US/Central
####################
[loggers]
keys = root, edbob, {{package_logger}}
keys = root, {{package_logger}}
[handlers]
keys = file, console, email
@ -95,15 +94,10 @@ keys = generic, console
handlers = file, console
level = WARNING
[logger_edbob]
qualname = edbob
handlers =
# level = INFO
[logger_{{package_logger}}]
qualname = {{package}}
handlers =
level = WARNING
# level = NOTSET
[handler_file]
class = FileHandler
@ -114,12 +108,10 @@ formatter = generic
class = StreamHandler
args = (sys.stderr,)
formatter = console
# level = NOTSET
[handler_email]
class = handlers.SMTPHandler
args = ('mail.example.com', '{{package}}@example.com', ['support@example.com'], '[{{project}} error]',
('user', 'pass'))
args = ('mail.example.com', '{{package}}@example.com', ['support@example.com'], '[{{project}} error]', ('user', 'pass'))
level = ERROR
formatter = generic

View file

@ -47,7 +47,7 @@ def init(config):
tz = config.get('edbob.time', 'timezone')
if tz:
set_timezone(tz)
log.debug("Timezone set to '%s'" % tz)
log.info("Timezone set to '%s'" % tz)
else:
log.warning("No timezone configured; falling back to US/Central")
set_timezone('US/Central')

View file

@ -121,7 +121,7 @@ setup(
#
# package # low high
'alembic', # 0.2.1
'alembic', # 0.3.4
'py-bcrypt', # 0.2
'SQLAlchemy', # 0.7.6
'Tempita', # 0.5.1