Skip to content

Commit

Permalink
Merge pull request #48 from ThomasWaldmann/py3
Browse files Browse the repository at this point in the history
python3 port
  • Loading branch information
ThomasWaldmann committed Nov 19, 2018
2 parents 667b0ad + c980c46 commit 0c5de0c
Show file tree
Hide file tree
Showing 30 changed files with 196 additions and 117 deletions.
16 changes: 16 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,22 @@ matrix:
os: linux
dist: trusty # pypy download on xenial is 403
env: TOXENV=pypy
- python: "3.4"
os: linux
dist: xenial
env: TOXENV=py34
- python: "3.5"
os: linux
dist: xenial
env: TOXENV=py35
- python: "3.6"
os: linux
dist: xenial
env: TOXENV=py36
- python: "3.7"
os: linux
dist: xenial
env: TOXENV=py37

install: pip install tox

Expand Down
24 changes: 24 additions & 0 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,27 @@
'index': ['globaltoc.html', 'relations.html', 'links.html', 'sourcelink.html', 'searchbox.html'],
'genindex': ['links.html', 'sourcelink.html', 'searchbox.html'],
}

# Avoid issues like below when running under python 3.x:
#
# Expected:
# [u'hello', u'world']
#
# Got:
# ['hello', 'world']

import re
import sys
import doctest
OrigOutputChecker = doctest.OutputChecker


class Py23OutputChecker(OrigOutputChecker):
def check_output(self, want, got, optionflags):
if sys.version_info[0] > 2:
want = re.sub("u'(.*?)'", "'\\1'", want)
want = re.sub('u"(.*?)"', '"\\1"', want)
return OrigOutputChecker.check_output(self, want, got, optionflags)


doctest.OutputChecker = Py23OutputChecker
4 changes: 2 additions & 2 deletions docs/source/intro.rst
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ used as-is in output templates for form layout, redisplay and error reporting.
.. doctest::

>>> as_regular_python_data = form.value
>>> type(as_regular_python_data)
<type 'dict'>
>>> isinstance(as_regular_python_data, dict)
True
>>> as_regular_python_data['username']
u'jek'
>>> form2 = SignInForm(as_regular_python_data)
Expand Down
50 changes: 25 additions & 25 deletions docs/source/markup.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ Basic "Do What I Mean" form binding:

.. doctest:: generatorintro

>>> print html.input(form['username'])
>>> print(html.input(form['username']))
<input name="username" value="jek" />

Likewise with Genshi.
Expand Down Expand Up @@ -93,7 +93,7 @@ This time, the Generator is used in a Jinja2 template.
>>> template = Template("""\
... {{ html.input(form.username, name="other", class_="custom") }}
... """)
>>> print template.render(html=html, form=form)
>>> print(template.render(html=html, form=form))
<input name="other" value="jek" class="custom" />

These features are very similar in Genshi, too.
Expand Down Expand Up @@ -173,13 +173,13 @@ participate in ``tabindex=`` generation.
.. doctest:: transforms1

>>> html = Generator(tabindex=100)
>>> print html.textarea()
>>> print(html.textarea())
<textarea></textarea>
>>> print html.textarea(auto_tabindex=True)
>>> print(html.textarea(auto_tabindex=True))
<textarea tabindex="100"></textarea>
>>> html.set(auto_tabindex=True)
u''
>>> print html.textarea()
>>> print(html.textarea())
<textarea tabindex="101"></textarea>


Expand All @@ -189,7 +189,7 @@ that normally would not be transformed.

.. doctest:: transforms1

>>> print html.tag('squiznart', auto_tabindex=True)
>>> print(html.tag('squiznart', auto_tabindex=True))
<squiznart tabindex="102" />

The Python APIs and the Generator tags use "_"-separated transform names
Expand Down Expand Up @@ -220,21 +220,21 @@ uses XML-friendly "-"-separated attribute names in markup.

.. doctest:: transforms2

>>> print html.input(form['username'], type="text")
>>> print(html.input(form['username'], type="text"))
<input type="text" name="username" value="jek" />

Uses the explicitly provided ``name="foo"``:

.. doctest:: transforms2

>>> print html.input(form['username'], type="text", name='foo')
>>> print(html.input(form['username'], type="text", name='foo'))
<input type="text" name="foo" value="jek" />

Replaces ``name="foo"`` with the element's name:

.. doctest:: transforms2

>>> print html.input(form['username'], type="text", name='foo', auto_name=True)
>>> print(html.input(form['username'], type="text", name='foo', auto_name=True))
<input type="text" name="username" value="jek" />


Expand All @@ -255,14 +255,14 @@ uses XML-friendly "-"-separated attribute names in markup.

