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:
parent
3d75732d36
commit
a6decbb313
36 changed files with 2910 additions and 172 deletions
141
COPYING.txt
141
COPYING.txt
|
@ -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>.
|
||||
|
|
10
README.txt
10
README.txt
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
299
edbob/commands.py
Normal 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
355
edbob/configuration.py
Normal 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
50
edbob/console.py
Normal 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, ''
|
|
@ -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
134
edbob/db/__init__.py
Normal 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
72
edbob/db/auth.py
Normal 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
163
edbob/db/classes.py
Normal 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
129
edbob/db/mappers.py
Normal 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
109
edbob/db/model.py
Normal 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
84
edbob/db/perms.py
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
101
edbob/initialization.py
Normal 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
0
edbob/lib/__init__.py
Normal file
76
edbob/lib/pretty.py
Normal file
76
edbob/lib/pretty.py
Normal 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
139
edbob/mail.py
Normal 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)
|
|
@ -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
|
||||
|
|
0
edbob/pyramid/__init__.py
Normal file
0
edbob/pyramid/__init__.py
Normal file
0
edbob/pyramid/forms/__init__.py
Normal file
0
edbob/pyramid/forms/__init__.py
Normal file
28
edbob/pyramid/forms/formalchemy.py
Normal file
28
edbob/pyramid/forms/formalchemy.py
Normal 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
|
||||
"""
|
||||
|
0
edbob/pyramid/handlers/__init__.py
Normal file
0
edbob/pyramid/handlers/__init__.py
Normal file
291
edbob/pyramid/handlers/base.py
Normal file
291
edbob/pyramid/handlers/base.py
Normal 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)
|
72
edbob/pyramid/handlers/util.py
Normal file
72
edbob/pyramid/handlers/util.py
Normal 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
33
edbob/pyramid/helpers.py
Normal 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 *
|
37
edbob/pyramid/paster_templates/__init__.py
Normal file
37
edbob/pyramid/paster_templates/__init__.py
Normal 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)
|
66
edbob/pyramid/subscribers.py
Normal file
66
edbob/pyramid/subscribers.py
Normal 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
39
edbob/sqlalchemy.py
Normal 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
|
|
@ -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
55
edbob/util.py
Normal 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
95
edbob/wx/GenerateUuid.py
Normal 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
87
edbob/wx/__init__.py
Normal 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()
|
65
edbob/wx/wxglade/GenerateUuid.wxg
Normal file
65
edbob/wx/wxglade/GenerateUuid.wxg
Normal 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>&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>&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>&Close</label>
|
||||
<id>wx.ID_OK</id>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</application>
|
60
setup.py
60
setup.py
|
@ -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
|
||||
|
||||
""",
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue