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 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): class Command(edbob.Object):
""" """
The primary command for the application. The primary command for the application.
@ -107,7 +121,7 @@ Try '%(name)s help <command>' for more help.""" % self
accordingly (or displays help text). accordingly (or displays help text).
""" """
parser = argparse.ArgumentParser( parser = ArgumentParser(
prog=self.name, prog=self.name,
description=self.description, description=self.description,
add_help=False, add_help=False,
@ -167,7 +181,7 @@ Try '%(name)s help <command>' for more help.""" % self
# And finally, do something of real value... # And finally, do something of real value...
cmd = self.subcommands[cmd](parent=self) cmd = self.subcommands[cmd](parent=self)
cmd._run(*args.command) cmd._run(*(args.command + args.argv))
class Subcommand(edbob.Object): class Subcommand(edbob.Object):
@ -231,14 +245,91 @@ class Subcommand(edbob.Object):
pass 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): class ShellCommand(Subcommand):
""" """
Launches a Python shell (of your choice) with ``edbob`` pre-loaded; called Launches a Python shell (of your choice) with ``edbob`` pre-loaded; called
as ``edbob shell``. 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):: assumed)::
.. highlight:: ini
[edbob] [edbob]
shell.python = ipython shell.python = ipython
""" """

View file

@ -32,7 +32,7 @@ import logging
import sqlalchemy.exc import sqlalchemy.exc
# from sqlalchemy.orm import clear_mappers # from sqlalchemy.orm import clear_mappers
# import migrate.versioning.api import migrate.versioning.api
# from migrate.versioning.schema import ControlledSchema # from migrate.versioning.schema import ControlledSchema
# import rattail # import rattail
@ -48,7 +48,11 @@ import edbob.db
from edbob.db import exceptions from edbob.db import exceptions
from edbob.db import Session from edbob.db import Session
from edbob.db.classes import ActiveExtension 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 from edbob.util import requires_impl
@ -144,35 +148,35 @@ class Extension(edbob.Object):
pass pass
# def activate_extension(extension, engine=None): def activate_extension(extension, engine=None):
# """ """
# Activates the :class:`RattailExtension` instance represented by Activates the :class:`Extension` instance represented by ``extension``
# ``extension`` (which can be the actual instance, or the extension's name) (which can be the actual instance, or the extension's name) by installing
# by installing its schema and registering it within the database, and its schema and registering it within the database, and immediately applies
# immediately applies it to the current ORM API. 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: if engine is None:
# engine = rattail.engine engine = edbob.db.engine
# if not isinstance(extension, RattailExtension): if not isinstance(extension, Extension):
# extension = get_extension(extension) extension = get_extension(extension)
# log.info("Activating extension: %s" % extension.name) log.info("Activating extension: %s" % extension.name)
# install_extension_schema(extension, engine) install_extension_schema(extension, engine)
# session = Session(bind=engine) session = Session(bind=engine)
# if not session.query(ActiveExtension).get(extension.name): if not session.query(ActiveExtension).get(extension.name):
# session.add(ActiveExtension(name=extension.name)) session.add(ActiveExtension(name=extension.name))
# session.commit() session.commit()
# session.close() session.close()
# merge_extension_metadata(extension) merge_extension_metadata(extension)
# extension.extend_classes() extension.extend_classes()
# extension.extend_mappers(rattail.metadata) extension.extend_mappers(rattail.metadata)
# _active_extensions[extension.name] = extension active_extensions[extension.name] = extension
# def deactivate_extension(extension, engine=None): # def deactivate_extension(extension, engine=None):
@ -217,7 +221,10 @@ def extend_framework():
found will be used to extend the ORM/API in-place. found will be used to extend the ORM/API in-place.
""" """
# Do we even have an engine?
engine = edbob.db.engine engine = edbob.db.engine
if not engine:
return
# Check primary database connection. # Check primary database connection.
try: try:
@ -304,32 +311,34 @@ def get_extension(name):
raise exceptions.ExtensionNotFound(name) raise exceptions.ExtensionNotFound(name)
# def install_extension_schema(extension, engine=None): def install_extension_schema(extension, engine=None):
# """ """
# Installs an extension's schema to the database and adds version control for Installs an extension's schema to the database and adds version control for
# it. it. ``extension`` must be a valid :class:`Extension` instance.
# """
# if engine is None: If ``engine`` is not provided, :attr:`edbob.db.engine` is assumed.
# engine = rattail.engine """
# # Extensionls aren't required to provide metadata... if engine is None:
# ext_meta = extension.get_metadata() engine = edbob.db.engine
# if not ext_meta:
# return
# # ...but if they do they must also provide a SQLAlchemy-Migrate repository! # Extensions aren't required to provide metadata...
# assert extension.schema, "Extension does not implement 'schema': %s" % extension.name ext_meta = extension.get_metadata()
if not ext_meta:
return
# meta = rattail.metadata # ...but if they do they must also provide a SQLAlchemy-Migrate repository.
# for table in meta.sorted_tables: assert extension.schema, "Extension does not implement 'schema': %s" % extension.name
# 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( meta = edbob.db.metadata
# str(engine.url), get_repository_path(extension), get_repository_version(extension)) 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))
def merge_extension_metadata(ext): 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 import edbob
from {{package}}._version import __version__ from {{package}}._version import __version__
from {{package}}.db import DBSession
def main(global_config, **settings): def main(global_config, **settings):
@ -37,6 +36,7 @@ def main(global_config, **settings):
# Configure Pyramid # Configure Pyramid
config = Configurator(settings=settings) config = Configurator(settings=settings)
config.include('edbob.pyramid') config.include('edbob.pyramid')
config.include('{{package}}.subscribers')
config.scan() config.scan()
# Configure Beaker # Configure Beaker
@ -49,7 +49,6 @@ def main(global_config, **settings):
edbob.init('{{package}}', os.path.abspath(settings['edbob.config'])) edbob.init('{{package}}', os.path.abspath(settings['edbob.config']))
# Add static views # 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('css', 'static/css', cache_max_age=3600)
# config.add_static_view('img', 'static/img', 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) # config.add_static_view('js', 'static/js', cache_max_age=3600)

