Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,35 @@
Changes
=======

0.7 (unreleased)
----------------

- Provide a compatibility option for clients that do not implement the "list of
tuples" processing according to the HTML spec's "Constructing the form data
set" algorithm.

To help those clients you can suffix each field name with a unique marker
so that they will properly preserver order and thus emulate the
"list of tuples" behaviour.

If your application relies on this, then provide the ``unique_key_separator``
argument to the parse function ``parse(fields, unique_key_separator=":"")`` and
suffix the input field names accordingly. The actual IDs do not matter,
as long as the combination of field name + id ends up as unique. :

<input type="hidden" name="__start__:1234" value="series:sequence"/>
<input type="hidden" name="name:1234" value="Bob" />
<input type="hidden" name="__end__:1234" />

<input type="hidden" name="__start__:1235" value="series:sequence"/>
<input type="hidden" name="name:1235" value="Ken" />
<input type="hidden" name="__end__:1235" />

For readability you can reuse the same ID on start/end markers, but they could
also be different and don't have to be numbers, UUIDs would work as well but
will increase payload size.


0.6 (2018-08-24)
----------------

Expand Down
7 changes: 5 additions & 2 deletions peppercorn/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def data_type(value):
TYPS = (SEQUENCE, MAPPING, RENAME, IGNORE)


def parse(tokens):
def parse(tokens, unique_key_separator=None):
""" Infer a data structure from the ordered set of fields and
return it."""
target = typ = None
Expand All @@ -22,6 +22,9 @@ def parse(tokens):

for token in tokens:
key, val = token
if unique_key_separator:
key = key.rsplit(unique_key_separator, maxsplit=1)[0]

if key == START:
stack.append((target, typ, out))
target, typ = data_type(val)
Expand All @@ -46,7 +49,7 @@ def parse(tokens):
target = prev_target
typ = prev_typ
else:
out.append(token)
out.append((key, val))

if stack:
raise ValueError("Not enough end markers")
Expand Down
42 changes: 39 additions & 3 deletions peppercorn/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
from peppercorn.compat import PY3

class TestParse(unittest.TestCase):
def _callFUT(self, fields):
def _callFUT(self, fields, **kw):
from peppercorn import parse
return parse(fields)
return parse(fields, **kw)

def _makeEnviron(self, kw=None):
if kw is None: # pragma: no cover
Expand Down Expand Up @@ -73,7 +73,43 @@ def test_bare(self):
fields = self._getFields()
result = self._callFUT(fields)
self._assertFieldsResult(result)


def test_bare_with_marker(self):
i = 0
def next_id():
nonlocal i
i += 1
return str(i)
fields = [
(key + ":" + next_id(), value)
for key, value in self._getFields()]
result = self._callFUT(fields, unique_key_separator=":")
self._assertFieldsResult(result)

def test_bare_without_marker(self):
# This is proof that ":" isn't something special when we don't
# provide a unique key separator

from peppercorn import START, END, MAPPING

fields = []
for key, value in self._getFields():
if key not in [START, END]:
key = key + ":something"
fields.append((key, value))

result = self._callFUT(fields)

self.assertEqual(
result,
{'series':
{'name:something':'date series 1',
'dates': [['10', '12', '2008'],
['10', '12', '2009']],
},
'name:something': 'project1',
'title:something': 'Cool project'})

def test_fieldstorage(self):
fs = self._makeMultipartFieldStorage(self._getFields())

Expand Down