diff --git a/last_commit.txt b/last_commit.txt index b4ca576a5a..63b8911c05 100644 --- a/last_commit.txt +++ b/last_commit.txt @@ -1,64 +1,42 @@ -Repository: plone.app.upgrade +Repository: diazo Branch: refs/heads/master -Date: 2021-12-04T00:38:09+01:00 +Date: 2021-12-17T23:21:28+01:00 Author: Maurits van Rees (mauritsvanrees) -Commit: https://github.com/plone/plone.app.upgrade/commit/9be379bebe3e1f5851663f7bdd5b835079eb1336 +Commit: https://github.com/plone/diazo/commit/ef3b849f2b8aa9f07a54c639740eba40a0c6f0e9 -Fix unicode properties. +Remove FormEncode test dependency. -This goes through all objects in the site and does two things: +Instead copy its xml_compare and text_compare functions. +Taken from version 2.0.1. -1. Make sure lines contain only strings, instead of bytes, or worse: a combination of strings and bytes. -2. Replace deprecated ulines, utext, utoken, and ustring properties with their non-unicode variant, using native strings. - -See https://github.com/plone/Products.CMFPlone/issues/3305 - -I have just created and merged a PR that adds a utility function in Zope that we use: -https://github.com/zopefoundation/Zope/pull/993 -It will be included in Zope 5.4, but this is not released yet, so for now we use our own copy. - -Files changed: -A news/3305.bugfix -M plone/app/upgrade/v60/alphas.py -M plone/app/upgrade/v60/configure.zcml - -b'diff --git a/news/3305.bugfix b/news/3305.bugfix\nnew file mode 100644\nindex 00000000..3156b6ba\n--- /dev/null\n+++ b/news/3305.bugfix\n@@ -0,0 +1,3 @@\n+Fix unicode properties.\n+See `issue 3305 `_.\n+[maurits]\ndiff --git a/plone/app/upgrade/v60/alphas.py b/plone/app/upgrade/v60/alphas.py\nindex 7b2cc6c1..1fcf2c11 100644\n--- a/plone/app/upgrade/v60/alphas.py\n+++ b/plone/app/upgrade/v60/alphas.py\n@@ -4,6 +4,7 @@\n from plone.uuid.interfaces import ATTRIBUTE_NAME\n from plone.uuid.interfaces import IUUIDGenerator\n from Products.CMFCore.utils import getToolByName\n+from Products.CMFPlone.utils import safe_unicode\n from ZODB.broken import Broken\n from zope.component import queryUtility\n from zope.component.hooks import getSite\n@@ -117,3 +118,149 @@ def index_siteroot(context):\n """Index the Plone Site"""\n portal = getSite()\n portal.reindexObject()\n+\n+\n+def _string_tuple(value):\n+ # Copy of ZPublisher.utils._string_tuple which will be released in Zope 5.4.\n+ if not value:\n+ return ()\n+ return tuple([safe_unicode(element) for element in value])\n+\n+\n+def _fix_properties(obj, path=None):\n+ """Fix properties on object.\n+\n+ Copy of ZPublisher.utils.fix_properties which will be released in Zope 5.4.\n+ See https://github.com/zopefoundation/Zope/pull/993\n+\n+ This does two things:\n+\n+ 1. Make sure lines contain only strings, instead of bytes,\n+ or worse: a combination of strings and bytes.\n+ 2. Replace deprecated ulines, utext, utoken, and ustring properties\n+ with their non-unicode variant, using native strings.\n+\n+ See https://github.com/zopefoundation/Zope/issues/987\n+\n+ Since Zope 5.3, a lines property stores strings instead of bytes.\n+ But there is no migration yet. (We do that here.)\n+ Result is that getProperty on an already created lines property\n+ will return the old value with bytes, but a newly created lines property\n+ will return strings. And you might get combinations.\n+\n+ Also since Zope 5.3, the ulines property type is deprecated.\n+ You should use a lines property instead.\n+ Same for a few others: utext, utoken, ustring.\n+ The unicode variants are planned to be removed in Zope 6.\n+\n+ Intended usage:\n+ app.ZopeFindAndApply(app, apply_func=fix_properties)\n+ """\n+ if path is None:\n+ # When using ZopeFindAndApply, path is always given.\n+ # But we may be called by other code.\n+ if hasattr(object, \'getPhysicalPath\'):\n+ path = \'/\'.join(object.getPhysicalPath())\n+ else:\n+ # Some simple object, for example in tests.\n+ # We don\'t care about the path then, it is only shown in logs.\n+ path = "/dummy"\n+\n+ try:\n+ prop_map = obj.propertyMap()\n+ except AttributeError:\n+ # Object does not inherit from PropertyManager.\n+ # For example \'MountedObject\'.\n+ return\n+\n+ for prop_info in prop_map:\n+ # Example: {\'id\': \'title\', \'type\': \'string\', \'mode\': \'w\'}\n+ prop_id = prop_info.get("id")\n+ current = obj.getProperty(prop_id)\n+ if current is None:\n+ continue\n+ new_type = prop_type = prop_info.get("type")\n+ if prop_type == "lines":\n+ new = _string_tuple(current)\n+ elif prop_type == "ulines":\n+ new_type = "lines"\n+ new = _string_tuple(current)\n+ elif prop_type == "utokens":\n+ new_type = "tokens"\n+ new = _string_tuple(current)\n+ elif prop_type == "utext":\n+ new_type = "text"\n+ new = safe_unicode(current)\n+ elif prop_type == "ustring":\n+ new_type = "string"\n+ new = safe_unicode(current)\n+ else:\n+ continue\n+ if prop_type != new_type:\n+ # Replace with non-unicode variant.\n+ # This could easily lead to:\n+ # Exceptions.BadRequest: Invalid or duplicate property id.\n+ # obj._delProperty(prop_id)\n+ # obj._setProperty(prop_id, new, new_type)\n+ # So fix it by using internal details.\n+ for prop in obj._properties:\n+ if prop.get("id") == prop_id:\n+ prop["type"] = new_type\n+ obj._p_changed = True\n+ break\n+ else:\n+ # This probably cannot happen.\n+ # If it does, we want to know.\n+ logger.warning(\n+ "Could not change property %s from %s to %s for %s",\n+ prop_id,\n+ prop_type,\n+ new_type,\n+ path,\n+ )\n+ continue\n+ obj._updateProperty(prop_id, new)\n+ logger.info(\n+ "Changed property %s from %s to %s for %s",\n+ prop_id,\n+ prop_type,\n+ new_type,\n+ path,\n+ )\n+ continue\n+ if current != new:\n+ obj._updateProperty(prop_id, new)\n+ logger.info(\n+ "Changed property %s at %s so value fits the type %s: %r",\n+ prop_id,\n+ path,\n+ prop_type,\n+ new,\n+ )\n+\n+\n+def fix_unicode_properties(context):\n+ """Fix unicode properties.\n+\n+ This does two things:\n+\n+ 1. Make sure lines contain only strings, instead of bytes,\n+ or worse: a combination of strings and bytes.\n+ 2. Replace deprecated ulines, utext, utoken, and ustring properties\n+ with their non-unicode variant, using native strings.\n+\n+ See https://github.com/plone/Products.CMFPlone/issues/3305\n+\n+ The main function we use here will be in Zope 5.4:\n+ https://github.com/zopefoundation/Zope/pull/993\n+ If it is not there, we use our own copy.\n+ The Zope one should be leading though.\n+ Our copy can be removed when Zope 5.4. is released.\n+ """\n+ try:\n+ from ZPublisher.utils import fix_properties\n+ except ImportError:\n+ fix_properties = _fix_properties\n+ portal = getSite()\n+ portal.reindexObject()\n+ portal.ZopeFindAndApply(portal, apply_func=fix_properties)\ndiff --git a/plone/app/upgrade/v60/configure.zcml b/plone/app/upgrade/v60/configure.zcml\nindex bffb3f6d..6f25c87d 100644\n--- a/plone/app/upgrade/v60/configure.zcml\n+++ b/plone/app/upgrade/v60/configure.zcml\n@@ -65,11 +65,10 @@\n destination="6003"\n profile="Products.CMFPlone:plone">\n \n- \n+ \n \n \n \n' - -Repository: plone.app.upgrade - - -Branch: refs/heads/master -Date: 2021-12-15T00:35:41+01:00 -Author: Maurits van Rees (mauritsvanrees) -Commit: https://github.com/plone/plone.app.upgrade/commit/8ada20fe4fdd57405f3fdd713d24ddef3766c108 - -Fix unicode properties: fix sub objects as well. - -I thought this was the default. +I ran into a problem compiling the 1.3.1 egg, so I looked into who was using FormEncode in Plone, +and it turned out to only be a test dependency of Diazo. Files changed: -M plone/app/upgrade/v60/alphas.py +A news/83.bugfix +M lib/diazo/tests/test_diazo.py +M setup.py -b'diff --git a/plone/app/upgrade/v60/alphas.py b/plone/app/upgrade/v60/alphas.py\nindex 1fcf2c11..d9c2e292 100644\n--- a/plone/app/upgrade/v60/alphas.py\n+++ b/plone/app/upgrade/v60/alphas.py\n@@ -154,7 +154,7 @@ def _fix_properties(obj, path=None):\n The unicode variants are planned to be removed in Zope 6.\n \n Intended usage:\n- app.ZopeFindAndApply(app, apply_func=fix_properties)\n+ app.ZopeFindAndApply(app, search_sub=1, apply_func=fix_properties)\n """\n if path is None:\n # When using ZopeFindAndApply, path is always given.\n@@ -263,4 +263,4 @@ def fix_unicode_properties(context):\n fix_properties = _fix_properties\n portal = getSite()\n portal.reindexObject()\n- portal.ZopeFindAndApply(portal, apply_func=fix_properties)\n+ portal.ZopeFindAndApply(portal, search_sub=1, apply_func=fix_properties)\n' +b'diff --git a/lib/diazo/tests/test_diazo.py b/lib/diazo/tests/test_diazo.py\nindex 3853413..fdf7b8d 100644\n--- a/lib/diazo/tests/test_diazo.py\n+++ b/lib/diazo/tests/test_diazo.py\n@@ -3,7 +3,6 @@\n from __future__ import print_function\n \n from diazo.utils import quote_param\n-from formencode.doctest_xml_compare import xml_compare\n from future.builtins import str\n from io import BytesIO\n from io import open\n@@ -15,6 +14,7 @@\n import difflib\n import os\n import pkg_resources\n+import six\n import sys\n import unittest\n \n@@ -39,6 +39,68 @@\n )\n \n \n+def text_compare(t1, t2):\n+ # Copied from formencode.doctest_xml_compare.text_compare 2.0.1.\n+ # See note in xml_compare below.\n+ if not t1 and not t2:\n+ return True\n+ if t1 == \'*\' or t2 == \'*\':\n+ return True\n+ return (t1 or \'\').strip() == (t2 or \'\').strip()\n+\n+\n+def xml_compare(x1, x2):\n+ """Compare two xml items.\n+\n+ Copied from formencode.doctest_xml_compare.xml_compare 2.0.1,\n+ without the (unused by us) optional \'reporter\' argument.\n+\n+ License: MIT\n+\n+ Copyright (c) 2015 Ian Bicking and FormEncode Contributors\n+\n+ Permission is hereby granted, free of charge, to any person obtaining a copy\n+ of this software and associated documentation files (the "Software"), to deal\n+ in the Software without restriction, including without limitation the rights\n+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n+ copies of the Software, and to permit persons to whom the Software is\n+ furnished to do so, subject to the following conditions:\n+\n+ The above copyright notice and this permission notice shall be included in\n+ all copies or substantial portions of the Software.\n+\n+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n+ THE SOFTWARE.\n+ """\n+ if x1.tag != x2.tag:\n+ return False\n+ for name, value in six.iteritems(x1.attrib):\n+ if x2.attrib.get(name) != value:\n+ return False\n+ for name in x2.attrib:\n+ if name not in x1.attrib:\n+ return False\n+ if not text_compare(x1.text, x2.text):\n+ return False\n+ if not text_compare(x1.tail, x2.tail):\n+ return False\n+ cl1 = list(x1)\n+ cl2 = list(x2)\n+ if len(cl1) != len(cl2):\n+ return False\n+ i = 0\n+ for c1, c2 in zip(cl1, cl2):\n+ i += 1\n+ if not xml_compare(c1, c2):\n+ return False\n+ return True\n+\n+\n class DiazoTestCase(unittest.TestCase):\n \n writefiles = os.environ.get(\'DiazoTESTS_WRITE_FILES\', False)\ndiff --git a/news/83.bugfix b/news/83.bugfix\nnew file mode 100644\nindex 0000000..59cfad9\n--- /dev/null\n+++ b/news/83.bugfix\n@@ -0,0 +1,2 @@\n+Remove FormEncode test dependency.\n+[maurits]\ndiff --git a/setup.py b/setup.py\nindex 3994bbb..60f9d5e 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -10,7 +10,6 @@\n \'WebOb\',\n ],\n \'test\': [\n- \'formencode\',\n \'repoze.xmliter\',\n \'WebOb\',\n ],\n' -Repository: plone.app.upgrade +Repository: diazo Branch: refs/heads/master -Date: 2021-12-20T11:53:36+01:00 +Date: 2021-12-20T11:54:54+01:00 Author: Jens W. Klein (jensens) -Commit: https://github.com/plone/plone.app.upgrade/commit/74eff4793a51cfe14d8fe72ebfed368a131b7a8c +Commit: https://github.com/plone/diazo/commit/5f8c07b13392bb606ac4f1879a9dbb1b2912a81e -Merge pull request #265 from plone/maurits-fix-unicode-properties +Merge pull request #83 from plone/maurits-remove-formencode -Fix unicode properties. +Remove FormEncode test dependency. Files changed: -A news/3305.bugfix -M plone/app/upgrade/v60/alphas.py -M plone/app/upgrade/v60/configure.zcml +A news/83.bugfix +M lib/diazo/tests/test_diazo.py +M setup.py -b'diff --git a/news/3305.bugfix b/news/3305.bugfix\nnew file mode 100644\nindex 00000000..3156b6ba\n--- /dev/null\n+++ b/news/3305.bugfix\n@@ -0,0 +1,3 @@\n+Fix unicode properties.\n+See `issue 3305 `_.\n+[maurits]\ndiff --git a/plone/app/upgrade/v60/alphas.py b/plone/app/upgrade/v60/alphas.py\nindex 7b2cc6c1..d9c2e292 100644\n--- a/plone/app/upgrade/v60/alphas.py\n+++ b/plone/app/upgrade/v60/alphas.py\n@@ -4,6 +4,7 @@\n from plone.uuid.interfaces import ATTRIBUTE_NAME\n from plone.uuid.interfaces import IUUIDGenerator\n from Products.CMFCore.utils import getToolByName\n+from Products.CMFPlone.utils import safe_unicode\n from ZODB.broken import Broken\n from zope.component import queryUtility\n from zope.component.hooks import getSite\n@@ -117,3 +118,149 @@ def index_siteroot(context):\n """Index the Plone Site"""\n portal = getSite()\n portal.reindexObject()\n+\n+\n+def _string_tuple(value):\n+ # Copy of ZPublisher.utils._string_tuple which will be released in Zope 5.4.\n+ if not value:\n+ return ()\n+ return tuple([safe_unicode(element) for element in value])\n+\n+\n+def _fix_properties(obj, path=None):\n+ """Fix properties on object.\n+\n+ Copy of ZPublisher.utils.fix_properties which will be released in Zope 5.4.\n+ See https://github.com/zopefoundation/Zope/pull/993\n+\n+ This does two things:\n+\n+ 1. Make sure lines contain only strings, instead of bytes,\n+ or worse: a combination of strings and bytes.\n+ 2. Replace deprecated ulines, utext, utoken, and ustring properties\n+ with their non-unicode variant, using native strings.\n+\n+ See https://github.com/zopefoundation/Zope/issues/987\n+\n+ Since Zope 5.3, a lines property stores strings instead of bytes.\n+ But there is no migration yet. (We do that here.)\n+ Result is that getProperty on an already created lines property\n+ will return the old value with bytes, but a newly created lines property\n+ will return strings. And you might get combinations.\n+\n+ Also since Zope 5.3, the ulines property type is deprecated.\n+ You should use a lines property instead.\n+ Same for a few others: utext, utoken, ustring.\n+ The unicode variants are planned to be removed in Zope 6.\n+\n+ Intended usage:\n+ app.ZopeFindAndApply(app, search_sub=1, apply_func=fix_properties)\n+ """\n+ if path is None:\n+ # When using ZopeFindAndApply, path is always given.\n+ # But we may be called by other code.\n+ if hasattr(object, \'getPhysicalPath\'):\n+ path = \'/\'.join(object.getPhysicalPath())\n+ else:\n+ # Some simple object, for example in tests.\n+ # We don\'t care about the path then, it is only shown in logs.\n+ path = "/dummy"\n+\n+ try:\n+ prop_map = obj.propertyMap()\n+ except AttributeError:\n+ # Object does not inherit from PropertyManager.\n+ # For example \'MountedObject\'.\n+ return\n+\n+ for prop_info in prop_map:\n+ # Example: {\'id\': \'title\', \'type\': \'string\', \'mode\': \'w\'}\n+ prop_id = prop_info.get("id")\n+ current = obj.getProperty(prop_id)\n+ if current is None:\n+ continue\n+ new_type = prop_type = prop_info.get("type")\n+ if prop_type == "lines":\n+ new = _string_tuple(current)\n+ elif prop_type == "ulines":\n+ new_type = "lines"\n+ new = _string_tuple(current)\n+ elif prop_type == "utokens":\n+ new_type = "tokens"\n+ new = _string_tuple(current)\n+ elif prop_type == "utext":\n+ new_type = "text"\n+ new = safe_unicode(current)\n+ elif prop_type == "ustring":\n+ new_type = "string"\n+ new = safe_unicode(current)\n+ else:\n+ continue\n+ if prop_type != new_type:\n+ # Replace with non-unicode variant.\n+ # This could easily lead to:\n+ # Exceptions.BadRequest: Invalid or duplicate property id.\n+ # obj._delProperty(prop_id)\n+ # obj._setProperty(prop_id, new, new_type)\n+ # So fix it by using internal details.\n+ for prop in obj._properties:\n+ if prop.get("id") == prop_id:\n+ prop["type"] = new_type\n+ obj._p_changed = True\n+ break\n+ else:\n+ # This probably cannot happen.\n+ # If it does, we want to know.\n+ logger.warning(\n+ "Could not change property %s from %s to %s for %s",\n+ prop_id,\n+ prop_type,\n+ new_type,\n+ path,\n+ )\n+ continue\n+ obj._updateProperty(prop_id, new)\n+ logger.info(\n+ "Changed property %s from %s to %s for %s",\n+ prop_id,\n+ prop_type,\n+ new_type,\n+ path,\n+ )\n+ continue\n+ if current != new:\n+ obj._updateProperty(prop_id, new)\n+ logger.info(\n+ "Changed property %s at %s so value fits the type %s: %r",\n+ prop_id,\n+ path,\n+ prop_type,\n+ new,\n+ )\n+\n+\n+def fix_unicode_properties(context):\n+ """Fix unicode properties.\n+\n+ This does two things:\n+\n+ 1. Make sure lines contain only strings, instead of bytes,\n+ or worse: a combination of strings and bytes.\n+ 2. Replace deprecated ulines, utext, utoken, and ustring properties\n+ with their non-unicode variant, using native strings.\n+\n+ See https://github.com/plone/Products.CMFPlone/issues/3305\n+\n+ The main function we use here will be in Zope 5.4:\n+ https://github.com/zopefoundation/Zope/pull/993\n+ If it is not there, we use our own copy.\n+ The Zope one should be leading though.\n+ Our copy can be removed when Zope 5.4. is released.\n+ """\n+ try:\n+ from ZPublisher.utils import fix_properties\n+ except ImportError:\n+ fix_properties = _fix_properties\n+ portal = getSite()\n+ portal.reindexObject()\n+ portal.ZopeFindAndApply(portal, search_sub=1, apply_func=fix_properties)\ndiff --git a/plone/app/upgrade/v60/configure.zcml b/plone/app/upgrade/v60/configure.zcml\nindex bffb3f6d..6f25c87d 100644\n--- a/plone/app/upgrade/v60/configure.zcml\n+++ b/plone/app/upgrade/v60/configure.zcml\n@@ -65,11 +65,10 @@\n destination="6003"\n profile="Products.CMFPlone:plone">\n \n- \n+ \n \n \n \n' +b'diff --git a/lib/diazo/tests/test_diazo.py b/lib/diazo/tests/test_diazo.py\nindex 3853413..fdf7b8d 100644\n--- a/lib/diazo/tests/test_diazo.py\n+++ b/lib/diazo/tests/test_diazo.py\n@@ -3,7 +3,6 @@\n from __future__ import print_function\n \n from diazo.utils import quote_param\n-from formencode.doctest_xml_compare import xml_compare\n from future.builtins import str\n from io import BytesIO\n from io import open\n@@ -15,6 +14,7 @@\n import difflib\n import os\n import pkg_resources\n+import six\n import sys\n import unittest\n \n@@ -39,6 +39,68 @@\n )\n \n \n+def text_compare(t1, t2):\n+ # Copied from formencode.doctest_xml_compare.text_compare 2.0.1.\n+ # See note in xml_compare below.\n+ if not t1 and not t2:\n+ return True\n+ if t1 == \'*\' or t2 == \'*\':\n+ return True\n+ return (t1 or \'\').strip() == (t2 or \'\').strip()\n+\n+\n+def xml_compare(x1, x2):\n+ """Compare two xml items.\n+\n+ Copied from formencode.doctest_xml_compare.xml_compare 2.0.1,\n+ without the (unused by us) optional \'reporter\' argument.\n+\n+ License: MIT\n+\n+ Copyright (c) 2015 Ian Bicking and FormEncode Contributors\n+\n+ Permission is hereby granted, free of charge, to any person obtaining a copy\n+ of this software and associated documentation files (the "Software"), to deal\n+ in the Software without restriction, including without limitation the rights\n+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n+ copies of the Software, and to permit persons to whom the Software is\n+ furnished to do so, subject to the following conditions:\n+\n+ The above copyright notice and this permission notice shall be included in\n+ all copies or substantial portions of the Software.\n+\n+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n+ THE SOFTWARE.\n+ """\n+ if x1.tag != x2.tag:\n+ return False\n+ for name, value in six.iteritems(x1.attrib):\n+ if x2.attrib.get(name) != value:\n+ return False\n+ for name in x2.attrib:\n+ if name not in x1.attrib:\n+ return False\n+ if not text_compare(x1.text, x2.text):\n+ return False\n+ if not text_compare(x1.tail, x2.tail):\n+ return False\n+ cl1 = list(x1)\n+ cl2 = list(x2)\n+ if len(cl1) != len(cl2):\n+ return False\n+ i = 0\n+ for c1, c2 in zip(cl1, cl2):\n+ i += 1\n+ if not xml_compare(c1, c2):\n+ return False\n+ return True\n+\n+\n class DiazoTestCase(unittest.TestCase):\n \n writefiles = os.environ.get(\'DiazoTESTS_WRITE_FILES\', False)\ndiff --git a/news/83.bugfix b/news/83.bugfix\nnew file mode 100644\nindex 0000000..59cfad9\n--- /dev/null\n+++ b/news/83.bugfix\n@@ -0,0 +1,2 @@\n+Remove FormEncode test dependency.\n+[maurits]\ndiff --git a/setup.py b/setup.py\nindex 3994bbb..60f9d5e 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -10,7 +10,6 @@\n \'WebOb\',\n ],\n \'test\': [\n- \'formencode\',\n \'repoze.xmliter\',\n \'WebOb\',\n ],\n'