View file

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

View file

@ -8,4 +8,6 @@ from sqlalchemy.orm import scoped_session, sessionmaker
from zope.sqlalchemy import ZopeTransactionExtension 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" /> <%inherit file="edbob/base.mako" />
<%def name="global_title()">{{project}}</%def> <%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()} ${parent.body()}

View file

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

View file

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

View file

@ -8,10 +8,15 @@ Welcome to the {{project}} project.
Getting Started 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 $ virtualenv {{package}}
$ cd <{{package}}-src-dir>
- $venv/bin/pserve --reload development.ini $ 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', 'Framework :: Pylons',
'Operating System :: OS Independent', 'Operating System :: OS Independent',
'Programming Language :: Python', 'Programming Language :: Python',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP',
'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
], ],
@ -92,7 +94,7 @@ setup(
{{package}} = {{package}}.commands:main {{package}} = {{package}}.commands:main
[{{package}}.commands] [{{package}}.commands]
init-db = {{package}}.commands:InitDatabaseCommand initialize = {{package}}.commands:InitCommand
[paste.app_factory] [paste.app_factory]
main = {{package}}:main main = {{package}}:main

View file

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

View file

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

View file

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

View file

@ -30,6 +30,7 @@ except ImportError:
use_setuptools() use_setuptools()
import sys
import os.path import os.path
from setuptools import setup, find_packages from setuptools import setup, find_packages
@ -39,33 +40,7 @@ execfile(os.path.join(here, 'edbob', '_version.py'))
readme = open(os.path.join(here, 'README.txt')).read() readme = open(os.path.join(here, 'README.txt')).read()
setup( requires = [
name = "edbob",
version = __version__,
author = "Lance Edgar",
author_email = "lance@edbob.org",
url = "http://edbob.org/",
license = "GNU Affero GPL v3",
description = "Pythonic Software Framework",
long_description = readme,
classifiers = [
'Development Status :: 3 - Alpha',
'Environment :: Console',
'Environment :: Web Environment',
'Environment :: Win32 (MS Windows)',
'Environment :: X11 Applications',
'Intended Audience :: Developers',
'License :: OSI Approved :: GNU Affero General Public License v3',
'Natural Language :: English',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Topic :: Software Development :: Libraries :: Python Modules',
],
install_requires = [
# #
# Version numbers within comments below have specific meanings. # Version numbers within comments below have specific meanings.
# Basically the 'low' value is a "soft low," and 'high' a "soft high." # Basically the 'low' value is a "soft low," and 'high' a "soft high."
@ -96,23 +71,58 @@ setup(
'progressbar', # 2.3 'progressbar', # 2.3
'pytz', # 2012b 'pytz', # 2012b
]
# If using Python < 2.7, you must install 'argparse' yourself... if sys.version_info < (2, 7):
# 'argparse', # 1.2.1 # Python < 2.7 has a standard library in need of supplementation.
requires += [
#
# package # low high
#
'argparse', # 1.2.1
]
setup(
name = "edbob",
version = __version__,
author = "Lance Edgar",
author_email = "lance@edbob.org",
url = "http://edbob.org/",
license = "GNU Affero GPL v3",
description = "Pythonic Software Framework",
long_description = readme,
classifiers = [
'Development Status :: 3 - Alpha',
'Environment :: Console',
'Environment :: Web Environment',
'Environment :: Win32 (MS Windows)',
'Environment :: X11 Applications',
'Intended Audience :: Developers',
'License :: OSI Approved :: GNU Affero General Public License v3',
'Natural Language :: English',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Topic :: Software Development :: Libraries :: Python Modules',
], ],
install_requires = requires,
extras_require = { extras_require = {
#
# Same guidelines apply to the extra dependency versions.
'db': [ 'db': [
# #
# package # low high # package # low high
# #
'alembic', # 0.2.1
'decorator', # 3.3.2 'decorator', # 3.3.2
'py-bcrypt', # 0.2 'py-bcrypt', # 0.2
'SQLAlchemy', # 0.7.6 'SQLAlchemy', # 0.7.6
'sqlalchemy-migrate', # 0.7.2 # 'sqlalchemy-migrate', # 0.7.2
'Tempita', # 0.5.1 'Tempita', # 0.5.1
], ],
@ -150,6 +160,7 @@ edbobw = edbob.commands:main
edbob = edbob.pyramid.scaffolds:Template edbob = edbob.pyramid.scaffolds:Template
[edbob.commands] [edbob.commands]
db = edbob.commands:DatabaseCommand
shell = edbob.commands:ShellCommand shell = edbob.commands:ShellCommand
uuid = edbob.commands:UuidCommand uuid = edbob.commands:UuidCommand