save point (see note)

Added 'db' command, tweaked pyramid scaffold a bit (added 'initialize' command
there),  removed migrate 'schema' repository in preparation for alembic.
This commit is contained in:
Lance Edgar 2012-03-25 13:48:13 -05:00
parent 69e92c13db
commit 727b9a5fa7
20 changed files with 270 additions and 144 deletions

View file

@ -37,6 +37,20 @@ import edbob
from edbob.util import requires_impl
class ArgumentParser(argparse.ArgumentParser):
"""
Customized version of ``argparse.ArgumentParser``, which overrides some of
the argument parsing logic. This is necessary for the application's
primary command (:class:`Command` class); but is not used with
:class:`Subcommand` derivatives.
"""
def parse_args(self, args=None, namespace=None):
args, argv = self.parse_known_args(args, namespace)
args.argv = argv
return args
class Command(edbob.Object):
"""
The primary command for the application.
@ -107,7 +121,7 @@ Try '%(name)s help <command>' for more help.""" % self
accordingly (or displays help text).
"""
parser = argparse.ArgumentParser(
parser = ArgumentParser(
prog=self.name,
description=self.description,
add_help=False,
@ -167,7 +181,7 @@ Try '%(name)s help <command>' for more help.""" % self
# And finally, do something of real value...
cmd = self.subcommands[cmd](parent=self)
cmd._run(*args.command)
cmd._run(*(args.command + args.argv))
class Subcommand(edbob.Object):
@ -231,14 +245,91 @@ class Subcommand(edbob.Object):
pass
class DatabaseCommand(Subcommand):
"""
Provides tools for managing an ``edbob`` database; called as ``edbob db``.
This command requires additional arguments; see ``edbob help db`` for more
information.
"""
name = 'db'
description = "Tools for managing edbob databases"
def add_parser_args(self, parser):
parser.add_argument('-D', '--database', metavar='URL',
help="Database engine (default is edbob.db.engine)")
parser.add_argument('command', choices=['upgrade', 'extensions', 'activate', 'deactivate'],
help="Command to execute against database")
def run(self, args):
if args.database:
from sqlalchemy import create_engine
from sqlalchemy.exc import ArgumentError
try:
engine = create_engine(args.database)
except ArgumentError, err:
print err
return
else:
from edbob.db import engine
if not engine:
print >> sys.stderr, "Database not configured; please change that or specify -D URL"
return
if args.command == 'upgrade':
print 'got upgrade ..'
# class ExtensionsCommand(RattailCommand):
# """
# Displays the currently-installed (available) extensions, and whether or not
# each has been activated within the configured database. Called as
# ``rattail extensions``.
# """
# short_description = "Displays available extensions and their statuses"
# def __call__(self, options, **args):
# from sqlalchemy.exc import OperationalError
# from rattail import engine
# from rattail.db.util import core_schema_installed
# from rattail.db.ext import get_available_extensions, extension_active
# try:
# engine.connect()
# except OperationalError, e:
# print >> sys.stderr, "Cannot connect to database:"
# print >> sys.stderr, engine.url
# print >> sys.stderr, e[0].strip()
# return
# if not core_schema_installed():
# print >> sys.stderr, "Database lacks core schema:"
# print >> sys.stderr, engine.url
# return
# extensions = get_available_extensions()
# print ''
# print ' %-25s %-10s' % ("Extension", "Active?")
# print '-' * 35
# for name in sorted(extensions):
# active = 'Y' if extension_active(name) else 'N'
# print ' %-28s %s' % (name, active)
# print ''
# print "Use 'rattail {activate|deactivate} EXTENSION' to change."
class ShellCommand(Subcommand):
"""
Launches a Python shell (of your choice) with ``edbob`` pre-loaded; called
as ``edbob shell``.
You can configure the shell within ``edbob.conf`` (otherwise ``python`` is
You can define the shell within your config file (otherwise ``python`` is
assumed)::
.. highlight:: ini
[edbob]
shell.python = ipython
"""

View file

@ -32,7 +32,7 @@ import logging
import sqlalchemy.exc
# from sqlalchemy.orm import clear_mappers
# import migrate.versioning.api
import migrate.versioning.api
# from migrate.versioning.schema import ControlledSchema
# import rattail
@ -48,7 +48,11 @@ import edbob.db
from edbob.db import exceptions
from edbob.db import Session
from edbob.db.classes import ActiveExtension
from edbob.db.util import get_database_version
from edbob.db.util import (
get_database_version,
get_repository_path,
get_repository_version,
)
from edbob.util import requires_impl
@ -144,35 +148,35 @@ class Extension(edbob.Object):
pass
# def activate_extension(extension, engine=None):
# """
# Activates the :class:`RattailExtension` instance represented by
# ``extension`` (which can be the actual instance, or the extension's name)
# by installing its schema and registering it within the database, and
# immediately applies it to the current ORM API.
def activate_extension(extension, engine=None):
"""
Activates the :class:`Extension` instance represented by ``extension``
(which can be the actual instance, or the extension's name) by installing
its schema and registering it within the database, and immediately applies
it to the current ORM/API.
# If ``engine`` is not provided, then ``rattail.engine`` is assumed.
# """
If ``engine`` is not provided, then :attr:`edbob.db.engine` is assumed.
"""
# if engine is None:
# engine = rattail.engine
if engine is None:
engine = edbob.db.engine
# if not isinstance(extension, RattailExtension):
# extension = get_extension(extension)
if not isinstance(extension, Extension):
extension = get_extension(extension)
# log.info("Activating extension: %s" % extension.name)
# install_extension_schema(extension, engine)
log.info("Activating extension: %s" % extension.name)
install_extension_schema(extension, engine)
# session = Session(bind=engine)
# if not session.query(ActiveExtension).get(extension.name):
# session.add(ActiveExtension(name=extension.name))
# session.commit()
# session.close()
session = Session(bind=engine)
if not session.query(ActiveExtension).get(extension.name):
session.add(ActiveExtension(name=extension.name))
session.commit()
session.close()
# merge_extension_metadata(extension)
# extension.extend_classes()
# extension.extend_mappers(rattail.metadata)
# _active_extensions[extension.name] = extension
merge_extension_metadata(extension)
extension.extend_classes()
extension.extend_mappers(rattail.metadata)
active_extensions[extension.name] = extension
# def deactivate_extension(extension, engine=None):
@ -217,7 +221,10 @@ def extend_framework():
found will be used to extend the ORM/API in-place.
"""
# Do we even have an engine?
engine = edbob.db.engine
if not engine:
return
# Check primary database connection.
try:
@ -304,32 +311,34 @@ def get_extension(name):
raise exceptions.ExtensionNotFound(name)
# def install_extension_schema(extension, engine=None):
# """
# Installs an extension's schema to the database and adds version control for
# it.
# """
def install_extension_schema(extension, engine=None):
"""
Installs an extension's schema to the database and adds version control for
it. ``extension`` must be a valid :class:`Extension` instance.
# if engine is None:
# engine = rattail.engine
If ``engine`` is not provided, :attr:`edbob.db.engine` is assumed.
"""
# # Extensionls aren't required to provide metadata...
# ext_meta = extension.get_metadata()
# if not ext_meta:
# return
if engine is None:
engine = edbob.db.engine
# # ...but if they do they must also provide a SQLAlchemy-Migrate repository!
# assert extension.schema, "Extension does not implement 'schema': %s" % extension.name
# Extensions aren't required to provide metadata...
ext_meta = extension.get_metadata()
if not ext_meta:
return
# ...but if they do they must also provide a SQLAlchemy-Migrate repository.
assert extension.schema, "Extension does not implement 'schema': %s" % extension.name
# meta = rattail.metadata
# for table in meta.sorted_tables:
# table.tometadata(ext_meta)
# for table in ext_meta.sorted_tables:
# if table.name not in meta.tables:
# table.create(bind=engine, checkfirst=True)
meta = edbob.db.metadata
for table in meta.sorted_tables:
table.tometadata(ext_meta)
for table in ext_meta.sorted_tables:
if table.name not in meta.tables:
table.create(bind=engine, checkfirst=True)
# migrate.versioning.api.version_control(
# str(engine.url), get_repository_path(extension), get_repository_version(extension))
migrate.versioning.api.version_control(
str(engine.url), get_repository_path(extension), get_repository_version(extension))
def merge_extension_metadata(ext):

View file

@ -1,4 +0,0 @@
This is a database migration repository.
More information at
http://code.google.com/p/sqlalchemy-migrate/

View file

@ -1,5 +0,0 @@
#!/usr/bin/env python
from migrate.versioning.shell import main
if __name__ == '__main__':
main(debug='False')

View file

@ -1,25 +0,0 @@
[db_settings]
# Used to identify which repository this database is versioned under.
# You can use the name of your project.
repository_id=edbob
# The name of the database table used to track the schema version.
# This name shouldn't already be used by your project.
# If this is changed once a database is under version control, you'll need to
# change the table name in each database too.
version_table=migrate_version
# When committing a change script, Migrate will attempt to generate the
# sql for all supported databases; normally, if one of them fails - probably
# because you don't have that database installed - it is ignored and the
# commit continues, perhaps ending successfully.
# Databases in this list MUST compile successfully during a commit, or the
# entire commit will fail. List the databases your application will actually
# be using to ensure your updates to that database work properly.
# This must be a list; example: ['postgres','sqlite']
required_dbs=[]
# When creating new change scripts, Migrate will stamp the new script with
# a version number. By default this is latest_version + 1. You can set this
# to 'true' to tell Migrate to use the UTC timestamp instead.
use_timestamp_numbering=False

View file

@ -12,7 +12,6 @@ from pyramid.config import Configurator
import edbob
from {{package}}._version import __version__
from {{package}}.db import DBSession
def main(global_config, **settings):
@ -37,6 +36,7 @@ def main(global_config, **settings):
# Configure Pyramid
config = Configurator(settings=settings)
config.include('edbob.pyramid')
config.include('{{package}}.subscribers')
config.scan()
# Configure Beaker
@ -49,7 +49,6 @@ def main(global_config, **settings):
edbob.init('{{package}}', os.path.abspath(settings['edbob.config']))
# Add static views
config.add_static_view('favicon.ico', 'static/favicon.ico')
# config.add_static_view('css', 'static/css', cache_max_age=3600)
# config.add_static_view('img', 'static/img', cache_max_age=3600)
# config.add_static_view('js', 'static/js', cache_max_age=3600)

View file

@ -23,34 +23,39 @@ class Command(commands.Command):
long_description = ''
class InitDatabaseCommand(commands.Subcommand):
class InitCommand(commands.Subcommand):
"""
Initializes the database. This is meant to be leveraged as part of setting
up the application. The database used by this command will be determined
by config, for example::
Initializes the database; called as ``{{package}} initialize``. This is
meant to be leveraged as part of setting up the application. The database
used by this command will be determined by config, for example::
.. highlight:: ini
.. highlight:: ini
[edbob.db]
sqlalchemy.url = postgresql://user:pass@localhost/{{package}}
"""
name = 'init-db'
name = 'initialize'
description = "Initialize the database"
def run(self, args):
from edbob.db import engine
from edbob.db.util import install_core_schema
from edbob.db.exceptions import CoreSchemaAlreadyInstalled
from edbob.db.extensions import activate_extension
# Install core schema to database.
try:
install_core_schema(engine)
except CoreSchemaAlreadyInstalled, err:
print err
print '%s:' % err
print ' %s' % engine.url
return
# Activate any extensions you like here...
# activate_extension('shrubbery')
# Okay, on to bootstrapping...
from edbob.db import Session
from edbob.db.classes import Role, User
@ -68,7 +73,8 @@ class InitDatabaseCommand(commands.Subcommand):
session.commit()
session.close()
print "Initialized database %s" % engine.url
print "Initialized database:"
print ' %s' % engine.url
def main(*args):

View file

@ -8,4 +8,6 @@ from sqlalchemy.orm import scoped_session, sessionmaker
from zope.sqlalchemy import ZopeTransactionExtension
DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
__all__ = ['Session']
Session = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))

View file

@ -0,0 +1,21 @@
#!/usr/bin/env python
"""
``{{package}}.subscribers`` -- Pyramid Event Subscribers
"""
import {{package}}
def before_render(event):
"""
Adds goodies to the global template renderer context.
"""
renderer_globals = event
renderer_globals['{{package}}'] = {{package}}
def includeme(config):
config.add_subscriber('{{package}}.subscribers:before_render',
'pyramid.events.BeforeRender')

View file

@ -1,3 +1,7 @@
<%inherit file="edbob/base.mako" />
<%def name="global_title()">{{project}}</%def>
<%def name="footer()">
{{project}} v${{{package}}.__version__} powered by
${h.link_to("edbob", 'http://edbob.org/', target='_blank')} v${edbob.__version__}
</%def>
${parent.body()}

View file

@ -1,11 +1,11 @@
<%inherit file="base.mako" />
<h1>Welcome to {{project}} !</h1>
<h1>Welcome to {{project}}</h1>
<p>You must choose, but choose wisely:</p>
<br />
<ul style="line-height: 200%;">
<li>links should...</li>
<li>...go here</li>
<li>some...</li>
<li>...links</li>
</ul>

View file

@ -1,3 +1,4 @@
0.1a1
-----

View file

@ -8,10 +8,15 @@ Welcome to the {{project}} project.
Getting Started
---------------
- cd <directory containing this file>
{{project}} should install and run without issue on Linux and Windows::
- $venv/bin/python setup.py develop
.. highlight:: sh
- $venv/bin/populate_{{package}} development.ini
- $venv/bin/pserve --reload development.ini
$ virtualenv {{package}}
$ cd <{{package}}-src-dir>
$ python setup.py develop
$ cd <virtual-env-dir>
$ {{package}} make-app <app-dir>
$ cd <app-dir>
$ {{package}} -c development.ini initialize
$ pserve --reload development.ini

View file

