From 00a24292137dc31e36034c816274b6e9b3a77329 Mon Sep 17 00:00:00 2001 From: Jens Vagelpohl Date: Tue, 31 Jan 2023 17:39:21 +0100 Subject: [PATCH 1/5] Bumped version for breaking release. --- CHANGES.rst | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index c3f2f75..a6b0836 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,7 +1,7 @@ Changelog ========= -2.3 (unreleased) +3.0 (unreleased) ---------------- * Lint the code. diff --git a/setup.py b/setup.py index b8379dc..06d0d2d 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup -version = '2.3.dev0' +version = '3.0.dev0' setup( name='five.formlib', From edda1cd2fbd9a443165d5e9e5ceee6f123a810ea Mon Sep 17 00:00:00 2001 From: Jens Vagelpohl Date: Tue, 31 Jan 2023 17:39:49 +0100 Subject: [PATCH 2/5] Drop support for Python 2.7, 3.5, 3.6. --- CHANGES.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index a6b0836..c2ba0bf 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,9 +4,11 @@ Changelog 3.0 (unreleased) ---------------- +* Drop support for Python 2.7, 3.5, 3.6. + * Lint the code. -* Add support for Python 3.10. +* Add support for Python 3.10 and 3.11. 2.2 (2020-10-26) From 80700c87c572844b3fe9d134088054d2932ddfc3 Mon Sep 17 00:00:00 2001 From: Jens Vagelpohl Date: Tue, 31 Jan 2023 18:06:00 +0100 Subject: [PATCH 3/5] - Drop support for Python 2.7, 3.5, 3.6 --- .github/workflows/tests.yml | 17 +++++------ .meta.toml | 5 ++-- MANIFEST.in | 1 - buildout4.cfg | 4 --- setup.cfg | 4 +-- setup.py | 12 +++----- src/five/formlib/__init__.py | 17 +---------- src/five/formlib/formbase.py | 4 +-- src/five/formlib/metaconfigure.py | 4 +-- src/five/formlib/tests/content.py | 14 ++++----- src/five/formlib/tests/formlib.txt | 19 ++---------- src/five/formlib/tests/forms.txt | 40 +++++++------------------ src/five/formlib/tests/schemacontent.py | 28 ++++++++--------- src/five/formlib/tests/test_formlib.py | 3 +- tox.ini | 16 ++++------ 15 files changed, 61 insertions(+), 127 deletions(-) delete mode 100644 buildout4.cfg diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8911d57..deb6535 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,29 +17,28 @@ jobs: fail-fast: false matrix: os: - - ubuntu + - ["ubuntu", "ubuntu-20.04"] config: # [Python version, tox env] - ["3.9", "lint"] - - ["2.7", "py27"] - - ["3.5", "py35"] - - ["3.6", "py36"] - ["3.7", "py37"] - ["3.8", "py38"] - ["3.9", "py39"] - ["3.10", "py310"] + - ["3.11", "py311"] - ["3.9", "coverage"] - runs-on: ${{ matrix.os }}-latest + runs-on: ${{ matrix.os[1] }} + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name name: ${{ matrix.config[1] }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.config[0] }} - name: Pip cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ matrix.config[0] }}-${{ hashFiles('setup.*', 'tox.ini') }} @@ -55,7 +54,7 @@ jobs: - name: Coverage if: matrix.config[1] == 'coverage' run: | - pip install coveralls coverage-python-version + pip install coveralls coveralls --service=github env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.meta.toml b/.meta.toml index e25c7ab..297bb39 100644 --- a/.meta.toml +++ b/.meta.toml @@ -2,14 +2,14 @@ # https://github.com/zopefoundation/meta/tree/master/config/zope-product [meta] template = "zope-product" -commit-id = "9a5db8c650c45a439db06e3a7b2b86318006659d" +commit-id = "e5c611fb" [python] with-pypy = false -with-legacy-python = true with-sphinx-doctests = false with-windows = false with-future-python = false +with-macos = false [tox] use-flake8 = true @@ -22,7 +22,6 @@ known_zope = "AccessControl, Products.Five, Testing, ZPublisher" [manifest] additional-rules = [ - "include buildout4.cfg", "recursive-include src *.po", "recursive-include src *.pot", "recursive-include src *.pt", diff --git a/MANIFEST.in b/MANIFEST.in index 069bdb6..c90f6b8 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -7,7 +7,6 @@ include buildout.cfg include tox.ini recursive-include src *.py -include buildout4.cfg recursive-include src *.po recursive-include src *.pot recursive-include src *.pt diff --git a/buildout4.cfg b/buildout4.cfg deleted file mode 100644 index 4fac8a7..0000000 --- a/buildout4.cfg +++ /dev/null @@ -1,4 +0,0 @@ -[buildout] -extends = - buildout.cfg - https://zopefoundation.github.io/Zope/releases/4.x/versions.cfg diff --git a/setup.cfg b/setup.cfg index ac5d2d3..d44c102 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ # Generated from: # https://github.com/zopefoundation/meta/tree/master/config/zope-product [bdist_wheel] -universal = 1 +universal = 0 [flake8] doctests = 1 @@ -19,7 +19,7 @@ ignore-bad-ideas = force_single_line = True combine_as_imports = True sections = FUTURE,STDLIB,THIRDPARTY,ZOPE,FIRSTPARTY,LOCALFOLDER -known_third_party = six, docutils, pkg_resources +known_third_party = six, docutils, pkg_resources, pytz known_zope = AccessControl, Products.Five, Testing, ZPublisher known_first_party = default_section = ZOPE diff --git a/setup.py b/setup.py index 06d0d2d..a6d5e38 100644 --- a/setup.py +++ b/setup.py @@ -10,32 +10,28 @@ license='ZPL 2.1', description='zope.formlib integration for Zope.', author='Zope Foundation', - author_email='zope-dev@zope.org', + author_email='zope-dev@zope.dev', long_description=(open("README.rst").read() + "\n" + open("CHANGES.rst").read()), classifiers=[ - 'Development Status :: 6 - Mature', + "Development Status :: 6 - Mature", "Environment :: Web Environment", - "Framework :: Zope :: 4", "Framework :: Zope :: 5", "Intended Audience :: Developers", "License :: OSI Approved :: Zope Public License", "Operating System :: OS Independent", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", "Topic :: Software Development :: Libraries :: Application Frameworks", ], - keywords='zope zope4 five formlib', + keywords='zope zope5 five formlib', packages=['five', 'five.formlib'], package_dir={'': 'src'}, namespace_packages=['five'], diff --git a/src/five/formlib/__init__.py b/src/five/formlib/__init__.py index 85b4792..8f78bb2 100644 --- a/src/five/formlib/__init__.py +++ b/src/five/formlib/__init__.py @@ -37,19 +37,6 @@ from zope.schema.interfaces import ValidationError -try: - # Zope 4.x - from Products.Five.browser.decode import processInputs - from Products.Five.browser.decode import setPageEncoding -except ImportError: - # Zope 5.x - def processInputs(*args): - pass - - def setPageEncoding(*args): - pass - - _ = MessageFactory('zope') @@ -72,8 +59,6 @@ class EditView(BrowserView): def __init__(self, context, request): BrowserView.__init__(self, context, request) - processInputs(self.request, self.charsets) - setPageEncoding(self.request) self._setUpWidgets() def _setUpWidgets(self): @@ -180,7 +165,7 @@ def create(self, *args, **kw): # hack to please typical Zope 2 factories, which expect id and title # Any sane schema will use a unicode title, and may fail on a # non-unicode one. - args = ('tmp_id', u'Temporary title') + args + args = ('tmp_id', 'Temporary title') + args return self._factory(*args, **kw) def createAndAdd(self, data): diff --git a/src/five/formlib/formbase.py b/src/five/formlib/formbase.py index b52e3fe..ef47da5 100644 --- a/src/five/formlib/formbase.py +++ b/src/five/formlib/formbase.py @@ -31,7 +31,7 @@ _SUBPAGEFORM_PATH = os.path.join(_FORMLIB_DIR, 'subpageform.pt') -class FiveFormlibMixin(object): +class FiveFormlibMixin: # Overrides the formlib.form.FormBase.template attributes implemented # using NamedTemplates. NamedTemplates using ViewPageTemplateFile (like @@ -50,7 +50,7 @@ def update(self): 'Content-Type', 'text/html; charset=%s' % HTTPRequest.default_encoding ) - super(FiveFormlibMixin, self).update() + super().update() class FormBase(FiveFormlibMixin, form.FormBase): diff --git a/src/five/formlib/metaconfigure.py b/src/five/formlib/metaconfigure.py index c3c63ee..07701bf 100644 --- a/src/five/formlib/metaconfigure.py +++ b/src/five/formlib/metaconfigure.py @@ -34,7 +34,7 @@ def EditViewFactory(name, schema, label, permission, layer, template, default_template, bases, for_, fields, - fulledit_path=None, fulledit_label=None, menu=u''): + fulledit_path=None, fulledit_label=None, menu=''): class_ = SimpleViewClass(template, globals(), used_for=schema, bases=bases) class_.schema = schema @@ -99,7 +99,7 @@ def AddViewFactory(name, schema, label, permission, layer, template, default_template, bases, for_, fields, content_factory, arguments, keyword_arguments, set_before_add, set_after_add, - menu=u''): + menu=''): class_ = SimpleViewClass(template, globals(), used_for=schema, bases=bases) diff --git a/src/five/formlib/tests/content.py b/src/five/formlib/tests/content.py index 98532c5..e11bdf2 100644 --- a/src/five/formlib/tests/content.py +++ b/src/five/formlib/tests/content.py @@ -28,22 +28,22 @@ class IContent(Interface): id = ASCIILine( - title=_(u"Id"), - description=_(u"The object id."), + title=_("Id"), + description=_("The object id."), default='', required=True ) title = TextLine( - title=_(u"Title"), - description=_(u"A short description of the event."), - default=u"", + title=_("Title"), + description=_("A short description of the event."), + default="", required=True ) somelist = List( - title=_(u"Some List"), - value_type=TextLine(title=_(u"Some item")), + title=_("Some List"), + value_type=TextLine(title=_("Some item")), default=[], required=False ) diff --git a/src/five/formlib/tests/formlib.txt b/src/five/formlib/tests/formlib.txt index 9095979..3540d6a 100644 --- a/src/five/formlib/tests/formlib.txt +++ b/src/five/formlib/tests/formlib.txt @@ -9,10 +9,6 @@ zope/formlib/form.txt for tests and more explanations of zope.formlib Before we can begin, we need to set up a few things. -We prepare for Python 2/3 compatibility: - - >>> import six - We need a manager account: >>> uf = self.folder.acl_users @@ -53,23 +49,12 @@ Let's 'manually' create a Content instance and add it to the folder: >>> print(folder.content_1.title) Title -Now we can edit this content object, e.g. changing the title. Formlib, -like the traditional AddView and EditView, supports unicode. -But we need some hackery for Python 2/3 compatibility. For Python 2, -the test framework expects utf-8 encoded binary strings; for Python 3, -it expects text. The function below converts as necessary. - - >>> def to_input(v): - ... if six.PY2: - ... return v.encode("utf-8") - ... else: - ... return v - +Now we can edit this content object, e.g. changing the title. >>> browser.open("http://localhost/test_folder_1_/ftf/content_1/@@edit_content") >>> ni_hao = u'\u4f60\u597d' >>> ctl = browser.getControl(name="form.title") - >>> ctl.value = to_input(ni_hao) + >>> ctl.value = ni_hao >>> browser.getControl(name="form.actions.apply").click() >>> folder.content_1.title == ni_hao True diff --git a/src/five/formlib/tests/forms.txt b/src/five/formlib/tests/forms.txt index 3264dae..adf332d 100644 --- a/src/five/formlib/tests/forms.txt +++ b/src/five/formlib/tests/forms.txt @@ -3,10 +3,6 @@ Testing forms Before we can begin, we need to set up a few things. -We prepare for Python 2/3 compatibility: - - >>> import six - We need a manager account: >>> uf = self.folder.acl_users @@ -199,20 +195,6 @@ the content, this means that no IObjectModified event should have been fired Unicode-safety of forms ----------------------- -To test unicode values in both Python 2 and Python 3, we need -an auxiliary function. - -The ``http`` and ``http_request`` functions below expect as input -utf-8 encoded binary strings for Python 2 and strings -for Python 3. The following function converts our input values -accordingly: - - >>> def to_input(v): - ... if six.PY2: - ... return v.encode("utf-8") - ... else: - ... return v - Even though ZPublisher does not support unicode, automatically generated forms do. In the following we will enter the following two chinese sequences (How do you do? and I'm doing good) in forms: @@ -227,7 +209,7 @@ phrases in the integer field: ... "/test_folder_1_/ftf/+/addfieldcontent.html", ( ... ("field.title", "ChineseTitle"), ... ("field.description", "ChineseDescription"), - ... ("field.somenumber", to_input(ni_hao)), + ... ("field.somenumber", ni_hao), ... ("UPDATE_SUBMIT", "Add"), ... ("add_input_name", "unicodetest"), ... ), @@ -242,8 +224,8 @@ the form will submit correctly and create the object: >>> print(http_request( ... "/test_folder_1_/ftf/+/addfieldcontent.html", ( - ... ("field.title", to_input(ni_hao)), - ... ("field.description", to_input(wo_hen_hao)), + ... ("field.title", ni_hao), + ... ("field.description", wo_hen_hao), ... ("field.somenumber", "0"), ... ("UPDATE_SUBMIT", "Add"), ... ("add_input_name", "unicodetest"), @@ -272,7 +254,7 @@ again make the mistake of entering unicode data in the integer field: ... "/test_folder_1_/ftf/unicodetest/@@edit.html", ( ... ("field.title", "ChineseTitle"), ... ("field.description", "ChineseDescription"), - ... ("field.somenumber", to_input(ni_hao)), + ... ("field.somenumber", ni_hao), ... ("UPDATE_SUBMIT", "Change"), ... ), ... auth="Basic manager:r00t")) @@ -294,8 +276,8 @@ Now we provide some valid form data: >>> print(http_request( ... "/test_folder_1_/ftf/unicodetest/@@edit.html", ( - ... ("field.title", to_input(wo_hen_hao)), - ... ("field.description", to_input(ni_hao)), + ... ("field.title", wo_hen_hao), + ... ("field.description", ni_hao), ... ("field.somenumber", "1"), ... ("UPDATE_SUBMIT", "Change"), ... ), @@ -317,8 +299,8 @@ element to the list: >>> print(http_request( ... "/test_folder_1_/ftf/unicodetest/@@edit.html", ( - ... ("field.title", to_input(ni_hao)), - ... ("field.description", to_input(wo_hen_hao)), + ... ("field.title", ni_hao), + ... ("field.description", wo_hen_hao), ... ("field.somenumber", "1"), ... ("field.somelist.add", "Add Some item"), ... ("field.somelist.count", "0"), @@ -335,10 +317,10 @@ Now, let's enter some more Chinese: >>> print(http_request( ... "/test_folder_1_/ftf/unicodetest/@@edit.html", ( - ... ("field.title", to_input(ni_hao)), - ... ("field.description", to_input(wo_hen_hao)), + ... ("field.title", ni_hao), + ... ("field.description", wo_hen_hao), ... ("field.somenumber", "1"), - ... ("field.somelist.0.", to_input(de_guo)), + ... ("field.somelist.0.", de_guo), ... ("field.somelist.count", "1"), ... ("UPDATE_SUBMIT", "Change"), ... ), diff --git a/src/five/formlib/tests/schemacontent.py b/src/five/formlib/tests/schemacontent.py index 2d59be5..a64d2c1 100644 --- a/src/five/formlib/tests/schemacontent.py +++ b/src/five/formlib/tests/schemacontent.py @@ -33,28 +33,28 @@ class IFieldContent(Interface): title = TextLine( - title=_(u"Title"), - description=_(u"A short description of the event."), - default=u"", + title=_("Title"), + description=_("A short description of the event."), + default="", required=True ) description = Text( - title=_(u"Description"), - description=_(u"A long description of the event."), - default=u"", + title=_("Description"), + description=_("A long description of the event."), + default="", required=False ) somenumber = Int( - title=_(u"Some number"), + title=_("Some number"), default=0, required=False ) somelist = List( - title=_(u"Some List"), - value_type=TextLine(title=_(u"Some item")), + title=_("Some List"), + value_type=TextLine(title=_("Some item")), default=[], required=False ) @@ -82,15 +82,15 @@ def manage_addFieldContent(self, id, title, REQUEST=None): class IComplexSchemaContent(Interface): fishtype = TextLine( - title=u"Fish type", - description=u"The type of fish", - default=u"It was a lovely little fish. And it went wherever I did go.", + title="Fish type", + description="The type of fish", + default="It was a lovely little fish. And it went wherever I did go.", required=False) fish = Object( - title=u"Fish", + title="Fish", schema=IFieldContent, - description=u"The fishy object", + description="The fishy object", required=True) diff --git a/src/five/formlib/tests/test_formlib.py b/src/five/formlib/tests/test_formlib.py index e122fa0..cb1b59e 100644 --- a/src/five/formlib/tests/test_formlib.py +++ b/src/five/formlib/tests/test_formlib.py @@ -68,8 +68,7 @@ def http_request(url, form_parts=None, body=None, auth=None): """perform HTTP request from given parameters. The primary purpose of this auxiliary function is to compute - the `Content-Length` header. While Python 2's `cgi` module - seems resilient to a wrong `Content-Length`, Python 3's `cgi` fails + the `Content-Length` header. Python 3's `cgi` fails badly (at least for a too small value). If given, *form_parts* must be an iterable yielding pairs *name*,*value*. diff --git a/tox.ini b/tox.ini index ecebc02..3572062 100644 --- a/tox.ini +++ b/tox.ini @@ -4,25 +4,22 @@ minversion = 3.18 envlist = lint - py27 - py35 - py36 py37 py38 py39 py310 + py311 coverage [testenv] skip_install = true deps = - zc.buildout >= 3.0.0rc3 + zc.buildout >= 3.0.1 wheel > 0.37 commands_pre = - py27,py35: {envbindir}/buildout -nc {toxinidir}/buildout4.cfg buildout:directory={envdir} buildout:develop={toxinidir} install test - !py27-!py35: {envbindir}/buildout -nc {toxinidir}/buildout.cfg buildout:directory={envdir} buildout:develop={toxinidir} install test + {envbindir}/buildout -nc {toxinidir}/buildout.cfg buildout:directory={envdir} buildout:develop={toxinidir} install test commands = - {envbindir}/test {posargs:-cv} + {envdir}/bin/test {posargs:-cv} [testenv:lint] basepython = python3 @@ -32,7 +29,6 @@ allowlist_externals = mkdir commands = isort --check-only --diff {toxinidir}/src {toxinidir}/setup.py - - flake8 {toxinidir}/src {toxinidir}/setup.py flake8 {toxinidir}/src {toxinidir}/setup.py check-manifest check-python-versions @@ -62,16 +58,14 @@ allowlist_externals = deps = {[testenv]deps} coverage - coverage-python-version commands = mkdir -p {toxinidir}/parts/htmlcov - coverage run {envbindir}/test {posargs:-cv} + coverage run {envdir}/bin/test {posargs:-cv} coverage html coverage report -m --fail-under=82 [coverage:run] branch = True -plugins = coverage_python_version source = five.formlib [coverage:report] From 0c0055ae4a509db96dd23c4e10ebe0a171f66c6a Mon Sep 17 00:00:00 2001 From: Jens Vagelpohl Date: Wed, 1 Feb 2023 10:12:33 +0100 Subject: [PATCH 4/5] - fix up comments with Dieter's suggestions [ci skip] --- src/five/formlib/tests/forms.txt | 4 ++-- src/five/formlib/tests/test_formlib.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/five/formlib/tests/forms.txt b/src/five/formlib/tests/forms.txt index adf332d..394bf9f 100644 --- a/src/five/formlib/tests/forms.txt +++ b/src/five/formlib/tests/forms.txt @@ -195,8 +195,8 @@ the content, this means that no IObjectModified event should have been fired Unicode-safety of forms ----------------------- -Even though ZPublisher does not support unicode, automatically -generated forms do. In the following we will enter the following two +Automatically generated forms support unicode (native strings +under Python 3). Here we will enter the following two chinese sequences (How do you do? and I'm doing good) in forms: >>> ni_hao = u'\u4f60\u597d' diff --git a/src/five/formlib/tests/test_formlib.py b/src/five/formlib/tests/test_formlib.py index cb1b59e..54184c9 100644 --- a/src/five/formlib/tests/test_formlib.py +++ b/src/five/formlib/tests/test_formlib.py @@ -68,8 +68,7 @@ def http_request(url, form_parts=None, body=None, auth=None): """perform HTTP request from given parameters. The primary purpose of this auxiliary function is to compute - the `Content-Length` header. Python 3's `cgi` fails - badly (at least for a too small value). + the `Content-Length` header. If given, *form_parts* must be an iterable yielding pairs *name*,*value*. *body* is then computed as a typical form response from it. From fd2558aabbead04a573a37fca0197f51e929caaf Mon Sep 17 00:00:00 2001 From: Jens Vagelpohl Date: Wed, 1 Feb 2023 10:17:25 +0100 Subject: [PATCH 5/5] - work around Python bug in browser tests, thanks Michael --- src/five/formlib/tests/forms.txt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/five/formlib/tests/forms.txt b/src/five/formlib/tests/forms.txt index 394bf9f..210f7f6 100644 --- a/src/five/formlib/tests/forms.txt +++ b/src/five/formlib/tests/forms.txt @@ -31,6 +31,9 @@ Let's set up a testbrowser: >>> browser = Browser() >>> browser.addHeader('Accept-Language', 'en-US') + >>> # Work around https://github.com/python/cpython/issues/90113 + >>> browser.raiseHttpErrors = False + Add forms --------- @@ -44,10 +47,10 @@ An unprotected form can be accessed with anonymously: We don't have access, we will not be able to get to the protected add form: - >>> browser.open("http://localhost/test_folder_1_/ftf/+/protectedaddform.html") # doctest: +IGNORE_EXCEPTION_DETAIL - Traceback (most recent call last): - ... - HTTPError: HTTP Error 401: Unauthorized + >>> browser.open("http://localhost/test_folder_1_/ftf/+/protectedaddform.html") + >>> print(browser.headers) + Status: 401 ... + ... For a protected one we need a manager account: