save point (see note)

Changed license to AGPLv3, "finished" console command framework (for now?), and
added initial db/lib/pyramid/wx subpackages - though those are not yet well tested.
This commit is contained in:
Lance Edgar 2012-03-20 09:11:01 -05:00
parent 3d75732d36
commit a6decbb313
36 changed files with 2910 additions and 172 deletions

View file

@ -1,5 +1,5 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
@ -7,17 +7,15 @@
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
@ -72,7 +60,7 @@ modification follow.
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
@ -635,40 +633,29 @@ the "copyright" line and a pointer to where the full notice is found.
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
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.
This program 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 General Public License for more details.
GNU Affero General Public License for more details.
You should have received a copy of the GNU General Public License
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
For more information on this, and how to apply and follow the GNU AGPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

View file

@ -2,13 +2,15 @@
edbob
=====
edbob is a Pythonic software framework, released under the GPL.
edbob is a Pythonic software framework, released under the GNU Affero General
Public License.
It aims to be "environment-neutral" in that it can assist with development for
console, web, or GUI applications. Pay only for what you eat; however all of
its functionality combined may be considered a "full stack" of sorts.
For more information, please see http://edbob.org/.
For more information, please see `edbob.org <http://edbob.org/>`_ or send email
to `Lance Edgar <mailto:lance@edbob.org>`_.
Installation
@ -16,10 +18,6 @@ Installation
Install the software with::
$ easy_install edbob
or::
$ pip install edbob

View file

@ -2,23 +2,23 @@
# -*- coding: utf-8 -*-
################################################################################
#
# edbob -- Pythonic software framework
# Copyright © 2010,2011,2012 Lance Edgar
# 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 General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
# 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 General Public License for more
# details.
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# edbob. If not, see <http://www.gnu.org/licenses/>.
# You should have received a copy of the GNU Affero General Public License
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
@ -29,6 +29,11 @@
from edbob._version import __version__
from edbob.core import *
from edbob.times import *
from edbob.files import *
from edbob.modules import *
from edbob.times import *
from edbob.configuration import *
from edbob.initialization import *
inited = False

299
edbob/commands.py Normal file
View file

@ -0,0 +1,299 @@
#!/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.commands`` -- Console Commands
"""
import sys
import argparse
import subprocess
import logging
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.
You should subclass this within your own application if you wish to
leverage the command system provided by edbob.
"""
name = 'edbob'
version = edbob.__version__
description = "Pythonic Software Framework"
long_description = """
edbob is a Pythonic software framework.
Copyright (c) 2010-2012 Lance Edgar <lance@edbob.org>
This program comes with ABSOLUTELY NO WARRANTY. This is free software,
and you are welcome to redistribute it under certain conditions.
See the file COPYING.txt for more information.
"""
def __init__(self, **kwargs):
edbob.Object.__init__(self, **kwargs)
self.subcommands = edbob.entry_point_map('%s.commands' % self.name)
def __str__(self):
return str(self.name)
def iter_subcommands(self):
"""
Generator which yields associated :class:`Subcommand` classes, sorted
by :attr:`Subcommand.name`.
"""
for name in sorted(self.subcommands):
yield self.subcommands[name]
def print_help(self):
"""
Prints help text for the primary command, including a list of available
subcommands.
"""
print """%(description)s
Usage: %(name)s [options] <subcommand> [command-options]
Options:
-v, --verbose Increase logging level to INFO
-V, --version Display program version and exit
Subcommands:""" % self
for cmd in self.iter_subcommands():
print " %-12s %s" % (cmd.name, cmd.description)
def run(self, *args):
"""
Parses ``args`` and executes the appropriate subcommand action
accordingly (or displays help text).
"""
parser = ArgumentParser(
prog=self.name,
description=self.description,
add_help=False,
)
parser.add_argument('-v', '--verbose', action='store_true', dest='verbose')
parser.add_argument('-V', '--version', action='version',
version="%%(prog)s %s" % self.version)
parser.add_argument('subcommand', nargs='*')
args = parser.parse_args(list(args))
if not args or not args.subcommand:
self.print_help()
return
cmd = args.subcommand.pop(0)
if cmd == 'help':
if len(args.subcommand) != 1:
self.print_help()
return
cmd = args.subcommand[0]
if cmd not in self.subcommands:
self.print_help()
return
cmd = self.subcommands[cmd](parent=self)
cmd.parser.print_help()
return
elif cmd not in self.subcommands:
self.print_help()
return
if args.verbose:
logging.getLogger().setLevel(logging.INFO)
cmd = self.subcommands[cmd](parent=self)
cmd._run(*args.argv)
class Subcommand(edbob.Object):
"""
Base class for application "subcommands." You'll want to derive from this
class as needed to implement most of your application's command logic.
"""
def __init__(self, **kwargs):
edbob.Object.__init__(self, **kwargs)
self.parser = argparse.ArgumentParser(
prog='%s %s' % (self.parent, self.name),
description=self.description,
)
self.add_parser_args(self.parser)
def __repr__(self):
return "<Subcommand: %s>" % self.name
def add_parser_args(self, parser):
"""
If your subcommand accepts optional (or positional) arguments, you
should override this and add the possible arguments directly to parser
(which is a :class:`argparse.ArgumentParser` instance) via its
:meth:`add_argument()` method.
"""
pass
@property
@requires_impl(is_property=True)
def description(self):
"""
The description for the subcommand. This must be provided within your
subclass, as a simple class attribute.
"""
pass
@property
@requires_impl(is_property=True)
def name(self):
"""
The name of the subcommand. This must be provided within your
subclass, as a simple class attribute.
.. note::
The subcommand name should ideally be limited to 12 characters in
order to preserve formatting consistency when displaying help text.
"""
pass
def _run(self, *args):
args = self.parser.parse_args(list(args))
return self.run(args)
@requires_impl()
def run(self, args):
"""
Runs the subcommand. You must override this method within your
subclass. ``args`` will be a :class:`argparse.Namespace` object
containing all parsed arguments found on the original command line
executed by the user.
"""
pass
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
assumed)::
[edbob]
shell.python = ipython
"""
name = 'shell'
description = "Launch Python shell with edbob pre-loaded"
def run(self, args):
code = ['import edbob']
if edbob.inited:
code.append("edbob.init('edbob', %s, shell=True)" %
edbob.config.paths_attempted)
code.append('from edbob import *')
code = '; '.join(code)
print "edbob v%s launching Python shell...\n" % edbob.__version__
python = 'python'
if edbob.inited:
python = edbob.config.get('edbob', 'shell.python', default=python)
proc = subprocess.Popen([python, '-i', '-c', code])
while True:
try:
proc.wait()
except KeyboardInterrupt:
pass
else:
break
class UuidCommand(Subcommand):
"""
Command for generating an UUID; called as ``edbob uuid``.
If the ``--gui`` option is specified, this command launches a small
wxPython application for generating UUIDs; otherwise a single UUID will be
printed to the console.
"""
name = 'uuid'
description = "Generate an universally-unique identifier"
def add_parser_args(self, parser):
parser.add_argument('--gui', action='store_true',
help="Display graphical interface")
def run(self, args):
if args.gui:
from edbob.wx.GenerateUuid import main
main()
else:
print edbob.get_uuid()
def main(*args):
"""
The primary entry point for the edbob command system.
.. note::
This entry point is really for ``edbob`` proper. Your application
should provide its own command entry point which leverages *your*
:class:`Command` subclass instead of using this entry point (which uses
:class:`Command` directly).
There's not much involved in doing so; see the source code for
implementation details.
"""
if args:
args = list(args)
else:
args = sys.argv[1:]
edbob.init()
cmd = Command()
cmd.run(*args)

355
edbob/configuration.py Normal file
View file

@ -0,0 +1,355 @@
#!/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.configuration`` -- Configuration
"""
import os
import os.path
import sys
import ConfigParser
import logging
import logging.config
import edbob
from edbob import exceptions
__all__ = ['AppConfigParser']
log = logging.getLogger(__name__)
class AppConfigParser(ConfigParser.SafeConfigParser):
"""
Subclass of ``ConfigParser.SafeConfigParser``, with some conveniences
added.
"""
def __init__(self, appname, *args, **kwargs):
ConfigParser.SafeConfigParser.__init__(self, *args, **kwargs)
self.appname = appname
self.paths_attempted = []
self.paths_loaded = []
def clear(self):
"""
Completely clears the contents of the config instance.
"""
for section in self.sections():
self.remove_section(section)
del self.paths_attempted[:]
del self.paths_loaded[:]
def configure_logging(self):
"""
Saves the current (possibly cascaded) configuration to a temporary
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)
def get(self, section, option, raw=False, vars=None, default=None):
"""
Overridden version of ``ConfigParser.SafeConfigParser.get()``; this one
adds the ``default`` keyword parameter and will return it instead of
raising an error when the option doesn't exist.
"""
if self.has_option(section, option):
return ConfigParser.SafeConfigParser.get(self, section, option, raw, vars)
return default
def getboolean(self, section, option, default=None):
"""
Overriddes base class method to allow for a default.
"""
try:
val = ConfigParser.SafeConfigParser.getboolean(self, section, option)
except AttributeError:
return default
return val
def get_dict(self, section):
"""
Convenience method which returns a dictionary of options contained
within the given section.
"""
d = {}
for opt in self.options(section):
d[opt] = self.get(section, opt)
return d
def options(self, section):
"""
Overridden version of ``ConfigParser.SafeConfigParser.options()``.
This one doesn't raise an error if ``section`` doesn't exist, but
instead returns an empty list.
"""
if not self.has_section(section):
return []
return ConfigParser.SafeConfigParser.options(self, section)
def read(self, paths, recurse=True):
r"""
.. highlight:: ini
Overrides the ``RawConfigParser`` method by implementing the following
logic:
Prior to actually reading the contents of the file(s) specified by
``paths`` into the current config instance, a recursive algorithm will
inspect the config found in the file(s) to see if additional config
file(s) are to be included. All config files, whether specified
directly by ``paths`` or indirectly by way of primary configuration,
are finally read into the current config instance in the proper order
so that cascading works as expected.
If you pass ``recurse=False`` to this method then none of the magical
inclusion logic will happen at all.
Note that when a config file indicates that another file(s) is to be
included, the referenced file will be read into this config instance
*before* the original (primary) file is read into it. A convenient
setup then could be to maintain a "site-wide" config file, shared on
the network, including something like this::
# //file-server/share/edbob/site.conf
#
# This file contains settings relevant to all machines on the
# network. Mail and logging configuration at least would be good
# candidates for inclusion here.
[edbob.mail]
smtp.server = mail.example.com
# smtp.username = user
# smtp.password = pass
sender.default = noreply@example.com
recipients.default = ['tech-support@example.com']
[loggers]
keys = root, edbob
# ...etc. The bulk of logging configuration would go here.
Then a config file local to a particular machine could look something
like this::
# C:\ProgramData\edbob\edbob.conf
#
# This file contains settings specific to the local machine.
[edbob]
include_config = [r'\\file-server\share\edbob\site.conf']
# Add any local app config here, e.g. connection details for a
# database, etc.
# All logging config is inherited from the site-wide file, except
# we'll override the output file location so that it remains local.
# And maybe we need the level bumped up while we troubleshoot
# something.
[handler_file]
args = (r'C:\ProgramData\edbob\edbob.log', 'a')
[logger_edbob]
level = DEBUG
There is no right way to do this of course; the need should drive the
method. Since recursion is used, there is also no real limit to how
you go about it. A config file specific to a particular app on a
particular machine can further include a config file specific to the
local user on that machine, which in turn can include a file specific
to the local machine generally, which could then include one or more
site-wide files, etc. Or the "most specific" (initially read; primary)
config file could indicate which other files to include for every level
of that, in which case recursion would be less necessary (though still
technically used).
"""
if isinstance(paths, basestring):
paths = [paths]
for path in paths:
self.read_path(path, recurse=recurse)
return self.paths_loaded
def read_path(self, path, recurse=True):
"""
.. highlight:: ini
Reads a "single" config file into the instance. If ``recurse`` is ``True``,
*and* the config file references other "parent" config file(s), then the
parent(s) are read also in recursive fashion.
"Parent" config file paths may be specified in this way::
[edbob]
include_config = [
r'\\file-server\share\edbob\site.conf',
r'C:\ProgramData\edbob\special-stuff.conf',
]
See :meth:`read()` for more information.
"""
self.paths_attempted.append(path)
log.debug("Reading config file: %s" % path)
if not os.path.exists(path):
log.debug("File doesn't exist")
return
config = ConfigParser.SafeConfigParser()
if not config.read(path):
log.debug("Read failed")
return
include = None
if recurse:
if (config.has_section(self.appname) and
config.has_option(self.appname, 'include_config')):
include = config.get(self.appname, 'include_config')
if include:
log.debug("Including config: %s" % include)
for p in eval(include):
self.read_path(p)
ConfigParser.SafeConfigParser.read(self, path)
if include:
self.remove_option(self.appname, 'include_config')
self.paths_loaded.append(path)
log.info("Read config file: %s" % path)
def require(self, section, option, msg=None):
"""
Convenience method which will raise an exception if the given option
does not exist. ``msg`` can be used to override (some of) the error
text.
"""
value = self.get(section, option)
if value:
return value
raise exceptions.ConfigError(section, option, msg)
def save(self, filename, create_dir=True):
"""
Saves the current config contents to a file. Optionally can create the
parent folder(s) as necessary.
"""
config_folder = os.path.dirname(filename)
if create_dir and not os.path.exists(config_folder):
os.makedirs(config_folder)
config_file = open(filename, 'w')
self.write(config_file)
config_file.close()
def set(self, section, option, value):
"""
Overrides ``ConfigParser.SafeConfigParser.set()`` so that ``section``
is created if it doesn't already exist, instead of raising an error.
"""
if not self.has_section(section):
self.add_section(section)
ConfigParser.SafeConfigParser.set(self, section, option, value)
def default_system_paths(appname):
r"""
Returns a list of default system-level config file paths for the given
``appname``, according to ``sys.platform``.
For example, assuming an app name of ``'rattail'``, the following would be
returned:
``win32``:
* ``<COMMON_APPDATA>\rattail.conf``
* ``<COMMON_APPDATA>\rattail\rattail.conf``
Any other platform:
* ``/etc/rattail.conf``
* ``/etc/rattail/rattail.conf``
* ``/usr/local/etc/rattail.conf``
* ``/usr/local/etc/rattail/rattail.conf``
"""
if sys.platform == 'win32':
from win32com.shell import shell, shellcon
return [
os.path.join(shell.SHGetSpecialFolderPath(
0, shellcon.CSIDL_COMMON_APPDATA), '%s.conf' % appname),
os.path.join(shell.SHGetSpecialFolderPath(
0, shellcon.CSIDL_COMMON_APPDATA), appname, '%s.conf' % appname),
]
return [
'/etc/%s.conf' % appname,
'/etc/%s/%s.conf' % (appname, appname),
'/usr/local/etc/%s.conf' % appname,
'/usr/local/etc/%s/%s.conf' % (appname, appname),
]
def default_user_paths(appname):
r"""
Returns a list of default user-level config file paths for the given
``appname``, according to ``sys.platform``.
For example, assuming an app name of ``'rattail'``, the following would be
returned:
``win32``:
* ``<APPDATA>\rattail.conf``
* ``<APPDATA>\rattail\rattail.conf``
Any other platform:
* ``~/.rattail.conf``
* ``~/.rattail/rattail.conf``
"""
if sys.platform == 'win32':
from win32com.shell import shell, shellcon
return [
os.path.join(shell.SHGetSpecialFolderPath(
0, shellcon.CSIDL_APPDATA), '%s.conf' % appname),
os.path.join(shell.SHGetSpecialFolderPath(
0, shellcon.CSIDL_APPDATA), appname, '%s.conf' % appname),
]
return [
os.path.expanduser('~/.%s.conf' % appname),
os.path.expanduser('~/.%s/%s.conf' % (appname, appname)),
]

50
edbob/console.py Normal file
View file

@ -0,0 +1,50 @@
#!/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.console`` -- Console-Specific Stuff
"""
import sys
import progressbar
import edbob
class Progress(edbob.Object):
"""
Provides a console-based progress bar.
"""
def __init__(self, message, maximum):
print >> sys.stderr, "\n%s...(%u total)" % (message, maximum)
widgets = [progressbar.Percentage(), ' ', progressbar.Bar(), ' ', progressbar.ETA()]
self.progress = progressbar.ProgressBar(maxval=maximum, widgets=widgets).start()
def update(self, value):
self.progress.update(value)
return True
def destroy(self):
print >> sys.stderr, ''

View file

@ -2,23 +2,23 @@
# -*- coding: utf-8 -*-
################################################################################
#
# edbob -- Pythonic software framework
# Copyright © 2010,2011,2012 Lance Edgar
# 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 General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
# 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 General Public License for more
# details.
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# edbob. If not, see <http://www.gnu.org/licenses/>.
# You should have received a copy of the GNU Affero General Public License
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
@ -29,15 +29,16 @@
import logging
import uuid
from pkg_resources import iter_entry_points
__all__ = ['Object', 'basic_logging', 'get_uuid']
__all__ = ['Object', 'basic_logging', 'entry_point_map', 'get_uuid', 'graft']
class Object(object):
"""
Generic base class which provides a common ancestor, and some constructor
convenience.
Generic base class which provides a common ancestor, and some other
conveniences.
"""
def __init__(self, **kwargs):
@ -49,18 +50,43 @@ class Object(object):
for key in kwargs:
setattr(self, key, kwargs[key])
def __getitem__(self, key):
"""
Allows dict-like access to the object's attributes.
"""
def basic_logging():
if hasattr(self, key):
return getattr(self, key)
def basic_logging(appname):
"""
Does some basic configuration on the ``edbob`` logger. This only enables
console output at this point; it is assumed that if you intend to configure
logging that you will be using a proper config file and calling
:func:`edbob.init()`.
Does some basic configuration on the logger qualified by ``appname``.
.. note::
This only enables console output at this point; it is assumed that if
you intend to "truly" configure logging that you will be using a proper
config file and calling :func:`edbob.init()`.
"""
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('%(name)s: %(levelname)s: %(message)s'))
logging.getLogger('edbob').addHandler(handler)
logging.getLogger(appname).addHandler(handler)
def entry_point_map(key):
"""
Convenience function to retrieve a dictionary of entry points, keyed by
name.
``key`` must be the "section name" for the entry points you're after, e.g.
``'edbob.commands'``.
"""
epmap = {}
for ep in iter_entry_points(key):
epmap[ep.name] = ep.load()
return epmap
def get_uuid():
@ -70,3 +96,35 @@ def get_uuid():
"""
return uuid.uuid1().hex
def graft(target, source, names=None):
"""
Adds names to the ``target`` namespace, copying each from ``source``.
If ``names`` is provided, it can be a string if adding only one thing;
otherwise it should be a list of strings. If it is not provided, then
everything from ``source`` will be grafted.
.. note::
If "everything" is to be grafted (i.e. ``names is None``), then
``source.__all__`` will be consulted if available. If it is not, then
``dir(source)`` will be used instead.
"""
if names is None:
if hasattr(source, '__all__'):
names = source.__all__
else:
names = dir(source)
elif isinstance(names, basestring):
names = [names]
for name in names:
if hasattr(source, name):
setattr(target, name, getattr(source, name))
else:
setattr(target, name, source.get(name))
if not hasattr(target, '__all__'):
target.__all__ = []
target.__all__.append(name)

134
edbob/db/__init__.py Normal file
View file

@ -0,0 +1,134 @@
#!/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 sqlalchemy import engine_from_config
from sqlalchemy.orm import sessionmaker
import edbob
Session = sessionmaker()
def init():
"""
Called whenever ``'edbob.db'`` is configured to be auto-initialized.
This function is responsible for establishing the primary database engine
(a ``sqlalchemy.Engine`` instance, read from config), and extending the
root ``edbob`` namespace with the ORM classes (``Person``, ``User``, etc.),
as well as a few other things, e.g. ``engine``, ``Session`` and
``metadata``.
In addition to this, if a connection to the primary database can be
obtained, it will be consulted to see which extensions are active within
it. If any are found, edbob's ORM will be extended in-place accordingly.
"""
config = edbob.config.get_dict('edbob.db')
engine = engine_from_config(config)
edbob.graft(edbob, locals(), 'engine')
Session.configure(bind=engine)
edbob.graft(edbob, globals(), 'Session')
from edbob.db.model import get_metadata
metadata = get_metadata(bind=engine)
edbob.graft(edbob, locals(), 'metadata')
from edbob.db.mappers import make_mappers
make_mappers(metadata)
from edbob.db.ext import extend_framework
extend_framework()
# Note that we extend the framework before we graft the 'classes' module
# contents, since extensions may graft things to that module.
import edbob.db.classes as classes
edbob.graft(edbob, classes)
# Same goes for the enum module.
import edbob.db.enum as enum
edbob.graft(edbob, enum)
# Add settings functions.
edbob.graft(edbob, globals(), ('get_setting', 'save_setting'))
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 needs_session(func):
"""
Decorator which adds helpful session handling.
"""
def wrapped(*args, **kwargs):
session = kwargs.get('session')
_session = session
if not session:
session = Session()
kwargs['session'] = session
res = func(session, *args, **kwargs)
if not _session:
session.commit()
session.close()
return res
return wrapped

72
edbob/db/auth.py Normal file
View file

@ -0,0 +1,72 @@
#!/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.auth`` -- Authentication & Authorization
"""
from sqlalchemy.orm import object_session
from edbob.db.classes import Role, User
def get_administrator(session):
"""
Returns a :class:`edbob.Role` instance representing the "Administrator"
role, attached to the given ``session``.
"""
uuid = 'd937fa8a965611dfa0dd001143047286'
admin = session.query(Role).get(uuid)
if admin:
return admin
admin = Role(uuid=uuid, name='Administrator')
session.add(admin)
return admin
def has_permission(obj, perm):
"""
Checks the given ``obj`` (which may be either a :class:`edbob.User`` or
:class:`edbob.Role` instance), and returns a boolean indicating whether or
not the object is allowed the given permission. ``perm`` should be a
fully-qualified permission name, e.g. ``'users.create'``.
"""
if isinstance(obj, User):
roles = obj.roles
elif isinstance(obj, Role):
roles = [obj]
else:
raise TypeError, "You must pass either a User or Role for 'obj'; got: %s" % repr(obj)
session = object_session(obj)
assert session
admin = get_administrator(session)
for role in roles:
if role is admin:
return True
for permission in role.permissions:
if permission == perm:
return True
return False

163
edbob/db/classes.py Normal file
View file

