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
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
Version 3, 29 June 2007
|
Version 3, 19 November 2007
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
@ -7,17 +7,15 @@
|
||||||
|
|
||||||
Preamble
|
Preamble
|
||||||
|
|
||||||
The GNU General Public License is a free, copyleft license for
|
The GNU Affero General Public License is a free, copyleft license for
|
||||||
software and other kinds of works.
|
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
|
The licenses for most software and other practical works are designed
|
||||||
to take away your freedom to share and change the works. By contrast,
|
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
|
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
|
software for all its users.
|
||||||
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.
|
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not
|
When we speak of free software, we are referring to freedom, not
|
||||||
price. Our General Public Licenses are designed to make sure that you
|
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
|
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.
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
To protect your rights, we need to prevent others from denying you
|
Developers that use our General Public Licenses protect your rights
|
||||||
these rights or asking you to surrender the rights. Therefore, you have
|
with two steps: (1) assert copyright on the software, and (2) offer
|
||||||
certain responsibilities if you distribute copies of the software, or if
|
you this License which gives you legal permission to copy, distribute
|
||||||
you modify it: responsibilities to respect the freedom of others.
|
and/or modify the software.
|
||||||
|
|
||||||
For example, if you distribute copies of such a program, whether
|
A secondary benefit of defending all users' freedom is that
|
||||||
gratis or for a fee, you must pass on to the recipients the same
|
improvements made in alternate versions of the program, if they
|
||||||
freedoms that you received. You must make sure that they, too, receive
|
receive widespread use, become available for other developers to
|
||||||
or can get the source code. And you must show them these terms so they
|
incorporate. Many developers of free software are heartened and
|
||||||
know their rights.
|
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:
|
The GNU Affero General Public License is designed specifically to
|
||||||
(1) assert copyright on the software, and (2) offer you this License
|
ensure that, in such cases, the modified source code becomes available
|
||||||
giving you legal permission to copy, distribute and/or modify it.
|
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
|
An older license, called the Affero General Public License and
|
||||||
that there is no warranty for this free software. For both users' and
|
published by Affero, was designed to accomplish similar goals. This is
|
||||||
authors' sake, the GPL requires that modified versions be marked as
|
a different license, not a version of the Affero GPL, but Affero has
|
||||||
changed, so that their problems will not be attributed erroneously to
|
released a new version of the Affero GPL which permits relicensing under
|
||||||
authors of previous versions.
|
this license.
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and
|
The precise terms and conditions for copying, distribution and
|
||||||
modification follow.
|
modification follow.
|
||||||
|
@ -72,7 +60,7 @@ modification follow.
|
||||||
|
|
||||||
0. Definitions.
|
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
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
works, such as semiconductor masks.
|
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
|
the Program, the only way you could satisfy both those terms and this
|
||||||
License would be to refrain entirely from conveying the Program.
|
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
|
Notwithstanding any other provision of this License, you have
|
||||||
permission to link or combine any covered work with a work licensed
|
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
|
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,
|
License will continue to apply to the part which is the covered work,
|
||||||
but the special requirements of the GNU Affero General Public License,
|
but the work with which it is combined will remain governed by version
|
||||||
section 13, concerning interaction through a network will apply to the
|
3 of the GNU General Public License.
|
||||||
combination as such.
|
|
||||||
|
|
||||||
14. Revised Versions of this License.
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
The Free Software Foundation may publish revised and/or new versions of
|
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
|
the GNU Affero General Public License from time to time. Such new versions
|
||||||
be similar in spirit to the present version, but may differ in detail to
|
will be similar in spirit to the present version, but may differ in detail to
|
||||||
address new problems or concerns.
|
address new problems or concerns.
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the
|
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
|
Public License "or any later version" applies to it, you have the
|
||||||
option of following the terms and conditions either of that numbered
|
option of following the terms and conditions either of that numbered
|
||||||
version or of any later version published by the Free Software
|
version or of any later version published by the Free Software
|
||||||
Foundation. If the Program does not specify a version number of the
|
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.
|
by the Free Software Foundation.
|
||||||
|
|
||||||
If the Program specifies that a proxy can decide which future
|
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
|
public statement of acceptance of a version permanently authorizes you
|
||||||
to choose that version for the Program.
|
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>
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
(at your option) any later version.
|
(at your option) any later version.
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
This program is distributed in the hope that it will be useful,
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
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/>.
|
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.
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
If the program does terminal interaction, make it output a short
|
If your software can interact with users remotely through a computer
|
||||||
notice like this when it starts in an interactive mode:
|
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
|
||||||
<program> Copyright (C) <year> <name of author>
|
interface could display a "Source" link that leads users to an archive
|
||||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
of the code. There are many ways you could offer source, and different
|
||||||
This is free software, and you are welcome to redistribute it
|
solutions will be better for different programs; see section 13 for the
|
||||||
under certain conditions; type `show c' for details.
|
specific requirements.
|
||||||
|
|
||||||
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".
|
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or school,
|
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.
|
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/>.
|
<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
|
||||||
=====
|
=====
|
||||||
|
|
||||||
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
|
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
|
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.
|
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
|
Installation
|
||||||
|
@ -16,10 +18,6 @@ Installation
|
||||||
|
|
||||||
Install the software with::
|
Install the software with::
|
||||||
|
|
||||||
$ easy_install edbob
|
|
||||||
|
|
||||||
or::
|
|
||||||
|
|
||||||
$ pip install edbob
|
$ pip install edbob
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,23 +2,23 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# edbob -- Pythonic software framework
|
# edbob -- Pythonic Software Framework
|
||||||
# Copyright © 2010,2011,2012 Lance Edgar
|
# Copyright © 2010-2012 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of edbob.
|
# This file is part of edbob.
|
||||||
#
|
#
|
||||||
# edbob is free software: you can redistribute it and/or modify it under the
|
# 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
|
# terms of the GNU Affero General Public License as published by the Free
|
||||||
# Foundation, either version 3 of the License, or (at your option) any later
|
# Software Foundation, either version 3 of the License, or (at your option)
|
||||||
# version.
|
# any later version.
|
||||||
#
|
#
|
||||||
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
|
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
||||||
# details.
|
# more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License along with
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# edbob. If not, see <http://www.gnu.org/licenses/>.
|
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
|
@ -29,6 +29,11 @@
|
||||||
|
|
||||||
from edbob._version import __version__
|
from edbob._version import __version__
|
||||||
from edbob.core import *
|
from edbob.core import *
|
||||||
|
from edbob.times import *
|
||||||
from edbob.files import *
|
from edbob.files import *
|
||||||
from edbob.modules 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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# edbob -- Pythonic software framework
|
# edbob -- Pythonic Software Framework
|
||||||
# Copyright © 2010,2011,2012 Lance Edgar
|
# Copyright © 2010-2012 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of edbob.
|
# This file is part of edbob.
|
||||||
#
|
#
|
||||||
# edbob is free software: you can redistribute it and/or modify it under the
|
# 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
|
# terms of the GNU Affero General Public License as published by the Free
|
||||||
# Foundation, either version 3 of the License, or (at your option) any later
|
# Software Foundation, either version 3 of the License, or (at your option)
|
||||||
# version.
|
# any later version.
|
||||||
#
|
#
|
||||||
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
|
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
||||||
# details.
|
# more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License along with
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# edbob. If not, see <http://www.gnu.org/licenses/>.
|
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
|
@ -29,15 +29,16 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import uuid
|
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):
|
class Object(object):
|
||||||
"""
|
"""
|
||||||
Generic base class which provides a common ancestor, and some constructor
|
Generic base class which provides a common ancestor, and some other
|
||||||
convenience.
|
conveniences.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
|
@ -49,18 +50,43 @@ class Object(object):
|
||||||
for key in kwargs:
|
for key in kwargs:
|
||||||
setattr(self, key, kwargs[key])
|
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
|
Does some basic configuration on the logger qualified by ``appname``.
|
||||||
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
|
.. note::
|
||||||
:func:`edbob.init()`.
|
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 = logging.StreamHandler()
|
||||||
handler.setFormatter(logging.Formatter('%(name)s: %(levelname)s: %(message)s'))
|
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():
|
def get_uuid():
|
||||||
|
@ -70,3 +96,35 @@ def get_uuid():
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return uuid.uuid1().hex
|
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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# edbob -- Pythonic software framework
|
# edbob -- Pythonic Software Framework
|
||||||
# Copyright © 2010,2011,2012 Lance Edgar
|
# Copyright © 2010-2012 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of edbob.
|
# This file is part of edbob.
|
||||||
#
|
#
|
||||||
# edbob is free software: you can redistribute it and/or modify it under the
|
# 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
|
# terms of the GNU Affero General Public License as published by the Free
|
||||||
# Foundation, either version 3 of the License, or (at your option) any later
|
# Software Foundation, either version 3 of the License, or (at your option)
|
||||||
# version.
|
# any later version.
|
||||||
#
|
#
|
||||||
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
|
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
||||||
# details.
|
# more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License along with
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# edbob. If not, see <http://www.gnu.org/licenses/>.
|
# 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):
|
class LoadSpecError(Exception):
|
||||||
"""
|
"""
|
||||||
Raised when something obvious goes wrong with :func:`edbob.load_spec()`.
|
Raised when something obvious goes wrong with :func:`edbob.load_spec()`.
|
||||||
|
@ -55,3 +70,27 @@ class ModuleMissingAttribute(LoadSpecError):
|
||||||
def specifics(self):
|
def specifics(self):
|
||||||
mod, attr = self.spec.split(':')
|
mod, attr = self.spec.split(':')
|
||||||
return "module '%s' was loaded but '%s' attribute not found" % (mod, attr)
|
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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# edbob -- Pythonic software framework
|
# edbob -- Pythonic Software Framework
|
||||||
# Copyright © 2010,2011,2012 Lance Edgar
|
# Copyright © 2010-2012 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of edbob.
|
# This file is part of edbob.
|
||||||
#
|
#
|
||||||
# edbob is free software: you can redistribute it and/or modify it under the
|
# 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
|
# terms of the GNU Affero General Public License as published by the Free
|
||||||
# Foundation, either version 3 of the License, or (at your option) any later
|
# Software Foundation, either version 3 of the License, or (at your option)
|
||||||
# version.
|
# any later version.
|
||||||
#
|
#
|
||||||
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
|
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
||||||
# details.
|
# more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License along with
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# edbob. If not, see <http://www.gnu.org/licenses/>.
|
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
|
@ -28,10 +28,31 @@
|
||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import os.path
|
||||||
|
import shutil
|
||||||
import tempfile
|
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):
|
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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# edbob -- Pythonic software framework
|
# edbob -- Pythonic Software Framework
|
||||||
# Copyright © 2010,2011,2012 Lance Edgar
|
# Copyright © 2010-2012 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of edbob.
|
# This file is part of edbob.
|
||||||
#
|
#
|
||||||
# edbob is free software: you can redistribute it and/or modify it under the
|
# 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
|
# terms of the GNU Affero General Public License as published by the Free
|
||||||
# Foundation, either version 3 of the License, or (at your option) any later
|
# Software Foundation, either version 3 of the License, or (at your option)
|
||||||
# version.
|
# any later version.
|
||||||
#
|
#
|
||||||
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
|
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
||||||
# details.
|
# more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License along with
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# edbob. If not, see <http://www.gnu.org/licenses/>.
|
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
|
@ -35,6 +35,18 @@ from edbob import exceptions
|
||||||
__all__ = ['load_spec']
|
__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):
|
def last_module(module, module_path):
|
||||||
"""
|
"""
|
||||||
Returns the "last" module represented by ``module_path``, by walking
|
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))
|
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):
|
def load_spec(spec):
|
||||||
"""
|
"""
|
||||||
.. highlight:: none
|
.. 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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# edbob -- Pythonic software framework
|
# edbob -- Pythonic Software Framework
|
||||||
# Copyright © 2010,2011,2012 Lance Edgar
|
# Copyright © 2010-2012 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of edbob.
|
# This file is part of edbob.
|
||||||
#
|
#
|
||||||
# edbob is free software: you can redistribute it and/or modify it under the
|
# 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
|
# terms of the GNU Affero General Public License as published by the Free
|
||||||
# Foundation, either version 3 of the License, or (at your option) any later
|
# Software Foundation, either version 3 of the License, or (at your option)
|
||||||
# version.
|
# any later version.
|
||||||
#
|
#
|
||||||
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
|
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
||||||
# details.
|
# more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License along with
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# edbob. If not, see <http://www.gnu.org/licenses/>.
|
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ import datetime
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['local_time', 'set_timezone']
|
__all__ = ['local_time', 'set_timezone', 'utc_time']
|
||||||
|
|
||||||
_timezone = None
|
_timezone = None
|
||||||
|
|
||||||
|
@ -82,3 +82,16 @@ def set_timezone(tz):
|
||||||
_timezone = None
|
_timezone = None
|
||||||
else:
|
else:
|
||||||
_timezone = pytz.timezone(tz)
|
_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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# edbob -- Pythonic software framework
|
# edbob -- Pythonic Software Framework
|
||||||
# Copyright © 2010,2011,2012 Lance Edgar
|
# Copyright © 2010-2012 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of edbob.
|
# This file is part of edbob.
|
||||||
#
|
#
|
||||||
# edbob is free software: you can redistribute it and/or modify it under the
|
# 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
|
# terms of the GNU Affero General Public License as published by the Free
|
||||||
# Foundation, either version 3 of the License, or (at your option) any later
|
# Software Foundation, either version 3 of the License, or (at your option)
|
||||||
# version.
|
# any later version.
|
||||||
#
|
#
|
||||||
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
|
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
||||||
# details.
|
# more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License along with
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# edbob. If not, see <http://www.gnu.org/licenses/>.
|
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ setup(
|
||||||
author = "Lance Edgar",
|
author = "Lance Edgar",
|
||||||
author_email = "lance@edbob.org",
|
author_email = "lance@edbob.org",
|
||||||
url = "http://edbob.org/",
|
url = "http://edbob.org/",
|
||||||
license = "GNU GPL v3",
|
license = "GNU Affero GPL v3",
|
||||||
description = "Pythonic Software Framework",
|
description = "Pythonic Software Framework",
|
||||||
long_description = readme,
|
long_description = readme,
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ setup(
|
||||||
'Environment :: Win32 (MS Windows)',
|
'Environment :: Win32 (MS Windows)',
|
||||||
'Environment :: X11 Applications',
|
'Environment :: X11 Applications',
|
||||||
'Intended Audience :: Developers',
|
'Intended Audience :: Developers',
|
||||||
'License :: OSI Approved :: GNU General Public License (GPL)',
|
'License :: OSI Approved :: GNU Affero General Public License v3',
|
||||||
'Natural Language :: English',
|
'Natural Language :: English',
|
||||||
'Operating System :: OS Independent',
|
'Operating System :: OS Independent',
|
||||||
'Programming Language :: Python',
|
'Programming Language :: Python',
|
||||||
|
@ -93,10 +93,48 @@ setup(
|
||||||
#
|
#
|
||||||
# package # low high
|
# package # low high
|
||||||
|
|
||||||
|
'progressbar', # 2.3
|
||||||
'pytz', # 2012b
|
'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(),
|
packages = find_packages(),
|
||||||
include_package_data = True,
|
include_package_data = True,
|
||||||
zip_safe = False,
|
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