You can subscribe to this list here.
| 2004 |
Jan
|
Feb
|
Mar
(1) |
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
(10) |
Dec
(4) |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2005 |
Jan
(1) |
Feb
(8) |
Mar
(8) |
Apr
(4) |
May
(19) |
Jun
(1) |
Jul
(1) |
Aug
(18) |
Sep
(18) |
Oct
(19) |
Nov
(75) |
Dec
(80) |
| 2006 |
Jan
(86) |
Feb
(61) |
Mar
(60) |
Apr
(47) |
May
(39) |
Jun
(16) |
Jul
(30) |
Aug
(13) |
Sep
(13) |
Oct
(21) |
Nov
(1) |
Dec
(10) |
| 2007 |
Jan
(2) |
Feb
(7) |
Mar
(9) |
Apr
(3) |
May
(9) |
Jun
(4) |
Jul
(1) |
Aug
(2) |
Sep
|
Oct
(12) |
Nov
(1) |
Dec
(7) |
| 2008 |
Jan
|
Feb
(2) |
Mar
(14) |
Apr
(9) |
May
(23) |
Jun
(4) |
Jul
|
Aug
(13) |
Sep
(8) |
Oct
(15) |
Nov
(40) |
Dec
(14) |
| 2009 |
Jan
|
Feb
(4) |
Mar
(10) |
Apr
(2) |
May
(2) |
Jun
(1) |
Jul
|
Aug
|
Sep
|
Oct
|
Nov
|
Dec
|
| 2010 |
Jan
|
Feb
|
Mar
|
Apr
|
May
(1) |
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
|
Dec
|
| 2011 |
Jan
|
Feb
|
Mar
|
Apr
|
May
(1) |
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
|
Dec
|
|
From: <sub...@co...> - 2005-05-03 03:30:57
|
Author: ianb Date: 2005-05-03 03:30:46 +0000 (Tue, 03 May 2005) New Revision: 757 Modified: FormEncode/trunk/docs/Design.txt Log: Updated with new names, more limited ideas Modified: FormEncode/trunk/docs/Design.txt =================================================================== --- FormEncode/trunk/docs/Design.txt 2005-05-02 15:55:25 UTC (rev 756) +++ FormEncode/trunk/docs/Design.txt 2005-05-03 03:30:46 UTC (rev 757) @@ -1,10 +1,10 @@ -++++++++++++++++ -Validator Design -++++++++++++++++ ++++++++++++++++++ +FormEncode Design ++++++++++++++++++ .. contents:: -This is a document to describe why Validator looks the way it looks, +This is a document to describe why FormEncode looks the way it looks, and how it fits into other applications. It also talks some about the false starts I've made. @@ -15,33 +15,33 @@ Basic Metaphor ============== -Validator is performs look-before-you-leap validation. The idea being +FormEncode performs look-before-you-leap validation. The idea being that you check all the data related to an operation, then apply it. The alternative might be a transactional system, where you just start applying the data and if there's a problem you raise an exception. Someplace else you catch the exception and roll back the transaction. -Of course, Validator works fine with this system, but unlike this you -can use it without transactions. +Of course FormEncode works fine with such a system, but unlike this +you can use it without transactions. -Validator generally works on primitive types (though you could extend -it to deal with your own types if you wish). This fits in with +FormEncode generally works on primitive types (though you could extend +it to deal with your own types if you wish). These are things like +strings, lists, dictionaries, integers, etc. This fits in with look-before-you-leap; often your domain objects won't exist until after you apply the user's request, so it's necessary to work on an -early form of the data. Also, Validator doesn't know anything about +early form of the data. Also, FormEncode doesn't know anything about your domain objects or classes; it's just easier to keep it this way. -Validation only operates on a single value at a time. The key idea is -that the single value can itself be compound. So where some -validation packages have a built-in notion of fields, Validator simply -validates a dictionary; that it applies sub-validators to values in -that dictionary is an implementation detail internal to the validator. +Validation only operates on a single "value" at a time. This is +Python, collections are easy, and collections are themselves a single +"value" made up of many pieces. A "Schema validator" is a validator +made up of many subvalidators. Domain Objects ============== These are your objects, specific to your application. I know nothing -about them, and cannot know. Validator tries to make few requirements -on how these objects look. +about them, and cannot know. FormEncode doesn't do anything with +these objects, and doesn't try to know anything about them. At all. Validation as directional, not intrinsic ======================================== @@ -77,30 +77,29 @@ in a reasonable way, but we seldom trust the user under any circumstance. -In essence, there is an "inside" and an "outside". Validator is a -toolkit for bridging those two areas in a sensible manner. The -specific way we bridge this depends on the nature of the user +In essence, there is an "inside" and an "outside". FormEncode is a +toolkit for bridging those two areas in a sensible and secure way. +The specific way we bridge this depends on the nature of the user interface. An XML-RPC interface can make some assumptions that a GUI cannot make. An HTML interface can typically make even less assumptions, including the basic integrity of the input data. It isn't reasonable that the object should know about all means of inputs, and the varying UI requirements of those inputs; user interfaces are volatile, and more art than science, but domain objects -work better when they remain stable. +work better when they remain stable. For this reason the validation +schemas are kept in separate objects. It also didn't work well to annotate domain objects with validation -schemas, though the option remains open. Any object can be used as a -validator, so long as it can be adapted_ into a validator, so -potentially a domain class could be used as a validator, if you -register an adapter that can strip out the validation annotations. -But I leave this to the reader. +schemas, though the option remains open. This is experimentation that +belongs outside of FormEncode, simply because it's more specific to +your domain than it is to FormEncode. .. _adapted: Two sides, two aspects ====================== -Validator does both validation and version at the same time. +FormEncode does both validation and conversion at the same time. Validation necessarily happens with every conversion; for instance, you may want to convert string representation of dates to internal date objects; that conversion can fail if the string representation is @@ -108,7 +107,7 @@ To keep things simple, there's only one operation: conversion. An exception raised means there was an error. If you just want to -validate, that's a conversion that doesn't do anything. +validate, that's a conversion that doesn't change anything. Similarly, there's two sides to the system, the foreign data and the local data. In Validator the local data is called "python" (meaning, @@ -118,45 +117,34 @@ For instance, consider the date conversion. In one form, you may want a date like ``mm/dd/yyyy``. It's easy enough to make the necessary -converter; but the date object that results doesn't know how it's -supposed to be formatted for that form. ``repr`` or *any* method that -binds an object to its form representation is a bad idea. The -converter best knows how to undo its work. +converter; but the date object that the converter produces doesn't +know how it's supposed to be formatted for that form. ``repr`` or +*any* method that binds an object to its form representation is a bad +idea. The converter best knows how to undo its work. So a date +converter that expects ``mm/dd/yyyy`` will also know how to turn a +datetime into that format. (This becomes even more interesting with compound validators.) -Adaptation -========== - -So, now, adaptation. Validator uses PyProtocols_, which is an -interface/adaptation package (similar to Zope's interfaces). - -The basic idea behind adaptation is that objects can fulfill multiple -roles. When you adapt an object, you either see if the object -supports the given interface (``validator.interfaces.IValidator``), or -if there's any adapter registered that can convert your object. -PyProtocols describes it in more detail. - -.. _PyProtocols: http://peak.telecommunity.com/PyProtocols.html - -In the future, I might make PyProtocols optional, implementing some -stubs so that everything works in its absence. - Presentation ============ -FormEncode included form generation in addition to validation. The -form generation worked okay; it was reasonably attractive, and in many -ways quite powerful. I might revisit it. But generation is limited. -It works *great* at first, then you hit a wall -- you want to make a -change, and you just *can't*, it doesn't fit into the automatic -generation. +At one time FormEncode included form generation in addition to +validation. The form generation worked okay; it was reasonably +attractive, and in many ways quite powerful. I might revisit it. But +generation is limited. It works *great* at first, then you hit a wall +-- you want to make a change, and you just *can't*, it doesn't fit +into the automatic generation. -At this point, my preferred form generation is with htmlfill_. In -this model, you write the form however you want (maybe even generating -it in some fashion) and use ``htmlfill`` to fill in the default values -and report errors. +There are also many ways to approach the generation; again it's +something that is tied to the framework, the presentation layer, and +the domain objects, and FormEncode doesn't know anything about those. +Instead FormEncode uses htmlfill_. *You* produce the form however you +want. Write it out by hand. Use a templating language. Whatever. +Then htmlfill (which specifically understands HTML) fills in the form +and any error messages. + .. _htmlfill: htmlfill.html Declarative and Imperative @@ -165,9 +153,11 @@ All of the objects -- schemas, repeating elements, individual validators -- can be created imperatively, though more declarative styles often look better (specifically using subclassing instead of -construction). You are free to build validations from other sources. +construction). You are free to build the objects either way. -It would be cool, for instance, to augment htmlfill_ to look for -things like ``form:required`` or ``form:match-regex``, etc., and build -a validation schema out of that. +For instance, one extension to ``htmlfill`` +(``htmlfill_schemabuilder``) looks for special attributes in an HTML +form and builds a validator from that. Even though validation is +stored in a separate object from your domain, you can build those +validators programmatically. |
|
From: <sub...@co...> - 2005-04-24 18:24:21
|
Author: ianb Date: 2005-04-24 18:24:12 +0000 (Sun, 24 Apr 2005) New Revision: 737 Added: FormEncode/ FormEncode/branches/ FormEncode/branches/2004-pre-htmlfill/ FormEncode/trunk/ Removed: trunk/FormEncode/ trunk/Validator/ Log: Moved old FormEncode to a branch; made Validator the new trunk Copied: FormEncode/branches/2004-pre-htmlfill (from rev 736, trunk/FormEncode) Copied: FormEncode/trunk (from rev 736, trunk/Validator) |
|
From: <sub...@co...> - 2005-04-24 18:24:20
|
Author: ianb Date: 2005-04-24 18:24:12 +0000 (Sun, 24 Apr 2005) New Revision: 737 Added: FormEncode/ FormEncode/branches/ FormEncode/branches/2004-pre-htmlfill/ FormEncode/trunk/ Removed: trunk/FormEncode/ trunk/Validator/ Log: Moved old FormEncode to a branch; made Validator the new trunk Copied: FormEncode/branches/2004-pre-htmlfill (from rev 736, trunk/FormEncode) Copied: FormEncode/trunk (from rev 736, trunk/Validator) |
|
From: <sub...@co...> - 2005-04-15 21:53:37
|
Author: ianb
Date: 2005-04-15 21:53:30 +0000 (Fri, 15 Apr 2005)
New Revision: 725
Modified:
trunk/Validator/validator/dummy_protocols.py
Log:
Added missing symbol
Modified: trunk/Validator/validator/dummy_protocols.py
===================================================================
--- trunk/Validator/validator/dummy_protocols.py 2005-04-15 20:30:02 UTC (rev 724)
+++ trunk/Validator/validator/dummy_protocols.py 2005-04-15 21:53:30 UTC (rev 725)
@@ -15,3 +15,6 @@
def __init__(self, doc, name=None):
self.doc = doc
self.name = name
+
+class AdaptationFailure(Exception):
+ pass
|
|
From: <sub...@co...> - 2005-04-15 20:30:12
|
Author: ianb
Date: 2005-04-15 20:30:02 +0000 (Fri, 15 Apr 2005)
New Revision: 724
Modified:
trunk/Validator/validator/api.py
Log:
When PyProtocols isn't present, don't let people adapt non-validators to validator
Modified: trunk/Validator/validator/api.py
===================================================================
--- trunk/Validator/validator/api.py 2005-04-13 08:45:05 UTC (rev 723)
+++ trunk/Validator/validator/api.py 2005-04-15 20:30:02 UTC (rev 724)
@@ -1,7 +1,9 @@
try:
import protocols
+ dummy = False
except ImportError:
import dummy_protocols as protocols
+ dummy = True
from interfaces import *
from declarative import Declarative
@@ -16,6 +18,11 @@
try:
if isinstance(obj, type) and issubclass(obj, Declarative):
obj = obj.singleton()
+ if dummy:
+ if not isinstance(obj, Validator):
+ return None
+ else:
+ return obj
validator = protocols.adapt(
obj, IValidator)
if validator and state:
|
|
From: <sub...@co...> - 2005-03-13 20:47:17
|
Author: ianb
Date: 2005-03-13 20:47:13 +0000 (Sun, 13 Mar 2005)
New Revision: 680
Modified:
trunk/Validator/validator/schema.py
Log:
Added filter_extra_fields, from Ian Maurer; extra fields are ignored
if set true (and allow_extra_fields is also true)
Modified: trunk/Validator/validator/schema.py
===================================================================
--- trunk/Validator/validator/schema.py 2005-03-13 20:44:32 UTC (rev 679)
+++ trunk/Validator/validator/schema.py 2005-03-13 20:47:13 UTC (rev 680)
@@ -54,7 +54,8 @@
A schema validates a dictionary of values, applying different
validators (be key) to the different values. If
allow_extra_fields=True, keys without validators will be allowed;
- otherwise they will raise Invalid.
+ otherwise they will raise Invalid. If filter_extra_fields is
+ set to true, then extra fields are not passed back in the results.
Validators are associated with keys either with a class syntax, or
as keyword arguments (class syntax is usually easier). Something
@@ -80,6 +81,7 @@
chained_validators = []
pre_validators = []
allow_extra_fields = False
+ filter_extra_fields = False
compound = True
fields = {}
order = []
@@ -114,7 +116,8 @@
name=repr(name)),
value_dict, state)
else:
- new[name] = value
+ if not self.filter_extra_fields:
+ new[name] = value
continue
validator = adapt_validator(self.fields[name], state)
@@ -178,7 +181,8 @@
self.message('notExpected', state,
name=repr(name)),
value_dict, state)
- new[name] = value
+ if not self.filter_extra_fields:
+ new[name] = value
else:
try:
new[name] = from_python(self.fields[name],
|
|
From: <sub...@co...> - 2005-03-13 20:44:39
|
Author: ianb
Date: 2005-03-13 20:44:32 +0000 (Sun, 13 Mar 2005)
New Revision: 679
Added:
trunk/Validator/validator/htmlform.py
Log:
An experimental packaging of htmlfill and validators
Added: trunk/Validator/validator/htmlform.py
===================================================================
--- trunk/Validator/validator/htmlform.py 2005-03-13 09:31:40 UTC (rev 678)
+++ trunk/Validator/validator/htmlform.py 2005-03-13 20:44:32 UTC (rev 679)
@@ -0,0 +1,72 @@
+"""
+Class to encapsulate an HTML form, using htmlfill and
+htmlfill_schemabuilder
+
+Usage::
+
+ html = '<form action=...>...</form>'
+ class FormSchema(schema.Schema):
+ f1 = ...
+ form = HTMLForm(html, FormSchema())
+ errors = {}
+ if form_submitted:
+ form_result, errors = form.validate(request_dict)
+ if not errors:
+ do_action(form_result)
+ return
+ defaults = form.schema.from_python(get_defaults_from_model())
+ defaults.update(request_dict)
+ write(form.render(defaults, errors)
+
+You can also embed the schema in the form, using form:required, etc.,
+tags.
+
+"""
+
+import htmlfill
+import htmlfill_schemabuilder
+from validator import Invalid
+
+class HTMLForm(object):
+
+ def __init__(self, form, schema=None):
+ self.form = form
+ self._schema = schema
+
+ def schema__get(self):
+ if self._schema is not None:
+ return self._schema
+ self._schema = self.parse_schema()
+
+ def schema__set(self, value):
+ self._schema = value
+
+ def schema__del(self):
+ self._schema = None
+
+ schema = property(schema__get, schema__set, schema__del)
+
+ def parse_schema(self):
+ listener = htmlfill_schemabuilder.SchemaBuilder()
+ p = htmlfill.FillingParser(defaults={}, listener=listener)
+ p.feed(self.form)
+ p.close()
+ return listener.schema()
+
+ def render(self, defaults={}, errors={}):
+ p = htmlfill.FillingParser(
+ defaults=defaults, errors=errors,
+ use_all_keys=True)
+ p.feed(self.form)
+ p.close()
+ return p.text()
+
+ def validate(self, request_dict, state=None):
+ schema = self.schema
+ try:
+ result = schema.to_python(request_dict, state=state)
+ return result, None
+ except Invalid, e:
+ return None, e.error_dict
+
+
|
|
From: <sub...@co...> - 2005-03-13 09:31:44
|
Author: ianb
Date: 2005-03-13 09:31:40 +0000 (Sun, 13 Mar 2005)
New Revision: 678
Added:
trunk/Validator/validator/htmlfill_schemabuilder.py
Modified:
trunk/Validator/validator/htmlfill.py
trunk/Validator/validator/tests/htmlfill_data/data-schema1.txt
trunk/Validator/validator/tests/test_htmlfill.py
Log:
* Added a new schema builder, that is separate from htmlfill
* Minor test changes
Modified: trunk/Validator/validator/htmlfill.py
===================================================================
--- trunk/Validator/validator/htmlfill.py 2005-03-13 09:25:38 UTC (rev 677)
+++ trunk/Validator/validator/htmlfill.py 2005-03-13 09:31:40 UTC (rev 678)
@@ -1,8 +1,6 @@
import HTMLParser
import cgi
-import validators, schema, compound
-
def default_formatter(error):
return '<span class="error-message">%s</span><br />\n' % cgi.escape(error)
@@ -12,14 +10,6 @@
def escape_formatter(error):
return cgi.escape(error, 1)
-default_validators = dict([(name.lower(), getattr(validators, name)) for name in dir(validators)])
-
-def get_messages(cls, message):
- if not message:
- return {}
- else:
- return dict([(k, message) for k in cls._messages.keys()])
-
class FillingParser(HTMLParser.HTMLParser):
r"""
Fills HTML with default values, as in a form.
@@ -54,7 +44,7 @@
def __init__(self, defaults, errors=None, use_all_keys=False,
error_formatters=None, error_class='error',
- add_attributes=None, validators=default_validators):
+ add_attributes=None, listener=None):
HTMLParser.HTMLParser.__init__(self)
self.content = []
self.source = None
@@ -78,14 +68,14 @@
self.error_formatters = error_formatters
self.error_class = error_class
self.add_attributes = add_attributes or {}
- self.validators = validators
- self._schema = None
+ self.listener = listener
def feed(self, data):
self.source = data
self.lines = data.split('\n')
self.source_pos = 1, 0
- self._schema = schema.Schema()
+ if self.listener:
+ self.listener.reset()
HTMLParser.HTMLParser.feed(self, data)
def close(self):
@@ -132,7 +122,8 @@
return
else:
return
- self.update_schema(attrs)
+ if self.listener:
+ self.listener.listen_input(self, tag, attrs)
def handle_misc(self, whatever):
self.write_pos()
@@ -310,9 +301,6 @@
v.validators.append(vinst)
self._schema.add_field(name, v)
- def schema(self):
- return self._schema
-
def write_text(self, text):
self.content.append(text)
Added: trunk/Validator/validator/htmlfill_schemabuilder.py
===================================================================
--- trunk/Validator/validator/htmlfill_schemabuilder.py 2005-03-13 09:25:38 UTC (rev 677)
+++ trunk/Validator/validator/htmlfill_schemabuilder.py 2005-03-13 09:31:40 UTC (rev 678)
@@ -0,0 +1,64 @@
+import validators, schema, compound
+
+default_validators = dict(
+ [(name.lower(), getattr(validators, name))
+ for name in dir(validators)])
+
+def get_messages(cls, message):
+ if not message:
+ return {}
+ else:
+ return dict([(k, message) for k in cls._messages.keys()])
+
+def to_bool(value):
+ value = value.strip().lower()
+ if value in ('true', 't', 'yes', 'y', 'on', '1'):
+ return True
+ elif value in ('false', 'f', 'no', 'n', 'off', '0'):
+ return False
+ else:
+ raise ValueError("Not a boolean value: %r (use 'true'/'false')")
+
+class SchemaBuilder(object):
+
+ def __init__(self, validators=default_validators):
+ self.validators = validators
+ self._schema = None
+
+ def reset(self):
+ self._schema = schema.Schema()
+
+ def schema(self):
+ return self._schema
+
+ def listen_input(self, parser, tag, attrs):
+ get_attr = parser.get_attr
+ name = get_attr(attrs, 'name')
+ if not name:
+ # @@: should warn if you try to validate unnamed fields
+ return
+ v = compound.All()
+ message = get_attr(attrs, 'form:message')
+ required = to_bool(get_attr(attrs, 'form:required', 'false'))
+ if required:
+ v.validators.append(
+ validators.NotEmpty(
+ messages=get_messages(validators.NotEmpty, message)))
+ v_type = get_attr(attrs, 'form:validate', None)
+ if v_type:
+ pos = v_type.find(':')
+ if pos != -1:
+ # @@: should parse args
+ args = (v_type[pos+1:],)
+ v_type = v_type[:pos]
+ else:
+ args = ()
+ v_type = v_type.lower()
+ v_class = self.validators.get(v_type)
+ if not v_class:
+ raise ValueError("Invalid validation type: %r" % v_type)
+ kw_args={'messages': get_messages(v_class, message)}
+ v_inst = v_class(
+ *args, **kw_args)
+ v.validators.append(v_inst)
+ self._schema.add_field(name, v)
Modified: trunk/Validator/validator/tests/htmlfill_data/data-schema1.txt
===================================================================
--- trunk/Validator/validator/tests/htmlfill_data/data-schema1.txt 2005-03-13 09:25:38 UTC (rev 677)
+++ trunk/Validator/validator/tests/htmlfill_data/data-schema1.txt 2005-03-13 09:31:40 UTC (rev 678)
@@ -18,8 +18,7 @@
<input type="text" name="whatever" value="">
</form>
----
-def check(parser):
- schema = parser.schema()
+def check(parser, schema):
assert schema
names = schema.fields.keys()
names.sort()
Modified: trunk/Validator/validator/tests/test_htmlfill.py
===================================================================
--- trunk/Validator/validator/tests/test_htmlfill.py 2005-03-13 09:25:38 UTC (rev 677)
+++ trunk/Validator/validator/tests/test_htmlfill.py 2005-03-13 09:31:40 UTC (rev 678)
@@ -10,6 +10,7 @@
from validator.doctest_xml_compare import xml_compare
from elementtree import ElementTree as et
from xml.parsers.expat import ExpatError
+from validator import htmlfill_schemabuilder
def test_inputoutput():
data_dir = os.path.join(os.path.dirname(__file__), 'htmlfill_data')
@@ -41,12 +42,13 @@
checker = data['check']
del data['check']
else:
- def checker(p):
+ def checker(p, s):
pass
for name in data.keys():
if name.startswith('_') or hasattr(__builtins__, name):
del data[name]
- p = htmlfill.FillingParser(**data)
+ listener = htmlfill_schemabuilder.SchemaBuilder()
+ p = htmlfill.FillingParser(listener=listener, **data)
p.feed(template)
p.close()
output = p.text()
@@ -65,5 +67,4 @@
print '---- Expected: ----'
print expected
assert 0
- checker(p)
-
+ checker(p, listener.schema())
|
|
From: <sub...@co...> - 2005-03-13 09:25:46
|
Author: ianb
Date: 2005-03-13 09:25:38 +0000 (Sun, 13 Mar 2005)
New Revision: 677
Added:
trunk/Validator/validator/tests/htmlfill_data/data-schema1.txt
Modified:
trunk/Validator/validator/tests/test_htmlfill.py
Log:
Tests for schema creation
Added: trunk/Validator/validator/tests/htmlfill_data/data-schema1.txt
===================================================================
--- trunk/Validator/validator/tests/htmlfill_data/data-schema1.txt 2005-03-13 09:02:38 UTC (rev 676)
+++ trunk/Validator/validator/tests/htmlfill_data/data-schema1.txt 2005-03-13 09:25:38 UTC (rev 677)
@@ -0,0 +1,26 @@
+<form>
+<input type="text" form:required=t name="v">
+<input type="text" form:required="no" name="v2">
+<input type="text" form:validate="regex:[a-z]*" name="username"
+ form:message="lower case only!">
+<input type="text" form:validate="email" form:required="no"
+ name="email">
+<input type="text" form:validate="dateconverter" name="birth">
+<input type="text" name="whatever">
+</form>
+----
+<form>
+<input type="text" name="v" value="">
+<input type="text" name="v2" value="">
+<input type="text" name="username" value="">
+<input type="text" name="email" value="">
+<input type="text" name="birth" value="">
+<input type="text" name="whatever" value="">
+</form>
+----
+def check(parser):
+ schema = parser.schema()
+ assert schema
+ names = schema.fields.keys()
+ names.sort()
+ assert names == 'birth email username v v2 whatever'.split()
Modified: trunk/Validator/validator/tests/test_htmlfill.py
===================================================================
--- trunk/Validator/validator/tests/test_htmlfill.py 2005-03-13 09:02:38 UTC (rev 676)
+++ trunk/Validator/validator/tests/test_htmlfill.py 2005-03-13 09:25:38 UTC (rev 677)
@@ -32,10 +32,17 @@
assert 0, "Too many sections"
else:
data_content = ''
- data = {}
+ namespace = {}
if data_content:
- exec data_content in data
+ exec data_content in namespace
+ data = namespace.copy()
data['defaults'] = data.get('defaults', {})
+ if data.has_key('check'):
+ checker = data['check']
+ del data['check']
+ else:
+ def checker(p):
+ pass
for name in data.keys():
if name.startswith('_') or hasattr(__builtins__, name):
del data[name]
@@ -58,3 +65,5 @@
print '---- Expected: ----'
print expected
assert 0
+ checker(p)
+
|
|
From: <sub...@co...> - 2005-03-13 09:02:44
|
Author: ianb Date: 2005-03-13 09:02:38 +0000 (Sun, 13 Mar 2005) New Revision: 676 Added: trunk/Validator/validator/tests/htmlfill_data/data-fill3.txt Removed: trunk/Validator/validator/tests/htmlfill_data/data-3.txt Log: Renamed file Deleted: trunk/Validator/validator/tests/htmlfill_data/data-3.txt =================================================================== --- trunk/Validator/validator/tests/htmlfill_data/data-3.txt 2005-03-13 09:02:07 UTC (rev 675) +++ trunk/Validator/validator/tests/htmlfill_data/data-3.txt 2005-03-13 09:02:38 UTC (rev 676) @@ -1,44 +0,0 @@ -<form> -<select name="sel1"> -<option value="1">1 -<option value="2">2 -</select> -<textarea name="test2">this is a test</textarea> -<input type="checkbox" name="check1" checked> -<input type="checkbox" name="check2" value="cc" checked> -<input type="radio" name="radio1" value="a"> -<input type="radio" name="radio1" value="b"> -<input type="radio" name="radio1" value="c"> -<input type="hidden" name="hidden1"> -<input type="text" style="width: 100%" name="text1" value="X"> -<input type="submit" name="submit1"> -<input type="reset" name="reset1"> -</form> ----- -<form> -<select name="sel1"> -<option value="1">1 -<option value="2" selected="selected">2 -</select> -<textarea name="test2">TEXTAREA</textarea> -<input type="checkbox" name="check1" checked="checked"> -<input type="checkbox" name="check2" value="cc"> -<input type="radio" name="radio1" value="a"> -<input type="radio" name="radio1" value="b" checked="checked"> -<input type="radio" name="radio1" value="c"> -<input type="hidden" name="hidden1" value="H"> -<input type="text" style="width: 100%" name="text1" value="T"> -<input type="submit" name="submit1" value="SAVE"> -<input type="reset" name="reset1" value="CANCEL"> -</form> ----- -defaults = dict( - sel1='2', - test2='TEXTAREA', - check1=True, - check2=False, - radio1='b', - hidden1='H', - text1='T', - submit1='SAVE', - reset1='CANCEL') \ No newline at end of file Copied: trunk/Validator/validator/tests/htmlfill_data/data-fill3.txt (from rev 674, trunk/Validator/validator/tests/htmlfill_data/data-3.txt) |
|
From: <sub...@co...> - 2005-03-13 09:02:11
|
Author: ianb
Date: 2005-03-13 09:02:07 +0000 (Sun, 13 Mar 2005)
New Revision: 675
Added:
trunk/Validator/validator/tests/htmlfill_data/data-fill1.txt
trunk/Validator/validator/tests/htmlfill_data/data-fill2.txt
Removed:
trunk/Validator/validator/tests/htmlfill_data/data-1.txt
trunk/Validator/validator/tests/htmlfill_data/data-2.txt
Log:
Renamed files
Deleted: trunk/Validator/validator/tests/htmlfill_data/data-1.txt
===================================================================
--- trunk/Validator/validator/tests/htmlfill_data/data-1.txt 2005-03-13 08:57:42 UTC (rev 674)
+++ trunk/Validator/validator/tests/htmlfill_data/data-1.txt 2005-03-13 09:02:07 UTC (rev 675)
@@ -1,7 +0,0 @@
-<test tag>
-<html><body>
-<blah blah="2">
-----
-<test tag>
-<html><body>
-<blah blah="2">
Deleted: trunk/Validator/validator/tests/htmlfill_data/data-2.txt
===================================================================
--- trunk/Validator/validator/tests/htmlfill_data/data-2.txt 2005-03-13 08:57:42 UTC (rev 674)
+++ trunk/Validator/validator/tests/htmlfill_data/data-2.txt 2005-03-13 09:02:07 UTC (rev 675)
@@ -1,15 +0,0 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
-<html>
-<form action="">
-<input type="text" name="test">
-</form>
-</html>
-----
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
-<html>
-<form action="">
-<input type="text" name="test" value="whatever">
-</form>
-</html>
-----
-defaults={'test': 'whatever'}
Copied: trunk/Validator/validator/tests/htmlfill_data/data-fill1.txt (from rev 674, trunk/Validator/validator/tests/htmlfill_data/data-1.txt)
Copied: trunk/Validator/validator/tests/htmlfill_data/data-fill2.txt (from rev 674, trunk/Validator/validator/tests/htmlfill_data/data-2.txt)
|
|
From: <sub...@co...> - 2005-03-13 08:57:46
|
Author: ianb
Date: 2005-03-13 08:57:42 +0000 (Sun, 13 Mar 2005)
New Revision: 674
Added:
trunk/Validator/validator/tests/
trunk/Validator/validator/tests/htmlfill_data/
trunk/Validator/validator/tests/htmlfill_data/data-1.txt
trunk/Validator/validator/tests/htmlfill_data/data-2.txt
trunk/Validator/validator/tests/htmlfill_data/data-3.txt
trunk/Validator/validator/tests/htmlfill_data/data-error1.txt
trunk/Validator/validator/tests/test_htmlfill.py
Modified:
trunk/Validator/validator/htmlfill.py
Log:
* Added tests for htmlfill
* Fixed a bug with error class on <select>
* Fixed the newline-eating bug
* Added formatters "none" and "escape"
* Peter Hunts changes to allow validation schemas to be defined in a
form
* And his fix for XHTML-style tags
Modified: trunk/Validator/validator/htmlfill.py
===================================================================
--- trunk/Validator/validator/htmlfill.py 2005-03-13 08:55:50 UTC (rev 673)
+++ trunk/Validator/validator/htmlfill.py 2005-03-13 08:57:42 UTC (rev 674)
@@ -1,9 +1,25 @@
import HTMLParser
import cgi
+import validators, schema, compound
+
def default_formatter(error):
- return '<span class="error-message">%s</span><br>\n' % cgi.escape(error)
+ return '<span class="error-message">%s</span><br />\n' % cgi.escape(error)
+def none_formatter(error):
+ return error
+
+def escape_formatter(error):
+ return cgi.escape(error, 1)
+
+default_validators = dict([(name.lower(), getattr(validators, name)) for name in dir(validators)])
+
+def get_messages(cls, message):
+ if not message:
+ return {}
+ else:
+ return dict([(k, message) for k in cls._messages.keys()])
+
class FillingParser(HTMLParser.HTMLParser):
r"""
Fills HTML with default values, as in a form.
@@ -38,7 +54,7 @@
def __init__(self, defaults, errors=None, use_all_keys=False,
error_formatters=None, error_class='error',
- add_attributes=None):
+ add_attributes=None, validators=default_validators):
HTMLParser.HTMLParser.__init__(self)
self.content = []
self.source = None
@@ -55,16 +71,21 @@
self.used_keys = {}
self.used_errors = {}
if error_formatters is None:
- self.error_formatters = {'default': default_formatter}
+ self.error_formatters = {'default': default_formatter,
+ 'none': none_formatter,
+ 'escape': escape_formatter}
else:
self.error_formatters = error_formatters
self.error_class = error_class
self.add_attributes = add_attributes or {}
+ self.validators = validators
+ self._schema = None
def feed(self, data):
self.source = data
self.lines = data.split('\n')
self.source_pos = 1, 0
+ self._schema = schema.Schema()
HTMLParser.HTMLParser.feed(self, data)
def close(self):
@@ -92,20 +113,26 @@
def add_key(self, key):
self.used_keys[key] = 1
- def handle_starttag(self, tag, attrs):
+ def handle_starttag(self, tag, attrs, startend=False):
self.write_pos()
if tag == 'input':
- self.handle_input(attrs)
+ self.handle_input(attrs, startend)
elif tag == 'textarea':
self.handle_textarea(attrs)
elif tag == 'select':
self.handle_select(attrs)
elif tag == 'option':
self.handle_option(attrs)
+ return
elif tag == 'form:error':
self.handle_error(attrs)
+ return
elif tag == 'form:iferror':
self.handle_iferror(attrs)
+ return
+ else:
+ return
+ self.update_schema(attrs)
def handle_misc(self, whatever):
self.write_pos()
@@ -126,6 +153,9 @@
elif tag == 'form:iferror':
self.handle_end_iferror()
+ def handle_startendtag(self, tag, attrs):
+ return self.handle_starttag(tag, attrs, True)
+
def handle_iferror(self, attrs):
name = self.get_attr(attrs, 'name')
assert name, "Name attribute in <iferror> required (%s)" % self.getpos()
@@ -137,7 +167,7 @@
def handle_end_iferror(self):
self.in_error = None
self.skip_error = False
- self.skip_next = False
+ self.skip_next = True
def handle_error(self, attrs):
name = self.get_attr(attrs, 'name')
@@ -154,7 +184,7 @@
self.skip_next = True
self.used_errors[name] = 1
- def handle_input(self, attrs):
+ def handle_input(self, attrs, startend):
t = (self.get_attr(attrs, 'type') or 'text').lower()
name = self.get_attr(attrs, 'name')
value = self.defaults.get(name)
@@ -173,7 +203,7 @@
if t in ('text', 'hidden', 'submit', 'reset', 'button'):
self.set_attr(attrs, 'value', value or
self.get_attr(attrs, 'value', ''))
- self.write_tag('input', attrs)
+ self.write_tag('input', attrs, startend)
self.skip_next = True
self.add_key(name)
elif t == 'checkbox':
@@ -209,11 +239,11 @@
% (t, self.getpos())
def handle_textarea(self, attrs):
+ name = self.get_attr(attrs, 'name')
if (self.error_class
- and self.errors.get(self.get_attr(attrs, 'name'))):
+ and self.errors.get(name)):
self.add_class(attrs, self.error_class)
self.write_tag('textarea', attrs)
- name = self.get_attr(attrs, 'name')
value = self.defaults.get(name, '')
self.write_text(cgi.escape(value, 1))
self.write_text('</textarea>')
@@ -225,11 +255,15 @@
self.skip_next = True
def handle_select(self, attrs):
+ name = self.get_attr(attrs, 'name')
if (self.error_class
- and self.errors.get(self.get_attr(attrs, 'name'))):
+ and self.errors.get(name)):
self.add_class(attrs, self.error_class)
self.in_select = self.get_attr(attrs, 'name')
+ self.write_tag('select', attrs)
+ self.skip_next = True
self.add_key(self.in_select)
+
def handle_end_select(self):
self.in_select = None
@@ -245,12 +279,49 @@
self.write_tag('option', attrs)
self.skip_next = True
+ def update_schema(self, attrs):
+ name = self.get_attr(attrs, "name")
+ if not name:
+ return
+ v = compound.All()
+ message = self.get_attr(attrs, "form:message")
+ required = self.get_attr(attrs, "form:required", "no").lower()
+ required = (required == "yes") or (required == "true")
+ if required:
+ v.validators.append(validators.NotEmpty(messages=get_messages(validators.NotEmpty, message)))
+ t = self.get_attr(attrs, "form:validate", None)
+ if t:
+ # validatorname[:argument]
+ i = t.find(":")
+ if i > -1:
+ args = t[i+1:] # TODO: split on commas?
+ t = t[:i].lower()
+ else:
+ args = None
+ t = t.lower()
+ # get the validator class by name
+ vclass = self.validators.get(t)
+ if not vclass:
+ raise ValueError, "Invalid validation type: " + t
+ if args:
+ vinst = vclass(args, messages=get_messages(vclass, message)) # TODO: if something takes more than 1 string argument, wrap in a function which parses args
+ else:
+ vinst = vclass(messages=get_messages(vclass, message))
+ v.validators.append(vinst)
+ self._schema.add_field(name, v)
+
+ def schema(self):
+ return self._schema
+
def write_text(self, text):
self.content.append(text)
- def write_tag(self, tag, attrs):
+ def write_tag(self, tag, attrs, startend=False):
attr_text = ''.join([' %s="%s"' % (n, cgi.escape(str(v), 1))
- for (n, v) in attrs])
+ for (n, v) in attrs
+ if not n.startswith('form:')])
+ if startend:
+ attr_text += " /"
self.write_text('<%s%s>' % (tag, attr_text))
def write_pos(self):
@@ -268,6 +339,7 @@
else:
self.write_text(
self.lines[self.source_pos[0]-1][self.source_pos[1]:])
+ self.write_text('\n')
for i in range(self.source_pos[0]+1, cur_line):
self.write_text(self.lines[i-1])
self.write_text('\n')
Added: trunk/Validator/validator/tests/htmlfill_data/data-1.txt
===================================================================
--- trunk/Validator/validator/tests/htmlfill_data/data-1.txt 2005-03-13 08:55:50 UTC (rev 673)
+++ trunk/Validator/validator/tests/htmlfill_data/data-1.txt 2005-03-13 08:57:42 UTC (rev 674)
@@ -0,0 +1,7 @@
+<test tag>
+<html><body>
+<blah blah="2">
+----
+<test tag>
+<html><body>
+<blah blah="2">
Added: trunk/Validator/validator/tests/htmlfill_data/data-2.txt
===================================================================
--- trunk/Validator/validator/tests/htmlfill_data/data-2.txt 2005-03-13 08:55:50 UTC (rev 673)
+++ trunk/Validator/validator/tests/htmlfill_data/data-2.txt 2005-03-13 08:57:42 UTC (rev 674)
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<form action="">
+<input type="text" name="test">
+</form>
+</html>
+----
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<form action="">
+<input type="text" name="test" value="whatever">
+</form>
+</html>
+----
+defaults={'test': 'whatever'}
Added: trunk/Validator/validator/tests/htmlfill_data/data-3.txt
===================================================================
--- trunk/Validator/validator/tests/htmlfill_data/data-3.txt 2005-03-13 08:55:50 UTC (rev 673)
+++ trunk/Validator/validator/tests/htmlfill_data/data-3.txt 2005-03-13 08:57:42 UTC (rev 674)
@@ -0,0 +1,44 @@
+<form>
+<select name="sel1">
+<option value="1">1
+<option value="2">2
+</select>
+<textarea name="test2">this is a test</textarea>
+<input type="checkbox" name="check1" checked>
+<input type="checkbox" name="check2" value="cc" checked>
+<input type="radio" name="radio1" value="a">
+<input type="radio" name="radio1" value="b">
+<input type="radio" name="radio1" value="c">
+<input type="hidden" name="hidden1">
+<input type="text" style="width: 100%" name="text1" value="X">
+<input type="submit" name="submit1">
+<input type="reset" name="reset1">
+</form>
+----
+<form>
+<select name="sel1">
+<option value="1">1
+<option value="2" selected="selected">2
+</select>
+<textarea name="test2">TEXTAREA</textarea>
+<input type="checkbox" name="check1" checked="checked">
+<input type="checkbox" name="check2" value="cc">
+<input type="radio" name="radio1" value="a">
+<input type="radio" name="radio1" value="b" checked="checked">
+<input type="radio" name="radio1" value="c">
+<input type="hidden" name="hidden1" value="H">
+<input type="text" style="width: 100%" name="text1" value="T">
+<input type="submit" name="submit1" value="SAVE">
+<input type="reset" name="reset1" value="CANCEL">
+</form>
+----
+defaults = dict(
+ sel1='2',
+ test2='TEXTAREA',
+ check1=True,
+ check2=False,
+ radio1='b',
+ hidden1='H',
+ text1='T',
+ submit1='SAVE',
+ reset1='CANCEL')
\ No newline at end of file
Added: trunk/Validator/validator/tests/htmlfill_data/data-error1.txt
===================================================================
--- trunk/Validator/validator/tests/htmlfill_data/data-error1.txt 2005-03-13 08:55:50 UTC (rev 673)
+++ trunk/Validator/validator/tests/htmlfill_data/data-error1.txt 2005-03-13 08:57:42 UTC (rev 674)
@@ -0,0 +1,47 @@
+<form>
+<form:error name="" format="none">
+
+<form:iferror name="t1">
+!!!<form:error format="escape">!!!
+</form:iferror>
+<input type="text" name="t1">
+
+<form:error name="t2">
+<select name="t2">
+<option value="1">1</option>
+<option value="2">2</option>
+</select>
+
+<form:error name="t3">
+<textarea name="t3">hey</textarea>
+
+<form:iferror name="X">
+This should not display
+</form:iferror>
+
+----
+<form>
+<test!>
+
+
+!!!<HEY>!!!
+
+<input type="text" name="t1" class="error" value="">
+
+<span class="error-message"><error</span><br />
+
+<select name="t2" class="error">
+<option value="1">1</option>
+<option value="2">2</option>
+</select>
+
+<span class="error-message">last</span><br />
+
+<textarea name="t3" class="error"></textarea>
+----
+defaults = {}
+errors = dict(
+ t1='<HEY>',
+ t2='<error',
+ t3='last')
+errors[''] = '<test!>'
Added: trunk/Validator/validator/tests/test_htmlfill.py
===================================================================
--- trunk/Validator/validator/tests/test_htmlfill.py 2005-03-13 08:55:50 UTC (rev 673)
+++ trunk/Validator/validator/tests/test_htmlfill.py 2005-03-13 08:57:42 UTC (rev 674)
@@ -0,0 +1,60 @@
+import sys
+import os
+import re
+
+base_dir = os.path.dirname(os.path.dirname(os.path.dirname(
+ os.path.abspath(__file__))))
+if base_dir not in sys.path:
+ sys.path.insert(0, base_dir)
+from validator import htmlfill
+from validator.doctest_xml_compare import xml_compare
+from elementtree import ElementTree as et
+from xml.parsers.expat import ExpatError
+
+def test_inputoutput():
+ data_dir = os.path.join(os.path.dirname(__file__), 'htmlfill_data')
+ for fn in os.listdir(data_dir):
+ if fn.startswith('data-'):
+ fn = os.path.join(data_dir, fn)
+ yield run_filename, fn
+
+def run_filename(filename):
+ f = open(filename)
+ content = f.read()
+ f.close()
+ parts = re.split(r'---*', content)
+ template = parts[0]
+ expected = parts[1]
+ if len(parts) == 3:
+ data_content = parts[2].strip()
+ elif len(parts) > 3:
+ print parts[3:]
+ assert 0, "Too many sections"
+ else:
+ data_content = ''
+ data = {}
+ if data_content:
+ exec data_content in data
+ data['defaults'] = data.get('defaults', {})
+ for name in data.keys():
+ if name.startswith('_') or hasattr(__builtins__, name):
+ del data[name]
+ p = htmlfill.FillingParser(**data)
+ p.feed(template)
+ p.close()
+ output = p.text()
+ def reporter(v):
+ print v
+ try:
+ output_xml = et.XML(output)
+ expected_xml = et.XML(expected)
+ except ExpatError:
+ comp = output.strip() == expected.strip()
+ else:
+ comp = xml_compare(output_xml, expected_xml, reporter)
+ if not comp:
+ print '---- Output: ----'
+ print output
+ print '---- Expected: ----'
+ print expected
+ assert 0
|
|
From: <sub...@co...> - 2005-03-13 08:56:02
|
Author: ianb
Date: 2005-03-13 08:55:50 +0000 (Sun, 13 Mar 2005)
New Revision: 673
Added:
trunk/Validator/validator/dummy_protocols.py
Modified:
trunk/Validator/validator/api.py
trunk/Validator/validator/compound.py
trunk/Validator/validator/interfaces.py
trunk/Validator/validator/schema.py
trunk/Validator/validator/validators.py
Log:
Removed PyProtocols dependency
Modified: trunk/Validator/validator/api.py
===================================================================
--- trunk/Validator/validator/api.py 2005-03-10 23:51:32 UTC (rev 672)
+++ trunk/Validator/validator/api.py 2005-03-13 08:55:50 UTC (rev 673)
@@ -1,4 +1,7 @@
-import protocols
+try:
+ import protocols
+except ImportError:
+ import dummy_protocols as protocols
from interfaces import *
from declarative import Declarative
Modified: trunk/Validator/validator/compound.py
===================================================================
--- trunk/Validator/validator/compound.py 2005-03-10 23:51:32 UTC (rev 672)
+++ trunk/Validator/validator/compound.py 2005-03-13 08:55:50 UTC (rev 673)
@@ -157,7 +157,7 @@
return All(*v)
# @@: maybe this is fishy?
-import protocols
-from interfaces import *
-protocols.declareAdapter(_adaptListToAll, [IValidator],
- forTypes=[list, tuple])
+#import protocols
+#from interfaces import *
+#protocols.declareAdapter(_adaptListToAll, [IValidator],
+# forTypes=[list, tuple])
Added: trunk/Validator/validator/dummy_protocols.py
===================================================================
--- trunk/Validator/validator/dummy_protocols.py 2005-03-10 23:51:32 UTC (rev 672)
+++ trunk/Validator/validator/dummy_protocols.py 2005-03-13 08:55:50 UTC (rev 673)
@@ -0,0 +1,17 @@
+"""
+A trivial replacement of PyProtocols
+"""
+
+def adapt(obj, protocol):
+ return obj
+
+def advise(**kw):
+ pass
+
+class Interface(object):
+ pass
+
+class Attribute(object):
+ def __init__(self, doc, name=None):
+ self.doc = doc
+ self.name = name
Modified: trunk/Validator/validator/interfaces.py
===================================================================
--- trunk/Validator/validator/interfaces.py 2005-03-10 23:51:32 UTC (rev 672)
+++ trunk/Validator/validator/interfaces.py 2005-03-13 08:55:50 UTC (rev 673)
@@ -2,7 +2,10 @@
Interfaces for FormEncode
"""
-from protocols import Interface, Attribute
+try:
+ from protocols import Interface, Attribute
+except ImportError:
+ from dummy_protocols import Interface, Attribute
class IDeclarative(Interface):
Modified: trunk/Validator/validator/schema.py
===================================================================
--- trunk/Validator/validator/schema.py 2005-03-10 23:51:32 UTC (rev 672)
+++ trunk/Validator/validator/schema.py 2005-03-13 08:55:50 UTC (rev 673)
@@ -1,7 +1,10 @@
from interfaces import *
from api import *
import declarative
-import protocols
+try:
+ import protocols
+except ImportError:
+ import dummy_protocols as protocols
class SchemaMeta(declarative.DeclarativeMeta):
Modified: trunk/Validator/validator/validators.py
===================================================================
--- trunk/Validator/validator/validators.py 2005-03-10 23:51:32 UTC (rev 672)
+++ trunk/Validator/validator/validators.py 2005-03-13 08:55:50 UTC (rev 673)
@@ -31,7 +31,6 @@
urlparse = None
from interfaces import *
from api import *
-import protocols
from declarative import Declarative, DeclarativeMeta
True, False = (1==1), (0==1)
|
|
From: <sub...@co...> - 2005-02-24 00:56:42
|
Author: ianb
Date: 2005-02-24 00:56:37 +0000 (Thu, 24 Feb 2005)
New Revision: 644
Modified:
trunk/Validator/validator/htmlgen.py
Log:
* Removed dead class
* Keep repr() from getting out of hand
Modified: trunk/Validator/validator/htmlgen.py
===================================================================
--- trunk/Validator/validator/htmlgen.py 2005-02-24 00:27:45 UTC (rev 643)
+++ trunk/Validator/validator/htmlgen.py 2005-02-24 00:56:37 UTC (rev 644)
@@ -158,24 +158,13 @@
return str(self).decode(default_encoding)
def __repr__(self):
- return '<Element %r>' % str(self)
+ content = str(self)
+ if len(content) > 25:
+ content = repr(content[:25]) + '...'
+ else:
+ content = repr(content)
+ return '<Element %r>' % content
-class UnfinishedInput:
-
- def __init__(self, tag, type=None):
- self._type = type
- UnfinishedTag.__init__(self, tag)
-
- def __call__(self, *args, **kw):
- if self._type:
- kw['type'] = self._type
- return UnfinishedTag.__call__(self, *args, **kw)
-
- def __getattr__(self, attr):
- if attr.startswith('__'):
- raise AttributeError
- return self.__class__(self._tag, type=attr.lower())
-
class ElementList(list):
def __str__(self):
|
|
From: <sub...@co...> - 2005-02-24 00:27:50
|
Author: ianb
Date: 2005-02-24 00:27:45 +0000 (Thu, 24 Feb 2005)
New Revision: 643
Added:
trunk/Validator/validator/doctest24.py
trunk/Validator/validator/doctest_xml_compare.py
trunk/Validator/validator/formgen.py
trunk/Validator/validator/javascript/
trunk/Validator/validator/javascript/ordering.js
trunk/Validator/validator/test_doctest_xml_compare.py
trunk/Validator/validator/test_formgen.py
Log:
Moved htmlview over from FormEncode to formgen
* Tests
* Associated Javascript
* XML tools for doctest
Added: trunk/Validator/validator/doctest24.py
===================================================================
--- trunk/Validator/validator/doctest24.py 2005-02-24 00:27:08 UTC (rev 642)
+++ trunk/Validator/validator/doctest24.py 2005-02-24 00:27:45 UTC (rev 643)
@@ -0,0 +1,2665 @@
+# Module doctest.
+# Released to the public domain 16-Jan-2001, by Tim Peters (ti...@py...).
+# Major enhancements and refactoring by:
+# Jim Fulton
+# Edward Loper
+
+# Provided as-is; use at your own risk; no warranty; no promises; enjoy!
+
+r"""Module doctest -- a framework for running examples in docstrings.
+
+In simplest use, end each module M to be tested with:
+
+def _test():
+ import doctest
+ doctest.testmod()
+
+if __name__ == "__main__":
+ _test()
+
+Then running the module as a script will cause the examples in the
+docstrings to get executed and verified:
+
+python M.py
+
+This won't display anything unless an example fails, in which case the
+failing example(s) and the cause(s) of the failure(s) are printed to stdout
+(why not stderr? because stderr is a lame hack <0.2 wink>), and the final
+line of output is "Test failed.".
+
+Run it with the -v switch instead:
+
+python M.py -v
+
+and a detailed report of all examples tried is printed to stdout, along
+with assorted summaries at the end.
+
+You can force verbose mode by passing "verbose=True" to testmod, or prohibit
+it by passing "verbose=False". In either of those cases, sys.argv is not
+examined by testmod.
+
+There are a variety of other ways to run doctests, including integration
+with the unittest framework, and support for running non-Python text
+files containing doctests. There are also many ways to override parts
+of doctest's default behaviors. See the Library Reference Manual for
+details.
+"""
+
+__docformat__ = 'reStructuredText en'
+
+__all__ = [
+ # 0, Option Flags
+ 'register_optionflag',
+ 'DONT_ACCEPT_TRUE_FOR_1',
+ 'DONT_ACCEPT_BLANKLINE',
+ 'NORMALIZE_WHITESPACE',
+ 'ELLIPSIS',
+ 'IGNORE_EXCEPTION_DETAIL',
+ 'COMPARISON_FLAGS',
+ 'REPORT_UDIFF',
+ 'REPORT_CDIFF',
+ 'REPORT_NDIFF',
+ 'REPORT_ONLY_FIRST_FAILURE',
+ 'REPORTING_FLAGS',
+ # 1. Utility Functions
+ 'is_private',
+ # 2. Example & DocTest
+ 'Example',
+ 'DocTest',
+ # 3. Doctest Parser
+ 'DocTestParser',
+ # 4. Doctest Finder
+ 'DocTestFinder',
+ # 5. Doctest Runner
+ 'DocTestRunner',
+ 'OutputChecker',
+ 'DocTestFailure',
+ 'UnexpectedException',
+ 'DebugRunner',
+ # 6. Test Functions
+ 'testmod',
+ 'testfile',
+ 'run_docstring_examples',
+ # 7. Tester
+ 'Tester',
+ # 8. Unittest Support
+ 'DocTestSuite',
+ 'DocFileSuite',
+ 'set_unittest_reportflags',
+ # 9. Debugging Support
+ 'script_from_examples',
+ 'testsource',
+ 'debug_src',
+ 'debug',
+]
+
+import __future__
+
+import sys, traceback, inspect, linecache, os, re, types
+import unittest, difflib, pdb, tempfile
+import warnings
+from StringIO import StringIO
+
+# Don't whine about the deprecated is_private function in this
+# module's tests.
+warnings.filterwarnings("ignore", "is_private", DeprecationWarning,
+ __name__, 0)
+
+# There are 4 basic classes:
+# - Example: a <source, want> pair, plus an intra-docstring line number.
+# - DocTest: a collection of examples, parsed from a docstring, plus
+# info about where the docstring came from (name, filename, lineno).
+# - DocTestFinder: extracts DocTests from a given object's docstring and
+# its contained objects' docstrings.
+# - DocTestRunner: runs DocTest cases, and accumulates statistics.
+#
+# So the basic picture is:
+#
+# list of:
+# +------+ +---------+ +-------+
+# |object| --DocTestFinder-> | DocTest | --DocTestRunner-> |results|
+# +------+ +---------+ +-------+
+# | Example |
+# | ... |
+# | Example |
+# +---------+
+
+# Option constants.
+
+OPTIONFLAGS_BY_NAME = {}
+def register_optionflag(name):
+ flag = 1 << len(OPTIONFLAGS_BY_NAME)
+ OPTIONFLAGS_BY_NAME[name] = flag
+ return flag
+
+DONT_ACCEPT_TRUE_FOR_1 = register_optionflag('DONT_ACCEPT_TRUE_FOR_1')
+DONT_ACCEPT_BLANKLINE = register_optionflag('DONT_ACCEPT_BLANKLINE')
+NORMALIZE_WHITESPACE = register_optionflag('NORMALIZE_WHITESPACE')
+ELLIPSIS = register_optionflag('ELLIPSIS')
+IGNORE_EXCEPTION_DETAIL = register_optionflag('IGNORE_EXCEPTION_DETAIL')
+
+COMPARISON_FLAGS = (DONT_ACCEPT_TRUE_FOR_1 |
+ DONT_ACCEPT_BLANKLINE |
+ NORMALIZE_WHITESPACE |
+ ELLIPSIS |
+ IGNORE_EXCEPTION_DETAIL)
+
+REPORT_UDIFF = register_optionflag('REPORT_UDIFF')
+REPORT_CDIFF = register_optionflag('REPORT_CDIFF')
+REPORT_NDIFF = register_optionflag('REPORT_NDIFF')
+REPORT_ONLY_FIRST_FAILURE = register_optionflag('REPORT_ONLY_FIRST_FAILURE')
+
+REPORTING_FLAGS = (REPORT_UDIFF |
+ REPORT_CDIFF |
+ REPORT_NDIFF |
+ REPORT_ONLY_FIRST_FAILURE)
+
+# Special string markers for use in `want` strings:
+BLANKLINE_MARKER = '<BLANKLINE>'
+ELLIPSIS_MARKER = '...'
+
+######################################################################
+## Table of Contents
+######################################################################
+# 1. Utility Functions
+# 2. Example & DocTest -- store test cases
+# 3. DocTest Parser -- extracts examples from strings
+# 4. DocTest Finder -- extracts test cases from objects
+# 5. DocTest Runner -- runs test cases
+# 6. Test Functions -- convenient wrappers for testing
+# 7. Tester Class -- for backwards compatibility
+# 8. Unittest Support
+# 9. Debugging Support
+# 10. Example Usage
+
+######################################################################
+## 1. Utility Functions
+######################################################################
+
+def is_private(prefix, base):
+ """prefix, base -> true iff name prefix + "." + base is "private".
+
+ Prefix may be an empty string, and base does not contain a period.
+ Prefix is ignored (although functions you write conforming to this
+ protocol may make use of it).
+ Return true iff base begins with an (at least one) underscore, but
+ does not both begin and end with (at least) two underscores.
+
+ >>> is_private("a.b", "my_func")
+ False
+ >>> is_private("____", "_my_func")
+ True
+ >>> is_private("someclass", "__init__")
+ False
+ >>> is_private("sometypo", "__init_")
+ True
+ >>> is_private("x.y.z", "_")
+ True
+ >>> is_private("_x.y.z", "__")
+ False
+ >>> is_private("", "") # senseless but consistent
+ False
+ """
+ warnings.warn("is_private is deprecated; it wasn't useful; "
+ "examine DocTestFinder.find() lists instead",
+ DeprecationWarning, stacklevel=2)
+ return base[:1] == "_" and not base[:2] == "__" == base[-2:]
+
+def _extract_future_flags(globs):
+ """
+ Return the compiler-flags associated with the future features that
+ have been imported into the given namespace (globs).
+ """
+ flags = 0
+ for fname in __future__.all_feature_names:
+ feature = globs.get(fname, None)
+ if feature is getattr(__future__, fname):
+ flags |= feature.compiler_flag
+ return flags
+
+def _normalize_module(module, depth=2):
+ """
+ Return the module specified by `module`. In particular:
+ - If `module` is a module, then return module.
+ - If `module` is a string, then import and return the
+ module with that name.
+ - If `module` is None, then return the calling module.
+ The calling module is assumed to be the module of
+ the stack frame at the given depth in the call stack.
+ """
+ if inspect.ismodule(module):
+ return module
+ elif isinstance(module, (str, unicode)):
+ return __import__(module, globals(), locals(), ["*"])
+ elif module is None:
+ return sys.modules[sys._getframe(depth).f_globals['__name__']]
+ else:
+ raise TypeError("Expected a module, string, or None")
+
+def _indent(s, indent=4):
+ """
+ Add the given number of space characters to the beginning every
+ non-blank line in `s`, and return the result.
+ """
+ # This regexp matches the start of non-blank lines:
+ return re.sub('(?m)^(?!$)', indent*' ', s)
+
+def _exception_traceback(exc_info):
+ """
+ Return a string containing a traceback message for the given
+ exc_info tuple (as returned by sys.exc_info()).
+ """
+ # Get a traceback message.
+ excout = StringIO()
+ exc_type, exc_val, exc_tb = exc_info
+ traceback.print_exception(exc_type, exc_val, exc_tb, file=excout)
+ return excout.getvalue()
+
+# Override some StringIO methods.
+class _SpoofOut(StringIO):
+ def getvalue(self):
+ result = StringIO.getvalue(self)
+ # If anything at all was written, make sure there's a trailing
+ # newline. There's no way for the expected output to indicate
+ # that a trailing newline is missing.
+ if result and not result.endswith("\n"):
+ result += "\n"
+ # Prevent softspace from screwing up the next test case, in
+ # case they used print with a trailing comma in an example.
+ if hasattr(self, "softspace"):
+ del self.softspace
+ return result
+
+ def truncate(self, size=None):
+ StringIO.truncate(self, size)
+ if hasattr(self, "softspace"):
+ del self.softspace
+
+# Worst-case linear-time ellipsis matching.
+def _ellipsis_match(want, got):
+ """
+ Essentially the only subtle case:
+ >>> _ellipsis_match('aa...aa', 'aaa')
+ False
+ """
+ if ELLIPSIS_MARKER not in want:
+ return want == got
+
+ # Find "the real" strings.
+ ws = want.split(ELLIPSIS_MARKER)
+ assert len(ws) >= 2
+
+ # Deal with exact matches possibly needed at one or both ends.
+ startpos, endpos = 0, len(got)
+ w = ws[0]
+ if w: # starts with exact match
+ if got.startswith(w):
+ startpos = len(w)
+ del ws[0]
+ else:
+ return False
+ w = ws[-1]
+ if w: # ends with exact match
+ if got.endswith(w):
+ endpos -= len(w)
+ del ws[-1]
+ else:
+ return False
+
+ if startpos > endpos:
+ # Exact end matches required more characters than we have, as in
+ # _ellipsis_match('aa...aa', 'aaa')
+ return False
+
+ # For the rest, we only need to find the leftmost non-overlapping
+ # match for each piece. If there's no overall match that way alone,
+ # there's no overall match period.
+ for w in ws:
+ # w may be '' at times, if there are consecutive ellipses, or
+ # due to an ellipsis at the start or end of `want`. That's OK.
+ # Search for an empty string succeeds, and doesn't change startpos.
+ startpos = got.find(w, startpos, endpos)
+ if startpos < 0:
+ return False
+ startpos += len(w)
+
+ return True
+
+def _comment_line(line):
+ "Return a commented form of the given line"
+ line = line.rstrip()
+ if line:
+ return '# '+line
+ else:
+ return '#'
+
+class _OutputRedirectingPdb(pdb.Pdb):
+ """
+ A specialized version of the python debugger that redirects stdout
+ to a given stream when interacting with the user. Stdout is *not*
+ redirected when traced code is executed.
+ """
+ def __init__(self, out):
+ self.__out = out
+ pdb.Pdb.__init__(self)
+
+ def trace_dispatch(self, *args):
+ # Redirect stdout to the given stream.
+ save_stdout = sys.stdout
+ sys.stdout = self.__out
+ # Call Pdb's trace dispatch method.
+ try:
+ return pdb.Pdb.trace_dispatch(self, *args)
+ finally:
+ sys.stdout = save_stdout
+
+# [XX] Normalize with respect to os.path.pardir?
+def _module_relative_path(module, path):
+ if not inspect.ismodule(module):
+ raise TypeError, 'Expected a module: %r' % module
+ if path.startswith('/'):
+ raise ValueError, 'Module-relative files may not have absolute paths'
+
+ # Find the base directory for the path.
+ if hasattr(module, '__file__'):
+ # A normal module/package
+ basedir = os.path.split(module.__file__)[0]
+ elif module.__name__ == '__main__':
+ # An interactive session.
+ if len(sys.argv)>0 and sys.argv[0] != '':
+ basedir = os.path.split(sys.argv[0])[0]
+ else:
+ basedir = os.curdir
+ else:
+ # A module w/o __file__ (this includes builtins)
+ raise ValueError("Can't resolve paths relative to the module " +
+ module + " (it has no __file__)")
+
+ # Combine the base directory and the path.
+ return os.path.join(basedir, *(path.split('/')))
+
+######################################################################
+## 2. Example & DocTest
+######################################################################
+## - An "example" is a <source, want> pair, where "source" is a
+## fragment of source code, and "want" is the expected output for
+## "source." The Example class also includes information about
+## where the example was extracted from.
+##
+## - A "doctest" is a collection of examples, typically extracted from
+## a string (such as an object's docstring). The DocTest class also
+## includes information about where the string was extracted from.
+
+class Example:
+ """
+ A single doctest example, consisting of source code and expected
+ output. `Example` defines the following attributes:
+
+ - source: A single Python statement, always ending with a newline.
+ The constructor adds a newline if needed.
+
+ - want: The expected output from running the source code (either
+ from stdout, or a traceback in case of exception). `want` ends
+ with a newline unless it's empty, in which case it's an empty
+ string. The constructor adds a newline if needed.
+
+ - exc_msg: The exception message generated by the example, if
+ the example is expected to generate an exception; or `None` if
+ it is not expected to generate an exception. This exception
+ message is compared against the return value of
+ `traceback.format_exception_only()`. `exc_msg` ends with a
+ newline unless it's `None`. The constructor adds a newline
+ if needed.
+
+ - lineno: The line number within the DocTest string containing
+ this Example where the Example begins. This line number is
+ zero-based, with respect to the beginning of the DocTest.
+
+ - indent: The example's indentation in the DocTest string.
+ I.e., the number of space characters that preceed the
+ example's first prompt.
+
+ - options: A dictionary mapping from option flags to True or
+ False, which is used to override default options for this
+ example. Any option flags not contained in this dictionary
+ are left at their default value (as specified by the
+ DocTestRunner's optionflags). By default, no options are set.
+ """
+ def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
+ options=None):
+ # Normalize inputs.
+ if not source.endswith('\n'):
+ source += '\n'
+ if want and not want.endswith('\n'):
+ want += '\n'
+ if exc_msg is not None and not exc_msg.endswith('\n'):
+ exc_msg += '\n'
+ # Store properties.
+ self.source = source
+ self.want = want
+ self.lineno = lineno
+ self.indent = indent
+ if options is None: options = {}
+ self.options = options
+ self.exc_msg = exc_msg
+
+class DocTest:
+ """
+ A collection of doctest examples that should be run in a single
+ namespace. Each `DocTest` defines the following attributes:
+
+ - examples: the list of examples.
+
+ - globs: The namespace (aka globals) that the examples should
+ be run in.
+
+ - name: A name identifying the DocTest (typically, the name of
+ the object whose docstring this DocTest was extracted from).
+
+ - filename: The name of the file that this DocTest was extracted
+ from, or `None` if the filename is unknown.
+
+ - lineno: The line number within filename where this DocTest
+ begins, or `None` if the line number is unavailable. This
+ line number is zero-based, with respect to the beginning of
+ the file.
+
+ - docstring: The string that the examples were extracted from,
+ or `None` if the string is unavailable.
+ """
+ def __init__(self, examples, globs, name, filename, lineno, docstring):
+ """
+ Create a new DocTest containing the given examples. The
+ DocTest's globals are initialized with a copy of `globs`.
+ """
+ assert not isinstance(examples, basestring), \
+ "DocTest no longer accepts str; use DocTestParser instead"
+ self.examples = examples
+ self.docstring = docstring
+ self.globs = globs.copy()
+ self.name = name
+ self.filename = filename
+ self.lineno = lineno
+
+ def __repr__(self):
+ if len(self.examples) == 0:
+ examples = 'no examples'
+ elif len(self.examples) == 1:
+ examples = '1 example'
+ else:
+ examples = '%d examples' % len(self.examples)
+ return ('<DocTest %s from %s:%s (%s)>' %
+ (self.name, self.filename, self.lineno, examples))
+
+
+ # This lets us sort tests by name:
+ def __cmp__(self, other):
+ if not isinstance(other, DocTest):
+ return -1
+ return cmp((self.name, self.filename, self.lineno, id(self)),
+ (other.name, other.filename, other.lineno, id(other)))
+
+######################################################################
+## 3. DocTestParser
+######################################################################
+
+class DocTestParser:
+ """
+ A class used to parse strings containing doctest examples.
+ """
+ # This regular expression is used to find doctest examples in a
+ # string. It defines three groups: `source` is the source code
+ # (including leading indentation and prompts); `indent` is the
+ # indentation of the first (PS1) line of the source code; and
+ # `want` is the expected output (including leading indentation).
+ _EXAMPLE_RE = re.compile(r'''
+ # Source consists of a PS1 line followed by zero or more PS2 lines.
+ (?P<source>
+ (?:^(?P<indent> [ ]*) >>> .*) # PS1 line
+ (?:\n [ ]* \.\.\. .*)*) # PS2 lines
+ \n?
+ # Want consists of any non-blank lines that do not start with PS1.
+ (?P<want> (?:(?![ ]*$) # Not a blank line
+ (?![ ]*>>>) # Not a line starting with PS1
+ .*$\n? # But any other line
+ )*)
+ ''', re.MULTILINE | re.VERBOSE)
+
+ # A regular expression for handling `want` strings that contain
+ # expected exceptions. It divides `want` into three pieces:
+ # - the traceback header line (`hdr`)
+ # - the traceback stack (`stack`)
+ # - the exception message (`msg`), as generated by
+ # traceback.format_exception_only()
+ # `msg` may have multiple lines. We assume/require that the
+ # exception message is the first non-indented line starting with a word
+ # character following the traceback header line.
+ _EXCEPTION_RE = re.compile(r"""
+ # Grab the traceback header. Different versions of Python have
+ # said different things on the first traceback line.
+ ^(?P<hdr> Traceback\ \(
+ (?: most\ recent\ call\ last
+ | innermost\ last
+ ) \) :
+ )
+ \s* $ # toss trailing whitespace on the header.
+ (?P<stack> .*?) # don't blink: absorb stuff until...
+ ^ (?P<msg> \w+ .*) # a line *starts* with alphanum.
+ """, re.VERBOSE | re.MULTILINE | re.DOTALL)
+
+ # A callable returning a true value iff its argument is a blank line
+ # or contains a single comment.
+ _IS_BLANK_OR_COMMENT = re.compile(r'^[ ]*(#.*)?$').match
+
+ def parse(self, string, name='<string>'):
+ """
+ Divide the given string into examples and intervening text,
+ and return them as a list of alternating Examples and strings.
+ Line numbers for the Examples are 0-based. The optional
+ argument `name` is a name identifying this string, and is only
+ used for error messages.
+ """
+ string = string.expandtabs()
+ # If all lines begin with the same indentation, then strip it.
+ min_indent = self._min_indent(string)
+ if min_indent > 0:
+ string = '\n'.join([l[min_indent:] for l in string.split('\n')])
+
+ output = []
+ charno, lineno = 0, 0
+ # Find all doctest examples in the string:
+ for m in self._EXAMPLE_RE.finditer(string):
+ # Add the pre-example text to `output`.
+ output.append(string[charno:m.start()])
+ # Update lineno (lines before this example)
+ lineno += string.count('\n', charno, m.start())
+ # Extract info from the regexp match.
+ (source, options, want, exc_msg) = \
+ self._parse_example(m, name, lineno)
+ # Create an Example, and add it to the list.
+ if not self._IS_BLANK_OR_COMMENT(source):
+ output.append( Example(source, want, exc_msg,
+ lineno=lineno,
+ indent=min_indent+len(m.group('indent')),
+ options=options) )
+ # Update lineno (lines inside this example)
+ lineno += string.count('\n', m.start(), m.end())
+ # Update charno.
+ charno = m.end()
+ # Add any remaining post-example text to `output`.
+ output.append(string[charno:])
+ return output
+
+ def get_doctest(self, string, globs, name, filename, lineno):
+ """
+ Extract all doctest examples from the given string, and
+ collect them into a `DocTest` object.
+
+ `globs`, `name`, `filename`, and `lineno` are attributes for
+ the new `DocTest` object. See the documentation for `DocTest`
+ for more information.
+ """
+ return DocTest(self.get_examples(string, name), globs,
+ name, filename, lineno, string)
+
+ def get_examples(self, string, name='<string>'):
+ """
+ Extract all doctest examples from the given string, and return
+ them as a list of `Example` objects. Line numbers are
+ 0-based, because it's most common in doctests that nothing
+ interesting appears on the same line as opening triple-quote,
+ and so the first interesting line is called \"line 1\" then.
+
+ The optional argument `name` is a name identifying this
+ string, and is only used for error messages.
+ """
+ return [x for x in self.parse(string, name)
+ if isinstance(x, Example)]
+
+ def _parse_example(self, m, name, lineno):
+ """
+ Given a regular expression match from `_EXAMPLE_RE` (`m`),
+ return a pair `(source, want)`, where `source` is the matched
+ example's source code (with prompts and indentation stripped);
+ and `want` is the example's expected output (with indentation
+ stripped).
+
+ `name` is the string's name, and `lineno` is the line number
+ where the example starts; both are used for error messages.
+ """
+ # Get the example's indentation level.
+ indent = len(m.group('indent'))
+
+ # Divide source into lines; check that they're properly
+ # indented; and then strip their indentation & prompts.
+ source_lines = m.group('source').split('\n')
+ self._check_prompt_blank(source_lines, indent, name, lineno)
+ self._check_prefix(source_lines[1:], ' '*indent + '.', name, lineno)
+ source = '\n'.join([sl[indent+4:] for sl in source_lines])
+
+ # Divide want into lines; check that it's properly indented; and
+ # then strip the indentation. Spaces before the last newline should
+ # be preserved, so plain rstrip() isn't good enough.
+ want = m.group('want')
+ want_lines = want.split('\n')
+ if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
+ del want_lines[-1] # forget final newline & spaces after it
+ self._check_prefix(want_lines, ' '*indent, name,
+ lineno + len(source_lines))
+ want = '\n'.join([wl[indent:] for wl in want_lines])
+
+ # If `want` contains a traceback message, then extract it.
+ m = self._EXCEPTION_RE.match(want)
+ if m:
+ exc_msg = m.group('msg')
+ else:
+ exc_msg = None
+
+ # Extract options from the source.
+ options = self._find_options(source, name, lineno)
+
+ return source, options, want, exc_msg
+
+ # This regular expression looks for option directives in the
+ # source code of an example. Option directives are comments
+ # starting with "doctest:". Warning: this may give false
+ # positives for string-literals that contain the string
+ # "#doctest:". Eliminating these false positives would require
+ # actually parsing the string; but we limit them by ignoring any
+ # line containing "#doctest:" that is *followed* by a quote mark.
+ _OPTION_DIRECTIVE_RE = re.compile(r'#\s*doctest:\s*([^\n\'"]*)$',
+ re.MULTILINE)
+
+ def _find_options(self, source, name, lineno):
+ """
+ Return a dictionary containing option overrides extracted from
+ option directives in the given source string.
+
+ `name` is the string's name, and `lineno` is the line number
+ where the example starts; both are used for error messages.
+ """
+ options = {}
+ # (note: with the current regexp, this will match at most once:)
+ for m in self._OPTION_DIRECTIVE_RE.finditer(source):
+ option_strings = m.group(1).replace(',', ' ').split()
+ for option in option_strings:
+ if (option[0] not in '+-' or
+ option[1:] not in OPTIONFLAGS_BY_NAME):
+ raise ValueError('line %r of the doctest for %s '
+ 'has an invalid option: %r' %
+ (lineno+1, name, option))
+ flag = OPTIONFLAGS_BY_NAME[option[1:]]
+ options[flag] = (option[0] == '+')
+ if options and self._IS_BLANK_OR_COMMENT(source):
+ raise ValueError('line %r of the doctest for %s has an option '
+ 'directive on a line with no example: %r' %
+ (lineno, name, source))
+ return options
+
+ # This regular expression finds the indentation of every non-blank
+ # line in a string.
+ _INDENT_RE = re.compile('^([ ]*)(?=\S)', re.MULTILINE)
+
+ def _min_indent(self, s):
+ "Return the minimum indentation of any non-blank line in `s`"
+ indents = [len(indent) for indent in self._INDENT_RE.findall(s)]
+ if len(indents) > 0:
+ return min(indents)
+ else:
+ return 0
+
+ def _check_prompt_blank(self, lines, indent, name, lineno):
+ """
+ Given the lines of a source string (including prompts and
+ leading indentation), check to make sure that every prompt is
+ followed by a space character. If any line is not followed by
+ a space character, then raise ValueError.
+ """
+ for i, line in enumerate(lines):
+ if len(line) >= indent+4 and line[indent+3] != ' ':
+ raise ValueError('line %r of the docstring for %s '
+ 'lacks blank after %s: %r' %
+ (lineno+i+1, name,
+ line[indent:indent+3], line))
+
+ def _check_prefix(self, lines, prefix, name, lineno):
+ """
+ Check that every line in the given list starts with the given
+ prefix; if any line does not, then raise a ValueError.
+ """
+ for i, line in enumerate(lines):
+ if line and not line.startswith(prefix):
+ raise ValueError('line %r of the docstring for %s has '
+ 'inconsistent leading whitespace: %r' %
+ (lineno+i+1, name, line))
+
+
+######################################################################
+## 4. DocTest Finder
+######################################################################
+
+class DocTestFinder:
+ """
+ A class used to extract the DocTests that are relevant to a given
+ object, from its docstring and the docstrings of its contained
+ objects. Doctests can currently be extracted from the following
+ object types: modules, functions, classes, methods, staticmethods,
+ classmethods, and properties.
+ """
+
+ def __init__(self, verbose=False, parser=DocTestParser(),
+ recurse=True, _namefilter=None, exclude_empty=True):
+ """
+ Create a new doctest finder.
+
+ The optional argument `parser` specifies a class or
+ function that should be used to create new DocTest objects (or
+ objects that implement the same interface as DocTest). The
+ signature for this factory function should match the signature
+ of the DocTest constructor.
+
+ If the optional argument `recurse` is false, then `find` will
+ only examine the given object, and not any contained objects.
+
+ If the optional argument `exclude_empty` is false, then `find`
+ will include tests for objects with empty docstrings.
+ """
+ self._parser = parser
+ self._verbose = verbose
+ self._recurse = recurse
+ self._exclude_empty = exclude_empty
+ # _namefilter is undocumented, and exists only for temporary backward-
+ # compatibility support of testmod's deprecated isprivate mess.
+ self._namefilter = _namefilter
+
+ def find(self, obj, name=None, module=None, globs=None,
+ extraglobs=None):
+ """
+ Return a list of the DocTests that are defined by the given
+ object's docstring, or by any of its contained objects'
+ docstrings.
+
+ The optional parameter `module` is the module that contains
+ the given object. If the module is not specified or is None, then
+ the test finder will attempt to automatically determine the
+ correct module. The object's module is used:
+
+ - As a default namespace, if `globs` is not specified.
+ - To prevent the DocTestFinder from extracting DocTests
+ from objects that are imported from other modules.
+ - To find the name of the file containing the object.
+ - To help find the line number of the object within its
+ file.
+
+ Contained objects whose module does not match `module` are ignored.
+
+ If `module` is False, no attempt to find the module will be made.
+ This is obscure, of use mostly in tests: if `module` is False, or
+ is None but cannot be found automatically, then all objects are
+ considered to belong to the (non-existent) module, so all contained
+ objects will (recursively) be searched for doctests.
+
+ The globals for each DocTest is formed by combining `globs`
+ and `extraglobs` (bindings in `extraglobs` override bindings
+ in `globs`). A new copy of the globals dictionary is created
+ for each DocTest. If `globs` is not specified, then it
+ defaults to the module's `__dict__`, if specified, or {}
+ otherwise. If `extraglobs` is not specified, then it defaults
+ to {}.
+
+ """
+ # If name was not specified, then extract it from the object.
+ if name is None:
+ name = getattr(obj, '__name__', None)
+ if name is None:
+ raise ValueError("DocTestFinder.find: name must be given "
+ "when obj.__name__ doesn't exist: %r" %
+ (type(obj),))
+
+ # Find the module that contains the given object (if obj is
+ # a module, then module=obj.). Note: this may fail, in which
+ # case module will be None.
+ if module is False:
+ module = None
+ elif module is None:
+ module = inspect.getmodule(obj)
+
+ # Read the module's source code. This is used by
+ # DocTestFinder._find_lineno to find the line number for a
+ # given object's docstring.
+ try:
+ file = inspect.getsourcefile(obj) or inspect.getfile(obj)
+ source_lines = linecache.getlines(file)
+ if not source_lines:
+ source_lines = None
+ except TypeError:
+ source_lines = None
+
+ # Initialize globals, and merge in extraglobs.
+ if globs is None:
+ if module is None:
+ globs = {}
+ else:
+ globs = module.__dict__.copy()
+ else:
+ globs = globs.copy()
+ if extraglobs is not None:
+ globs.update(extraglobs)
+
+ # Recursively expore `obj`, extracting DocTests.
+ tests = []
+ self._find(tests, obj, name, module, source_lines, globs, {})
+ return tests
+
+ def _filter(self, obj, prefix, base):
+ """
+ Return true if the given object should not be examined.
+ """
+ return (self._namefilter is not None and
+ self._namefilter(prefix, base))
+
+ def _from_module(self, module, object):
+ """
+ Return true if the given object is defined in the given
+ module.
+ """
+ if module is None:
+ return True
+ elif inspect.isfunction(object):
+ return module.__dict__ is object.func_globals
+ elif inspect.isclass(object):
+ return module.__name__ == object.__module__
+ elif inspect.getmodule(object) is not None:
+ return module is inspect.getmodule(object)
+ elif hasattr(object, '__module__'):
+ return module.__name__ == object.__module__
+ elif isinstance(object, property):
+ return True # [XX] no way not be sure.
+ else:
+ raise ValueError("object must be a class or function")
+
+ def _find(self, tests, obj, name, module, source_lines, globs, seen):
+ """
+ Find tests for the given object and any contained objects, and
+ add them to `tests`.
+ """
+ if self._verbose:
+ print 'Finding tests in %s' % name
+
+ # If we've already processed this object, then ignore it.
+ if id(obj) in seen:
+ return
+ seen[id(obj)] = 1
+
+ # Find a test for this object, and add it to the list of tests.
+ test = self._get_test(obj, name, module, globs, source_lines)
+ if test is not None:
+ tests.append(test)
+
+ # Look for tests in a module's contained objects.
+ if inspect.ismodule(obj) and self._recurse:
+ for valname, val in obj.__dict__.items():
+ # Check if this contained object should be ignored.
+ if self._filter(val, name, valname):
+ continue
+ valname = '%s.%s' % (name, valname)
+ # Recurse to functions & classes.
+ if ((inspect.isfunction(val) or inspect.isclass(val)) and
+ self._from_module(module, val)):
+ self._find(tests, val, valname, module, source_lines,
+ globs, seen)
+
+ # Look for tests in a module's __test__ dictionary.
+ if inspect.ismodule(obj) and self._recurse:
+ for valname, val in getattr(obj, '__test__', {}).items():
+ if not isinstance(valname, basestring):
+ raise ValueError("DocTestFinder.find: __test__ keys "
+ "must be strings: %r" %
+ (type(valname),))
+ if not (inspect.isfunction(val) or inspect.isclass(val) or
+ inspect.ismethod(val) or inspect.ismodule(val) or
+ isinstance(val, basestring)):
+ raise ValueError("DocTestFinder.find: __test__ values "
+ "must be strings, functions, methods, "
+ "classes, or modules: %r" %
+ (type(val),))
+ valname = '%s.__test__.%s' % (name, valname)
+ self._find(tests, val, valname, module, source_lines,
+ globs, seen)
+
+ # Look for tests in a class's contained objects.
+ if inspect.isclass(obj) and self._recurse:
+ for valname, val in obj.__dict__.items():
+ # Check if this contained object should be ignored.
+ if self._filter(val, name, valname):
+ continue
+ # Special handling for staticmethod/classmethod.
+ if isinstance(val, staticmethod):
+ val = getattr(obj, valname)
+ if isinstance(val, classmethod):
+ val = getattr(obj, valname).im_func
+
+ # Recurse to methods, properties, and nested classes.
+ if ((inspect.isfunction(val) or inspect.isclass(val) or
+ isinstance(val, property)) and
+ self._from_module(module, val)):
+ valname = '%s.%s' % (name, valname)
+ self._find(tests, val, valname, module, source_lines,
+ globs, seen)
+
+ def _get_test(self, obj, name, module, globs, source_lines):
+ """
+ Return a DocTest for the given object, if it defines a docstring;
+ otherwise, return None.
+ """
+ # Extract the object's docstring. If it doesn't have one,
+ # then return None (no test for this object).
+ if isinstance(obj, basestring):
+ docstring = obj
+ else:
+ try:
+ if obj.__doc__ is None:
+ docstring = ''
+ else:
+ docstring = obj.__doc__
+ if not isinstance(docstring, basestring):
+ docstring = str(docstring)
+ except (TypeError, AttributeError):
+ docstring = ''
+
+ # Find the docstring's location in the file.
+ lineno = self._find_lineno(obj, source_lines)
+
+ # Don't bother if the docstring is empty.
+ if self._exclude_empty and not docstring:
+ return None
+
+ # Return a DocTest for this object.
+ if module is None:
+ filename = None
+ else:
+ filename = getattr(module, '__file__', module.__name__)
+ if filename[-4:] in (".pyc", ".pyo"):
+ filename = filename[:-1]
+ return self._parser.get_doctest(docstring, globs, name,
+ filename, lineno)
+
+ def _find_lineno(self, obj, source_lines):
+ """
+ Return a line number of the given object's docstring. Note:
+ this method assumes that the object has a docstring.
+ """
+ lineno = None
+
+ # Find the line number for modules.
+ if inspect.ismodule(obj):
+ lineno = 0
+
+ # Find the line number for classes.
+ # Note: this could be fooled if a class is defined multiple
+ # times in a single file.
+ if inspect.isclass(obj):
+ if source_lines is None:
+ return None
+ pat = re.compile(r'^\s*class\s*%s\b' %
+ getattr(obj, '__name__', '-'))
+ for i, line in enumerate(source_lines):
+ if pat.match(line):
+ lineno = i
+ break
+
+ # Find the line number for functions & methods.
+ if inspect.ismethod(obj): obj = obj.im_func
+ if inspect.isfunction(obj): obj = obj.func_code
+ if inspect.istraceback(obj): obj = obj.tb_frame
+ if inspect.isframe(obj): obj = obj.f_code
+ if inspect.iscode(obj):
+ lineno = getattr(obj, 'co_firstlineno', None)-1
+
+ # Find the line number where the docstring starts. Assume
+ # that it's the first line that begins with a quote mark.
+ # Note: this could be fooled by a multiline function
+ # signature, where a continuation line begins with a quote
+ # mark.
+ if lineno is not None:
+ if source_lines is None:
+ return lineno+1
+ pat = re.compile('(^|.*:)\s*\w*("|\')')
+ for lineno in range(lineno, len(source_lines)):
+ if pat.match(source_lines[lineno]):
+ return lineno
+
+ # We couldn't find the line number.
+ return None
+
+######################################################################
+## 5. DocTest Runner
+######################################################################
+
+class DocTestRunner:
+ """
+ A class used to run DocTest test cases, and accumulate statistics.
+ The `run` method is used to process a single DocTest case. It
+ returns a tuple `(f, t)`, where `t` is the number of test cases
+ tried, and `f` is the number of test cases that failed.
+
+ >>> tests = DocTestFinder().find(_TestClass)
+ >>> runner = DocTestRunner(verbose=False)
+ >>> for test in tests:
+ ... print runner.run(test)
+ (0, 2)
+ (0, 1)
+ (0, 2)
+ (0, 2)
+
+ The `summarize` method prints a summary of all the test cases that
+ have been run by the runner, and returns an aggregated `(f, t)`
+ tuple:
+
+ >>> runner.summarize(verbose=1)
+ 4 items passed all tests:
+ 2 tests in _TestClass
+ 2 tests in _TestClass.__init__
+ 2 tests in _TestClass.get
+ 1 tests in _TestClass.square
+ 7 tests in 4 items.
+ 7 passed and 0 failed.
+ Test passed.
+ (0, 7)
+
+ The aggregated number of tried examples and failed examples is
+ also available via the `tries` and `failures` attributes:
+
+ >>> runner.tries
+ 7
+ >>> runner.failures
+ 0
+
+ The comparison between expected outputs and actual outputs is done
+ by an `OutputChecker`. This comparison may be customized with a
+ number of option flags; see the documentation for `testmod` for
+ more information. If the option flags are insufficient, then the
+ comparison may also be customized by passing a subclass of
+ `OutputChecker` to the constructor.
+
+ The test runner's display output can be controlled in two ways.
+ First, an output function (`out) can be passed to
+ `TestRunner.run`; this function will be called with strings that
+ should be displayed. It defaults to `sys.stdout.write`. If
+ capturing the output is not sufficient, then the display output
+ can be also customized by subclassing DocTestRunner, and
+ overriding the methods `report_start`, `report_success`,
+ `report_unexpected_exception`, and `report_failure`.
+ """
+ # This divider string is used to separate failure messages, and to
+ # separate sections of the summary.
+ DIVIDER = "*" * 70
+
+ def __init__(self, checker=None, verbose=None, optionflags=0):
+ """
+ Create a new test runner.
+
+ Optional keyword arg `checker` is the `OutputChecker` that
+ should be used to compare the expected outputs and actual
+ outputs of doctest examples.
+
+ Optional keyword arg 'verbose' prints lots of stuff if true,
+ only failures if false; by default, it's true iff '-v' is in
+ sys.argv.
+
+ Optional argument `optionflags` can be used to control how the
+ test runner compares expected output to actual output, and how
+ it displays failures. See the documentation for `testmod` for
+ more information.
+ """
+ self._checker = checker or OutputChecker()
+ if verbose is None:
+ verbose = '-v' in sys.argv
+ self._verbose = verbose
+ self.optionflags = optionflags
+ self.original_optionflags = optionflags
+
+ # Keep track of the examples we've run.
+ self.tries = 0
+ self.failures = 0
+ self._name2ft = {}
+
+ # Create a fake output target for capturing doctest output.
+ self._fakeout = _SpoofOut()
+
+ #/////////////////////////////////////////////////////////////////
+ # Reporting methods
+ #/////////////////////////////////////////////////////////////////
+
+ def report_start(self, out, test, example):
+ """
+ Report that the test runner is about to process the given
+ example. (Only displays a message if verbose=True)
+ """
+ if self._verbose:
+ if example.want:
+ out('Trying:\n' + _indent(example.source) +
+ 'Expecting:\n' + _indent(example.want))
+ else:
+ out('Trying:\n' + _indent(example.source) +
+ 'Expecting nothing\n')
+
+ def report_success(self, out, test, example, got):
+ """
+ Report that the given example ran successfully. (Only
+ displays a message if verbose=True)
+ """
+ if self._verbose:
+ out("ok\n")
+
+ def report_failure(self, out, test, example, got):
+ """
+ Report that the given example failed.
+ """
+ out(self._failure_header(test, example) +
+ self._checker.output_difference(example, got, self.optionflags))
+
+ def report_unexpected_exception(self, out, test, example, exc_info):
+ """
+ Report that the given example raised an unexpected exception.
+ """
+ out(self._failure_header(test, example) +
+ 'Exception raised:\n' + _indent(_exception_traceback(exc_info)))
+
+ def _failure_header(self, test, example):
+ out = [self.DIVIDER]
+ if test.filename:
+ if test.lineno is not None and example.lineno is not None:
+ lineno = test.lineno + example.lineno + 1
+ else:
+ lineno = '?'
+ out.append('File "%s", line %s, in %s' %
+ (test.filename, lineno, test.name))
+ else:
+ out.append('Line %s, in %s' % (example.lineno+1, test.name))
+ out.append('Failed example:')
+ source = example.source
+ out.append(_indent(source))
+ return '\n'.join(out)
+
+ #/////////////////////////////////////////////////////////////////
+ # DocTest Running
+ #/////////////////////////////////////////////////////////////////
+
+ def __run(self, test, compileflags, out):
+ """
+ Run the examples in `test`. Write the outcome of each example
+ with one of the `DocTestRunner.report_*` methods, using the
+ writer function `out`. `compileflags` is the set of compiler
+ flags that should be used to execute examples. Return a tuple
+ `(f, t)`, where `t` is the number of examples tried, and `f`
+ is the number of examples that failed. The examples are run
+ in the namespace `test.globs`.
+ """
+ # Keep track of the number of failures and tries.
+ failures = tries = 0
+
+ # Save the option flags (since option directives can be used
+ # to modify them).
+ original_optionflags = self.optionflags
+
+ SUCCESS, FAILURE, BOOM = range(3) # `outcome` state
+
+ check = self._checker.check_output
+
+ # Process each example.
+ for examplenum, example in enumerate(test.examples):
+
+ # If REPORT_ONLY_FIRST_FAILURE is set, then supress
+ # reporting after the first failure.
+ quiet = (self.optionflags & REPORT_ONLY_FIRST_FAILURE and
+ failures > 0)
+
+ # Merge in the example's options.
+ self.optionflags = original_optionflags
+ if example.options:
+ for (optionflag, val) in example.options.items():
+ if val:
+ self.optionflags |= optionflag
+ else:
+ self.optionflags &= ~optionflag
+
+ # Record that we started this example.
+ tries += 1
+ if not quiet:
+ self.report_start(out, test, example)
+
+ # Use a special filename for compile(), so we can retrieve
+ # the source code during interactive debugging (see
+ # __patched_linecache_getlines).
+ filename = '<doctest %s[%d]>' % (test.name, examplenum)
+
+ # Run the example in the given context (globs), and record
+ # any exception that gets raised. (But don't intercept
+ # keyboard interrupts.)
+ try:
+ # Don't blink! This is where the user's code gets run.
+ exec compile(example.source, filename, "single",
+ compileflags, 1) in test.globs
+ self.debugger.set_continue() # ==== Example Finished ====
+ exception = None
+ except KeyboardInterrupt:
+ raise
+ except:
+ exception = sys.exc_info()
+ self.debugger.set_continue() # ==== Example Finished ====
+
+ got = self._fakeout.getvalue() # the actual output
+ self._fakeout.truncate(0)
+ outcome = FAILURE # guilty until proved innocent or insane
+
+ # If the example executed without raising any exceptions,
+ # verify its output.
+ if exception is None:
+ if check(example.want, got, self.optionflags):
+ outcome = SUCCESS
+
+ # The example raised an exception: check if it was expected.
+ else:
+ exc_info = sys.exc_info()
+ exc_msg = traceback.format_exception_only(*exc_info[:2])[-1]
+ if not quiet:
+ got += _exception_traceback(exc_info)
+
+ # If `example.exc_msg` is None, then we weren't expecting
+ # an exception.
+ if example.exc_msg is None:
+ outcome = BOOM
+
+ # We expected an exception: see whether it matches.
+ elif check(example.exc_msg, exc_msg, self.optionflags):
+ outcome = SUCCESS
+
+ # Another chance if they didn't care about the detail.
+ elif self.optionflags & IGNORE_EXCEPTION_DETAIL:
+ m1 = re.match(r'[^:]*:', example.exc_msg)
+ m2 = re.match(r'[^:]*:', exc_msg)
+ if m1 and m2 and check(m1.group(0), m2.group(0),
+ self.optionflags):
+ outcome = SUCCESS
+
+ # Report the outcome.
+ if outcome is SUCCESS:
+ if not quiet:
+ self.report_success(out, test, example, got)
+ elif outcome is FAILURE:
+ if not quiet:
+ self.report_failure(out, test, example, got)
+ failures += 1
+ elif outcome is BOOM:
+ if not quiet:
+ self.report_unexpected_exception(out, test, example,
+ exc_info)
+ failures += 1
+ else:
+ assert False, ("unknown outcome", outcome)
+
+ # Restore the option flags (in case they were modified)
+ self.optionflags = original_optionflags
+
+ # Record and return the number of failures and tries.
+ self.__record_outcome(test, failures, tries)
+ return failures, tries
+
+ def __record_outcome(self, test, f, t):
+ """
+ Record the fact that the given DocTest (`test`) generated `f`
+ failures out of `t` tried examples.
+ """
+ f2, t2 = self._name2ft.get(test.name, (0,0))
+ self._name2ft[test.name] = (f+f2, t+t2)
+ self.failures += f
+ self.tries += t
+
+ __LINECACHE_FILENAME_RE = re.compile(r'<doctest '
+ r'(?P<name>[\w\.]+)'
+ r'\[(?P<examplenum>\d+)\]>$')
+ def __patched_linecache_getlines(self, filename):
+ m = self.__LINECACHE_FILENAME_RE.match(filename)
+ if m and m.group('name') == self.test.name:
+ example = self.test.examples[int(m.group('examplenum'))]
+ return example.source.splitlines(True)
+ else:
+ return self.save_linecache_getlines(filename)
+
+ def run(self, test, compileflags=None, out=None, clear_globs=True):
+ """
+ Run the examples in `test`, and display the results using the
+ writer function `out`.
+
+ The examples are run in the namespace `test.globs`. If
+ `clear_globs` is true (the default), then this namespace will
+ be cleared after the test runs, to help with garbage
+ collection. If you would like to examine the namespace after
+ the test completes, then use `clear_globs=False`.
+
+ `compileflags` gives the set of flags that should be used by
+ the Python compiler when running the examples. If not
+ specified, then it will default to the set of future-import
+ flags that apply to `globs`.
+
+ The output of each example is checked using
+ `DocTestRunner.check_output`, and the results are formatted by
+ the `DocTestRunner.report_*` methods.
+ """
+ self.test = test
+
+ if compileflags is None:
+ compileflags = _extract_future_flags(test.globs)
+
+ save_stdout = sys.stdout
+ if out is None:
+ out = save_stdout.write
+ sys.stdout = self._fakeout
+
+ # Patch pdb.set_trace to restore sys.stdout during interactive
+ # debugging (so it's not still redirected to self._fakeout).
+ # Note that the interactive output will go to *our*
+ # save_stdout, even if that's not the real sys.stdout; this
+ # allows us to write test cases for the set_trace behavior.
+ save_set_trace = pdb.set_trace
+ self.debugger = _OutputRedirectingPdb(save_stdout)
+ self.debugger.reset()
+ pdb.set_trace = self.debugger.set_trace
+
+ # Patch linecache.getlines, so we can see the example's source
+ # when we're inside the debugger.
+ self.save_linecache_getlines = linecache.getlines
+ linecache.getlines = self.__patched_linecache_getlines
+
+ try:
+ return self.__run(test, compileflags, out)
+ finally:
+ sys.stdout = save_stdout
+ pdb.set_trace = save_set_trace
+ linecache.getlines = self.save_linecache_getlines
+ if clear_globs:
+ test.globs.clear()
+
+ #/////////////////////////////////////////////////////////////////
+ # Summarization
+ #/////////////////////////////////////////////////////////////////
+ def summarize(self, verbose=None):
+ """
+ Print a summary of all the test cases that have been run by
+ this DocTestRunner, and return a tuple `(f, t)`, where `f` is
+ the total number of failed examples, and `t` is the total
+ number of tried examples.
+
+ The optional `verbose` argument controls how detailed the
+ summary is. If the verbosity is not specified, then the
+ DocTestRunner's verbosity is used.
+ """
+ if verbose is None:
+ verbose = self._verbose
+ notests = []
+ passed = []
+ failed = []
+ totalt = totalf = 0
+ for x in self._name2ft.items():
+ name, (f, t) = x
+ assert f <= t
+ totalt += t
+ totalf += f
+ if t == 0:
+ notests.append(name)
+ elif f == 0:
+ passed.append( (name, t) )
+ else:
+ failed.append(x)
+ if verbose:
+ if notests:
+ print len(notests), "items had no tests:"
+ notests.sort()
+ for thing in notests:
+ print " ", thing
+ if passed:
+ print len(passed), "items passed all tests:"
+ passed.sort()
+ for thing, count in passed:
+ print " %3d tests in %s" % (count, thing)
+ if failed:
+ print self.DIVIDER
+ print len(failed), "items had failures:"
+ failed.sort()
+ for thing, (f, t) in failed:
+ print " %3d of %3d in %s" % (f, t, thing)
+ if verbose:
+ print totalt, "tests in", len(self._name2ft), "items."
+ print totalt - totalf, "passed and", totalf, "failed."
+ if totalf:
+ print "***Test Failed***", totalf, "failures."
+ elif verbose:
+ print "Test passed."
+ return totalf, totalt
+
+ #/////////////////////////////////////////////////////////////////
+ # Backward compatibility cruft to maintain doctest.master.
+ #/////////////////////////////////////////////////////////////////
+ def merge(self, other):
+ d = self._name2ft
+ for name, (f, t) in other._name2ft.items():
+ if name in d:
+ print "*** DocTestRunner.merge: '" + name + "' in both" \
+ " testers; summing outcomes."
+ f2, t2 = d[name]
+ f = f + f2
+ t = t + t2
+ d[name] = f, t
+
+class OutputChecker:
+ """
+ A class used to check the whether the actual output from a doctest
+ example matches the expected output. `OutputChecker` defines two
+ methods: `check_output`, which compares a given pair of outputs,
+ and returns true if they match; and `output_difference`, which
+ returns a string describing the differences between two outputs.
+ """
+ def check_output(self, want, got, optionflags):
+ """
+ Return True iff the actual output from an example (`got`)
+ matches the expected output (`want`). These strings are
+ always considered to match if they are identical; but
+ depending on what option flags the test runner is using,
+ several non-exact match types are also possible. See the
+ documentation for `TestRunner` for more information about
+ option flags.
+ """
+ # Handle the common case first, for efficiency:
+ # if they're string-identical, always return true.
+ if got == want:
+ ret...
[truncated message content] |
|
From: <sub...@co...> - 2005-02-24 00:27:13
|
Author: ianb
Date: 2005-02-24 00:27:08 +0000 (Thu, 24 Feb 2005)
New Revision: 642
Modified:
trunk/Validator/validator/htmlgen.py
trunk/Validator/validator/test_htmlgen.py
Log:
Allow for tags and attributes with :'s (to allow TAL)
Modified: trunk/Validator/validator/htmlgen.py
===================================================================
--- trunk/Validator/validator/htmlgen.py 2005-02-24 00:26:42 UTC (rev 641)
+++ trunk/Validator/validator/htmlgen.py 2005-02-24 00:27:08 UTC (rev 642)
@@ -67,6 +67,10 @@
if attr.startswith('_'):
raise AttributeError
attr = attr.lower()
+ if attr.endswith('_'):
+ attr = attr[:-1]
+ if attr.find('__') != -1:
+ attr = attr.replace('__', ':')
if attr == 'comment':
return Element(et.Comment, {})
else:
@@ -107,13 +111,20 @@
"'c' keyword argument, but not both")
args = kw['c']
del kw['c']
+ if not isinstance(args, (list, tuple)):
+ args = (args,)
for name, value in kw.items():
if value is None:
del kw[name]
continue
+ kw[name] = unicode(value)
if name.endswith('_'):
kw[name[:-1]] = value
del kw[name]
+ if name.find('__') != -1:
+ new_name = name.replace('__', ':')
+ kw[new_name] = value
+ del kw[name]
el.attrib.update(kw)
el.text = self.text
last = None
Modified: trunk/Validator/validator/test_htmlgen.py
===================================================================
--- trunk/Validator/validator/test_htmlgen.py 2005-02-24 00:26:42 UTC (rev 641)
+++ trunk/Validator/validator/test_htmlgen.py 2005-02-24 00:27:08 UTC (rev 642)
@@ -54,6 +54,11 @@
assert html.str(None) == ''
assert str(html.b(class_=None)('hey')) == '<b>hey</b>'
assert str(html.b(class_=' ')(None)) == '<b class=" " />'
+
+def test_namespace():
+ output = '<b tal:content="options/whatever" />'
+ assert str(html.b(**{'tal:content': 'options/whatever'})) == output
+ assert str(html.b(tal__content='options/whatever')) == output
if __name__ == '__main__':
# It's like a super-mini py.test...
|
|
From: <sub...@co...> - 2005-02-24 00:26:46
|
Author: ianb
Date: 2005-02-24 00:26:42 +0000 (Thu, 24 Feb 2005)
New Revision: 641
Modified:
trunk/Validator/validator/validators.py
Log:
Improved error message
Modified: trunk/Validator/validator/validators.py
===================================================================
--- trunk/Validator/validator/validators.py 2005-02-24 00:26:18 UTC (rev 640)
+++ trunk/Validator/validator/validators.py 2005-02-24 00:26:42 UTC (rev 641)
@@ -506,7 +506,7 @@
messages = {
'invalid': "Invalid value",
- 'notIn': "Value must be one of: %(items)s",
+ 'notIn': "Value must be one of: %(items)s (not %(value)r)",
}
def validate_python(self, value, state):
@@ -521,7 +521,8 @@
else:
items = '; '.join(map(str, self.list))
raise Invalid(self.message('notIn', state,
- items=items),
+ items=items,
+ value=value),
value, state)
class DictConverter(FancyValidator):
|
|
From: <sub...@co...> - 2005-02-24 00:26:24
|
Author: ianb
Date: 2005-02-24 00:26:18 +0000 (Thu, 24 Feb 2005)
New Revision: 640
Modified:
trunk/Validator/validator/htmlfill.py
Log:
Improved error message
Modified: trunk/Validator/validator/htmlfill.py
===================================================================
--- trunk/Validator/validator/htmlfill.py 2005-02-23 19:53:07 UTC (rev 639)
+++ trunk/Validator/validator/htmlfill.py 2005-02-24 00:26:18 UTC (rev 640)
@@ -145,8 +145,8 @@
if name is None:
name = self.in_error
assert name is not None, (
- "Name attribute in <error> required if not contained in "
- "<iferror> (%i:%i)" % self.getpos())
+ "Name attribute in <form:error> required if not contained in "
+ "<form:iferror> (%i:%i)" % self.getpos())
error = self.errors.get(name, '')
if error:
error = self.error_formatters[formatter](error)
|
|
From: <sub...@co...> - 2005-02-23 19:53:15
|
Author: ianb Date: 2005-02-23 19:53:07 +0000 (Wed, 23 Feb 2005) New Revision: 639 Modified: trunk/Validator/validator/htmlgen.py trunk/Validator/validator/test_htmlgen.py Log: Updated docstring Added new tests Doctests Got rid of Exclude, use None instead Modified: trunk/Validator/validator/htmlgen.py =================================================================== --- trunk/Validator/validator/htmlgen.py 2005-02-23 19:28:17 UTC (rev 638) +++ trunk/Validator/validator/htmlgen.py 2005-02-23 19:53:07 UTC (rev 639) @@ -1,65 +1,58 @@ """ -simplehtmlgen.py -5 Oct 2002 -Ian Bicking <ia...@co...> +Kind of like htmlgen, only much simpler. The only important symbol +that is exported is ``html``. -Kind of like htmlgen, only much simpler. The only important symbol that -is exported is ``html``. +This builds ElementTree nodes, but with some extra useful methods. +(Open issue: should it use ``ElementTree`` more, and the raw +``Element`` stuff less?) -You create tags with attribute access. I.e., the "A" anchor tag is -html.a. The attributes of the HTML tag are done with keyword +You create tags with attribute access. I.e., the ``A`` anchor tag is +``html.a``. The attributes of the HTML tag are done with keyword arguments. The contents of the tag are the non-keyword arguments -(concatenated). You can also use the special "c" keyword, passing a +(concatenated). You can also use the special ``c`` keyword, passing a list, tuple, or single tag, and it will make up the contents (this is useful because keywords have to come after all non-keyword arguments, -which is non-intuitive). +which is non-intuitive). Or you can chain them, adding the keywords +with one call, then the body with a second call, like:: -If the value of an attribute is Exclude, then no attribute will be -inserted. I.e.:: + >>> print html.a(href='http://yahoo.com')('<Yahoo>') + <a href=\"http://yahoo.com\"><Yahoo></a> - >>> html.a(href='http://www.yahoo.com', name=Exclude, c='Click Here') - '<a href=\"http://www.yahoo.com\">Click Here</a>' +Note that strings will be quoted; only tags given explicitly will +remain unquoted. +If the value of an attribute is None, then no attribute +will be inserted. So:: + + >>> print html.a(href='http://www.yahoo.com', name=None, + ... c='Click Here') + <a href=\"http://www.yahoo.com\">Click Here</a> + If the value is None, then the empty string is used. Otherwise str() is called on the value. -``html`` can also be called, and it will concatenate the string -representations of its arguments. +``html`` can also be called, and it will produce a special list from +its arguments, which adds a ``__str__`` method that does ``html.str`` +(which handles quoting, flattening these lists recursively, and using +'' for ``None``). -``html.input`` is special, in that you can use ``html.input.radio()`` -to get an ``<input type=\"radio\">`` tag. You can still use -``html.input(type=\"radio\")`` though, just like normal. - ``html.comment`` will generate an HTML comment, like -``html.comment('comment text', 'and some more text')`` -- note that -it cannot take keyword arguments (because they wouldn't mean anything). +``html.comment('comment text')`` -- note that it cannot take keyword +arguments (because they wouldn't mean anything). Examples:: >>> print html.html( - ... html.head(html.title("Page Title")), + ... html.head(html.title(\"Page Title\")), ... html.body( ... bgcolor='#000066', ... text='#ffffff', ... c=[html.h1('Page Title'), ... html.p('Hello world!')], ... )) - <html> - <head> - <title>Page Title</title> - </head> - <body text=\"#ffffff\" bgcolor=\"#000066\"> - <h1>Page Title</h1><p> - Hello world! - </p> - . - </body> - . - </html> - >>> html.a(href='#top', c='return to top') - '<a href=\"#top\">return to top</a>' - >>> 1.4 - 1.4 + <html><head><title>Page Title</title></head><body bgcolor=\"#000066\" text=\"#ffffff\"><h1>Page Title</h1><p>Hello world!</p></body></html> + >>> print html.a(href='#top')('return to top') + <a href=\"#top\">return to top</a> """ @@ -115,7 +108,7 @@ args = kw['c'] del kw['c'] for name, value in kw.items(): - if value is Exclude: + if value is None: del kw[name] continue if name.endswith('_'): @@ -128,6 +121,8 @@ last = item el.append(item) for arg in flatten(args): + if arg is None: + continue if not et.iselement(arg): if last is None: if el.text is None: @@ -170,9 +165,6 @@ raise AttributeError return self.__class__(self._tag, type=attr.lower()) -class Exclude: - pass - class ElementList(list): def __str__(self): @@ -183,8 +175,10 @@ def flatten(items): for item in items: - if isinstance(item, ElementList): + if isinstance(item, (list, tuple)): for sub in flatten(item): yield sub else: yield item + +__all__ = ['html'] Modified: trunk/Validator/validator/test_htmlgen.py =================================================================== --- trunk/Validator/validator/test_htmlgen.py 2005-02-23 19:28:17 UTC (rev 638) +++ trunk/Validator/validator/test_htmlgen.py 2005-02-23 19:53:07 UTC (rev 639) @@ -1,4 +1,5 @@ from htmlgen import html +import doctest # A test value that can't be encoded as ascii: uni_value = u'\xff' @@ -9,6 +10,8 @@ assert str(html.a('hey there')(href='test')) == output assert str(html.a(href='test', c='hey there')) == output assert str(html.a('hey there', href='test')) == output + assert str(html.a(href='test')('hey ', 'there')) == output + assert str(html.a(href='test')(['hey ', 'there'])) == output def test_compound(): output = '<b>Hey <i>you</i>!</b>' @@ -40,6 +43,17 @@ assert html.quote(None) == '' assert html.str(None) == '' assert str(html.b('<hey>')) == '<b><hey></b>' + +def test_comment(): + assert str(html.comment('test')) == '<!-- test -->' + assert (str(html.comment(uni_value)) + == '<!-- %s -->' % uni_value.encode('utf-8')) + assert str(html.comment('test')('this')) == '<!-- testthis -->' + +def test_none(): + assert html.str(None) == '' + assert str(html.b(class_=None)('hey')) == '<b>hey</b>' + assert str(html.b(class_=' ')(None)) == '<b class=" " />' if __name__ == '__main__': # It's like a super-mini py.test... @@ -47,3 +61,6 @@ if name.startswith('test'): print name value() + import htmlgen + doctest.testmod(htmlgen) + print 'doctest' |
|
From: <sub...@co...> - 2005-02-23 19:19:51
|
Author: ianb
Date: 2005-02-23 19:19:42 +0000 (Wed, 23 Feb 2005)
New Revision: 637
Modified:
trunk/Validator/validator/htmlgen.py
trunk/Validator/validator/test_htmlgen.py
Log:
Some more tweaks
Modified: trunk/Validator/validator/htmlgen.py
===================================================================
--- trunk/Validator/validator/htmlgen.py 2005-02-23 19:08:57 UTC (rev 636)
+++ trunk/Validator/validator/htmlgen.py 2005-02-23 19:19:42 UTC (rev 637)
@@ -83,11 +83,15 @@
return ElementList(args)
def quote(self, arg):
- return escape(unicode(arg), 1)
+ if arg is None:
+ return ''
+ return escape(unicode(arg).encode(default_encoding), 1)
def str(self, arg, encoding=None):
if isinstance(arg, str):
return arg
+ elif arg is None:
+ return ''
elif isinstance(arg, unicode):
return arg.encode(default_encoding)
elif isinstance(arg, (list, tuple)):
Modified: trunk/Validator/validator/test_htmlgen.py
===================================================================
--- trunk/Validator/validator/test_htmlgen.py 2005-02-23 19:08:57 UTC (rev 636)
+++ trunk/Validator/validator/test_htmlgen.py 2005-02-23 19:19:42 UTC (rev 637)
@@ -1,5 +1,8 @@
from htmlgen import html
+# A test value that can't be encoded as ascii:
+uni_value = u'\xff'
+
def test_basic():
output = '<a href="test">hey there</a>'
assert str(html.a(href='test')('hey there')) == output
@@ -20,7 +23,6 @@
assert str(html.b(inner)) == output
def test_unicode():
- uni_value = u'\xff'
try:
uni_value.encode('ascii')
except ValueError:
@@ -31,8 +33,17 @@
% (uni_value, uni_value.encode('ascii')))
assert (str(html.b(uni_value))
== ('<b>%s</b>' % uni_value).encode('utf-8'))
+
+def test_quote():
+ assert html.quote('<hey>!') == '<hey>!'
+ assert html.quote(uni_value) == uni_value.encode('utf-8')
+ assert html.quote(None) == ''
+ assert html.str(None) == ''
+ assert str(html.b('<hey>')) == '<b><hey></b>'
if __name__ == '__main__':
- test_basic()
- test_compound()
- test_unicode()
+ # It's like a super-mini py.test...
+ for name, value in globals().items():
+ if name.startswith('test'):
+ print name
+ value()
|
|
From: <sub...@co...> - 2005-02-23 19:09:09
|
Author: ianb Date: 2005-02-23 19:08:57 +0000 (Wed, 23 Feb 2005) New Revision: 636 Added: trunk/Validator/validator/htmlgen.py trunk/Validator/validator/test_htmlgen.py Log: Added an HTML-generation library, that uses ElementTree as its underlying structure. Added: trunk/Validator/validator/htmlgen.py =================================================================== --- trunk/Validator/validator/htmlgen.py 2005-02-22 12:32:34 UTC (rev 635) +++ trunk/Validator/validator/htmlgen.py 2005-02-23 19:08:57 UTC (rev 636) @@ -0,0 +1,186 @@ +""" +simplehtmlgen.py +5 Oct 2002 +Ian Bicking <ia...@co...> + +Kind of like htmlgen, only much simpler. The only important symbol that +is exported is ``html``. + +You create tags with attribute access. I.e., the "A" anchor tag is +html.a. The attributes of the HTML tag are done with keyword +arguments. The contents of the tag are the non-keyword arguments +(concatenated). You can also use the special "c" keyword, passing a +list, tuple, or single tag, and it will make up the contents (this is +useful because keywords have to come after all non-keyword arguments, +which is non-intuitive). + +If the value of an attribute is Exclude, then no attribute will be +inserted. I.e.:: + + >>> html.a(href='http://www.yahoo.com', name=Exclude, c='Click Here') + '<a href=\"http://www.yahoo.com\">Click Here</a>' + +If the value is None, then the empty string is used. Otherwise str() +is called on the value. + +``html`` can also be called, and it will concatenate the string +representations of its arguments. + +``html.input`` is special, in that you can use ``html.input.radio()`` +to get an ``<input type=\"radio\">`` tag. You can still use +``html.input(type=\"radio\")`` though, just like normal. + +``html.comment`` will generate an HTML comment, like +``html.comment('comment text', 'and some more text')`` -- note that +it cannot take keyword arguments (because they wouldn't mean anything). + +Examples:: + + >>> print html.html( + ... html.head(html.title("Page Title")), + ... html.body( + ... bgcolor='#000066', + ... text='#ffffff', + ... c=[html.h1('Page Title'), + ... html.p('Hello world!')], + ... )) + <html> + <head> + <title>Page Title</title> + </head> + <body text=\"#ffffff\" bgcolor=\"#000066\"> + <h1>Page Title</h1><p> + Hello world! + </p> + . + </body> + . + </html> + >>> html.a(href='#top', c='return to top') + '<a href=\"#top\">return to top</a>' + >>> 1.4 + 1.4 + +""" + +from cgi import escape +import elementtree.ElementTree as et + +default_encoding = 'utf-8' + +class _HTML: + + def __getattr__(self, attr): + if attr.startswith('_'): + raise AttributeError + attr = attr.lower() + if attr == 'comment': + return Element(et.Comment, {}) + else: + return Element(attr, {}) + + def __call__(self, *args): + return ElementList(args) + + def quote(self, arg): + return escape(unicode(arg), 1) + + def str(self, arg, encoding=None): + if isinstance(arg, str): + return arg + elif isinstance(arg, unicode): + return arg.encode(default_encoding) + elif isinstance(arg, (list, tuple)): + return ''.join(map(self.str, arg)) + elif isinstance(arg, Element): + return str(arg) + else: + return unicode(arg).encode(default_encoding) + +html = _HTML() + +class Element(et._ElementInterface): + + def __call__(self, *args, **kw): + el = self.__class__(self.tag, self.attrib) + if kw.has_key('c'): + if args: + raise ValueError( + "You may either provide positional arguments or a " + "'c' keyword argument, but not both") + args = kw['c'] + del kw['c'] + for name, value in kw.items(): + if value is Exclude: + del kw[name] + continue + if name.endswith('_'): + kw[name[:-1]] = value + del kw[name] + el.attrib.update(kw) + el.text = self.text + last = None + for item in self.getchildren(): + last = item + el.append(item) + for arg in flatten(args): + if not et.iselement(arg): + if last is None: + if el.text is None: + el.text = unicode(arg) + else: + el.text += unicode(arg) + else: + if last.tail is None: + last.tail = unicode(arg) + else: + last.tail += unicode(arg) + else: + last = arg + el.append(last) + return el + + def __str__(self): + return et.tostring(self, default_encoding) + + def __unicode__(self): + # This is lame! + return str(self).decode(default_encoding) + + def __repr__(self): + return '<Element %r>' % str(self) + +class UnfinishedInput: + + def __init__(self, tag, type=None): + self._type = type + UnfinishedTag.__init__(self, tag) + + def __call__(self, *args, **kw): + if self._type: + kw['type'] = self._type + return UnfinishedTag.__call__(self, *args, **kw) + + def __getattr__(self, attr): + if attr.startswith('__'): + raise AttributeError + return self.__class__(self._tag, type=attr.lower()) + +class Exclude: + pass + +class ElementList(list): + + def __str__(self): + return html.str(self) + + def __repr__(self): + return 'ElementList(%s)' % list.__repr__(self) + +def flatten(items): + for item in items: + if isinstance(item, ElementList): + for sub in flatten(item): + yield sub + else: + yield item Added: trunk/Validator/validator/test_htmlgen.py =================================================================== --- trunk/Validator/validator/test_htmlgen.py 2005-02-22 12:32:34 UTC (rev 635) +++ trunk/Validator/validator/test_htmlgen.py 2005-02-23 19:08:57 UTC (rev 636) @@ -0,0 +1,38 @@ +from htmlgen import html + +def test_basic(): + output = '<a href="test">hey there</a>' + assert str(html.a(href='test')('hey there')) == output + assert str(html.a('hey there')(href='test')) == output + assert str(html.a(href='test', c='hey there')) == output + assert str(html.a('hey there', href='test')) == output + +def test_compound(): + output = '<b>Hey <i>you</i>!</b>' + assert str(html.b('Hey ', html.i('you'), '!')) == output + assert str(html.b()('Hey ')(html.i()('you'))('!')) == output + inner = html('Hey ', html.i('you'), '!') + assert html.str(inner) == 'Hey <i>you</i>!' + assert str(inner) == 'Hey <i>you</i>!' + assert (repr(inner) + == "ElementList(['Hey ', <Element '<i>you</i>'>, " + "'!'])") + assert str(html.b(inner)) == output + +def test_unicode(): + uni_value = u'\xff' + try: + uni_value.encode('ascii') + except ValueError: + pass + else: + assert 0, ( + "We need something that can't be ASCII-encoded: %r (%r)" + % (uni_value, uni_value.encode('ascii'))) + assert (str(html.b(uni_value)) + == ('<b>%s</b>' % uni_value).encode('utf-8')) + +if __name__ == '__main__': + test_basic() + test_compound() + test_unicode() |
|
From: <sub...@co...> - 2005-01-11 17:42:11
|
Author: ianb
Date: 2005-01-11 17:42:05 +0000 (Tue, 11 Jan 2005)
New Revision: 531
Modified:
trunk/Validator/validator/htmlfill.py
Log:
Added some fixes from Michel Thadeu:
* Bug in handle_end_iferror
* Handle multiple checkboxes with the same name
* Handle password fields (like text fields)
Modified: trunk/Validator/validator/htmlfill.py
===================================================================
--- trunk/Validator/validator/htmlfill.py 2005-01-11 17:22:39 UTC (rev 530)
+++ trunk/Validator/validator/htmlfill.py 2005-01-11 17:42:05 UTC (rev 531)
@@ -179,7 +179,9 @@
elif t == 'checkbox':
if (str(value) == self.get_attr(attrs, 'value')
or (self.get_attr(attrs, 'value') is None
- and value)):
+ and value)
+ or (isinstance(value, (list, tuple))
+ and self.get_attr(attrs, 'value') in value)):
self.set_attr(attrs, 'checked', 'checked')
else:
self.del_attr(attrs, 'checked')
@@ -196,6 +198,12 @@
self.add_key(name)
elif t == 'file':
pass # don't skip next
+ elif t == 'password':
+ self.set_attr(attrs, 'value', value or
+ self.get_attr(attrs, 'value', ''))
+ self.write_tag('input', attrs)
+ self.skip_next = True
+ self.add_key(name)
else:
assert 0, "I don't know about this kind of <input>: %s (pos: %s)" \
% (t, self.getpos())
|
|
From: <sub...@co...> - 2004-12-14 22:05:14
|
Author: ianb
Date: 2004-12-14 22:05:10 +0000 (Tue, 14 Dec 2004)
New Revision: 485
Modified:
trunk/Validator/validator/validators.py
Log:
Better validation of dates; bugfix for date conversino
Modified: trunk/Validator/validator/validators.py
===================================================================
--- trunk/Validator/validator/validators.py 2004-12-14 20:47:30 UTC (rev 484)
+++ trunk/Validator/validator/validators.py 2004-12-14 22:05:10 UTC (rev 485)
@@ -656,36 +656,64 @@
"""
Validates that a date is within the given range. Be sure to call
DateConverter first if you aren't expecting mxDateTime input.
+
+ earliest_date and latest_date may be functions; if so, they will
+ be called each time before validating.
"""
## @@: This should work with datetime as well, and even better
## handle having some datetime and some mxDateTime dates working
## together.
- earliestDate = None
- latestDate = None
+ earliest_date = None
+ latest_date = None
+ after_now = False
messages = {
'after': "Date must be after %(date)s",
'before': "Date must be before %(date)s",
# Double %'s, because this will be substituted twice:
- 'dateFormat': "%%A, %%d %%B %%Y",
+ 'date_format': "%%A, %%d %%B %%Y",
+ 'future': "The date must be sometime in the future",
}
def validate_python(self, value, state):
- if self.earliestDate and value < self.earliestDate:
- date_formatted = self.earliestDate.strftime(
- self.message('dateFormat', state))
- raise Invalid(
- self.message('after', state,
- date=date_formatted),
- value, state)
- if self.latestDate and value > self.latestDate:
- date_formatted = self.latestDate.strftime(
- self.message('dateFormat', state))
- raise Invalid(
- self.message('before', state,
- date=date_formatted),
- value, state)
+ global DateTime
+ if self.earliest_date:
+ if callable(self.earliest_date):
+ earliest_date = self.earliest_date()
+ else:
+ earliest_date = self.earliest_date
+ if value < earliest_date:
+ date_formatted = earliest_date.strftime(
+ self.message('date_format', state))
+ raise Invalid(
+ self.message('after', state,
+ date=date_formatted),
+ value, state)
+ if self.latest_date:
+ if callable(self.latest_date):
+ latest_date = self.latest_date()
+ else:
+ latest_date = self.latest_date
+ if value > latest_date:
+ date_formatted = latest_date.strftime(
+ self.message('date_format', state))
+ raise Invalid(
+ self.message('before', state,
+ date=date_formatted),
+ value, state)
+ if self.after_now:
+ if DateTime is None:
+ from mx import DateTime
+ now = DateTime.now()
+ if value < now:
+ date_formatted = now.strftime(
+ self.message('date_format', state))
+ raise Invalid(
+ self.message('future', state,
+ date=date_formatted),
+ value, state)
+
class Int(FancyValidator):
@@ -1071,7 +1099,7 @@
if self.accept_day:
return self.convert_day(value, state)
else:
- return self.convert_month(value)
+ return self.convert_month(value, state)
def convert_day(self, value, state):
self.assert_string(value, state)
@@ -1139,7 +1167,7 @@
raise Invalid(self.message('wrongFormat', state,
format='mm/yyyy'),
value, state)
- month = self.make_month(match.group(1))
+ month = self.make_month(match.group(1), state)
year = self.make_year(match.group(2), state)
if month > 12 or month < 1:
raise Invalid(self.message('monthRange', state),
|
|
From: <sub...@co...> - 2004-12-13 23:12:56
|
Author: ianb
Date: 2004-12-13 23:12:52 +0000 (Mon, 13 Dec 2004)
New Revision: 482
Modified:
trunk/Validator/validator/declarative.py
Log:
Python2.2 compatibility
Modified: trunk/Validator/validator/declarative.py
===================================================================
--- trunk/Validator/validator/declarative.py 2004-12-11 04:57:29 UTC (rev 481)
+++ trunk/Validator/validator/declarative.py 2004-12-13 23:12:52 UTC (rev 482)
@@ -18,14 +18,24 @@
when the class is created (including subclasses).
"""
+from __future__ import generators
+
import copy
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
-import itertools
-counter = itertools.count()
+try:
+ import itertools
+ counter = itertools.count()
+except ImportError:
+ def _counter():
+ i = 0
+ while 1:
+ i += 1
+ yield i
+ counter = _counter()
class classinstancemethod(object):
"""
|
|
From: <sub...@co...> - 2004-12-03 20:08:22
|
Author: ianb
Date: 2004-12-03 20:08:17 +0000 (Fri, 03 Dec 2004)
New Revision: 440
Modified:
trunk/Validator/validator/schema.py
Log:
Get rid of traceback info when it's no longer applicable
Modified: trunk/Validator/validator/schema.py
===================================================================
--- trunk/Validator/validator/schema.py 2004-12-03 20:07:55 UTC (rev 439)
+++ trunk/Validator/validator/schema.py 2004-12-03 20:08:17 UTC (rev 440)
@@ -183,6 +183,8 @@
except Invalid, e:
errors[name] = e
+ del __traceback_info__
+
for name in unused:
validator = adapt_validator(self.fields[name], state)
try:
|