@ -0,0 +1,163 @@
#!/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.classes`` -- Data Class Definitions
"""
from sqlalchemy.ext.associationproxy import association_proxy
import edbob
from edbob.sqlalchemy import getset_factory
__all__ = ['Permission', 'Person', 'Role', 'Setting', 'User', 'UserRole']
class PersonDerivative(edbob.Object):
"""
Base class for classes which must derive certain functionality from the
:class:`Person` class, e.g. :class:`User`.
"""
display_name = association_proxy('person', 'display_name',
creator=lambda x: Person(display_name=x),
getset_factory=getset_factory)
first_name = association_proxy('person', 'first_name',
creator=lambda x: Person(first_name=x),
getset_factory=getset_factory)
last_name = association_proxy('person', 'last_name',
creator=lambda x: Person(last_name=x),
getset_factory=getset_factory)
class ActiveExtension(edbob.Object):
"""
Represents an extension which has been activated within a database.
"""
def __repr__(self):
return "<ActiveExtension: %s>" % self.name
class Permission(edbob.Object):
"""
Represents the fact that a particular :class:`Role` is allowed to do a
certain thing.
"""
def __repr__(self):
return "<Permission: %s: %s>" % (self.role, self.permission)
class Person(edbob.Object):
"""
Represents a real, living and breathing person. (Or, at least was
previously living and breathing, in the case of the deceased.)
"""
def __repr__(self):
return "<Person: %s>" % self.display_name
def __str__(self):
return str(self.display_name or '')
@property
def customer(self):
"""
Returns the first :class:`Customer` instance in
:attr:`Person.customers`, or ``None`` if that list is empty.
.. note::
As of this writing, :attr:`Person.customers` is an
arbitrarily-ordered list, so the only real certainty you have when
using :attr:`Person.customer` is when the :class:`Person` instance
is associated with exactly one (or zero) :class:`Customer`
instances.
"""
if self.customers:
return self.customers[0]
return None
class Role(edbob.Object):
"""
Represents a role within the organization; used to manage permissions.
"""
permissions = association_proxy('_permissions', 'permission',
creator=lambda x: Permission(permission=x),
getset_factory=getset_factory)
users = association_proxy('_users', 'user',
creator=lambda x: UserRole(user=x),
getset_factory=getset_factory)
def __repr__(self):
return "<Role: %s>" % self.name
def __str__(self):
return str(self.name or '')
class Setting(edbob.Object):
"""
Represents a setting stored within the database.
"""
def __repr__(self):
return "<Setting: %s>" % self.name
class User(PersonDerivative):
"""
Represents a user of the system. This may or may not correspond to a real
person, e.g. for data import jobs and the like.
"""
employee = association_proxy('person', 'employee',
creator=lambda x: Person(employee=x),
getset_factory=getset_factory)
roles = association_proxy('_roles', 'role',
creator=lambda x: UserRole(role=x),
getset_factory=getset_factory)
def __repr__(self):
return "<User: %s>" % self.username
def __str__(self):
return str(self.username or '')
class UserRole(edbob.Object):
"""
Represents the association between a :class:`User` and a :class:`Role`.
"""
def __repr__(self):
return "<UserRole: %s : %s>" % (self.user, self.role)

129
edbob/db/mappers.py Normal file
View file

@ -0,0 +1,129 @@
#!/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.mappers`` -- Object Relational Mappings
"""
from sqlalchemy.orm import mapper, relationship
import edbob.db.classes as c
def make_mappers(metadata):
"""
This function glues together the schema definition found in the ``models``
module with the data class definitions found in the ``classes`` module.
``metadata`` should be a ``sqlalchemy.MetaData`` instance.
It is meant to be called only once, by :func:`edbob.init()`.
"""
t = metadata.tables
# ActiveExtension
mapper(
c.ActiveExtension, t['active_extensions'],
)
# Permission
mapper(
c.Permission, t['permissions'],
)
# Person
mapper(
c.Person, t['people'],
properties=dict(
customers=relationship(
c.Customer,
backref='person',
),
employee=relationship(
c.Employee,
back_populates='person',
uselist=False,
),
user=relationship(
c.User,
back_populates='person',
uselist=False,
),
),
)
# Role
mapper(
c.Role, t['roles'],
properties=dict(
_permissions=relationship(
c.Permission,
backref='role',
),
_users=relationship(
c.UserRole,
backref='role',
),
),
)
# Setting
mapper(
c.Setting, t['settings'],
)
# User
mapper(
c.User, t['users'],
properties=dict(
person=relationship(
c.Person,
back_populates='user',
),
_roles=relationship(
c.UserRole,
backref='user',
cascade='save-update,merge,delete',
),
),
)
# UserRole
mapper(
c.UserRole, t['users_roles'],
)

109
edbob/db/model.py Normal file
View file

@ -0,0 +1,109 @@
#!/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.model`` -- Core Schema Definition
"""
from sqlalchemy import *
from edbob import get_uuid
def get_metadata(*args, **kwargs):
"""
Returns the core ``edbob`` schema definition.
Note that when :func:`edbob.init()` is called, the ``sqlalchemy.MetaData``
instance which is returned from this function will henceforth be available
as ``edbob.metadata``. However, ``edbob.init()`` may extend
``edbob.metadata`` as well, depending on which extensions are activated
within the primary database.
This function then serves two purposes: First, it provides the core
metadata instance. Secondly, it allows edbob to always know what its core
schema looks like, as opposed to what's held in the current
``edbob.metadata`` instance, which may have been extended locally. (The
latter use is necessary in order for edbob to properly manage its
extensions.)
All arguments (positional and keyword) are passed directly to the
``sqlalchemy.MetaData()`` constructor.
"""
metadata = MetaData(*args, **kwargs)
active_extensions = Table(
'active_extensions', metadata,
Column('name', String(50), primary_key=True),
)
def get_person_display_name(context):
first_name = context.current_parameters['first_name']
last_name = context.current_parameters['last_name']
if not (first_name or last_name):
return None
return '%(first_name)s %(last_name)s' % locals()
people = Table(
'people', metadata,
Column('uuid', String(32), primary_key=True, default=get_uuid),
Column('first_name', String(50)),
Column('last_name', String(50)),
Column('display_name', String(100), default=get_person_display_name),
)
permissions = Table(
'permissions', metadata,
Column('role_uuid', String(32), ForeignKey('roles.uuid'), primary_key=True),
Column('permission', String(50), primary_key=True),
)
roles = Table(
'roles', metadata,
Column('uuid', String(32), primary_key=True, default=get_uuid),
Column('name', String(25), nullable=False, unique=True),
)
settings = Table(
'settings', metadata,
Column('name', String(255), primary_key=True),
Column('value', Text),
)
users = Table(
'users', metadata,
Column('uuid', String(32), primary_key=True, default=get_uuid),
Column('username', String(25), nullable=False, unique=True),
Column('person_uuid', String(32), ForeignKey('people.uuid')),
)
users_roles = Table(
'users_roles', metadata,
Column('uuid', String(32), primary_key=True, default=get_uuid),
Column('user_uuid', String(32), ForeignKey('users.uuid')),
Column('role_uuid', String(32), ForeignKey('roles.uuid')),
)
return metadata

84
edbob/db/perms.py Normal file
View file

@ -0,0 +1,84 @@
#!/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.perms`` -- Roles & Permissions
"""
from sqlalchemy.orm import object_session
from edbob.db.classes import Role, User, Permission
def get_administrator(session):
"""
Returns the "Administrator" :class:`rattail.db.classes.Role` instance,
attached to the given ``session``.
"""
uuid = 'd937fa8a965611dfa0dd001143047286'
admin = session.query(Role).get(uuid)
if admin:
return admin
admin = Role(uuid=uuid, name='Administrator')
session.add(admin)
return admin
# def has_permission(object_, permission, session=None):
# '''
# Checks the given ``object_`` (which may be either a :class:`rattail.v1.User` or
# a :class:`rattail.v1.Role`) and returns a boolean indicating whether or not the
# object is allowed the given permission. ``permission`` may be either a
# :class:`rattail.v1.Permission` instance, or the fully-qualified name of one.
# If ``object_`` is ``None``, the permission check is made against the special
# "(Anybody)" role.
# '''
def has_permission(obj, perm):
"""
Checks the given ``obj`` (which may be either a
:class:`rattail.db.classes.User`` or :class:`rattail.db.classes.Role`
instance), and returns a boolean indicating whether or not the object is
allowed the given permission. ``perm`` should be a fully-qualified
permission name, e.g. ``'employees.admin'``.
"""
if isinstance(obj, User):
roles = obj.roles
elif isinstance(obj, Role):
roles = [obj]
else:
raise TypeError, "You must pass either a User or Role for 'obj'; got: %s" % repr(obj)
session = object_session(obj)
assert session
admin = get_administrator(session)
for role in roles:
if role is admin:
return True
for permission in role.permissions:
if permission == perm:
return True
return False

View file

@ -2,23 +2,23 @@
# -*- coding: utf-8 -*-
################################################################################
#
# edbob -- Pythonic software framework
# Copyright © 2010,2011,2012 Lance Edgar
# 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 General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
# 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 General Public License for more
# details.
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# edbob. If not, see <http://www.gnu.org/licenses/>.
# You should have received a copy of the GNU Affero General Public License
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
@ -27,6 +27,21 @@
"""
class ConfigError(Exception):
"""
Raised when configuration is missing or otherwise invalid.
"""
def __init__(self, section, option, msg=None):
self.section = section
self.option = option
self.msg = msg or "Missing or invalid config"
def __str__(self):
return "%s; please set '%s' in the [%s] section of your config file" % (
self.msg, self.option, self.section)
class LoadSpecError(Exception):
"""
Raised when something obvious goes wrong with :func:`edbob.load_spec()`.
@ -55,3 +70,27 @@ class ModuleMissingAttribute(LoadSpecError):
def specifics(self):
mod, attr = self.spec.split(':')
return "module '%s' was loaded but '%s' attribute not found" % (mod, attr)
class RecipientsNotFound(Exception):
"""
Raised when no recipients could be found in config.
"""
def __init__(self, key):
self.key = key
def __str__(self):
return "No recipients configured (set 'recipients.%s' in [edbob.mail])" % self.key
class SenderNotFound(Exception):
"""
Raised when no sender could be found in config.
"""
def __init__(self, key):
self.key = key
def __str__(self):
return "No sender configured (set 'sender.%s' in [edbob.mail])" % self.key

View file

@ -2,23 +2,23 @@
# -*- coding: utf-8 -*-
################################################################################
#
# edbob -- Pythonic software framework
# Copyright © 2010,2011,2012 Lance Edgar
# 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 General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
# 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 General Public License for more
# details.
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# edbob. If not, see <http://www.gnu.org/licenses/>.
# You should have received a copy of the GNU Affero General Public License
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
@ -28,10 +28,31 @@
import os
import os.path
import shutil
import tempfile
__all__ = ['count_lines', 'temp_path']
__all__ = ['change_newlines', 'count_lines', 'temp_path']
def change_newlines(path, newline):
"""
Rewrites the file at ``path``, changing its newline character(s) to that of
``newline``.
"""
root, ext = os.path.splitext(path)
temp_path = temp_path(suffix='.' + ext)
infile = open(path, 'rUb')
outfile = open(temp_path, 'wb')
for line in infile:
line = line.rstrip('\r\n')
outfile.write(line + newline)
infile.close()
outfile.close()
os.remove(path)
shutil.move(temp_path, path)
def count_lines(path):

101
edbob/initialization.py Normal file
View file

@ -0,0 +1,101 @@
#!/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 locale
import logging
from edbob.configuration import AppConfigParser
from edbob.configuration import default_system_paths, default_user_paths
from edbob.core import graft
from edbob.times import set_timezone
__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()
# loc = config.get('edbob', 'locale')
# if loc:
# locale.setlocale(locale.LC_ALL, loc)
# log.info("Set locale to '%s'" % loc)
tz = config.get('edbob', 'timezone')
if tz:
set_timezone(tz)
log.info("Set timezone to '%s'" % tz)
else:
log.warning("No timezone configured; falling back to US/Central")
set_timezone('US/Central')
import edbob
graft(edbob, locals(), 'config')
edbob.inited = True

0
edbob/lib/__init__.py Normal file
View file

76
edbob/lib/pretty.py Normal file
View file

@ -0,0 +1,76 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
pretty
Formats dates, numbers, etc. in a pretty, human readable format.
"""
__author__ = "S Anand (sanand@s-anand.net)"
__copyright__ = "Copyright 2010, S Anand"
__license__ = "WTFPL"
# Note that some modifications exist between this file and the original as
# downloaded from http://pypi.python.org/pypi/py-pretty
from datetime import datetime
import edbob
def _df(seconds, denominator=1, text='', past=True):
if past: return str((seconds + denominator/2)/ denominator) + text + ' ago'
else: return 'in ' + str((seconds + denominator/2)/ denominator) + text
def date(time=False, asdays=False, short=False):
'''Returns a pretty formatted date.
Inputs:
time is a datetime object or an int timestamp
asdays is True if you only want to measure days, not seconds
short is True if you want "1d ago", "2d ago", etc. False if you want
'''
now = edbob.utc_time()
if type(time) is int: time = datetime.fromtimestamp(time)
elif not time: time = now
if time > now: past, diff = False, time - now
else: past, diff = True, now - time
seconds = diff.seconds
days = diff.days
if short:
if days == 0 and not asdays:
if seconds < 10: return 'now'
elif seconds < 60: return _df(seconds, 1, 's', past)
elif seconds < 3600: return _df(seconds, 60, 'm', past)
else: return _df(seconds, 3600, 'h', past)
else:
if days == 0: return 'today'
elif days == 1: return past and 'yest' or 'tom'
elif days < 7: return _df(days, 1, 'd', past)
elif days < 31: return _df(days, 7, 'w', past)
elif days < 365: return _df(days, 30, 'mo', past)
else: return _df(days, 365, 'y', past)
else:
if days == 0 and not asdays:
if seconds < 10: return 'now'
elif seconds < 60: return _df(seconds, 1, ' seconds', past)
elif seconds < 120: return past and 'a minute ago' or 'in a minute'
elif seconds < 3600: return _df(seconds, 60, ' minutes', past)
elif seconds < 7200: return past and 'an hour ago' or'in an hour'
else: return _df(seconds, 3600, ' hours', past)
else:
if days == 0: return 'today'
elif days == 1: return past and 'yesterday' or'tomorrow'
elif days == 2: return past and 'day before' or 'day after'
elif days < 7: return _df(days, 1, ' days', past)
elif days < 14: return past and 'last week' or 'next week'
elif days < 31: return _df(days, 7, ' weeks', past)
elif days < 61: return past and 'last month' or 'next month'
elif days < 365: return _df(days, 30, ' months', past)
elif days < 730: return past and 'last year' or 'next year'
else: return _df(days, 365, ' years', past)

139
edbob/mail.py Normal file
View file

@ -0,0 +1,139 @@
#!/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.mail`` -- Email Framework
"""
import logging
import smtplib
from email.message import Message
import edbob
from edbob import exceptions
log = logging.getLogger(__name__)
def sendmail(sender, recipients, subject, body, content_type='text/plain'):
"""
Sends an email message from ``sender`` to each address in ``recipients``
(which should be a sequence), with subject ``subject`` and body ``body``.
If sending an HTML message instead of plain text, be sure to set
``content_type`` to ``'text/html'``.
"""
message = Message()
message.set_type(content_type)
message['From'] = sender
for recipient in recipients:
message['To'] = recipient
message['Subject'] = subject
message.set_payload(body)
server = edbob.config.get('edbob.mail', 'smtp.server',
default='localhost')
username = edbob.config.get('edbob.mail', 'smtp.username')
password = edbob.config.get('edbob.mail', 'smtp.password')
log.debug("sendmail: connecting to server: %s" % server)
session = smtplib.SMTP(server)
if username and password:
res = session.login(username, password)
log.debug("sendmail: login result is: %s" % str(res))
res = session.sendmail(message['From'], message.get_all('To'), message.as_string())
log.debug("sendmail: sendmail result is: %s" % res)
session.quit()
def get_sender(key):
sender = edbob.config.get('edbob.mail', 'sender.'+key)
if sender:
return sender
sender = edbob.config.get('edbob.mail', 'sender.default')
if sender:
return sender
raise exceptions.SenderNotFound(key)
def get_recipients(key):
recips = edbob.config.get('edbob.mail', 'recipients.'+key)
if recips:
return eval(recips)
recips = edbob.config.get('edbob.mail', 'recipients.default')
if recips:
return eval(recips)
raise exceptions.RecipientsNotFound(key)
def get_subject(key):
subject = edbob.config.get('edbob.mail', 'subject.'+key)
if subject:
return subject
subject = edbob.config.get('edbob.mail', 'subject.default')
if subject:
return subject
return "[edbob]"
def sendmail_with_config(key, body, subject=None, **kwargs):
"""
.. highlight:: ini
Sends mail using sender/recipient/subject values found in config, according
to ``key``. Probably the easiest way to explain would be to show an example::
[edbob.mail]
smtp.server = localhost
sender.default = Lance Edgar <lance@edbob.org>
subject.default = A Nice Shrubbery, Not Too Expensive
recipients.nightly_reports = ['Lance Edgar <lance@edbob.org>']
subject.tragic_errors = The World Is Nearing The End!!
recipients.tragic_errors = ['Lance Edgar <lance@edbob.org>']
Anything not configured explicitly will fall back to defaults where
possible. Note however that a sender and recipients (default or otherwise)
*must* be found or else an exception will be raised.
The above does not include a default recipient list, but it would work the same
as the subject and sender as far as the key goes. To send these mails then::
from edbob.mail import sendmail_with_config
# just a report
sendmail_with_config('nightly_reports', open('report.txt').read())
# you might want to sit down for this one...
sendmail_with_config('tragic_errors', open('OMGWTFBBQ.txt').read())
If you do not provide ``subject`` to this function, and there is no
``subject.default`` setting found in config, a default of "[edbob]" will
be used.
"""
if subject is None:
subject = get_subject(key)
return sendmail(get_sender(key), get_recipients(key), subject, body, **kwargs)