.. doctest:: transforms2

>>> print html.input(form['username'], type="text")
>>> print(html.input(form['username'], type="text"))
<input type="text" name="username" value="jek" />

Uses the explicitly provided ``value="quux"``:

.. doctest:: transforms2

>>> print html.input(form['username'], type="text", value='quux')
>>> print(html.input(form['username'], type="text", value='quux'))
<input type="text" name="username" value="quux" />

``<input>`` types **password**, **image** and **file**:
Expand All @@ -271,14 +271,14 @@ uses XML-friendly "-"-separated attribute names in markup.

.. doctest:: transforms2

>>> print html.input(form['password'], type="password")
>>> print(html.input(form['password'], type="password"))
<input type="password" name="password" />

But this behavior can be forced:

.. doctest:: transforms2

>>> print html.input(form['password'], type="password", auto_value=True)
>>> print(html.input(form['password'], type="password", auto_value=True))
<input type="password" name="password" value="secret" />

``<input>`` type **radio**:
Expand All @@ -293,11 +293,11 @@ uses XML-friendly "-"-separated attribute names in markup.

.. doctest:: transforms2

>>> print form['username'].u
>>> print(form['username'].u)
jek
>>> print html.input(form['username'], type="radio", value="quux")
>>> print(html.input(form['username'], type="radio", value="quux"))
<input type="radio" name="username" value="quux" />
>>> print html.input(form['username'], type="radio", value="jek")
>>> print(html.input(form['username'], type="radio", value="jek"))
<input type="radio" name="username" value="jek" checked="checked" />

``<input>`` type **checkbox**:
Expand All @@ -307,11 +307,11 @@ uses XML-friendly "-"-separated attribute names in markup.

.. doctest:: transforms2

>>> print form['username'].u
>>> print(form['username'].u)
jek
>>> print html.input(form['username'], type="checkbox", value="quux")
>>> print(html.input(form['username'], type="checkbox", value="quux"))
<input type="checkbox" name="username" value="quux" />
>>> print html.input(form['username'], type="checkbox", value="jek")
>>> print(html.input(form['username'], type="checkbox", value="jek"))
<input type="checkbox" name="username" value="jek" checked="checked" />

Or, if the bind is a :class:`~flatland.Container`, ``value=`` will be
Expand All @@ -324,7 +324,7 @@ uses XML-friendly "-"-separated attribute names in markup.
>>> Bag = Array.named('bag').of(String)
>>> bag = Bag(['a', 'c'])
>>> for value in 'a', 'b', 'c':
... print html.input(bag, type="checkbox", value=value)
... print(html.input(bag, type="checkbox", value=value))
...
<input type="checkbox" name="bag" value="a" checked="checked" />
<input type="checkbox" name="bag" value="b" />
Expand All @@ -336,15 +336,15 @@ uses XML-friendly "-"-separated attribute names in markup.

.. doctest:: transforms2

>>> print html.input(form['username'], type="checkbox")
>>> print(html.input(form['username'], type="checkbox"))
<input type="checkbox" name="username" />
>>> from flatland import Boolean
>>> toggle = Boolean.named('toggle')()
>>> print html.input(toggle, type="checkbox")
>>> print(html.input(toggle, type="checkbox"))
<input type="checkbox" name="toggle" value="1" />
>>> toggle.set(True)
True
>>> print html.input(toggle, type="checkbox")
>>> print(html.input(toggle, type="checkbox"))
<input type="checkbox" name="toggle" value="1" checked="checked" />
>>> toggle.true = "yes"

Expand All @@ -361,9 +361,9 @@ uses XML-friendly "-"-separated attribute names in markup.

.. doctest:: transforms2

>>> print html.textarea(form['username'])
>>> print(html.textarea(form['username']))
<textarea name="username">jek</textarea>
>>> print html.textarea(form['username'], contents="quux")
>>> print(html.textarea(form['username'], contents="quux"))
<textarea name="username">quux</textarea>

Note that in Genshi, these two forms are equivalent.
Expand Down
4 changes: 2 additions & 2 deletions docs/source/patterns/widgets.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ we stub out widgets for each field in arbitrary order:
from flatland.out.genshi import setup
loader = TemplateLoader(['docs/source/_fixtures/genshi'], callback=setup)
template = loader.load('form.html')
print template.generate(form=form).render()
print(template.generate(form=form).render())


.. testoutput:: genshi
Expand Down Expand Up @@ -112,7 +112,7 @@ environment::
env = Environment(loader=loader, extensions=['jinja2.ext.do'])
env.globals['form_generator'] = Generator('html')
template = env.get_template('form.html')
print template.render(form=form)
print(template.render(form=form))