@ -76,6 +76,8 @@ setup(
'Framework :: Pylons',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
],
@ -92,7 +94,7 @@ setup(
{{package}} = {{package}}.commands:main
[{{package}}.commands]
init-db = {{package}}.commands:InitDatabaseCommand
initialize = {{package}}.commands:InitCommand
[paste.app_factory]
main = {{package}}:main

View file

@ -95,6 +95,12 @@ ul.sub-menu li {
margin-right: 15px;
}
#footer {
clear: both;
margin-top: 40px;
text-align: center;
}
#body {
padding-top: 15px;
}

View file

@ -1,6 +1,9 @@
<%def name="global_title()">edbob</%def>
<%def name="title()"></%def>
<%def name="head_tags()"></%def>
<%def name="footer()">
powered by ${h.link_to('edbob', 'http://edbob.org', target='_blank')} v${edbob.__version__}
</%def>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html style="direction: ltr;" xmlns="http://www.w3.org/1999/xhtml" lang="en-us">
<head>
@ -53,8 +56,8 @@
${self.body()}
</div><!-- body -->
<div id="footer" style="clear: both; margin-top: 40px; text-align: center;">
powered by ${h.link_to('edbob', 'http://edbob.org', target='_blank')} v${edbob.__version__}
<div id="footer">
${self.footer()}
</div>
</div><!-- main -->

View file

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

View file

@ -30,6 +30,7 @@ except ImportError:
use_setuptools()
import sys
import os.path
from setuptools import setup, find_packages
@ -39,6 +40,50 @@ execfile(os.path.join(here, 'edbob', '_version.py'))
readme = open(os.path.join(here, 'README.txt')).read()
requires = [
#
# Version numbers within comments below have specific meanings.
# Basically the 'low' value is a "soft low," and 'high' a "soft high."
# In other words:
#
# If either a 'low' or 'high' value exists, the primary point to be
# made about the value is that it represents the most current (stable)
# version available for the package (assuming typical public access
# methods) whenever this project was started and/or documented.
# Therefore:
#
# If a 'low' version is present, you should know that attempts to use
# versions of the package significantly older than the 'low' version
# may not yield happy results. (A "hard" high limit may or may not be
# indicated by a true version requirement.)
#
# Similarly, if a 'high' version is present, and especially if this
# project has laid dormant for a while, you may need to refactor a bit
# when attempting to support a more recent version of the package. (A
# "hard" low limit should be indicated by a true version requirement
# when a 'high' version is present.)
#
# In any case, developers and other users are encouraged to play
# outside the lines with regard to these soft limits. If bugs are
# encountered then they should be filed as such.
#
# package # low high
'progressbar', # 2.3
'pytz', # 2012b
]
if sys.version_info < (2, 7):
# Python < 2.7 has a standard library in need of supplementation.
requires += [
#
# package # low high
#
'argparse', # 1.2.1
]
setup(
name = "edbob",
version = __version__,
@ -65,54 +110,19 @@ setup(
'Topic :: Software Development :: Libraries :: Python Modules',
],
install_requires = [
#
# Version numbers within comments below have specific meanings.
# Basically the 'low' value is a "soft low," and 'high' a "soft high."
# In other words:
#
# If either a 'low' or 'high' value exists, the primary point to be
# made about the value is that it represents the most current (stable)
# version available for the package (assuming typical public access
# methods) whenever this project was started and/or documented.
# Therefore:
#
# If a 'low' version is present, you should know that attempts to use
# versions of the package significantly older than the 'low' version
# may not yield happy results. (A "hard" high limit may or may not be
# indicated by a true version requirement.)
#
# Similarly, if a 'high' version is present, and especially if this
# project has laid dormant for a while, you may need to refactor a bit
# when attempting to support a more recent version of the package. (A
# "hard" low limit should be indicated by a true version requirement
# when a 'high' version is present.)
#
# In any case, developers and other users are encouraged to play
# outside the lines with regard to these soft limits. If bugs are
# encountered then they should be filed as such.
#
# package # low high
'progressbar', # 2.3
'pytz', # 2012b
# If using Python < 2.7, you must install 'argparse' yourself...
# 'argparse', # 1.2.1
],
install_requires = requires,
extras_require = {
#
# Same guidelines apply to the extra dependency versions.
'db': [
#
# package # low high
#
'alembic', # 0.2.1
'decorator', # 3.3.2
'py-bcrypt', # 0.2
'SQLAlchemy', # 0.7.6
'sqlalchemy-migrate', # 0.7.2
# 'sqlalchemy-migrate', # 0.7.2
'Tempita', # 0.5.1
],
@ -150,6 +160,7 @@ edbobw = edbob.commands:main
edbob = edbob.pyramid.scaffolds:Template
[edbob.commands]
db = edbob.commands:DatabaseCommand
shell = edbob.commands:ShellCommand
uuid = edbob.commands:UuidCommand