View file

@ -2,23 +2,23 @@
# -*- coding: utf-8 -*-
################################################################################
#
# edbob -- Pythonic software framework
# Copyright © 2010,2011,2012 Lance Edgar
# 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 General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
# 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 General Public License for more
# details.
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# edbob. If not, see <http://www.gnu.org/licenses/>.
# You should have received a copy of the GNU Affero General Public License
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
@ -35,6 +35,18 @@ from edbob import exceptions
__all__ = ['load_spec']
def import_module_path(module_path):
"""
Imports and returns an arbitrary Python module, given its "dotted" path
(i.e. not its file path).
"""
if module_path in sys.modules:
return sys.modules[module_path]
module = __import__(module_path)
return last_module(module, module_path)
def last_module(module, module_path):
"""
Returns the "last" module represented by ``module_path``, by walking
@ -57,18 +69,6 @@ def last_module(module, module_path):
return last_module(child, '.'.join(parts))
def import_module_path(module_path):
"""
Imports and returns an arbitrary Python module, given its "dotted" path
(i.e. not its file path).
"""
if module_path in sys.modules:
return sys.modules[module_path]
module = __import__(module_path)
return last_module(module, module_path)
def load_spec(spec):
"""
.. highlight:: none

View file

View file

View file

@ -0,0 +1,28 @@
#!/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.pyramid.forms.formalchemy`` -- FormAlchemy Interface
"""

View file

View file

@ -0,0 +1,291 @@
#!/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.pyramid.handlers.base`` -- Base Handlers
"""
from pyramid.renderers import render_to_response
from pyramid.httpexceptions import HTTPException, HTTPFound, HTTPOk, HTTPUnauthorized
import sqlahelper
# import rattail.pyramid.forms.util as util
from rattail.db.perms import has_permission
from rattail.pyramid.forms.formalchemy import Grid
class needs_perm(object):
"""
Decorator to be used for handler methods which should restrict access based
on the current user's permissions.
"""
def __init__(self, permission, **kwargs):
self.permission = permission
self.kwargs = kwargs
def __call__(self, fn):
permission = self.permission
kw = self.kwargs
def wrapped(self):
if not self.request.current_user:
self.request.session['referrer'] = self.request.url_generator.current()
self.request.session.flash("You must be logged in to do that.", 'error')
return HTTPFound(location=self.request.route_url('login'))
if not has_permission(self.request.current_user, permission):
self.request.session.flash("You do not have permission to do that.", 'error')
home = kw.get('redirect', self.request.route_url('home'))
return HTTPFound(location=home)
return fn(self)
return wrapped
def needs_user(fn):
"""
Decorator for handler methods which require simply that a user be currently
logged in.
"""
def wrapped(self):
if not self.request.current_user:
self.request.session['referrer'] = self.request.url_generator.current()
self.request.session.flash("You must be logged in to do that.", 'error')
return HTTPFound(location=self.request.route_url('login'))
return fn(self)
return wrapped
class Handler(object):
def __init__(self, request):
self.request = request
self.Session = sqlahelper.get_session()
# def json_response(self, data={}):
# response = render_to_response('json', data, request=self.request)
# response.headers['Content-Type'] = 'application/json'
# return response
class CrudHandler(Handler):
# """
# This handler provides all the goodies typically associated with general
# CRUD functionality, e.g. search filters and grids.
# """
def crud(self, cls, fieldset_factory, home=None, delete=None, post_sync=None, pre_render=None):
"""
Adds a common CRUD mechanism for objects.
``cls`` should be a SQLAlchemy-mapped class, presumably deriving from
:class:`rattail.Object`.
``fieldset_factory`` must be a callable which accepts the fieldset's
"model" as its only positional argument.
``home`` will be used as the redirect location once a form is fully
validated and data saved. If you do not speficy this parameter, the
user will be redirected to be the CRUD page for the new object (e.g. so
an object may be created before certain properties may be edited).
``delete`` may either be a string containing a URL to which the user
should be redirected after the object has been deleted, or else a
callback which will be executed *instead of* the normal algorithm
(which is merely to delete the object via the Session).
``post_sync`` may be a callback which will be executed immediately
after ``FieldSet.sync()`` is called, i.e. after validation as well.
``pre_render`` may be a callback which will be executed after any POST
processing has occured, but just before rendering.
"""
uuid = self.request.params.get('uuid')
obj = self.Session.query(cls).get(uuid) if uuid else cls
assert obj
if self.request.params.get('delete'):
if delete:
if isinstance(delete, basestring):
self.Session.delete(obj)
return HTTPFound(location=delete)
res = delete(obj)
if res:
return res
else:
self.Session.delete(obj)
if not home:
raise ValueError("Must specify 'home' or 'delete' url "
"in call to CrudHandler.crud()")
return HTTPFound(location=home)
fs = fieldset_factory(obj)
# if not fs.readonly and self.request.params.get('fieldset'):
# fs.rebind(data=self.request.params)
# if fs.validate():
# fs.sync()
# if post_sync:
# res = post_sync(fs)
# if isinstance(res, HTTPFound):
# return res
# if self.request.params.get('partial'):
# self.Session.flush()
# return self.json_success(uuid=fs.model.uuid)
# return HTTPFound(location=self.request.route_url(objects, action='index'))
if not fs.readonly and self.request.POST:
# print self.request.POST
fs.rebind(data=self.request.params)
if fs.validate():
fs.sync()
if post_sync:
res = post_sync(fs)
if res:
return res
if self.request.params.get('partial'):
self.Session.flush()
return self.json_success(uuid=fs.model.uuid)
if not home:
self.Session.flush()
home = self.request.url_generator.current() + '?uuid=' + fs.model.uuid
self.request.session.flash("%s \"%s\" has been %s." % (
fs.crud_title, fs.get_display_text(),
'updated' if fs.edit else 'created'))
return HTTPFound(location=home)
data = {'fieldset': fs, 'crud': True}
if pre_render:
res = pre_render(fs)
if res:
if isinstance(res, HTTPException):
return res
data.update(res)
# data = {'fieldset':fs}
# if self.request.params.get('partial'):
# return render_to_response('/%s/crud_partial.mako' % objects,
# data, request=self.request)
# return data
return data
def grid(self, *args, **kwargs):
"""
Convenience function which returns a grid. The only functionality this
method adds is the ``session`` parameter.
"""
return Grid(session=self.Session(), *args, **kwargs)
# def get_grid(self, name, grid, query, search=None, url=None, **defaults):
# """
# Convenience function for obtaining the configuration for a grid,
# and then obtaining the grid itself.
# ``name`` is essentially the config key, e.g. ``'products.lookup'``, and
# in fact is expected to take that precise form (where the first part is
# considered the handler name and the second part the action name).
# ``grid`` must be a callable with a signature of ``grid(query,
# config)``, and ``query`` will be passed directly to the ``grid``
# callable. ``search`` will be used to inform the grid of the search in
# effect, if any. ``defaults`` will be used to customize the grid config.
# """
# if not url:
# handler, action = name.split('.')
# url = self.request.route_url(handler, action=action)
# config = util.get_grid_config(name, self.request, search,
# url=url, **defaults)
# return grid(query, config)
# def get_search_form(self, name, labels={}, **defaults):
# """
# Convenience function for obtaining the configuration for a search form,
# and then obtaining the form itself.
# ``name`` is essentially the config key, e.g. ``'products.lookup'``.
# The ``labels`` dictionary can be used to override the default labels
# displayed for the various search fields. The ``defaults`` dictionary
# is used to customize the search config.
# """
# config = util.get_search_config(name, self.request,
# self.filter_map(), **defaults)
# form = util.get_search_form(config, **labels)
# return form
# def object_crud(self, cls, objects=None, post_sync=None):
# """
# This method is a desperate attempt to encapsulate shared CRUD logic
# which is useful across all editable data objects.
# ``objects``, if provided, should be the plural name for the class as
# used in internal naming, e.g. ``'products'``. A default will be used
# if you do not provide this value.
# ``post_sync``, if provided, should be a callable which accepts a
# ``formalchemy.Fieldset`` instance as its only argument. It will be
# called immediately after the fieldset is synced.
# """
# if not objects:
# objects = cls.__name__.lower() + 's'
# uuid = self.request.params.get('uuid')
# obj = self.Session.query(cls).get(uuid) if uuid else cls
# assert obj
# fs = self.fieldset(obj)
# if not fs.readonly and self.request.params.get('fieldset'):
# fs.rebind(data=self.request.params)
# if fs.validate():
# fs.sync()
# if post_sync:
# res = post_sync(fs)
# if isinstance(res, HTTPFound):
# return res
# if self.request.params.get('partial'):
# self.Session.flush()
# return self.json_success(uuid=fs.model.uuid)
# return HTTPFound(location=self.request.route_url(objects, action='index'))
# data = {'fieldset':fs}
# if self.request.params.get('partial'):
# return render_to_response('/%s/crud_partial.mako' % objects,
# data, request=self.request)
# return data
# def render_grid(self, grid, search=None, **kwargs):
# """
# Convenience function to render a standard grid. Really just calls
# :func:`dtail.forms.util.render_grid()`.
# """
# return util.render_grid(self.request, grid, search, **kwargs)

View file

@ -0,0 +1,72 @@
#!/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.pyramid.handlers.util`` -- Handler Utilities
"""
from pyramid.httpexceptions import HTTPFound
from edbob.db.perms import has_permission
class needs_perm(object):
"""
Decorator to be used for handler methods which should restrict access based
on the current user's permissions.
"""
def __init__(self, permission, **kwargs):
self.permission = permission
self.kwargs = kwargs
def __call__(self, fn):
permission = self.permission
kw = self.kwargs
def wrapped(self):
if not self.request.current_user:
self.request.session['referrer'] = self.request.url_generator.current()
self.request.session.flash("You must be logged in to do that.", 'error')
return HTTPFound(location=self.request.route_url('login'))
if not has_permission(self.request.current_user, permission):
self.request.session.flash("You do not have permission to do that.", 'error')
home = kw.get('redirect', self.request.route_url('home'))
return HTTPFound(location=home)
return fn(self)
return wrapped
def needs_user(fn):
"""
Decorator for handler methods which require simply that a user be currently
logged in.
"""
def wrapped(self):
if not self.request.current_user:
self.request.session['referrer'] = self.request.url_generator.current()
self.request.session.flash("You must be logged in to do that.", 'error')
return HTTPFound(location=self.request.route_url('login'))
return fn(self)
return wrapped

33
edbob/pyramid/helpers.py Normal file
View file

@ -0,0 +1,33 @@
#!/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.pyramid.helpers`` -- Template Context Helpers
"""
import datetime
from decimal import Decimal
from webhelpers.html import *
from webhelpers.html.tags import *

View file

@ -0,0 +1,37 @@
#!/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.pyramid.paster_templates`` -- Paster Templates
"""
from paste.util.template import paste_script_template_renderer
from pyramid.paster import PyramidTemplate
class EdbobPyramidTemplate(PyramidTemplate):
_template_dir = 'edbob'
summary = "edbob/pyramid project"
template_renderer = staticmethod(paste_script_template_renderer)

View file

@ -0,0 +1,66 @@
#!/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.pyramid.subscribers`` -- Subscribers
"""
# import pyramid.threadlocal as threadlocal
from pyramid import threadlocal
from pyramid.exceptions import ConfigurationError
from akhet.urlgenerator import URLGenerator
import edbob
from edbob.pyramid import helpers
def add_request_attributes(event):
"""
Adds goodies to the ``request`` object.
"""
request = event.request
context = request.context
url_generator = URLGenerator(context, request, qualified=True)
request.url_generator = url_generator
def add_renderer_globals(event):
"""
Adds goodies to the global template renderer context.
"""
renderer_globals = event
renderer_globals['h'] = helpers
request = event.get('request') or threadlocal.get_current_request()
if not request:
return
tmpl_context = request.tmpl_context
try:
renderer_globals['session'] = request.session
except ConfigurationError:
pass
renderer_globals['url'] = request.url_generator
renderer_globals['edbob'] = edbob

39
edbob/sqlalchemy.py Normal file
View file

@ -0,0 +1,39 @@
#!/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.sqlalchemy`` -- SQLAlchemy Stuff
"""
def getset_factory(collection_class, proxy):
"""
Helper function, useful for SQLAlchemy's "association proxy" configuration.
"""
def getter(obj):
if obj is None: return None
return getattr(obj, proxy.value_attr)
setter = lambda obj, val: setattr(obj, proxy.value_attr, val)
return getter, setter

View file

@ -2,23 +2,23 @@
# -*- coding: utf-8 -*-
################################################################################
#
# edbob -- Pythonic software framework
# Copyright © 2010,2011,2012 Lance Edgar
# 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 General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
# 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 General Public License for more
# details.
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# edbob. If not, see <http://www.gnu.org/licenses/>.
# You should have received a copy of the GNU Affero General Public License
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
@ -30,7 +30,7 @@ import datetime
import pytz
__all__ = ['local_time', 'set_timezone']
__all__ = ['local_time', 'set_timezone', 'utc_time']
_timezone = None
@ -82,3 +82,16 @@ def set_timezone(tz):
_timezone = None
else:
_timezone = pytz.timezone(tz)
def utc_time(timestamp=None):
"""
Returns a timestamp whose ``tzinfo`` member is set to the UTC timezone.
If ``timestamp`` is not provided, then ``datetime.datetime.utcnow()`` will
be called to obtain the value.
"""
if timestamp is None:
timestamp = datetime.datetime.utcnow()
return pytz.utc.localize(timestamp)

55
edbob/util.py Normal file
View file

@ -0,0 +1,55 @@
#!/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.util`` -- Utilities
"""
import edbob
class requires_impl(edbob.Object):
"""
Decorator for properties or methods defined on parent classes only for
documentation's sake, but which in fact rely on the derived class entirely
for implementation.
This merely adds a helpful message to the ``NotImplementedError`` exception
which will be raised.
"""
is_property = False
def __call__(self, func):
if self.is_property:
message = "Please define the %s.%s attribute"
else:
message = "Please implement the %s.%s() method"
def wrapped(self, *args, **kwargs):
msg = message % (self.__class__.__name__, func.__name__)
msg += " (within the %s module)" % self.__class__.__module__
raise NotImplementedError(msg)
return wrapped

95
edbob/wx/GenerateUuid.py Normal file
View file

