2007-07-16 20:22:38 +00:00
|
|
|
import cherrypy
|
2008-11-18 23:11:58 +00:00
|
|
|
import re
|
2007-07-16 20:22:38 +00:00
|
|
|
from cgi import escape
|
|
|
|
from Html_cleaner import Html_cleaner
|
|
|
|
|
|
|
|
|
|
|
|
class Validation_error( Exception ):
|
|
|
|
"""
|
|
|
|
An exception raised when form validation fails for some reason.
|
|
|
|
"""
|
|
|
|
|
|
|
|
MESSAGE_MAP = {
|
|
|
|
int: u"can only contain digits",
|
|
|
|
}
|
|
|
|
|
|
|
|
def __init__( self, name, value, value_type, message = None ):
|
|
|
|
Exception.__init__( self )
|
|
|
|
self.__name = name
|
|
|
|
self.__value = value
|
|
|
|
self.__value_type = value_type
|
|
|
|
|
|
|
|
if message is None:
|
|
|
|
# if the value's type has a message member, use that. otherwise, look up the type in a map
|
|
|
|
if hasattr( value_type, u"message" ):
|
|
|
|
self.__message = value_type.message
|
|
|
|
else:
|
|
|
|
self.__message = self.MESSAGE_MAP.get( value_type, u"is invalid" )
|
|
|
|
else:
|
|
|
|
self.__message = message
|
|
|
|
|
|
|
|
def __str__( self ):
|
|
|
|
return self.__message
|
|
|
|
|
|
|
|
def to_dict( self ):
|
|
|
|
return dict(
|
Merged revisions 401-446 via svnmerge from
svn+ssh://torsion.org/home/luminotes/repos/luminotes/branches/postgres
................
r402 | witten | 2007-10-04 00:48:49 -0700 (Thu, 04 Oct 2007) | 3 lines
Initialized merge tracking via "svnmerge" with revisions "1-401" from
svn+ssh://torsion.org/home/luminotes/repos/luminotes/trunk
................
r404 | witten | 2007-10-04 01:17:07 -0700 (Thu, 04 Oct 2007) | 2 lines
Beginning a conversion from bsddb to postgres.
................
r405 | witten | 2007-10-04 01:18:58 -0700 (Thu, 04 Oct 2007) | 9 lines
Merged revisions 402-404 via svnmerge from
svn+ssh://torsion.org/home/luminotes/repos/luminotes/trunk
........
r403 | witten | 2007-10-04 01:14:45 -0700 (Thu, 04 Oct 2007) | 2 lines
Yay, no more stupid deprecation warnings from simplejson about the sre module.
........
................
r406 | witten | 2007-10-04 15:34:39 -0700 (Thu, 04 Oct 2007) | 4 lines
* Switched back to Python 2.4 because many Python modules in Debian are not packaged to work with Python 2.5
* Began removal of all references to Scheduler, @async, yield, and so on.
* Converted Database.py to support PostgreSQL and updated its unit tests accordingly.
................
r407 | witten | 2007-10-04 16:34:01 -0700 (Thu, 04 Oct 2007) | 2 lines
All unit tests for the new model classes now pass.
................
r409 | witten | 2007-10-05 00:53:56 -0700 (Fri, 05 Oct 2007) | 2 lines
Reordering some columns and adding some indices.
................
r410 | witten | 2007-10-05 16:08:37 -0700 (Fri, 05 Oct 2007) | 4 lines
Now adding trash notebooks to user_notebook table. Also switching db
conversion/verification tools back to require Python 2.5, since they still use
the old Scheduler, which requires 2.5 generator features.
................
r411 | witten | 2007-10-06 16:26:56 -0700 (Sat, 06 Oct 2007) | 2 lines
Lots more unit tests passing. Most of the recent work was on controller.Users and related stuff.
................
r412 | witten | 2007-10-07 01:52:12 -0700 (Sun, 07 Oct 2007) | 2 lines
controller.Users unit tests now finally pass!
................
r413 | witten | 2007-10-07 02:14:10 -0700 (Sun, 07 Oct 2007) | 3 lines
Got controller.Root unit tests passing.
Moved fake sql_* function shenanigans from Test_users.py to Test_controller.py, for use by other controller unit tests.
................
r414 | witten | 2007-10-08 23:11:11 -0700 (Mon, 08 Oct 2007) | 2 lines
All unit tests pass! Fuck yeah!
................
r415 | witten | 2007-10-08 23:13:07 -0700 (Mon, 08 Oct 2007) | 2 lines
Removing all references to Scheduler from luminotes.py
................
r416 | witten | 2007-10-08 23:54:51 -0700 (Mon, 08 Oct 2007) | 3 lines
Converted deleted_from to deleted_from_id in a few more places.
Fixed bug in Users.contents().
................
r417 | witten | 2007-10-09 00:11:59 -0700 (Tue, 09 Oct 2007) | 3 lines
Typo fix in Note sql method.
Adding autocommit flag to Database.next_id() method.
................
r418 | witten | 2007-10-09 00:13:19 -0700 (Tue, 09 Oct 2007) | 2 lines
Updating unit test for new auto commit flag.
................
r419 | witten | 2007-10-09 00:14:09 -0700 (Tue, 09 Oct 2007) | 2 lines
Removing debugging print.
................
r420 | witten | 2007-10-09 00:20:55 -0700 (Tue, 09 Oct 2007) | 2 lines
More sql fixes. I really need some funtional tests that hit the database and exercise the SQL.
................
r421 | witten | 2007-10-09 00:51:34 -0700 (Tue, 09 Oct 2007) | 3 lines
Fixed controller.Database handling of tuple as an Object_type.
Made SQL for user storage calculation better at handling null values and also more succinct.
................
r422 | witten | 2007-10-09 13:32:16 -0700 (Tue, 09 Oct 2007) | 2 lines
Converting Wiki.js to trash_id notebook member instead of trash object.
................
r423 | witten | 2007-10-09 13:42:10 -0700 (Tue, 09 Oct 2007) | 2 lines
No longer displaying "download as html" on the front page, as people see "download" and think they're downloading the software.
................
r424 | witten | 2007-10-09 14:24:40 -0700 (Tue, 09 Oct 2007) | 2 lines
Notebooks.contents() now returns notebooks with correct read-write status.
................
r425 | witten | 2007-10-09 14:32:25 -0700 (Tue, 09 Oct 2007) | 2 lines
Fixed reporting of validation errors to the user. Now says "The blah is missing." instead of just "is missing"
................
r426 | witten | 2007-10-09 17:05:22 -0700 (Tue, 09 Oct 2007) | 2 lines
No longer redirecting to trash notebook upon login.
................
r427 | witten | 2007-10-09 17:20:33 -0700 (Tue, 09 Oct 2007) | 2 lines
Made controller.Database use a connection pool.
................
r429 | witten | 2007-10-09 20:13:30 -0700 (Tue, 09 Oct 2007) | 2 lines
Converted initdb.py and updatedb.py to Postgres from bsddb.
................
r430 | witten | 2007-10-09 20:37:14 -0700 (Tue, 09 Oct 2007) | 2 lines
Changing error message to remove underscores from variable names.
................
r431 | witten | 2007-10-10 13:23:30 -0700 (Wed, 10 Oct 2007) | 2 lines
Removing unused note_title parameter from Wiki.create_editor().
................
r432 | witten | 2007-10-10 13:25:16 -0700 (Wed, 10 Oct 2007) | 2 lines
Revision regular expression now supports timezone notation.
................
r433 | witten | 2007-10-10 14:43:47 -0700 (Wed, 10 Oct 2007) | 2 lines
Finished implementing ranked ordering for startup notes. (However, there's no way to change the rank from the client yet.)
................
r434 | witten | 2007-10-10 16:25:19 -0700 (Wed, 10 Oct 2007) | 4 lines
More strict access checking. Fixed oversight in Postgres DB conversion where,
in certain controller.Notebook methods, access was only checked at the
notebook level, not at the note level as well.
................
r435 | witten | 2007-10-10 17:45:18 -0700 (Wed, 10 Oct 2007) | 3 lines
Now loading revisions on demand from client when the "changes" button is clicked. Also caching
loading revisions so subsequent clicks don't have to reload.
................
r436 | witten | 2007-10-10 21:31:20 -0700 (Wed, 10 Oct 2007) | 2 lines
Tweaking some of the error handling in Expose and Root so that unhandled errors give a generic error message to the client.
................
r437 | witten | 2007-10-10 21:33:49 -0700 (Wed, 10 Oct 2007) | 2 lines
The release script no longer runs initdb.py, because the default database is no longer a single file included in the tarball.
................
r438 | witten | 2007-10-10 21:40:11 -0700 (Wed, 10 Oct 2007) | 2 lines
Updated install instructuctions to include use of initdb.py.
................
r439 | witten | 2007-10-10 21:56:42 -0700 (Wed, 10 Oct 2007) | 3 lines
Made initdb.py only nuke (drop tables/views) when given a command-line flag.
Also made install directions more correct.
................
r440 | witten | 2007-10-10 21:58:48 -0700 (Wed, 10 Oct 2007) | 2 lines
IE 6 doesn't like commas.
................
r441 | witten | 2007-10-10 22:08:50 -0700 (Wed, 10 Oct 2007) | 4 lines
load your notebook. without clicking on "changes", edit a note that has previous revisions. click on "changes". it'll only show
the most recent revision. fixed by not appending to changes as a result of a save unless the client-side revisions list cache has
something in it
................
r442 | witten | 2007-10-10 23:30:41 -0700 (Wed, 10 Oct 2007) | 2 lines
Forgot to actually save off the new revision as editor.revision.
................
r443 | witten | 2007-10-11 01:35:54 -0700 (Thu, 11 Oct 2007) | 13 lines
More intelligent datetime handling:
* convertdb.py assumes old bsddb database timestamps are Pacific, and then
converts them to UTC before inserting them into the new PostgreSQL
database.
* No longer using naked timezoneless datetime objects in model/controller
code, except in unit tests that need compatability with pysqlite. Now
using UTC everwhere.
* Asking PostgreSQL to give us all timestamps back in UTC.
* New dependency on python-tz (pytz) package, noted in INSTALL doc.
* Client now responsible for converting UTC timestamps to local time for
display.
................
r444 | witten | 2007-10-11 01:46:09 -0700 (Thu, 11 Oct 2007) | 2 lines
Tweak to prevent potential race in IE.
................
r445 | witten | 2007-10-11 01:49:58 -0700 (Thu, 11 Oct 2007) | 2 lines
Got JavaScript "unit" tests passing again.
................
r446 | witten | 2007-10-11 01:53:58 -0700 (Thu, 11 Oct 2007) | 2 lines
Noting that js tests require the Luminotes server on localhost.
................
2007-10-11 09:03:43 +00:00
|
|
|
error = u"The %s %s." % ( self.__name.replace( u"_", " " ), self.__message ),
|
2007-07-16 20:22:38 +00:00
|
|
|
name = self.__name,
|
|
|
|
value = self.__value,
|
|
|
|
)
|
|
|
|
|
|
|
|
name = property( lambda self: self.__name )
|
|
|
|
value = property( lambda self: self.__value )
|
|
|
|
value_type = property( lambda self: self.__value_type )
|
|
|
|
message = property( lambda self: self.__message )
|
|
|
|
|
|
|
|
|
|
|
|
class Valid_string( object ):
|
|
|
|
"""
|
|
|
|
Validator for a string of certain minimum and maximum lengths.
|
|
|
|
"""
|
|
|
|
moron_map = {
|
|
|
|
u"\xa0": u" ",
|
|
|
|
u"\xa9": u"(c)",
|
|
|
|
u"\xae": u"(r)",
|
|
|
|
u"\xb7": u"*",
|
|
|
|
u"\u2002": u" ",
|
|
|
|
u"\u2003": u" ",
|
|
|
|
u"\u2009": u" ",
|
|
|
|
u"\u2010": u"-",
|
|
|
|
u"\u2011": u"-",
|
|
|
|
u"\u2013": u"-",
|
|
|
|
u"\u2014": u"--",
|
|
|
|
u"\u2015": u"--",
|
|
|
|
u"\u2016": u"--",
|
|
|
|
u"\u2017": u"||",
|
|
|
|
u"\u2018": u"'",
|
|
|
|
u"\u2019": u"'",
|
|
|
|
u"\u201a": u",",
|
|
|
|
u"\u201b": u"'",
|
|
|
|
u"\u201c": u'"',
|
|
|
|
u"\u201d": u'"',
|
|
|
|
u"\u201e": u",,",
|
|
|
|
u"\u201f": u'"',
|
|
|
|
u"\u2022": u"*",
|
|
|
|
u"\u2023": u"*",
|
|
|
|
u"\u2024": u".",
|
|
|
|
u"\u2025": u"..",
|
|
|
|
u"\u2026": u"...",
|
|
|
|
u"\u2027": u".",
|
|
|
|
u"\u2122": u"(tm)",
|
|
|
|
}
|
|
|
|
|
2008-08-08 23:39:53 +00:00
|
|
|
def __init__( self, min = None, max = None, escape_html = True, require_link_target = False ):
|
2007-07-16 20:22:38 +00:00
|
|
|
self.min = min
|
|
|
|
self.max = max
|
|
|
|
self.escape_html = escape_html
|
2008-08-08 23:39:53 +00:00
|
|
|
self.require_link_target = require_link_target
|
2007-07-16 20:22:38 +00:00
|
|
|
self.message = None
|
|
|
|
|
|
|
|
def __call__( self, value ):
|
|
|
|
value = self.__demoronize( value.strip() )
|
|
|
|
|
|
|
|
if self.min is not None and len( value ) < self.min:
|
|
|
|
if self.min == 1:
|
|
|
|
self.message = u"is missing"
|
|
|
|
else:
|
|
|
|
self.message = u"must be at least %s characters long" % self.min
|
|
|
|
raise ValueError()
|
|
|
|
|
|
|
|
# either escape all html completely or just clean up the html, stripping out everything that's
|
|
|
|
# not on a tag/attribute whitelist
|
|
|
|
if self.escape_html:
|
2008-08-06 00:42:56 +00:00
|
|
|
value = escape( value, quote = True )
|
2007-07-16 20:22:38 +00:00
|
|
|
else:
|
2008-08-08 23:39:53 +00:00
|
|
|
cleaner = Html_cleaner( self.require_link_target )
|
2008-08-06 00:42:56 +00:00
|
|
|
value = cleaner.strip( value )
|
|
|
|
|
|
|
|
# check for max length after cleaning html, as cleaning can reduce the html's size
|
|
|
|
if self.max is not None and len( value ) > self.max:
|
2008-08-15 22:00:16 +00:00
|
|
|
self.message = u"must be no longer than %s characters. Please try removing some of the text" % self.max
|
2008-08-06 00:42:56 +00:00
|
|
|
raise ValueError()
|
|
|
|
|
|
|
|
return value
|
2007-07-16 20:22:38 +00:00
|
|
|
|
|
|
|
def __demoronize( self, value ):
|
|
|
|
"""
|
|
|
|
Convert stupid Microsoft unicode symbols to saner, cross-platform equivalents.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
for ( moron_symbol, replacement ) in self.moron_map.items():
|
|
|
|
value = value.replace( moron_symbol, replacement )
|
|
|
|
except:
|
|
|
|
import traceback
|
|
|
|
traceback.print_exc()
|
|
|
|
raise
|
|
|
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
|
|
class Valid_bool( object ):
|
|
|
|
"""
|
|
|
|
Validator for a boolean value.
|
|
|
|
"""
|
2008-03-18 22:22:19 +00:00
|
|
|
def __init__( self, none_okay = False ):
|
|
|
|
self.__none_okay = none_okay
|
|
|
|
|
2007-07-16 20:22:38 +00:00
|
|
|
def __call__( self, value ):
|
|
|
|
value = value.strip()
|
2008-03-18 22:22:19 +00:00
|
|
|
if self.__none_okay and value in ( None, "None", "" ): return None
|
2007-07-16 20:22:38 +00:00
|
|
|
|
|
|
|
if value in ( u"True", u"true" ): return True
|
|
|
|
if value in ( u"False", u"false" ): return False
|
|
|
|
|
|
|
|
raise ValueError()
|
|
|
|
|
|
|
|
|
2007-10-30 23:05:46 +00:00
|
|
|
class Valid_int( object ):
|
|
|
|
"""
|
|
|
|
Validator for an integer value.
|
|
|
|
"""
|
2008-03-24 22:33:00 +00:00
|
|
|
def __init__( self, min = None, max = None, none_okay = False ):
|
2007-10-30 23:05:46 +00:00
|
|
|
self.min = min
|
|
|
|
self.max = max
|
|
|
|
self.message = None
|
2008-03-24 22:33:00 +00:00
|
|
|
self.__none_okay = none_okay
|
2007-10-30 23:05:46 +00:00
|
|
|
|
|
|
|
def __call__( self, value ):
|
2008-03-24 22:33:00 +00:00
|
|
|
if self.__none_okay and value in ( None, "None", "" ): return None
|
2007-10-30 23:05:46 +00:00
|
|
|
value = int( value )
|
|
|
|
|
2008-02-18 20:08:07 +00:00
|
|
|
if self.min is not None and value < self.min:
|
2007-10-30 23:05:46 +00:00
|
|
|
self.message = "is too small"
|
2008-02-18 20:08:07 +00:00
|
|
|
raise ValueError()
|
|
|
|
if self.max is not None and value > self.max:
|
2007-10-30 23:05:46 +00:00
|
|
|
self.message = "is too large"
|
2008-02-18 20:08:07 +00:00
|
|
|
raise ValueError()
|
2007-10-30 23:05:46 +00:00
|
|
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
2008-11-18 23:11:58 +00:00
|
|
|
class Valid_friendly_id( object ):
|
|
|
|
FRIENDLY_ID_PATTERN = re.compile( "^[a-zA-Z0-9\-]+$" )
|
|
|
|
|
|
|
|
def __call__( self, value ):
|
|
|
|
if self.FRIENDLY_ID_PATTERN.search( value ):
|
|
|
|
return value
|
|
|
|
|
|
|
|
raise ValueError()
|
|
|
|
|
|
|
|
|
2007-07-28 04:22:44 +00:00
|
|
|
def validate( **expected ):
|
2007-07-16 20:22:38 +00:00
|
|
|
"""
|
|
|
|
validate() can be used to require that the arguments of the decorated method successfully pass
|
|
|
|
through particular validators. The validate() method itself is evaluated where it is used as a
|
|
|
|
decorator, which just returns decorate() to be used as the actual decorator.
|
|
|
|
|
|
|
|
Example usage:
|
|
|
|
|
|
|
|
@validate(
|
|
|
|
foo = Valid_string( min = 5, max = 10 ),
|
|
|
|
bar = int
|
|
|
|
)
|
|
|
|
def method( self, foo, bar ): pass
|
|
|
|
|
|
|
|
Note that validate() currently only works for instance methods (methods that take self as the
|
|
|
|
first argument). Also note that you can use multiple validators for a single argument.
|
|
|
|
|
|
|
|
Example usage:
|
|
|
|
|
|
|
|
@validate(
|
|
|
|
foo = Valid_string( min = 5, max = 10 ),
|
|
|
|
bar = ( int, valid_bar )
|
|
|
|
)
|
|
|
|
def method( self, foo, bar ): pass
|
|
|
|
|
|
|
|
"""
|
|
|
|
def decorate( function ):
|
|
|
|
"""
|
|
|
|
When the method being decorated is invoked, its decorator gets invoked instead and is supposed
|
|
|
|
to return a new function to use in place of the method being decorated (or a modified version
|
|
|
|
of that function). In this case, the decorator is our decorate() function, and the function it
|
|
|
|
returns is the check() function. decorate()'s first argument is the method being decorated.
|
|
|
|
"""
|
|
|
|
def check( *args, **kwargs ):
|
|
|
|
"""
|
|
|
|
check() pretends that it's the method being decorated. It takes the same arguments and then
|
|
|
|
invokes the actual method being decorated, passing in those arguments, but only after first
|
|
|
|
validating all of those arguments to that function. If validation fails, a Validation_error
|
|
|
|
is raised. Note that in Python, keyword argument names have to be str, not unicode.
|
|
|
|
"""
|
|
|
|
args = list( args )
|
|
|
|
args_index = 1 # skip the self argument
|
|
|
|
|
2009-03-20 04:38:42 +00:00
|
|
|
# make sure all kwarg names are lowercase and don't have dashes
|
|
|
|
for ( kwarg_name, value ) in kwargs.items():
|
|
|
|
new_kwarg_name = kwarg_name.replace( "-", "_" ).lower()
|
|
|
|
if new_kwarg_name != kwarg_name:
|
|
|
|
del( kwargs[ kwarg_name ] )
|
|
|
|
kwargs[ new_kwarg_name ] = value
|
|
|
|
|
2007-07-28 04:22:44 +00:00
|
|
|
# determine the expected argument names from the decorated function itself
|
|
|
|
code = function.func_code
|
|
|
|
expected_names = code.co_varnames[ : code.co_argcount ]
|
|
|
|
|
|
|
|
# validate each of the expected arguments
|
|
|
|
for expected_name in expected_names:
|
|
|
|
if expected_name == u"self": continue
|
|
|
|
expected_type = expected.get( expected_name )
|
|
|
|
|
|
|
|
# look for expected_name in kwargs and store the validated value there
|
|
|
|
if expected_name in kwargs:
|
|
|
|
value = kwargs.get( expected_name )
|
|
|
|
# if there's a tuple of multiple validators for this expected_name, use all of them
|
|
|
|
if isinstance( expected_type, tuple ):
|
|
|
|
for validator in expected_type:
|
2007-07-16 20:22:38 +00:00
|
|
|
try:
|
|
|
|
value = validator( value )
|
|
|
|
except ( ValueError, TypeError ):
|
2007-07-28 04:22:44 +00:00
|
|
|
raise Validation_error( expected_name, value, validator )
|
|
|
|
kwargs[ str( expected_name ) ] = value
|
2007-07-16 20:22:38 +00:00
|
|
|
# otherwise, there's just a single validator
|
|
|
|
else:
|
|
|
|
try:
|
2007-07-28 04:22:44 +00:00
|
|
|
kwargs[ str( expected_name ) ] = expected_type( value )
|
2007-07-16 20:22:38 +00:00
|
|
|
except ( ValueError, TypeError ):
|
2007-07-28 04:22:44 +00:00
|
|
|
raise Validation_error( expected_name, value, expected_type )
|
2007-07-16 20:22:38 +00:00
|
|
|
continue
|
|
|
|
|
2007-07-28 04:22:44 +00:00
|
|
|
# expected_name wasn't found in kwargs, so look for it in args. if it's not there either,
|
|
|
|
# raise unless there's a default value for the argument in the decorated function
|
2007-07-16 20:22:38 +00:00
|
|
|
if args_index >= len( args ):
|
2007-07-28 04:22:44 +00:00
|
|
|
if function.func_defaults and args_index >= len( args ) - len( function.func_defaults ):
|
|
|
|
continue
|
|
|
|
raise Validation_error( expected_name, None, expected_type, message = u"is required" )
|
2007-07-16 20:22:38 +00:00
|
|
|
value = args[ args_index ]
|
|
|
|
|
2007-07-28 04:22:44 +00:00
|
|
|
# if there's a tuple of multiple validators for this expected_name, use all of them
|
|
|
|
if isinstance( expected_type, tuple ):
|
|
|
|
for validator in expected_type:
|
2007-07-16 20:22:38 +00:00
|
|
|
try:
|
|
|
|
value = validator( value )
|
|
|
|
except ( ValueError, TypeError ):
|
2007-07-28 04:22:44 +00:00
|
|
|
raise Validation_error( expected_name, value, validator )
|
2007-07-16 20:22:38 +00:00
|
|
|
args[ args_index ] = value
|
|
|
|
# otherwise, there's just a single validator
|
|
|
|
else:
|
|
|
|
try:
|
2007-07-28 04:22:44 +00:00
|
|
|
args[ args_index ] = expected_type( value )
|
2007-07-16 20:22:38 +00:00
|
|
|
except ( ValueError, TypeError ):
|
2007-07-28 04:22:44 +00:00
|
|
|
raise Validation_error( expected_name, value, expected_type )
|
2007-07-16 20:22:38 +00:00
|
|
|
args_index += 1
|
|
|
|
|
2007-07-28 04:22:44 +00:00
|
|
|
# if there are any unexpected arguments, raise
|
2007-07-16 20:22:38 +00:00
|
|
|
for ( arg_name, arg_value ) in kwargs.items():
|
2007-07-28 04:22:44 +00:00
|
|
|
if not arg_name in expected_names:
|
2007-07-16 20:22:38 +00:00
|
|
|
raise Validation_error( arg_name, arg_value, None, message = u"is an unknown argument" )
|
|
|
|
|
|
|
|
return function( *args, **kwargs )
|
|
|
|
|
|
|
|
return check
|
|
|
|
|
|
|
|
return decorate
|