.. testoutput:: jinja
Expand Down
4 changes: 2 additions & 2 deletions docs/source/schema/traversal.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ application. An element's :attr:`~base.Element.value` is a full & recursive

>>> ann1['title'] # ann1 is a flatland structure
<String u'title'; value=u'Interesting Spot'>
>>> type(ann1.value) # but its .value is not
<type 'dict'>
>>> isinstance(ann1.value, dict) # but its .value is not
True
>>> ann1.value == sample_data
True

Expand Down
4 changes: 2 additions & 2 deletions docs/source/validation/basic.rst
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ aid in debugging.
@validator_validated.connect
def monitor_validation(sender, element, state, result):
# print or logging.debug validations as they happen:
print "validation: %s(%s) valid == %r" % (
sender, element.flattened_name(), result)
print("validation: %s(%s) valid == %r" % (
sender, element.flattened_name(), result))

.. doctest:: signals

Expand Down
2 changes: 1 addition & 1 deletion docs/source/validation/custom.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ validity and status of child elements.

>>> from flatland import Dict, String
>>> def tattle(element, state):
... print element.name
... print(element.name)
... return True
...
>>> schema = (Dict.named('outer').
Expand Down
2 changes: 1 addition & 1 deletion flatland/out/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ def transform_tabindex(tagname, attributes, contents, context, bind):

current = attributes.get(u'tabindex')
if forced or current is None and tagname in _auto_tags[u'tabindex']:
attributes[u'tabindex'] = bytestring_type(tabindex).decode('ascii')
attributes[u'tabindex'] = text_type(str((tabindex)))
if tabindex > 0:
context[u'tabindex'] = tabindex + 1
return contents
Expand Down
30 changes: 14 additions & 16 deletions flatland/out/markup.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,9 @@ def option(self):
attribute. To provide tag body, either supply *contents* or use the
:meth:`~Tag.open` and :meth:`~Tag.close` method of the returned tag::
print generator.option.open(style='bold')
print '<strong>contents</strong>'
print generator.option.close()
print(generator.option.open(style='bold'))
print('<strong>contents</strong>')
print(generator.option.close())
"""
return self._tag(u'option', False, True)
Expand Down Expand Up @@ -216,14 +216,14 @@ class Tag(object):
Tags are generated by :class:`Generator` and are usually called
immediately, returning a fully formed markup string::
print generator.textarea(contents="hello!")
print(generator.textarea(contents="hello!"))
For more fine-tuned control over your markup, you may instead choose to
use the :meth:`open` and :meth:`close` methods of the tag::
print generator.textarea.open()
print "hello!"
print generator.textarea.close()
print(generator.textarea.open())
print("hello!")
print(generator.textarea.close())
"""

Expand Down Expand Up @@ -259,11 +259,7 @@ def close(self):
def _open(self, bind, kwargs):
"""Return a ``'<partial'`` opener tag with no terminator."""
contents = kwargs.pop('contents', None)

if PY2:
attributes = _unicode_keyed(kwargs)
else:
attributes = kwargs
attributes = _transform_keys(kwargs)
tagname = self.tagname
new_contents = transform(
tagname, attributes, contents, self._context, bind)
Expand Down Expand Up @@ -322,11 +318,13 @@ def _attribute_escape(string):
replace(u'"', u'&quot;')


def _unicode_keyed(bytestring_keyed):
def _transform_keys(d):
rekeyed = {}
for key, value in bytestring_keyed.items():
as_unicode = key.rstrip('_').decode('ascii')
rekeyed[as_unicode] = value
for key, value in d.items():
if PY2:
key = key.decode('ascii')
key = key.rstrip('_')
rekeyed[key] = value
return rekeyed


Expand Down
3 changes: 1 addition & 2 deletions flatland/schema/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def named(cls, name):
:returns: a new class
"""
if PY2 and isinstance(name, bytestring_type):
if not isinstance(name, (text_type, NoneType)):
name = text_type(name)
cls.name = name
return cls
Expand Down Expand Up @@ -347,7 +347,6 @@ def parents(self):
while element is not None:
yield element
element = element.parent
raise StopIteration()

@property
def path(self):
Expand Down
4 changes: 2 additions & 2 deletions flatland/schema/compound.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from functools import wraps
from functools import wraps, reduce
import operator

from flatland._compat import (
Expand Down Expand Up @@ -43,7 +43,7 @@ def __call__(cls, value=Unspecified, **kw):
# Find **kw that would override existing class properties and
# remove them from kw.
overrides = {}
for key in kw.keys():
for key in list(kw.keys()):
if hasattr(cls, key):
overrides[key] = kw.pop(key)

Expand Down

0 comments on commit 0c5de0c

Please sign in to comment.