@ -0,0 +1,95 @@
#!/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/>.
#
################################################################################
import wx
# begin wxGlade: extracode
# end wxGlade
import edbob
class GenerateUuidDialog(wx.Dialog):
def __init__(self, *args, **kwds):
# begin wxGlade: GenerateUuidDialog.__init__
kwds["style"] = wx.DEFAULT_DIALOG_STYLE
wx.Dialog.__init__(self, *args, **kwds)
self.label_1 = wx.StaticText(self, -1, "&UUID:")
self.Uuid = wx.TextCtrl(self, -1, "", style=wx.TE_READONLY)
self.Generate = wx.Button(self, -1, "&Generate UUID")
self.Close = wx.Button(self, wx.ID_OK, "&Close")
self.__set_properties()
self.__do_layout()
self.Bind(wx.EVT_BUTTON, self.OnGenerateButton, self.Generate)
# end wxGlade
self.GenerateUuid()
def __set_properties(self):
# begin wxGlade: GenerateUuidDialog.__set_properties
self.SetTitle("Generate UUID")
self.Uuid.SetMinSize((300, -1))
# end wxGlade
def __do_layout(self):
# begin wxGlade: GenerateUuidDialog.__do_layout
sizer_1 = wx.BoxSizer(wx.VERTICAL)
sizer_2 = wx.BoxSizer(wx.VERTICAL)
sizer_3 = wx.BoxSizer(wx.HORIZONTAL)
sizer_2.Add(self.label_1, 0, 0, 0)
sizer_2.Add(self.Uuid, 0, wx.TOP|wx.EXPAND, 5)
sizer_3.Add(self.Generate, 0, 0, 0)
sizer_3.Add(self.Close, 0, wx.LEFT, 10)
sizer_2.Add(sizer_3, 0, wx.TOP|wx.ALIGN_CENTER_HORIZONTAL, 10)
sizer_1.Add(sizer_2, 1, wx.ALL|wx.EXPAND, 10)
self.SetSizer(sizer_1)
sizer_1.Fit(self)
self.Layout()
self.Centre()
# end wxGlade
def OnGenerateButton(self, event): # wxGlade: GenerateUuidDialog.<event_handler>
self.GenerateUuid()
event.Skip()
def GenerateUuid(self):
self.Uuid.SetValue(edbob.get_uuid())
self.Uuid.SetSelection(-1, -1)
self.Uuid.SetFocus()
# end of class GenerateUuidDialog
def main():
app = wx.PySimpleApp()
dlg = GenerateUuidDialog(None)
dlg.ShowModal()
dlg.Destroy()
app.MainLoop()
if __name__ == "__main__":
main()

87
edbob/wx/__init__.py Normal file
View file

@ -0,0 +1,87 @@
#!/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.wx`` -- wxPython Framework
"""
from __future__ import absolute_import
import wx
class ProgressDialog(wx.ProgressDialog):
def __init__(self, parent, message, maximum, title="Processing...", can_abort=True, *args, **kwargs):
style = wx.PD_SMOOTH|wx.PD_AUTO_HIDE|wx.PD_APP_MODAL|wx.PD_ELAPSED_TIME|wx.PD_REMAINING_TIME
if can_abort:
style |= wx.PD_CAN_ABORT
if 'style' in kwargs:
style &= kwargs['style']
kwargs['style'] = style
wx.ProgressDialog.__init__(self, title, message, maximum=maximum, parent=parent, *args, **kwargs)
def update(self, value, *args, **kwargs):
if not wx.ProgressDialog.Update(self, value, *args, **kwargs)[0]:
if self.ConfirmAbort():
return False
self.Resume()
return True
def destroy(self):
self.Destroy()
def ConfirmAbort(self):
dlg = wx.MessageDialog(self, "Do you really wish to cancel this process?",
"Really Cancel?", wx.ICON_QUESTION|wx.YES_NO|wx.NO_DEFAULT)
res = dlg.ShowModal()
dlg.Destroy()
return res == wx.ID_YES
class ProgressFactory(object):
def __init__(self, parent, *args, **kwargs):
self.parent = parent
self.args = args
self.kwargs = kwargs
def __call__(self, message, maximum, *args, **kwargs):
message = '%s ...' % message
args = self.args + args
_kwargs = self.kwargs.copy()
_kwargs.update(kwargs)
return ProgressDialog(self.parent, message, maximum, *args, **_kwargs)
def LaunchDialog(dialog_class):
"""
Creates a ``wx.PySimpleApp``, then instantiates ``dialog_class`` and shows
it modally.
"""
app = wx.PySimpleApp()
dlg = dialog_class(None)
dlg.ShowModal()
dlg.Destroy()
app.MainLoop()

View file

@ -0,0 +1,65 @@
<?xml version="1.0"?>
<!-- generated by wxGlade 0.6.3 on Tue Dec 13 00:27:15 2011 -->
<application path="../GenerateUuid.py" name="" class="" option="0" language="python" top_window="GenerateUuidDialog" encoding="UTF-8" use_gettext="0" overwrite="0" use_new_namespace="1" for_version="2.8" is_template="0">
<object class="GenerateUuidDialog" name="GenerateUuidDialog" base="EditDialog">
<style>wxDEFAULT_DIALOG_STYLE</style>
<title>Generate UUID</title>
<centered>1</centered>
<object class="wxBoxSizer" name="sizer_1" base="EditBoxSizer">
<orient>wxVERTICAL</orient>
<object class="sizeritem">
<flag>wxALL|wxEXPAND</flag>
<border>10</border>
<option>1</option>
<object class="wxBoxSizer" name="sizer_2" base="EditBoxSizer">
<orient>wxVERTICAL</orient>
<object class="sizeritem">
<border>0</border>
<option>0</option>
<object class="wxStaticText" name="label_1" base="EditStaticText">
<attribute>1</attribute>
<label>&amp;UUID:</label>
</object>
</object>
<object class="sizeritem">
<flag>wxTOP|wxEXPAND</flag>
<border>5</border>
<option>0</option>
<object class="wxTextCtrl" name="Uuid" base="EditTextCtrl">
<style>wxTE_READONLY</style>
<size>300, -1</size>
</object>
</object>
<object class="sizeritem">
<flag>wxTOP|wxALIGN_CENTER_HORIZONTAL</flag>
<border>10</border>
<option>0</option>
<object class="wxBoxSizer" name="sizer_3" base="EditBoxSizer">
<orient>wxHORIZONTAL</orient>
<object class="sizeritem">
<border>0</border>
<option>0</option>
<object class="wxButton" name="Generate" base="EditButton">
<label>&amp;Generate UUID</label>
<events>
<handler event="EVT_BUTTON">OnGenerateButton</handler>
</events>
</object>
</object>
<object class="sizeritem">
<flag>wxLEFT</flag>
<border>10</border>
<option>0</option>
<object class="wxButton" name="Close" base="EditButton">
<label>&amp;Close</label>
<id>wx.ID_OK</id>
</object>
</object>
</object>
</object>
</object>
</object>
</object>
</object>
</application>

View file

@ -2,23 +2,23 @@
# -*- coding: utf-8 -*-
################################################################################
#
# edbob -- Pythonic software framework
# Copyright © 2010,2011,2012 Lance Edgar
# 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 General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
# 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 General Public License for more
# details.
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# edbob. If not, see <http://www.gnu.org/licenses/>.
# You should have received a copy of the GNU Affero General Public License
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
@ -45,7 +45,7 @@ setup(
author = "Lance Edgar",
author_email = "lance@edbob.org",
url = "http://edbob.org/",
license = "GNU GPL v3",
license = "GNU Affero GPL v3",
description = "Pythonic Software Framework",
long_description = readme,
@ -56,7 +56,7 @@ setup(
'Environment :: Win32 (MS Windows)',
'Environment :: X11 Applications',
'Intended Audience :: Developers',
'License :: OSI Approved :: GNU General Public License (GPL)',
'License :: OSI Approved :: GNU Affero General Public License v3',
'Natural Language :: English',
'Operating System :: OS Independent',
'Programming Language :: Python',
@ -93,10 +93,48 @@ setup(
#
# package # low high
'progressbar', # 2.3
'pytz', # 2012b
],
# extras_require = {
# #
# # Same guidelines apply to the extra dependencies:
# 'db': [
# #
# # package # low high
# #
# 'SQLAlchemy', # 0.6.7
# 'sqlalchemy-migrate', # 0.6.1
# ],
# 'pyramid': [
# #
# # package # low high
# #
# # Pyramid 1.3 introduced 'pcreate' command (and friends) to replace
# # deprecated 'paster create' (and friends).
# 'pyramid>=1.3a1', # 1.3b2
# ],
# },
packages = find_packages(),
include_package_data = True,
zip_safe = False,
entry_points = """
[console_scripts]
edbob = edbob.commands:main
[gui_scripts]
edbobw = edbob.commands:main
[edbob.commands]
shell = edbob.commands:ShellCommand
uuid = edbob.commands:UuidCommand
""",
)